I did not find nothing for now, I tried to write a custom BeanWrapper base on BeanPropertyRowMapper soruce.
import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
/**
* @author tobia.scapin
*
* BeanRowMapper for nesting beans of 1:n entity this uses query aliases to build entity nesting.
* Field names should be exactly the same of bean property, respect cases and do not use underscore for field names
* "id" columnname/alias is used to check if a nested entity should be null.
*
* example:
* select
* a.p1 as property1
* b.id as entityname_id //<-- if this is values is null, the entity will be null
* b.p1 as entityname_property1
* b.p2 as entityname_property2
* c.id as entityname_subentity_id //<-- if this is values is null, the subentity will be null
* c.p1 as entityname_subentity_property1
* from a,b,c
*
* @param <T>
*/
public class NestedBeanAliasRowMapper<T> implements RowMapper<T> {
private static final String NESTING_SEPARATOR = "_";
private static final String NULLIZER_FIELD = "id";
@SuppressWarnings("rawtypes")
private final static List<Class> TYPES;
static{
TYPES=Arrays.asList(new Class[]{ int.class, boolean.class, byte.class, short.class, long.class, double.class, float.class, Boolean.class, Integer.class, Byte.class, Short.class, Long.class, BigDecimal.class, Double.class, Float.class, String.class, Date.class});
}
private Class<T> mappedClass;
private Map<String, PropertyDescriptor> mappedFields;
private Map<String, PropertyDescriptor> mappedBeans;
@SuppressWarnings("rawtypes")
private Map<Class,NestedBeanAliasRowMapper> mappersCache=new HashMap<Class,NestedBeanAliasRowMapper>();
private Map<String,BeanProp> beanproperties=null;
public NestedBeanAliasRowMapper(Class<T> mappedClass) {
initialize(mappedClass);
}
/**
* Initialize the mapping metadata for the given class.
* @param mappedClass the mapped class
*/
protected void initialize(Class<T> mappedClass) {
this.mappedClass = mappedClass;
mappersCache.put(mappedClass, this);
this.mappedFields = new HashMap<String, PropertyDescriptor>();
this.mappedBeans = new HashMap<String, PropertyDescriptor>();
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
if(TYPES.contains(pd.getPropertyType()))
this.mappedFields.put(pd.getName(), pd);
else
this.mappedBeans.put(pd.getName(), pd);
}
}
}
@Override
public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
List<Integer> cols=new ArrayList<Integer>();
for (int index = 1; index <= columnCount; index++)
cols.add(index);
return mapRow(rs, rowNumber, cols, "", true);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public T mapRow(ResultSet rs, int rowNumber, List<Integer> cols, String aliasPrefix, boolean root) throws SQLException {
T mappedObject = BeanUtils.instantiate(this.mappedClass);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
ResultSetMetaData rsmd = rs.getMetaData();
if(rowNumber==0) beanproperties=new HashMap<String,BeanProp>();
for (int index : cols) {
String column = JdbcUtils.lookupColumnName(rsmd, index);
if(aliasPrefix!=null && column.length()>aliasPrefix.length() && column.substring(0, aliasPrefix.length()).equals(aliasPrefix))
column=column.substring(aliasPrefix.length()); //remove the prefix from column-name
PropertyDescriptor pd = this.mappedFields.get(column);
if (pd != null) {
try {
Object value = getColumnValue(rs, index, pd);
if(!root && NULLIZER_FIELD.equals(column) && value==null)
return null;
bw.setPropertyValue(pd.getName(), value);
}
catch (NotWritablePropertyException ex) {
throw new DataRetrievalFailureException("Unable to map column '" + column + "' to property '" + pd.getName() + "'", ex);
}
}else if(rowNumber==0 && column.contains(NESTING_SEPARATOR)){
String[] arr=column.split(NESTING_SEPARATOR);
column=arr[0];
PropertyDescriptor bpd = this.mappedBeans.get(column);
if(bpd!=null){
BeanProp beanprop=beanproperties.get(column);
if(beanprop==null){
beanprop=new BeanProp();
beanprop.setClazz(bpd.getPropertyType());
beanproperties.put(column, beanprop);
}
beanprop.addIndex(index);
}
}
}
if(!beanproperties.isEmpty()) for (String beanname : beanproperties.keySet()) {
BeanProp beanprop=beanproperties.get(beanname);
NestedBeanAliasRowMapper mapper=mappersCache.get(beanprop.getClazz());
if(mapper==null){
mapper=new NestedBeanAliasRowMapper<>(beanprop.getClazz());
mappersCache.put(beanprop.getClazz(), mapper);
}
Object value = mapper.mapRow(rs, rowNumber, beanprop.getIndexes(), aliasPrefix+beanname+NESTING_SEPARATOR, false);
bw.setPropertyValue(beanname, value);
}
return mappedObject;
}
protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException {
return JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType());
}
public static <T> BeanPropertyRowMapper<T> newInstance(Class<T> mappedClass) {
return new BeanPropertyRowMapper<T>(mappedClass);
}
@SuppressWarnings("rawtypes")
private class BeanProp{
private Class clazz;
private List<Integer> indexes=new ArrayList<Integer>();
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public List<Integer> getIndexes() {
return indexes;
}
public void addIndex(Integer index) {
this.indexes.add(index);
}
}
}