mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-05-03 04:19:47 +01:00
JdbcClient holds ConversionService for queries with mapped classes
Closes gh-33467
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -70,6 +70,19 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code SingleColumnRowMapper}.
|
||||
* @param requiredType the type that each result object is expected to match
|
||||
* @param conversionService a {@link ConversionService} for converting a fetched value
|
||||
* @since 7.0
|
||||
*/
|
||||
public SingleColumnRowMapper(Class<T> requiredType, @Nullable ConversionService conversionService) {
|
||||
if (requiredType != Object.class) {
|
||||
setRequiredType(requiredType);
|
||||
}
|
||||
setConversionService(conversionService);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the type that each result object is expected to match.
|
||||
@@ -84,12 +97,13 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
|
||||
* Set a {@link ConversionService} for converting a fetched value.
|
||||
* <p>Default is the {@link DefaultConversionService}.
|
||||
* @since 5.0.4
|
||||
* @see DefaultConversionService#getSharedInstance
|
||||
* @see DefaultConversionService#getSharedInstance()
|
||||
*/
|
||||
public void setConversionService(@Nullable ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a value for the single column in the current row.
|
||||
* <p>Validates that there is only one column selected,
|
||||
|
||||
+13
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,8 @@ import javax.sql.DataSource;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||
@@ -64,24 +66,25 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
|
||||
private final NamedParameterJdbcOperations namedParamOps;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final Map<Class<?>, RowMapper<?>> rowMapperCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public DefaultJdbcClient(DataSource dataSource) {
|
||||
this.classicOps = new JdbcTemplate(dataSource);
|
||||
this.namedParamOps = new NamedParameterJdbcTemplate(this.classicOps);
|
||||
this(new JdbcTemplate(dataSource));
|
||||
}
|
||||
|
||||
public DefaultJdbcClient(JdbcOperations jdbcTemplate) {
|
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
|
||||
this.classicOps = jdbcTemplate;
|
||||
this.namedParamOps = new NamedParameterJdbcTemplate(jdbcTemplate);
|
||||
this(new NamedParameterJdbcTemplate(jdbcTemplate), null);
|
||||
}
|
||||
|
||||
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate) {
|
||||
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate, @Nullable ConversionService conversionService) {
|
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
|
||||
this.classicOps = jdbcTemplate.getJdbcOperations();
|
||||
this.namedParamOps = jdbcTemplate;
|
||||
this.conversionService =
|
||||
(conversionService != null ? conversionService : DefaultConversionService.getSharedInstance());
|
||||
}
|
||||
|
||||
|
||||
@@ -201,8 +204,9 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
@Override
|
||||
public <T> MappedQuerySpec<T> query(Class<T> mappedClass) {
|
||||
RowMapper<?> rowMapper = rowMapperCache.computeIfAbsent(mappedClass, key ->
|
||||
BeanUtils.isSimpleProperty(mappedClass) ? new SingleColumnRowMapper<>(mappedClass) :
|
||||
new SimplePropertyRowMapper<>(mappedClass));
|
||||
BeanUtils.isSimpleProperty(mappedClass) ?
|
||||
new SingleColumnRowMapper<>(mappedClass, conversionService) :
|
||||
new SimplePropertyRowMapper<>(mappedClass, conversionService));
|
||||
return query((RowMapper<T>) rowMapper);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.support.DataAccessUtils;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
@@ -107,7 +108,22 @@ public interface JdbcClient {
|
||||
* @param jdbcTemplate the delegate to perform operations on
|
||||
*/
|
||||
static JdbcClient create(NamedParameterJdbcOperations jdbcTemplate) {
|
||||
return new DefaultJdbcClient(jdbcTemplate);
|
||||
return new DefaultJdbcClient(jdbcTemplate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code JdbcClient} for the given {@link NamedParameterJdbcOperations} delegate,
|
||||
* typically an {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}.
|
||||
* <p>Use this factory method to reuse existing {@code NamedParameterJdbcTemplate}
|
||||
* configuration, including its underlying {@code JdbcTemplate} and {@code DataSource},
|
||||
* along with a custom {@link ConversionService} for queries with mapped classes.
|
||||
* @param jdbcTemplate the delegate to perform operations on
|
||||
* @param conversionService a {@link ConversionService} for converting fetched JDBC values
|
||||
* to mapped classes in {@link StatementSpec#query(Class)}
|
||||
* @since 7.0
|
||||
*/
|
||||
static JdbcClient create(NamedParameterJdbcOperations jdbcTemplate, ConversionService conversionService) {
|
||||
return new DefaultJdbcClient(jdbcTemplate, conversionService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+19
-3
@@ -16,10 +16,12 @@
|
||||
|
||||
package org.springframework.jdbc.core.simple;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLFeatureNotSupportedException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -33,6 +35,11 @@ import javax.sql.DataSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -593,13 +600,22 @@ class JdbcClientQueryTests {
|
||||
@Test
|
||||
void queryForMappedFieldHolderWithNamedParam() throws Exception {
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
given(resultSet.getObject(1, BigInteger.class)).willThrow(new SQLFeatureNotSupportedException());
|
||||
given(resultSet.getObject(1)).willReturn("big22");
|
||||
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
conversionService.addConverter(new Converter<String, BigInteger>() { // explicit for generics
|
||||
@Override
|
||||
public BigInteger convert(String source) {
|
||||
return NumberUtils.parseNumber(source.substring(3), BigInteger.class);
|
||||
}
|
||||
});
|
||||
client = JdbcClient.create(new NamedParameterJdbcTemplate(dataSource), conversionService);
|
||||
AgeFieldHolder value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.param("id", 3)
|
||||
.query(AgeFieldHolder.class).single();
|
||||
|
||||
assertThat(value.age).isEqualTo(22);
|
||||
assertThat(value.age).isEqualTo(BigInteger.valueOf(22));
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
verify(preparedStatement).setObject(1, 3);
|
||||
verify(resultSet).close();
|
||||
@@ -656,7 +672,7 @@ class JdbcClientQueryTests {
|
||||
|
||||
static class AgeFieldHolder {
|
||||
|
||||
public int age;
|
||||
public BigInteger age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user