Introduce SimplePropertyRowMapper with flexible constructor/property/field mapping
Includes query(Class) method with value and property mapping support on JdbcClient. JdbcClient's singleColumn/singleValue are declared without a Class parameter now. Closes gh-26594 See gh-30931
This commit is contained in:
@@ -609,6 +609,31 @@ public abstract class ReflectionUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find a {@link Field field} on the supplied {@link Class} with the
|
||||
* supplied {@code name}. Searches all superclasses up to {@link Object}.
|
||||
* @param clazz the class to introspect
|
||||
* @param name the name of the field (with upper/lower case to be ignored)
|
||||
* @return the corresponding Field object, or {@code null} if not found
|
||||
* @since 6.1
|
||||
*/
|
||||
@Nullable
|
||||
public static Field findFieldIgnoreCase(Class<?> clazz, String name) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Class<?> searchType = clazz;
|
||||
while (Object.class != searchType && searchType != null) {
|
||||
Field[] fields = getDeclaredFields(searchType);
|
||||
for (Field field : fields) {
|
||||
if (name.equalsIgnoreCase(field.getName())) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
searchType = searchType.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the field represented by the supplied {@linkplain Field field object} on
|
||||
* the specified {@linkplain Object target object} to the specified {@code value}.
|
||||
|
||||
@@ -61,7 +61,7 @@ import org.springframework.util.StringUtils;
|
||||
* long, Long, float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc.
|
||||
*
|
||||
* <p>To facilitate mapping between columns and properties that don't have matching
|
||||
* names, try using column aliases in the SQL statement like
|
||||
* names, try using underscore-separated column aliases in the SQL statement like
|
||||
* {@code "select fname as first_name from customer"}, where {@code first_name}
|
||||
* can be mapped to a {@code setFirstName(String)} method in the target class.
|
||||
*
|
||||
@@ -87,6 +87,7 @@ import org.springframework.util.StringUtils;
|
||||
* @since 2.5
|
||||
* @param <T> the result type
|
||||
* @see DataClassRowMapper
|
||||
* @see SimplePropertyRowMapper
|
||||
*/
|
||||
public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
|
||||
@@ -278,6 +279,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
* @param name the original name
|
||||
* @return the converted name
|
||||
* @since 4.2
|
||||
* @see #underscoreName
|
||||
*/
|
||||
protected String lowerCaseName(String name) {
|
||||
return name.toLowerCase(Locale.US);
|
||||
@@ -289,25 +291,10 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
* @param name the original name
|
||||
* @return the converted name
|
||||
* @since 4.2
|
||||
* @see #lowerCaseName
|
||||
* @see JdbcUtils#convertPropertyNameToUnderscoreName
|
||||
*/
|
||||
protected String underscoreName(String name) {
|
||||
if (!StringUtils.hasLength(name)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(Character.toLowerCase(name.charAt(0)));
|
||||
for (int i = 1; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
result.append('_').append(Character.toLowerCase(c));
|
||||
}
|
||||
else {
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
return JdbcUtils.convertPropertyNameToUnderscoreName(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ import org.springframework.util.Assert;
|
||||
* @author Sam Brannen
|
||||
* @since 5.3
|
||||
* @param <T> the result type
|
||||
* @see SimplePropertyRowMapper
|
||||
*/
|
||||
public class DataClassRowMapper<T> extends BeanPropertyRowMapper<T> {
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jdbc.core;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link RowMapper} implementation that converts a row into a new instance
|
||||
* of the specified mapped target class. The mapped target class must be a
|
||||
* top-level class or {@code static} nested class, and it may expose either a
|
||||
* <em>data class</em> constructor with named parameters corresponding to column
|
||||
* names or classic bean property setter methods with property names corresponding
|
||||
* to column names or fields with corresponding field names.
|
||||
*
|
||||
* <p>When combining a data class constructor with setter methods, any property
|
||||
* mapped successfully via a constructor argument will not be mapped additionally
|
||||
* via a corresponding setter method or field mapping. This means that constructor
|
||||
* arguments take precedence over property setter methods which in turn take
|
||||
* precedence over direct field mappings.
|
||||
*
|
||||
* <p>To facilitate mapping between columns and properties that don't have matching
|
||||
* names, try using underscore-separated column aliases in the SQL statement like
|
||||
* {@code "select fname as first_name from customer"}, where {@code first_name}
|
||||
* can be mapped to a {@code setFirstName(String)} method in the target class.
|
||||
*
|
||||
* <p>This is a flexible alternative to {@link DataClassRowMapper} and
|
||||
* {@link BeanPropertyRowMapper} for scenarios where no specific customization
|
||||
* and no pre-defined property mappings are needed.
|
||||
*
|
||||
* <p>In terms of its fallback property discovery algorithm, this class is similar to
|
||||
* {@link org.springframework.jdbc.core.namedparam.SimplePropertySqlParameterSource}
|
||||
* and is similarly used for {@link org.springframework.jdbc.core.simple.JdbcClient}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.1
|
||||
* @param <T> the result type
|
||||
* @see DataClassRowMapper
|
||||
* @see BeanPropertyRowMapper
|
||||
* @see org.springframework.jdbc.core.simple.JdbcClient.StatementSpec#query(Class)
|
||||
* @see org.springframework.jdbc.core.namedparam.SimplePropertySqlParameterSource
|
||||
*/
|
||||
public class SimplePropertyRowMapper<T> implements RowMapper<T> {
|
||||
|
||||
private static final Object NO_DESCRIPTOR = new Object();
|
||||
|
||||
private final Class<T> mappedClass;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final Constructor<T> mappedConstructor;
|
||||
|
||||
private final String[] constructorParameterNames;
|
||||
|
||||
private final TypeDescriptor[] constructorParameterTypes;
|
||||
|
||||
private final Map<String, Object> propertyDescriptors = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code SimplePropertyRowMapper}.
|
||||
* @param mappedClass the class that each row should be mapped to
|
||||
*/
|
||||
public SimplePropertyRowMapper(Class<T> mappedClass) {
|
||||
this(mappedClass, DefaultConversionService.getSharedInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code SimplePropertyRowMapper}.
|
||||
* @param mappedClass the class that each row should be mapped to
|
||||
* @param conversionService a {@link ConversionService} for binding
|
||||
* JDBC values to bean properties
|
||||
*/
|
||||
public SimplePropertyRowMapper(Class<T> mappedClass, ConversionService conversionService) {
|
||||
Assert.notNull(mappedClass, "Mapped Class must not be null");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
this.mappedClass = mappedClass;
|
||||
this.conversionService = conversionService;
|
||||
|
||||
this.mappedConstructor = BeanUtils.getResolvableConstructor(mappedClass);
|
||||
int paramCount = this.mappedConstructor.getParameterCount();
|
||||
this.constructorParameterNames = (paramCount > 0 ?
|
||||
BeanUtils.getParameterNames(this.mappedConstructor) : new String[0]);
|
||||
this.constructorParameterTypes = new TypeDescriptor[paramCount];
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
this.constructorParameterTypes[i] = new TypeDescriptor(new MethodParameter(this.mappedConstructor, i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
Object[] args = new Object[this.constructorParameterNames.length];
|
||||
Set<Integer> usedIndex = new HashSet<>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String name = this.constructorParameterNames[i];
|
||||
int index;
|
||||
try {
|
||||
// Try direct name match first
|
||||
index = rs.findColumn(name);
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
// Try underscored name match instead
|
||||
index = rs.findColumn(JdbcUtils.convertPropertyNameToUnderscoreName(name));
|
||||
}
|
||||
TypeDescriptor td = this.constructorParameterTypes[i];
|
||||
Object value = JdbcUtils.getResultSetValue(rs, index, td.getType());
|
||||
usedIndex.add(index);
|
||||
args[i] = this.conversionService.convert(value, td);
|
||||
}
|
||||
T mappedObject = BeanUtils.instantiateClass(this.mappedConstructor, args);
|
||||
|
||||
ResultSetMetaData rsmd = rs.getMetaData();
|
||||
int columnCount = rsmd.getColumnCount();
|
||||
for (int index = 1; index <= columnCount; index++) {
|
||||
if (!usedIndex.contains(index)) {
|
||||
Object desc = getDescriptor(JdbcUtils.lookupColumnName(rsmd, index));
|
||||
if (desc instanceof MethodParameter mp) {
|
||||
Method method = mp.getMethod();
|
||||
if (method != null) {
|
||||
Object value = JdbcUtils.getResultSetValue(rs, index, mp.getParameterType());
|
||||
value = this.conversionService.convert(value, new TypeDescriptor(mp));
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
ReflectionUtils.invokeMethod(method, mappedObject, value);
|
||||
}
|
||||
}
|
||||
else if (desc instanceof Field field) {
|
||||
Object value = JdbcUtils.getResultSetValue(rs, index, field.getType());
|
||||
value = this.conversionService.convert(value, new TypeDescriptor(field));
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ReflectionUtils.setField(field, mappedObject, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mappedObject;
|
||||
}
|
||||
|
||||
private Object getDescriptor(String column) {
|
||||
return this.propertyDescriptors.computeIfAbsent(column, name -> {
|
||||
|
||||
// Try direct match first
|
||||
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(this.mappedClass, name);
|
||||
if (pd != null && pd.getWriteMethod() != null) {
|
||||
return BeanUtils.getWriteMethodParameter(pd);
|
||||
}
|
||||
Field field = ReflectionUtils.findField(this.mappedClass, name);
|
||||
if (field != null) {
|
||||
return field;
|
||||
}
|
||||
|
||||
// Try de-underscored match instead
|
||||
String adaptedName = JdbcUtils.convertUnderscoreNameToPropertyName(name);
|
||||
if (!adaptedName.equals(name)) {
|
||||
pd = BeanUtils.getPropertyDescriptor(this.mappedClass, adaptedName);
|
||||
if (pd != null && pd.getWriteMethod() != null) {
|
||||
return BeanUtils.getWriteMethodParameter(pd);
|
||||
}
|
||||
field = ReflectionUtils.findField(this.mappedClass, adaptedName);
|
||||
if (field != null) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: case-insensitive match
|
||||
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(this.mappedClass);
|
||||
for (PropertyDescriptor candidate : pds) {
|
||||
if (name.equalsIgnoreCase(candidate.getName())) {
|
||||
return BeanUtils.getWriteMethodParameter(candidate);
|
||||
}
|
||||
}
|
||||
field = ReflectionUtils.findFieldIgnoreCase(this.mappedClass, name);
|
||||
if (field != null) {
|
||||
return field;
|
||||
}
|
||||
|
||||
return NO_DESCRIPTOR;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -53,6 +53,7 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
|
||||
@Nullable
|
||||
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code SingleColumnRowMapper} for bean-style configuration.
|
||||
* @see #setRequiredType
|
||||
@@ -65,7 +66,9 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
|
||||
* @param requiredType the type that each result object is expected to match
|
||||
*/
|
||||
public SingleColumnRowMapper(Class<T> requiredType) {
|
||||
setRequiredType(requiredType);
|
||||
if (requiredType != Object.class) {
|
||||
setRequiredType(requiredType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ package org.springframework.jdbc.core.namedparam;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jdbc.core.StatementCreatorUtils;
|
||||
@@ -44,12 +44,16 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @since 6.1
|
||||
* @see NamedParameterJdbcTemplate
|
||||
* @see BeanPropertySqlParameterSource
|
||||
* @see org.springframework.jdbc.core.simple.JdbcClient.StatementSpec#paramSource(Object)
|
||||
* @see org.springframework.jdbc.core.SimplePropertyRowMapper
|
||||
*/
|
||||
public class SimplePropertySqlParameterSource extends AbstractSqlParameterSource {
|
||||
|
||||
private static final Object NO_DESCRIPTOR = new Object();
|
||||
|
||||
private final Object paramObject;
|
||||
|
||||
private final Map<String, Object> descriptorMap = new HashMap<>();
|
||||
private final Map<String, Object> propertyDescriptors = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
@@ -64,7 +68,7 @@ public class SimplePropertySqlParameterSource extends AbstractSqlParameterSource
|
||||
|
||||
@Override
|
||||
public boolean hasValue(String paramName) {
|
||||
return (getDescriptor(paramName) != null);
|
||||
return (getDescriptor(paramName) != NO_DESCRIPTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,14 +107,17 @@ public class SimplePropertySqlParameterSource extends AbstractSqlParameterSource
|
||||
return TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getDescriptor(String paramName) {
|
||||
return this.descriptorMap.computeIfAbsent(paramName, name -> {
|
||||
Object pd = BeanUtils.getPropertyDescriptor(this.paramObject.getClass(), name);
|
||||
if (pd == null) {
|
||||
pd = ReflectionUtils.findField(this.paramObject.getClass(), name);
|
||||
return this.propertyDescriptors.computeIfAbsent(paramName, name -> {
|
||||
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(this.paramObject.getClass(), name);
|
||||
if (pd != null && pd.getReadMethod() != null) {
|
||||
return pd;
|
||||
}
|
||||
return pd;
|
||||
Field field = ReflectionUtils.findField(this.paramObject.getClass(), name);
|
||||
if (field != null) {
|
||||
return field;
|
||||
}
|
||||
return NO_DESCRIPTOR;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,19 @@ package org.springframework.jdbc.core.simple;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.SimplePropertyRowMapper;
|
||||
import org.springframework.jdbc.core.SingleColumnRowMapper;
|
||||
import org.springframework.jdbc.core.SqlParameterValue;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
@@ -54,6 +58,8 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
|
||||
private final NamedParameterJdbcOperations namedParamOps;
|
||||
|
||||
private final Map<Class<?>, RowMapper<?>> rowMapperCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public DefaultJdbcClient(DataSource dataSource) {
|
||||
this.classicOps = new JdbcTemplate(dataSource);
|
||||
@@ -169,6 +175,15 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
new IndexedParamResultQuerySpec());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> MappedQuerySpec<T> query(Class<T> mappedClass) {
|
||||
RowMapper<?> rowMapper = rowMapperCache.computeIfAbsent(mappedClass, key ->
|
||||
BeanUtils.isSimpleProperty(mappedClass) ? new SingleColumnRowMapper<>(mappedClass) :
|
||||
new SimplePropertyRowMapper<>(mappedClass));
|
||||
return query((RowMapper<T>) rowMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> MappedQuerySpec<T> query(RowMapper<T> rowMapper) {
|
||||
return (useNamedParams() ?
|
||||
@@ -239,9 +254,10 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
return classicOps.queryForMap(sql, indexedParams.toArray());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<T> singleColumn(Class<T> requiredType) {
|
||||
return classicOps.queryForList(sql, requiredType, indexedParams.toArray());
|
||||
public <T> List<T> singleColumn() {
|
||||
return (List<T>) classicOps.queryForList(sql, Object.class, indexedParams.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,9 +279,10 @@ final class DefaultJdbcClient implements JdbcClient {
|
||||
return namedParamOps.queryForMap(sql, namedParamSource);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<T> singleColumn(Class<T> requiredType) {
|
||||
return namedParamOps.queryForList(sql, namedParamSource, requiredType);
|
||||
public <T> List<T> singleColumn() {
|
||||
return (List<T>) namedParamOps.queryForList(sql, namedParamSource, Object.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,8 +193,10 @@ public interface JdbcClient {
|
||||
* based on its JavaBean properties, record components or raw fields.
|
||||
* A Map instance can be provided as a complete parameter source as well.
|
||||
* @param namedParamObject a custom parameter object (e.g. a JavaBean or
|
||||
* record class) with named properties serving as statement parameters
|
||||
* record class or field holder) with named properties serving as
|
||||
* statement parameters
|
||||
* @return this statement specification (for chaining)
|
||||
* @see #paramSource(SqlParameterSource)
|
||||
* @see org.springframework.jdbc.core.namedparam.MapSqlParameterSource
|
||||
* @see org.springframework.jdbc.core.namedparam.SimplePropertySqlParameterSource
|
||||
*/
|
||||
@@ -218,6 +220,19 @@ public interface JdbcClient {
|
||||
*/
|
||||
ResultQuerySpec query();
|
||||
|
||||
/**
|
||||
* Proceed towards execution of a mapped query, with several options
|
||||
* available in the returned query specification.
|
||||
* @param mappedClass the target class to apply a RowMapper for
|
||||
* (either a simple value type for a single column mapping or a
|
||||
* JavaBean / record class / field holder for a multi-column mapping)
|
||||
* @return the mapped query specification
|
||||
* @see #query(RowMapper)
|
||||
* @see org.springframework.jdbc.core.SingleColumnRowMapper
|
||||
* @see org.springframework.jdbc.core.SimplePropertyRowMapper
|
||||
*/
|
||||
<T> MappedQuerySpec<T> query(Class<T> mappedClass);
|
||||
|
||||
/**
|
||||
* Proceed towards execution of a mapped query, with several options
|
||||
* available in the returned query specification.
|
||||
@@ -296,7 +311,7 @@ public interface JdbcClient {
|
||||
* @return a (potentially empty) list of rows, with each
|
||||
* row represented as a column value of the given type
|
||||
*/
|
||||
<T> List<T> singleColumn(Class<T> requiredType);
|
||||
<T> List<T> singleColumn();
|
||||
|
||||
/**
|
||||
* Retrieve a single value result.
|
||||
@@ -304,8 +319,8 @@ public interface JdbcClient {
|
||||
* column value of the given type
|
||||
* @see DataAccessUtils#requiredSingleResult(Collection)
|
||||
*/
|
||||
default <T> T singleValue(Class<T> requiredType) {
|
||||
return DataAccessUtils.requiredSingleResult(singleColumn(requiredType));
|
||||
default <T> T singleValue() {
|
||||
return DataAccessUtils.requiredSingleResult(singleColumn());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -510,34 +510,64 @@ public abstract class JdbcUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a column name with underscores to the corresponding property name using "camel case".
|
||||
* A name like "customer_number" would match a "customerNumber" property name.
|
||||
* @param name the column name to be converted
|
||||
* @return the name using "camel case"
|
||||
* Convert a property name using "camelCase" to a corresponding column name with underscores.
|
||||
* A name like "customerNumber" would match a "customer_number" column name.
|
||||
* @param name the property name to be converted
|
||||
* @return the column name using underscores
|
||||
* @since 6.1
|
||||
* @see #convertUnderscoreNameToPropertyName
|
||||
*/
|
||||
public static String convertUnderscoreNameToPropertyName(@Nullable String name) {
|
||||
public static String convertPropertyNameToUnderscoreName(@Nullable String name) {
|
||||
if (!StringUtils.hasLength(name)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean nextIsUpper = false;
|
||||
if (name != null && name.length() > 0) {
|
||||
if (name.length() > 1 && name.charAt(1) == '_') {
|
||||
result.append(Character.toUpperCase(name.charAt(0)));
|
||||
result.append(Character.toLowerCase(name.charAt(0)));
|
||||
for (int i = 1; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
result.append('_').append(Character.toLowerCase(c));
|
||||
}
|
||||
else {
|
||||
result.append(Character.toLowerCase(name.charAt(0)));
|
||||
result.append(c);
|
||||
}
|
||||
for (int i = 1; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (c == '_') {
|
||||
nextIsUpper = true;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a column name with underscores to the corresponding property name using "camelCase".
|
||||
* A name like "customer_number" would match a "customerNumber" property name.
|
||||
* @param name the potentially underscores-based column name to be converted
|
||||
* @return the name using "camelCase"
|
||||
* @see #convertPropertyNameToUnderscoreName
|
||||
*/
|
||||
public static String convertUnderscoreNameToPropertyName(@Nullable String name) {
|
||||
if (!StringUtils.hasLength(name)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean nextIsUpper = false;
|
||||
if (name.length() > 1 && name.charAt(1) == '_') {
|
||||
result.append(Character.toUpperCase(name.charAt(0)));
|
||||
}
|
||||
else {
|
||||
result.append(Character.toLowerCase(name.charAt(0)));
|
||||
}
|
||||
for (int i = 1; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (c == '_') {
|
||||
nextIsUpper = true;
|
||||
}
|
||||
else {
|
||||
if (nextIsUpper) {
|
||||
result.append(Character.toUpperCase(c));
|
||||
nextIsUpper = false;
|
||||
}
|
||||
else {
|
||||
if (nextIsUpper) {
|
||||
result.append(Character.toUpperCase(c));
|
||||
nextIsUpper = false;
|
||||
}
|
||||
else {
|
||||
result.append(Character.toLowerCase(c));
|
||||
}
|
||||
result.append(Character.toLowerCase(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@ public abstract class AbstractRowMapperTests {
|
||||
}
|
||||
else {
|
||||
given(resultSet.findColumn("birthdate")).willThrow(new SQLException());
|
||||
given(resultSet.findColumn("birthDate")).willThrow(new SQLException());
|
||||
given(resultSet.findColumn("birth_date")).willReturn(3);
|
||||
}
|
||||
given(resultSet.findColumn("balance")).willReturn(4);
|
||||
|
||||
@@ -95,7 +95,7 @@ class DataClassRowMapperTests extends AbstractRowMapperTests {
|
||||
}
|
||||
|
||||
|
||||
static record RecordPerson(String name, long age, Date birth_date, BigDecimal balance) {
|
||||
record RecordPerson(String name, long age, Date birth_date, BigDecimal balance) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jdbc.core;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.jdbc.core.test.ConcretePerson;
|
||||
import org.springframework.jdbc.core.test.ConstructorPerson;
|
||||
import org.springframework.jdbc.core.test.ConstructorPersonWithGenerics;
|
||||
import org.springframework.jdbc.core.test.ConstructorPersonWithSetters;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SimplePropertyRowMapper}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.1
|
||||
*/
|
||||
class SimplePropertyRowMapperTests extends AbstractRowMapperTests {
|
||||
|
||||
@Test
|
||||
void staticQueryWithDataClass() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
ConstructorPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new SimplePropertyRowMapper<>(ConstructorPerson.class));
|
||||
verifyPerson(person);
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticQueryWithDataClassAndGenerics() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
ConstructorPersonWithGenerics person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new SimplePropertyRowMapper<>(ConstructorPersonWithGenerics.class));
|
||||
assertThat(person.name()).isEqualTo("Bubba");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
|
||||
assertThat(person.balance()).containsExactly(new BigDecimal("1234.56"));
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticQueryWithDataClassAndSetters() throws Exception {
|
||||
Mock mock = new Mock(MockType.FOUR);
|
||||
ConstructorPersonWithSetters person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birthdate, balance from people",
|
||||
new SimplePropertyRowMapper<>(ConstructorPersonWithSetters.class));
|
||||
assertThat(person.name()).isEqualTo("BUBBA");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
|
||||
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticQueryWithPlainSetters() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new SimplePropertyRowMapper<>(ConcretePerson.class));
|
||||
verifyPerson(person);
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticQueryWithDataRecord() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
RecordPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new SimplePropertyRowMapper<>(RecordPerson.class));
|
||||
verifyPerson(person);
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticQueryWithDataFields() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
FieldPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new SimplePropertyRowMapper<>(FieldPerson.class));
|
||||
verifyPerson(person);
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticQueryWithIncompleteDataFields() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
IncompleteFieldPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new SimplePropertyRowMapper<>(IncompleteFieldPerson.class));
|
||||
verifyPerson(person);
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
|
||||
protected void verifyPerson(RecordPerson person) {
|
||||
assertThat(person.name()).isEqualTo("Bubba");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
|
||||
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
|
||||
verifyPersonViaBeanWrapper(person);
|
||||
}
|
||||
|
||||
protected void verifyPerson(FieldPerson person) {
|
||||
assertThat(person.name).isEqualTo("Bubba");
|
||||
assertThat(person.age).isEqualTo(22L);
|
||||
assertThat(person.birth_date).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
|
||||
assertThat(person.balance).isEqualTo(new BigDecimal("1234.56"));
|
||||
}
|
||||
|
||||
protected void verifyPerson(IncompleteFieldPerson person) {
|
||||
assertThat(person.name).isEqualTo("Bubba");
|
||||
assertThat(person.age).isEqualTo(22L);
|
||||
assertThat(person.balance).isEqualTo(new BigDecimal("1234.56"));
|
||||
}
|
||||
|
||||
|
||||
record RecordPerson(String name, long age, Date birth_date, BigDecimal balance) {
|
||||
}
|
||||
|
||||
|
||||
static class FieldPerson {
|
||||
|
||||
String name;
|
||||
long age;
|
||||
Date birth_date;
|
||||
BigDecimal balance;
|
||||
}
|
||||
|
||||
|
||||
static class IncompleteFieldPerson {
|
||||
|
||||
String name;
|
||||
long age;
|
||||
BigDecimal balance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForListWithIndexedParamAndSingleRowAndColumn() throws Exception {
|
||||
public void testQueryForListWithIndexedParamAndSingleRow() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getObject(1)).willReturn(11);
|
||||
@@ -123,14 +123,14 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForListWithIndexedParamAndIntegerElementAndSingleRowAndColumn() throws Exception {
|
||||
public void testQueryForListWithIndexedParamAndSingleColumn() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(11);
|
||||
given(resultSet.getObject(1)).willReturn(11);
|
||||
|
||||
List<Integer> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?")
|
||||
.param(1, 3)
|
||||
.query().singleColumn(Integer.class);
|
||||
.query().singleColumn();
|
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(1);
|
||||
assertThat(li.get(0)).as("First row is Integer").isEqualTo(11);
|
||||
@@ -139,7 +139,7 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForMapWithIndexedParamAndSingleRowAndColumn() throws Exception {
|
||||
public void testQueryForMapWithIndexedParamAndSingleRow() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getObject(1)).willReturn(11);
|
||||
@@ -185,11 +185,11 @@ public class JdbcClientQueryTests {
|
||||
public void testQueryForObjectWithIndexedParamAndInteger() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
given(resultSet.getObject(1)).willReturn(22);
|
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?")
|
||||
.param(1, 3)
|
||||
.query().singleValue(Integer.class);
|
||||
.query().singleValue();
|
||||
|
||||
assertThat(value).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -200,11 +200,11 @@ public class JdbcClientQueryTests {
|
||||
public void testQueryForIntWithIndexedParam() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
given(resultSet.getObject(1)).willReturn(22);
|
||||
|
||||
int i = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?")
|
||||
.param(1, 3)
|
||||
.query().singleValue(Integer.class);
|
||||
.query().singleValue();
|
||||
|
||||
assertThat(i).as("Return of an int").isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -246,7 +246,7 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForListWithNamedParamAndSingleRowAndColumn() throws Exception {
|
||||
public void testQueryForListWithNamedParamAndSingleRow() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getObject(1)).willReturn(11);
|
||||
@@ -262,14 +262,14 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForListWithNamedParamAndIntegerElementAndSingleRowAndColumn() throws Exception {
|
||||
public void testQueryForListWithNamedParamAndSingleColumn() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(11);
|
||||
given(resultSet.getObject(1)).willReturn(11);
|
||||
|
||||
List<Integer> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id")
|
||||
.param("id", 3)
|
||||
.query().singleColumn(Integer.class);
|
||||
.query().singleColumn();
|
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(1);
|
||||
assertThat(li.get(0)).as("First row is Integer").isEqualTo(11);
|
||||
@@ -278,7 +278,7 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForMapWithNamedParamAndSingleRowAndColumn() throws Exception {
|
||||
public void testQueryForMapWithNamedParamAndSingleRow() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getObject(1)).willReturn(11);
|
||||
@@ -308,14 +308,63 @@ public class JdbcClientQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForObjectWithNamedParamAndInteger() throws Exception {
|
||||
public void testQueryForObjectWithNamedParamAndMappedSimpleValue() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.param("id", 3)
|
||||
.query().singleValue(Integer.class);
|
||||
.query(Integer.class)
|
||||
.single();
|
||||
|
||||
assertThat(value).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
verify(preparedStatement).setObject(1, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForObjectWithNamedParamAndMappedRecord() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.findColumn("age")).willReturn(1);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
|
||||
AgeRecord value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.param("id", 3)
|
||||
.query(AgeRecord.class)
|
||||
.single();
|
||||
|
||||
assertThat(value.age()).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
verify(preparedStatement).setObject(1, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForObjectWithNamedParamAndMappedFieldHolder() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
|
||||
AgeFieldHolder value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.param("id", 3)
|
||||
.query(AgeFieldHolder.class)
|
||||
.single();
|
||||
|
||||
assertThat(value.age).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
verify(preparedStatement).setObject(1, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForObjectWithNamedParamAndInteger() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getObject(1)).willReturn(22);
|
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.param("id", 3)
|
||||
.query().singleValue();
|
||||
|
||||
assertThat(value).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -326,11 +375,11 @@ public class JdbcClientQueryTests {
|
||||
public void testQueryForObjectWithNamedParamAndList() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
given(resultSet.getObject(1)).willReturn(22);
|
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)")
|
||||
.param("ids", Arrays.asList(3, 4))
|
||||
.query().singleValue(Integer.class);
|
||||
.query().singleValue();
|
||||
|
||||
assertThat(value).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)");
|
||||
@@ -341,14 +390,14 @@ public class JdbcClientQueryTests {
|
||||
public void testQueryForObjectWithNamedParamAndListOfExpressionLists() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
given(resultSet.getObject(1)).willReturn(22);
|
||||
|
||||
List<Object[]> l1 = new ArrayList<>();
|
||||
l1.add(new Object[] {3, "Rod"});
|
||||
l1.add(new Object[] {4, "Juergen"});
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN (:multiExpressionList)")
|
||||
.param("multiExpressionList", l1)
|
||||
.query().singleValue(Integer.class);
|
||||
.query().singleValue();
|
||||
|
||||
assertThat(value).isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN ((?, ?), (?, ?))");
|
||||
@@ -359,11 +408,11 @@ public class JdbcClientQueryTests {
|
||||
public void testQueryForIntWithNamedParam() throws Exception {
|
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(22);
|
||||
given(resultSet.getObject(1)).willReturn(22);
|
||||
|
||||
int i = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.param("id", 3)
|
||||
.query().singleValue(Integer.class);
|
||||
.query().singleValue();
|
||||
|
||||
assertThat(i).as("Return of an int").isEqualTo(22);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -378,7 +427,7 @@ public class JdbcClientQueryTests {
|
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.paramSource(new ParameterBean(3))
|
||||
.query().singleValue(Long.class);
|
||||
.query(Long.class).single();
|
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -393,7 +442,7 @@ public class JdbcClientQueryTests {
|
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)")
|
||||
.paramSource(new ParameterCollectionBean(3, 5))
|
||||
.query().singleValue(Long.class);
|
||||
.query(Long.class).single();
|
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)");
|
||||
@@ -409,7 +458,7 @@ public class JdbcClientQueryTests {
|
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.paramSource(new ParameterRecord(3))
|
||||
.query().singleValue(Long.class);
|
||||
.query(Long.class).single();
|
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -424,7 +473,7 @@ public class JdbcClientQueryTests {
|
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
|
||||
.paramSource(new ParameterFieldHolder(3))
|
||||
.query().singleValue(Long.class);
|
||||
.query(Long.class).single();
|
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87);
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
|
||||
@@ -473,4 +522,14 @@ public class JdbcClientQueryTests {
|
||||
public int id;
|
||||
}
|
||||
|
||||
|
||||
record AgeRecord(int age) {
|
||||
}
|
||||
|
||||
|
||||
static class AgeFieldHolder {
|
||||
|
||||
public int age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -29,15 +29,15 @@ public class ConstructorPersonWithGenerics {
|
||||
|
||||
private final long age;
|
||||
|
||||
private final Date birthDate;
|
||||
private final Date bd;
|
||||
|
||||
private final List<BigDecimal> balance;
|
||||
|
||||
|
||||
public ConstructorPersonWithGenerics(String name, long age, Date birth_date, List<BigDecimal> balance) {
|
||||
public ConstructorPersonWithGenerics(String name, long age, Date birthDate, List<BigDecimal> balance) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.birthDate = birth_date;
|
||||
this.bd = birthDate;
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class ConstructorPersonWithGenerics {
|
||||
}
|
||||
|
||||
public Date birthDate() {
|
||||
return this.birthDate;
|
||||
return this.bd;
|
||||
}
|
||||
|
||||
public List<BigDecimal> balance() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -33,20 +33,19 @@ public class ConstructorPersonWithSetters {
|
||||
private BigDecimal balance;
|
||||
|
||||
|
||||
public ConstructorPersonWithSetters(String name, long age, Date birthDate, BigDecimal balance) {
|
||||
public ConstructorPersonWithSetters(String name, long age, BigDecimal balance) {
|
||||
this.name = name.toUpperCase();
|
||||
this.age = age;
|
||||
this.birthDate = birthDate;
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void setAge(long age) {
|
||||
this.age = age;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void setBirthDate(Date birthDate) {
|
||||
@@ -54,7 +53,7 @@ public class ConstructorPersonWithSetters {
|
||||
}
|
||||
|
||||
public void setBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public String name() {
|
||||
|
||||
Reference in New Issue
Block a user