Last active
August 29, 2015 14:00
-
-
Save efenderbosch/11253284 to your computer and use it in GitHub Desktop.
JPA Mapper for Dropwizard
This file contains hidden or 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
package com.segmint.jdbi; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.ParameterizedType; | |
import java.lang.reflect.Type; | |
import java.math.BigDecimal; | |
import java.sql.Array; | |
import java.sql.ResultSet; | |
import java.sql.SQLException; | |
import java.sql.Timestamp; | |
import java.util.HashMap; | |
import java.util.Map; | |
import javax.persistence.Basic; | |
import javax.persistence.Column; | |
import org.apache.commons.lang3.reflect.FieldUtils; | |
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 com.segmint.gizmo.JdbcUtil; | |
/** | |
* Maps attributes to columns annotated with @Column.<br> | |
* <br> | |
* | |
* Use @Basic(optional = * true) to indicate the column may not be present in | |
* the projection.<br> | |
* <br> | |
* | |
* Supports Long, Long[], String, String[], Boolean, Boolean[], BigDecimal, | |
* BigDecimal[], Integer, Integer[], Byte, Float, Float[], Double, Double[], | |
* Short, DateTime (Joda) and DateTime[] attributes by default.<br> | |
* <br> | |
* | |
* Extend JpaMapper.Mapper and use registerMapper to add new mappers. | |
*/ | |
public abstract class JpaMapper<T> implements ResultSetMapper<T> { | |
private static final Logger log = LoggerFactory.getLogger(JpaMapper.class); | |
private static final Map<Type, Mapper<?>> mappers = new HashMap<>(); | |
static { | |
registerMapper(new LongMapper()); | |
registerMapper(new LongArrayMapper()); | |
registerMapper(new StringMapper()); | |
registerMapper(new StringArrayMapper()); | |
registerMapper(new BooleanMapper()); | |
registerMapper(new BooleanArrayMapper()); | |
registerMapper(new BigDecimalMapper()); | |
registerMapper(new BigDecimalArrayMapper()); | |
registerMapper(new IntegerMapper()); | |
registerMapper(new IntegerArrayMapper()); | |
registerMapper(new ByteMapper()); | |
registerMapper(new FloatMapper()); | |
registerMapper(new FloatArrayMapper()); | |
registerMapper(new DoubleMapper()); | |
registerMapper(new DoubleArrayMapper()); | |
registerMapper(new ShortMapper()); | |
registerMapper(new DateTimeMapper()); | |
registerMapper(new DateTimeArrayMapper()); | |
} | |
private final Constructor<T> constructor; | |
private final Map<String, Field> columns = new HashMap<>(); | |
private final Map<String, Field> optionalColumns = new HashMap<>(); | |
protected static void registerMapper(Mapper<?> mapper) { | |
Mapper<?> replaced = mappers.put(mapper.getType(), mapper); | |
if (replaced != null) { | |
log.warn("replaced mapper {} with {}", replaced, mapper); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
protected JpaMapper() { | |
ParameterizedType genericSuperClass = (ParameterizedType) getClass().getGenericSuperclass(); | |
Type[] genericTypes = genericSuperClass.getActualTypeArguments(); | |
Class<T> entityClass = (Class<T>) genericTypes[0]; | |
try { | |
constructor = entityClass.getConstructor(new Class<?>[0]); | |
} catch (NoSuchMethodException | SecurityException e) { | |
throw new RuntimeException("subclasses must have a no-arg public constructor"); | |
} | |
Field[] fields = FieldUtils.getAllFields(entityClass); | |
for (Field field : fields) { | |
mapFieldToColumn(field); | |
} | |
} | |
private void mapFieldToColumn(Field field) { | |
Column column = field.getAnnotation(Column.class); | |
if (column != null) { | |
String columnName = column.name(); | |
if ("".equals(columnName)) { | |
// convert field name to snake case | |
columnName = field.getName().replaceAll("([A-Z])", "_$1").toLowerCase(); | |
} | |
Basic basic = field.getAnnotation(Basic.class); | |
if (basic != null && basic.optional()) { | |
optionalColumns.put(columnName, field); | |
} else { | |
columns.put(columnName, field); | |
} | |
} | |
} | |
@Override | |
public T map(int index, ResultSet r, StatementContext ctx) throws SQLException { | |
T entity = null; | |
try { | |
entity = constructor.newInstance((Object[]) null); | |
mapColumns(r, entity, columns, false); | |
mapColumns(r, entity, optionalColumns, true); | |
} catch (ReflectiveOperationException | IllegalArgumentException e) { | |
throw new SQLException(e); | |
} | |
return entity; | |
} | |
private static void mapColumns(ResultSet r, Object entity, Map<String, Field> columns, boolean optional) | |
throws SQLException, IllegalAccessException { | |
for (Map.Entry<String, Field> column : columns.entrySet()) { | |
String columnName = column.getKey(); | |
Field field = column.getValue(); | |
Type type = field.getGenericType(); | |
Mapper<?> mapper = mappers.get(type); | |
if (mapper == null) { | |
log.warn("no mapper found for {}", field); | |
continue; | |
} | |
Object value = null; | |
if (optional) { | |
value = mapOptionalColumn(r, columnName, mapper); | |
} else { | |
value = mapper.getObject(r, columnName); | |
} | |
FieldUtils.writeField(field, entity, value, true); | |
} | |
} | |
private static Object mapOptionalColumn(ResultSet r, String columnName, Mapper<?> mapper) throws SQLException { | |
try { | |
return mapper.getObject(r, columnName); | |
} catch (SQLException e) { | |
if (!JdbcUtil.isUndefinedColumnException(e)) { | |
throw e; | |
} | |
} | |
return null; | |
} | |
public static abstract class Mapper<T> { | |
private final Type clazz; | |
protected Mapper() { | |
ParameterizedType genericSuperClass = (ParameterizedType) getClass().getGenericSuperclass(); | |
Type[] genericTypes = genericSuperClass.getActualTypeArguments(); | |
clazz = genericTypes[0]; | |
} | |
public abstract T getObject(ResultSet r, String columnName) throws SQLException; | |
Type getType() { | |
return clazz; | |
} | |
} | |
static class LongMapper extends Mapper<Long> { | |
@Override | |
public Long getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getLong(r, columnName); | |
} | |
} | |
static class LongArrayMapper extends Mapper<Long[]> { | |
@Override | |
public Long[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (Long[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class StringMapper extends Mapper<String> { | |
@Override | |
public String getObject(ResultSet r, String columnName) throws SQLException { | |
return r.getString(columnName); | |
} | |
} | |
static class StringArrayMapper extends Mapper<String[]> { | |
@Override | |
public String[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (String[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class BooleanMapper extends Mapper<Boolean> { | |
@Override | |
public Boolean getObject(ResultSet r, String columnName) throws SQLException { | |
return r.getBoolean(columnName); | |
} | |
} | |
static class BooleanArrayMapper extends Mapper<Boolean[]> { | |
@Override | |
public Boolean[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (Boolean[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class BigDecimalMapper extends Mapper<BigDecimal> { | |
@Override | |
public BigDecimal getObject(ResultSet r, String columnName) throws SQLException { | |
return r.getBigDecimal(columnName); | |
} | |
} | |
static class BigDecimalArrayMapper extends Mapper<BigDecimal[]> { | |
@Override | |
public BigDecimal[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (BigDecimal[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class IntegerMapper extends Mapper<Integer> { | |
@Override | |
public Integer getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getInteger(r, columnName); | |
} | |
} | |
static class IntegerArrayMapper extends Mapper<Integer[]> { | |
@Override | |
public Integer[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (Integer[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class ByteMapper extends Mapper<Byte> { | |
@Override | |
public Byte getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getByte(r, columnName); | |
} | |
} | |
static class FloatMapper extends Mapper<Float> { | |
@Override | |
public Float getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getFloat(r, columnName); | |
} | |
} | |
static class FloatArrayMapper extends Mapper<Float[]> { | |
@Override | |
public Float[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (Float[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class DoubleMapper extends Mapper<Double> { | |
@Override | |
public Double getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getDouble(r, columnName); | |
} | |
} | |
static class DoubleArrayMapper extends Mapper<Double[]> { | |
@Override | |
public Double[] getObject(ResultSet r, String columnName) throws SQLException { | |
return (Double[]) JdbcUtil.getArray(r, columnName); | |
} | |
} | |
static class ShortMapper extends Mapper<Short> { | |
@Override | |
public Short getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getShort(r, columnName); | |
} | |
} | |
static class DateTimeMapper extends Mapper<DateTime> { | |
@Override | |
public DateTime getObject(ResultSet r, String columnName) throws SQLException { | |
return JdbcUtil.getDateTime(r, columnName); | |
} | |
} | |
static class DateTimeArrayMapper extends Mapper<DateTime[]> { | |
@Override | |
public DateTime[] getObject(ResultSet r, String columnName) throws SQLException { | |
Array array = r.getArray(columnName); | |
if (array == null) { | |
return new DateTime[0]; | |
} | |
Timestamp[] ts = (Timestamp[]) array.getArray(); | |
DateTime[] dt = new DateTime[ts.length]; | |
for (int i = 0; i < ts.length; i++) { | |
dt[i] = ts[i] == null ? null : new DateTime(ts[i]); | |
} | |
return dt; | |
} | |
} | |
public abstract static class EnumMapper<T extends Enum<T>> extends Mapper<T> { | |
@Override | |
public T getObject(ResultSet r, String columnName) throws SQLException { | |
String name = r.getString(columnName); | |
if (name == null) { | |
return null; | |
} | |
@SuppressWarnings("unchecked") | |
Class<T> enumType = (Class<T>) getType(); | |
return Enum.valueOf(enumType, name); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment