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:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user