|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2013 the original author or authors. |
|
|
|
* Copyright 2002-2016 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -37,6 +37,7 @@ import org.springframework.dao.DataRetrievalFailureException; |
|
|
|
import org.springframework.dao.InvalidDataAccessApiUsageException; |
|
|
|
import org.springframework.dao.InvalidDataAccessApiUsageException; |
|
|
|
import org.springframework.jdbc.support.JdbcUtils; |
|
|
|
import org.springframework.jdbc.support.JdbcUtils; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
|
|
|
|
import org.springframework.util.ClassUtils; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -56,14 +57,14 @@ import org.springframework.util.StringUtils; |
|
|
|
* <p>To facilitate mapping between columns and fields that don't have matching names, |
|
|
|
* <p>To facilitate mapping between columns and fields that don't have matching names, |
|
|
|
* try using column aliases in the SQL statement like "select fname as first_name from customer". |
|
|
|
* try using column aliases in the SQL statement like "select fname as first_name from customer". |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>For 'null' values read from the databasem, we will attempt to call the setter, but in the case of |
|
|
|
* <p>For 'null' values read from the database, we will attempt to call the setter, but in the case of |
|
|
|
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the |
|
|
|
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the |
|
|
|
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value. |
|
|
|
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value. |
|
|
|
* Be aware that if you use the values from the generated bean to update the database the primitive value |
|
|
|
* Be aware that if you use the values from the generated bean to update the database the primitive value |
|
|
|
* will have been set to the primitive's default value instead of null. |
|
|
|
* will have been set to the primitive's default value instead of null. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>Please note that this class is designed to provide convenience rather than high performance. |
|
|
|
* <p>Please note that this class is designed to provide convenience rather than high performance. |
|
|
|
* For best performance consider using a custom RowMapper. |
|
|
|
* For best performance, consider using a custom {@link RowMapper} implementation. |
|
|
|
* |
|
|
|
* |
|
|
|
* @author Thomas Risberg |
|
|
|
* @author Thomas Risberg |
|
|
|
* @author Juergen Hoeller |
|
|
|
* @author Juergen Hoeller |
|
|
|
@ -91,7 +92,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new BeanPropertyRowMapper for bean-style configuration. |
|
|
|
* Create a new {@code BeanPropertyRowMapper} for bean-style configuration. |
|
|
|
* @see #setMappedClass |
|
|
|
* @see #setMappedClass |
|
|
|
* @see #setCheckFullyPopulated |
|
|
|
* @see #setCheckFullyPopulated |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@ -99,8 +100,8 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new BeanPropertyRowMapper, accepting unpopulated properties |
|
|
|
* Create a new {@code BeanPropertyRowMapper}, accepting unpopulated |
|
|
|
* in the target bean. |
|
|
|
* properties in the target bean. |
|
|
|
* <p>Consider using the {@link #newInstance} factory method instead, |
|
|
|
* <p>Consider using the {@link #newInstance} factory method instead, |
|
|
|
* which allows for specifying the mapped type once only. |
|
|
|
* which allows for specifying the mapped type once only. |
|
|
|
* @param mappedClass the class that each row should be mapped to |
|
|
|
* @param mappedClass the class that each row should be mapped to |
|
|
|
@ -110,7 +111,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new BeanPropertyRowMapper. |
|
|
|
* Create a new {@code BeanPropertyRowMapper}. |
|
|
|
* @param mappedClass the class that each row should be mapped to |
|
|
|
* @param mappedClass the class that each row should be mapped to |
|
|
|
* @param checkFullyPopulated whether we're strictly validating that |
|
|
|
* @param checkFullyPopulated whether we're strictly validating that |
|
|
|
* all bean properties have been mapped from corresponding database fields |
|
|
|
* all bean properties have been mapped from corresponding database fields |
|
|
|
@ -136,9 +137,51 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Get the class that we are mapping to. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public final Class<T> getMappedClass() { |
|
|
|
|
|
|
|
return this.mappedClass; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Set whether we're strictly validating that all bean properties have been mapped |
|
|
|
|
|
|
|
* from corresponding database fields. |
|
|
|
|
|
|
|
* <p>Default is {@code false}, accepting unpopulated properties in the target bean. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setCheckFullyPopulated(boolean checkFullyPopulated) { |
|
|
|
|
|
|
|
this.checkFullyPopulated = checkFullyPopulated; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Return whether we're strictly validating that all bean properties have been |
|
|
|
|
|
|
|
* mapped from corresponding database fields. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public boolean isCheckFullyPopulated() { |
|
|
|
|
|
|
|
return this.checkFullyPopulated; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Set whether we're defaulting Java primitives in the case of mapping a null value |
|
|
|
|
|
|
|
* from corresponding database fields. |
|
|
|
|
|
|
|
* <p>Default is {@code false}, throwing an exception when nulls are mapped to Java primitives. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) { |
|
|
|
|
|
|
|
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Return whether we're defaulting Java primitives in the case of mapping a null value |
|
|
|
|
|
|
|
* from corresponding database fields. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public boolean isPrimitivesDefaultedForNullValue() { |
|
|
|
|
|
|
|
return this.primitivesDefaultedForNullValue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Initialize the mapping metadata for the given class. |
|
|
|
* Initialize the mapping metadata for the given class. |
|
|
|
* @param mappedClass the mapped class. |
|
|
|
* @param mappedClass the mapped class
|
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected void initialize(Class<T> mappedClass) { |
|
|
|
protected void initialize(Class<T> mappedClass) { |
|
|
|
this.mappedClass = mappedClass; |
|
|
|
this.mappedClass = mappedClass; |
|
|
|
@ -160,7 +203,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
/** |
|
|
|
/** |
|
|
|
* Convert a name in camelCase to an underscored name in lower case. |
|
|
|
* Convert a name in camelCase to an underscored name in lower case. |
|
|
|
* Any upper case letters are converted to lower case with a preceding underscore. |
|
|
|
* Any upper case letters are converted to lower case with a preceding underscore. |
|
|
|
* @param name the string containing original name |
|
|
|
* @param name the original name |
|
|
|
* @return the converted name |
|
|
|
* @return the converted name |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private String underscoreName(String name) { |
|
|
|
private String underscoreName(String name) { |
|
|
|
@ -182,48 +225,6 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
return result.toString(); |
|
|
|
return result.toString(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Get the class that we are mapping to. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public final Class<T> getMappedClass() { |
|
|
|
|
|
|
|
return this.mappedClass; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Set whether we're strictly validating that all bean properties have been |
|
|
|
|
|
|
|
* mapped from corresponding database fields. |
|
|
|
|
|
|
|
* <p>Default is {@code false}, accepting unpopulated properties in the |
|
|
|
|
|
|
|
* target bean. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setCheckFullyPopulated(boolean checkFullyPopulated) { |
|
|
|
|
|
|
|
this.checkFullyPopulated = checkFullyPopulated; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Return whether we're strictly validating that all bean properties have been |
|
|
|
|
|
|
|
* mapped from corresponding database fields. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public boolean isCheckFullyPopulated() { |
|
|
|
|
|
|
|
return this.checkFullyPopulated; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Set whether we're defaulting Java primitives in the case of mapping a null value |
|
|
|
|
|
|
|
* from corresponding database fields. |
|
|
|
|
|
|
|
* <p>Default is {@code false}, throwing an exception when nulls are mapped to Java primitives. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) { |
|
|
|
|
|
|
|
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Return whether we're defaulting Java primitives in the case of mapping a null value |
|
|
|
|
|
|
|
* from corresponding database fields. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public boolean isPrimitivesDefaultedForNullValue() { |
|
|
|
|
|
|
|
return primitivesDefaultedForNullValue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Extract the values for all columns in the current row. |
|
|
|
* Extract the values for all columns in the current row. |
|
|
|
@ -242,26 +243,30 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
|
|
|
|
|
|
|
|
for (int index = 1; index <= columnCount; index++) { |
|
|
|
for (int index = 1; index <= columnCount; index++) { |
|
|
|
String column = JdbcUtils.lookupColumnName(rsmd, index); |
|
|
|
String column = JdbcUtils.lookupColumnName(rsmd, index); |
|
|
|
PropertyDescriptor pd = this.mappedFields.get(column.replaceAll(" ", "").toLowerCase()); |
|
|
|
String field = column.replaceAll(" ", "").toLowerCase(); |
|
|
|
|
|
|
|
PropertyDescriptor pd = this.mappedFields.get(field); |
|
|
|
if (pd != null) { |
|
|
|
if (pd != null) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
Object value = getColumnValue(rs, index, pd); |
|
|
|
Object value = getColumnValue(rs, index, pd); |
|
|
|
if (logger.isDebugEnabled() && rowNumber == 0) { |
|
|
|
if (rowNumber == 0 && logger.isDebugEnabled()) { |
|
|
|
logger.debug("Mapping column '" + column + "' to property '" + |
|
|
|
logger.debug("Mapping column '" + column + "' to property '" + pd.getName() + |
|
|
|
pd.getName() + "' of type " + pd.getPropertyType()); |
|
|
|
"' of type [" + ClassUtils.getQualifiedName(pd.getPropertyType()) + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
try { |
|
|
|
try { |
|
|
|
bw.setPropertyValue(pd.getName(), value); |
|
|
|
bw.setPropertyValue(pd.getName(), value); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (TypeMismatchException e) { |
|
|
|
catch (TypeMismatchException ex) { |
|
|
|
if (value == null && primitivesDefaultedForNullValue) { |
|
|
|
if (value == null && this.primitivesDefaultedForNullValue) { |
|
|
|
logger.debug("Intercepted TypeMismatchException for row " + rowNumber + |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
" and column '" + column + "' with value " + value + |
|
|
|
logger.debug("Intercepted TypeMismatchException for row " + rowNumber + |
|
|
|
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() + |
|
|
|
" and column '" + column + "' with null value when setting property '" + |
|
|
|
" on object: " + mappedObject); |
|
|
|
pd.getName() + "' of type [" + |
|
|
|
|
|
|
|
ClassUtils.getQualifiedName(pd.getPropertyType()) + |
|
|
|
|
|
|
|
"] on object: " + mappedObject, ex); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
throw e; |
|
|
|
throw ex; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (populatedProperties != null) { |
|
|
|
if (populatedProperties != null) { |
|
|
|
@ -270,14 +275,21 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
} |
|
|
|
} |
|
|
|
catch (NotWritablePropertyException ex) { |
|
|
|
catch (NotWritablePropertyException ex) { |
|
|
|
throw new DataRetrievalFailureException( |
|
|
|
throw new DataRetrievalFailureException( |
|
|
|
"Unable to map column " + column + " to property " + pd.getName(), ex); |
|
|
|
"Unable to map column '" + column + "' to property '" + pd.getName() + "'", ex); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
// No PropertyDescriptor found
|
|
|
|
|
|
|
|
if (rowNumber == 0 && logger.isDebugEnabled()) { |
|
|
|
|
|
|
|
logger.debug("No property found for column '" + column + "' mapped to field '" + field + "'"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) { |
|
|
|
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) { |
|
|
|
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " + |
|
|
|
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " + |
|
|
|
"necessary to populate object of class [" + this.mappedClass + "]: " + this.mappedProperties); |
|
|
|
"necessary to populate object of class [" + this.mappedClass.getName() + "]: " + |
|
|
|
|
|
|
|
this.mappedProperties); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return mappedObject; |
|
|
|
return mappedObject; |
|
|
|
@ -312,7 +324,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Static factory method to create a new BeanPropertyRowMapper |
|
|
|
* Static factory method to create a new {@code BeanPropertyRowMapper} |
|
|
|
* (with the mapped class specified only once). |
|
|
|
* (with the mapped class specified only once). |
|
|
|
* @param mappedClass the class that each row should be mapped to |
|
|
|
* @param mappedClass the class that each row should be mapped to |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
|