Last active
October 8, 2017 19:38
-
-
Save DheerendraRathor/564a20a625b099abbe83 to your computer and use it in GitHub Desktop.
Mapping JDBI fluent queries to advance JavaBeans as nested objects
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 java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.sql.ResultSet; | |
import java.sql.ResultSetMetaData; | |
import java.sql.SQLException; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import org.apache.commons.beanutils.BeanUtils; | |
import org.apache.commons.beanutils.ConvertUtils; | |
import org.apache.commons.beanutils.Converter; | |
import org.apache.commons.beanutils.converters.AbstractConverter; | |
import org.apache.commons.beanutils.converters.DateConverter; | |
import org.joda.time.DateTime; | |
import org.skife.jdbi.v2.StatementContext; | |
import org.skife.jdbi.v2.tweak.ResultSetMapper; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import ch.qos.logback.core.pattern.ConverterUtil; | |
/** | |
*Generic Mapper for every Class type with compatible java type with sql Types | |
* @author dheerendra Created on Jun 29, 2014 | |
* | |
* @param <T> | |
*/ | |
public class GenericMapper<Type> implements ResultSetMapper<Type> { | |
/** | |
* Final Class in which you want you response | |
*/ | |
private Class<Type> clazz; | |
/** | |
* Character by which your sql column names are linked <br> | |
* eg. a.b.c.fieldName , here linker is '.' <br> | |
* OR a_b_c_fieldName, here linker is '_' | |
*/ | |
private Character linker = '_'; | |
private static final Logger LOG = LoggerFactory.getLogger(GenericMapper.class); | |
/** | |
* Constructor to be used When default linker '_' is assumed | |
* This is Constructor of GenericMapper.java | |
* @param clazz | |
*/ | |
public GenericMapper(Class<Type> clazz) { | |
this.clazz = clazz; | |
} | |
/** | |
* | |
* This is Constructor of GenericMapper.java | |
* @param clazz | |
* @param linker | |
*/ | |
public GenericMapper(Class<Type> clazz, Character linker) { | |
this.clazz = clazz; | |
this.linker = linker; | |
} | |
/** | |
* Fucntion of Registering converters from Object to type<br> | |
* Must Register a converter when non java.* classes are used | |
* @param converter | |
* @param type | |
* @see Converter | |
* @see AbstractConverter | |
*/ | |
public void register(Converter converter, Class<?> type) { | |
ConvertUtils.register(converter, type); | |
} | |
/** | |
* Mapper Function | |
* <ul> How it works | |
* <li> First it extracts all columns present in ResultSet <br> | |
* Using ResultSetMetaData </li> | |
* <li> Call the function {@link #createMap(List)}</li> | |
* <li> Call function {@link #mapHelper(String, Map, ResultSet, Class)}</li> | |
* </ul> | |
* @see ResultSetMapper#map(int, ResultSet, StatementContext) | |
* | |
* | |
*/ | |
@Override | |
public Type map(int index, ResultSet rs, StatementContext ctx) throws SQLException { | |
ResultSetMetaData rsmt = rs.getMetaData(); | |
/** | |
* Getting all columns present in ResultSetMetaData | |
*/ | |
List<String> columnName = new ArrayList<String>(); | |
Integer columnCount = rsmt.getColumnCount(); | |
for (int i = 1; i <= columnCount; i++) { | |
columnName.add(rsmt.getColumnName(i)); | |
} | |
Map<String, List<String>> map = createMap(columnName); | |
Type returnObject = null; | |
try { | |
returnObject = mapHelper("", map, rs, clazz); | |
} catch (InstantiationException | IllegalAccessException | NoSuchFieldException | SecurityException e) { | |
LOG.info("Error in Generic Mapper", e); | |
} | |
return returnObject; | |
} | |
/** | |
* How it works | |
* <ol> | |
* <li> First it creates a newInstance of clazz </li> | |
* <li> Get all fields (public+ private) and create a HashMap <br> | |
* of form (fieldName(in lower case), Field) which will be used for <br> | |
* mapping columns from resultSet to Fields</li> | |
* <li> Get simple fields (non nested classes like int, Integer, Strings etc) <br> | |
* and set their property by fetching objects from ResultSet with <br> | |
* their fieldNames </li> | |
* <li> For non primitive Objects, it create another nested map with their property<br> | |
* and recurse it on mapHelper with same ResultSet and same new classType</li> | |
* <li> Return the constructed Object </li> | |
* </ol> | |
* @param prepand | |
* @param map | |
* @param rs | |
* @param clazz | |
* @return mapped object of type Type from resultset | |
* @throws InstantiationException | |
* @throws SQLException | |
* @throws IllegalAccessException | |
* @throws NoSuchFieldException | |
* @throws SecurityException | |
*/ | |
private Type mapHelper(String prepand, Map<String, List<String>> map, ResultSet rs, Class<Type> clazz) | |
throws InstantiationException, SQLException, IllegalAccessException, NoSuchFieldException, SecurityException { | |
/** | |
* Instantiating Object of Classs | |
*/ | |
Type clazzInst = clazz.newInstance(); | |
/** | |
* Getting list of all fields in class (private + public) | |
*/ | |
Field[] fields = clazz.getDeclaredFields(); | |
Map<String, Field> fieldMap = new HashMap<String, Field>(); | |
/** | |
* Mapping name of fields to themselves for sql columns (as they are in lowercase by default) | |
*/ | |
for (Field fieldIter : fields) { | |
fieldMap.put(fieldIter.getName().toLowerCase(), fieldIter); | |
} | |
for (Entry<String, List<String>> entry : map.entrySet()) { | |
String key = entry.getKey(); | |
if (key == "default") { | |
List<String> values = entry.getValue(); | |
for (String str : values) { | |
/** | |
* Getting field which is mapped to column in sql resultset | |
*/ | |
Field field = fieldMap.get(str.toLowerCase()); | |
field.setAccessible(true); | |
/** | |
* Setting value to field of clazzInst from resultset | |
*/ | |
try { | |
BeanUtils.setProperty(clazzInst, field.getName(), rs.getObject(prepand + str)); | |
} catch (InvocationTargetException e) { | |
LOG.info( | |
"Invocation Exception while setting property to " + field.getName() + " in class " + clazz.getName(), e); | |
} | |
} | |
} else { | |
Map<String, List<String>> tempMap = createMap(entry.getValue()); | |
Field field = fieldMap.get(key.toLowerCase()); | |
field.setAccessible(true); | |
try { | |
BeanUtils.setProperty(clazzInst, field.getName(), | |
mapHelper(prepand + key + linker, tempMap, rs, (Class<Type>) field.getType())); | |
} catch (InvocationTargetException e) { | |
LOG.info( | |
"Invocation Exception while setting property to " + field.getName() + " in class " + clazz.getName(), e); | |
} | |
} | |
} | |
return clazzInst; | |
} | |
/** | |
* Create a Map of nested objects of level 1 <br> | |
* eg. for column names {"id","text","classAInstant_id","classBInstant_id","classAInstant_name","a_b_c","a_b_d"}<br> | |
* will convert to ["default" => {"id","text"}, "classAInstant" => {"id","name"}, "classBInstant" => {"id"}, "a" => {"b_c","b_d"}] | |
* @param list | |
* @return | |
*/ | |
private Map<String, List<String>> createMap(List<String> list) { | |
Map<String, List<String>> map = new HashMap<String, List<String>>(); | |
for (String str : list) { | |
/** | |
* Checking index of linker<br> | |
* Checking whether linker is present or not | |
*/ | |
int index = str.indexOf(linker); | |
/** | |
* In case of linker is not there | |
*/ | |
if (index == -1) { | |
List<String> temp = map.get("default"); | |
if (temp == null) { | |
temp = new ArrayList<String>(); | |
} | |
temp.add(str); | |
map.put("default", temp); | |
} else { | |
/** | |
* Getting substring till first instant of linker | |
*/ | |
String abc = str.substring(0, index); | |
List<String> temp = map.get(abc); | |
if (temp == null) { | |
temp = new ArrayList<String>(); | |
} | |
/** | |
* Gettign substring after the first linkers | |
*/ | |
temp.add(str.substring(index + 1)); | |
map.put(abc, temp); | |
} | |
} | |
return map; | |
} | |
} |
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
/* | |
A SampleConverter implementation of org.apache.commons.beanutils.converters.Converter for registering converters in GenericMapper | |
Raw | |
*/ | |
import java.sql.Timestamp; | |
import java.util.Date; | |
import org.apache.commons.beanutils.converters.AbstractConverter; | |
import org.joda.time.DateTime; | |
/** | |
* A CoverterClass for converting java.sql.Timestamp to com.joda.time.DateTime | |
* @author dheerendra Created on Jul 1, 2014 | |
* | |
*/ | |
public class SampleConverter extends AbstractConverter{ | |
@Override | |
protected <T> T convertToType(Class<T> type, Object value) throws Throwable { | |
if (value instanceof Timestamp) { | |
Long milli = ((Timestamp) value).getTime(); | |
Date normalDate = new Date(milli); | |
DateTime dt = new DateTime(normalDate); | |
return (T) dt; | |
} | |
return null; | |
} | |
@Override | |
protected Class<?> getDefaultType() { | |
return DateTime.class; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment