Created
September 19, 2013 20:07
-
-
Save stianl/6629122 to your computer and use it in GitHub Desktop.
Implementation of BeanPropertyRowMapper from spring jdbc that enables the use of value objects/DTO without getters, setter, lombok and credit cards Should probably also check field level access to only allow setting public fields, maybe some other time.
Unfortunately methods like underscoreName from BeanPropertyRowMapper is private so it's copie…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.beans.*; | |
import org.springframework.dao.DataRetrievalFailureException; | |
import org.springframework.dao.InvalidDataAccessApiUsageException; | |
import org.springframework.jdbc.core.BeanPropertyRowMapper; | |
import org.springframework.jdbc.support.JdbcUtils; | |
import org.springframework.util.Assert; | |
import org.springframework.util.StringUtils; | |
import java.beans.PropertyDescriptor; | |
import java.lang.reflect.Field; | |
import java.sql.ResultSet; | |
import java.sql.ResultSetMetaData; | |
import java.sql.SQLException; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
/** | |
* Enables the use of value objects/DTO without getters, setter, lombok and credit cards: | |
* | |
* SQL: select smell_like, taste_like from coffeebeans | |
* | |
* Bean: | |
* | |
* public class Bean { | |
* public String smellLike; | |
* public String tasteLike; | |
* } | |
*/ | |
public class DirectFieldRowMapper<T> extends BeanPropertyRowMapper<T> { | |
private boolean primitivesDefaultedForNullValue = false; | |
private Map<String, Field> mappedFields; | |
private Class<T> mappedClass; | |
private Set<String> mappedProperties; | |
public DirectFieldRowMapper(Class<T> dealerGroupClass) { | |
super(dealerGroupClass); | |
} | |
/** | |
* Copied from BeanPropertyRowMapper | |
* <p/> | |
* Initialize the mapping metadata for the given class. | |
* | |
* @param mappedClass the mapped class. | |
*/ | |
@Override | |
protected void initialize(Class<T> mappedClass) { | |
this.mappedClass = mappedClass; | |
this.mappedFields = new HashMap<String, Field>(); | |
this.mappedProperties = new HashSet<String>(); | |
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass); | |
Field[] declaredFields = mappedClass.getDeclaredFields(); | |
for (Field declaredField : declaredFields) { | |
this.mappedFields.put(declaredField.getName().toLowerCase(), declaredField); | |
String underscoredName = underscoreName(declaredField.getName()).toLowerCase(); | |
if (!declaredField.getName().toLowerCase().equals(underscoredName)) | |
this.mappedFields.put(underscoredName, declaredField); | |
this.mappedProperties.add(declaredField.getName()); | |
} | |
} | |
/** | |
* Copied from BeanPropertyRowMapper | |
* <p/> | |
* 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. | |
* | |
* @param name the string containing original name | |
* @return the converted name | |
*/ | |
private String underscoreName(String name) { | |
if (!StringUtils.hasLength(name)) { | |
return ""; | |
} | |
StringBuilder result = new StringBuilder(); | |
result.append(name.substring(0, 1).toLowerCase()); | |
for (int i = 1; i < name.length(); i++) { | |
String s = name.substring(i, i + 1); | |
String slc = s.toLowerCase(); | |
if (!s.equals(slc)) { | |
result.append("_").append(slc); | |
} else { | |
result.append(s); | |
} | |
} | |
return result.toString(); | |
} | |
@Override | |
public T mapRow(ResultSet rs, int rowNum) throws SQLException { | |
Assert.state(this.mappedClass != null, "Mapped class was not specified"); | |
T mappedObject = BeanUtils.instantiate(this.mappedClass); | |
ConfigurablePropertyAccessor bw = PropertyAccessorFactory.forDirectFieldAccess(mappedObject); | |
ResultSetMetaData rsmd = rs.getMetaData(); | |
int columnCount = rsmd.getColumnCount(); | |
Set<String> populatedProperties = (isCheckFullyPopulated() ? new HashSet<String>() : null); | |
for (int index = 1; index <= columnCount; index++) { | |
String column = JdbcUtils.lookupColumnName(rsmd, index); | |
Field f = this.mappedFields.get(column.replaceAll(" ", "").toLowerCase()); | |
if (f != null) { | |
try { | |
Object value = getColumnValue(rs, index, f); | |
if (logger.isDebugEnabled() && rowNum == 0) { | |
logger.debug("Mapping column '" + column + "' to property '" + | |
f.getName() + "' of type " + f.getType()); | |
} | |
try { | |
bw.setPropertyValue(f.getName(), value); | |
} catch (TypeMismatchException e) { | |
if (value == null && primitivesDefaultedForNullValue) { | |
logger.debug("Intercepted TypeMismatchException for row " + rowNum + | |
" and column '" + column + "' with value " + value + | |
" when setting property '" + f.getName() + "' of type " + f.getClass() + | |
" on object: " + mappedObject); | |
} else { | |
throw e; | |
} | |
} | |
if (populatedProperties != null) { | |
populatedProperties.add(f.getName()); | |
} | |
} catch (NotWritablePropertyException ex) { | |
throw new DataRetrievalFailureException( | |
"Unable to map column " + column + " to property " + f.getName(), ex); | |
} | |
} | |
} | |
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) { | |
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " + | |
"necessary to populate object of class [" + this.mappedClass + "]: " + this.mappedProperties); | |
} | |
return mappedObject; | |
} | |
private Object getColumnValue(ResultSet rs, int index, Field f) throws SQLException { | |
return JdbcUtils.getResultSetValue(rs, index, f.getType()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment