Skip to content

Instantly share code, notes, and snippets.

@William-Lake
Created October 11, 2018 18:55
Show Gist options
  • Save William-Lake/97102142112598d77368f024d9ccb6f7 to your computer and use it in GitHub Desktop.
Save William-Lake/97102142112598d77368f024d9ccb6f7 to your computer and use it in GitHub Desktop.
Generic ResultSet to POJO Converter
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* RSToPOJOConverter
*
* Converts a ResultSet to a List of Objects.
*
* Built this Class to avoid having to rewrite
* different methods for building various objects
* individually.
*
* Only works if the following is true:
*
* 1. The ResultSet field names match up with the names
* of the associated target Class fields like so:
* EXAMPLE_VALUE -> exampleValue
*
* 2. The target class fields are primitive values.
*
* You can overcome #1 via SQL aliases: http://www.geeksengine.com/database/basic-select/column-alias.php
*
* You can overcome #2 by only passing in ResultSets whose
* field values match with only the primitive fields in the
* target Class. E.g. If the target class has a custom object
* for a field value, pass a ResultSet who doesn't contain it.
*
* =======================================================================
*
* I realize there are libraries who can do a lot
* of this for me, but this was for a very
* small project that didn't justify a large
* framework. This lightweight class met my needs.
*/
public class RSToPOJOConverter
{
/**
* Converts the given ResultSet to a List of objects,
* using the provided target Class to determine what
* objects to create from the provided ResultSet.
*
* @param resultSet
* The ResultSet to pull the data out of.
* @param targetClass
* The class the ResultSet should be converted into.
* @return The List of objects converted from the ResultSet.
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
public static List<?> convertResultSetToObjectList(ResultSet resultSet, Class<?> targetClass) throws SQLException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
List<Object> createdObjects = new ArrayList<>();
/*
* Maps the name of a field in the ResultSet to
* the name of the Java method that should be
* used to set the gathered value in an instance
* of the target Class.
*
* E.g. If the field name = "ITEM_ID", the expected setter name is setItemId()
*/
Map<String,Method> dbFieldNameSetterMap = new HashMap<>();
/*
* Gather all the methods for the given class,
* filter out all but the setters,
* for each method,
* take the setter's method name and
* create the expected field name for the value the DB (E.g. EXAMPLE_VALUE),
*
* map the field name to the setter method.
*/
Arrays
.asList(targetClass.getMethods())
.stream()
.filter(method -> method.getName().startsWith("set"))
.forEach(setter ->
{
// setExampleValue -> EXAMPLE_VALUE
String fieldName = convertSetterNameToFieldName(setter.getName());
dbFieldNameSetterMap.put(fieldName, setter);
});
// Process the ResultSet
// While there are still results to process,
while(resultSet.next())
{
// Create a new instance of the target class,
Object targetObject = targetClass.newInstance();
/*
* Loop through the field names you've created,
* Get the associated setter,
* Gather the value from the ResultSet,
* Review/Verify the datatype you're saving,
* Use the target setter to save the gathered value
* in the instance of the target object.
*/
for (String dbFieldName : dbFieldNameSetterMap.keySet())
{
Method setter = dbFieldNameSetterMap.get(dbFieldName);
Object dbFieldValue = resultSet.getObject(dbFieldName);
dbFieldValue = reviewValue(dbFieldValue);
setter.invoke(targetObject, dbFieldValue);
}
createdObjects.add(targetObject);
}
return createdObjects;
}
/**
* Converts a setter method's name to
* the associated Database field name.
*
* @param setterName
* The name of the setter method.
* @return The name of the associated Database field.
*/
private static String convertSetterNameToFieldName(String setterName)
{
String fieldName = setterName.replaceAll("([A-Z])", "_$1");
fieldName = fieldName.substring(4);
return fieldName.toUpperCase();
}
/**
* Required for a number of reasons:
*
* 1. ResultSet.getObject(String columnName) returns a BigDecimal for the id columns where I expected a Long. This is an issue
* because the equivalent POJO Instance Ids were using Longs for their ids, not BigDecimals. Using the dataType object to
* cast it (Class.cast()) doesn't work because you can't cast a BigDecimal to a Long.
* 2. ResultSet.getObject(String columnName, Class<?> type) is returning AbstractMethodError, even for a STRING.
*
* After way too much wasted time trying to figure this out, instead wrote this method which works.
*
* The goal is to review and change what jdbc returns from the ResultSet, when necessary.
*
* At the moment only converts a BigDecimal to a Long.
*
* Other values are returned as is.
*
* @param databaseValue
* The value to review.
* @return The reviewed and potentially correctly casted value.
*/
private static Object reviewValue(Object databaseValue)
{
if (databaseValue instanceof BigDecimal) return ((BigDecimal) databaseValue).longValue();
return databaseValue;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment