Skip to content

Instantly share code, notes, and snippets.

@efenderbosch
Last active August 29, 2015 14:00
Show Gist options
  • Save efenderbosch/11253284 to your computer and use it in GitHub Desktop.
Save efenderbosch/11253284 to your computer and use it in GitHub Desktop.
JPA Mapper for Dropwizard
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