From 115a62293e7d93a9bb45ce5a48831f29fecc47a7 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Sun, 18 Jun 2017 21:58:55 -0400 Subject: [PATCH] Allow CompilingRowToObjectMapper to use reflection for non-public fields optionally --- .../jdbc/util/ReflectionUtil.java | 26 ++++ .../jdbc/CachingResultSetMapper.java | 21 +-- .../CleaningCompilingResultSetMapper.java | 40 +----- .../jdbc/CompilingResultSetMapper.java | 127 ++---------------- .../jdbc/CompilingRowToObjectMapper.java | 87 ++++++++++-- .../jdbc/ReflectionAccessibleObject.java | 17 +++ .../jdbc/RowToObjectMapper.java | 10 +- .../jdbc/StaticCompilingResultSetMapper.java | 6 +- .../moparisthebest/jdbc/util/CacheUtil.java | 35 +++++ .../moparisthebest/jdbc/QueryMapperTest.java | 2 +- 10 files changed, 185 insertions(+), 186 deletions(-) create mode 100644 common/src/main/java/com/moparisthebest/jdbc/util/ReflectionUtil.java create mode 100644 querymapper/src/main/java/com/moparisthebest/jdbc/ReflectionAccessibleObject.java create mode 100644 querymapper/src/main/java/com/moparisthebest/jdbc/util/CacheUtil.java diff --git a/common/src/main/java/com/moparisthebest/jdbc/util/ReflectionUtil.java b/common/src/main/java/com/moparisthebest/jdbc/util/ReflectionUtil.java new file mode 100644 index 0000000..02278b6 --- /dev/null +++ b/common/src/main/java/com/moparisthebest/jdbc/util/ReflectionUtil.java @@ -0,0 +1,26 @@ +package com.moparisthebest.jdbc.util; + +import java.lang.reflect.Field; + +/** + * Created by mopar on 6/19/17. + */ +public class ReflectionUtil { + public static Field getAccessibleField(final Class clazz, final String declaredField) { + try { + final Field ret = clazz.getDeclaredField(declaredField); + ret.setAccessible(true); + return ret; + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + public static void setValue(final Field field, final Object obj, final Object value) { + try { + field.set(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CachingResultSetMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CachingResultSetMapper.java index 9bd4209..4be64fb 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CachingResultSetMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CachingResultSetMapper.java @@ -1,5 +1,7 @@ package com.moparisthebest.jdbc; +import com.moparisthebest.jdbc.util.CacheUtil; + import java.sql.ResultSet; import java.util.Calendar; import java.util.HashMap; @@ -24,18 +26,7 @@ public class CachingResultSetMapper extends ResultSetMapper { */ public CachingResultSetMapper(final Calendar cal, final int arrayMaxLength, final int maxEntries) { super(cal, arrayMaxLength); - if (maxEntries > 0) { // we want a limited cache - final float loadFactor = 0.75f; // default for HashMaps - // if we set the initialCapacity this way, nothing should ever need re-sized - final int initialCapacity = ((int) Math.ceil(maxEntries / loadFactor)) + 1; - cache = new LinkedHashMap>(initialCapacity, loadFactor, true) { - @Override - protected boolean removeEldestEntry(final Map.Entry> eldest) { - return size() > maxEntries; - } - }; - } else - cache = new HashMap>(); + cache = CacheUtil.getCache(maxEntries); } /** @@ -105,11 +96,7 @@ public class CachingResultSetMapper extends ResultSetMapper { * @param threadSafe true uses a thread-safe cache implementation (currently ConcurrentHashMap), false uses regular HashMap */ public CachingResultSetMapper(final Calendar cal, final int arrayMaxLength, final boolean threadSafe) { - this(cal, arrayMaxLength, threadSafe ? - new ConcurrentHashMap>() - : - new HashMap>() - ); + this(cal, arrayMaxLength, CacheUtil.>getCache(threadSafe)); } /** diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CleaningCompilingResultSetMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CleaningCompilingResultSetMapper.java index bd6d263..340c763 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CleaningCompilingResultSetMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CleaningCompilingResultSetMapper.java @@ -11,52 +11,22 @@ public class CleaningCompilingResultSetMapper extends CompilingResultSetMappe private final Cleaner cleaner; - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final Calendar cal, final int arrayMaxLength, final int maxEntries) { - super(cal, arrayMaxLength, maxEntries); - this.cleaner = cleaner; - } - - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final Calendar cal, final int arrayMaxLength) { - super(cal, arrayMaxLength); - this.cleaner = cleaner; - } - - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final int arrayMaxLength) { - super(arrayMaxLength); - this.cleaner = cleaner; - } - - public CleaningCompilingResultSetMapper(final Cleaner cleaner) { - this.cleaner = cleaner; - } - - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final Calendar cal, final int arrayMaxLength, final Map> cache) { + public CleaningCompilingResultSetMapper(final Calendar cal, final int arrayMaxLength, final CompilingRowToObjectMapper.Cache cache, final Cleaner cleaner) { super(cal, arrayMaxLength, cache); this.cleaner = cleaner; } - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final int arrayMaxLength, final Map> cache) { - super(arrayMaxLength, cache); - this.cleaner = cleaner; - } - - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final Map> cache) { + public CleaningCompilingResultSetMapper(final CompilingRowToObjectMapper.Cache cache, final Cleaner cleaner) { super(cache); this.cleaner = cleaner; } - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final Calendar cal, final int arrayMaxLength, final boolean threadSafe) { - super(cal, arrayMaxLength, threadSafe); + public CleaningCompilingResultSetMapper(final int arrayMaxLength, final CompilingRowToObjectMapper.Cache cache, final Cleaner cleaner) { + super(arrayMaxLength, cache); this.cleaner = cleaner; } - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final int arrayMaxLength, final boolean threadSafe) { - super(arrayMaxLength, threadSafe); - this.cleaner = cleaner; - } - - public CleaningCompilingResultSetMapper(final Cleaner cleaner, final boolean threadSafe) { - super(threadSafe); + public CleaningCompilingResultSetMapper(final Cleaner cleaner) { this.cleaner = cleaner; } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java index 988d361..2e3e043 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java @@ -4,10 +4,6 @@ import com.moparisthebest.classgen.Compiler; import java.sql.ResultSet; import java.util.Calendar; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * This generally follows the contract of ResultSetMapper, with the differences specified in CompilingRowToObjectMapper. @@ -15,132 +11,33 @@ import java.util.concurrent.ConcurrentHashMap; * This does cache compiled code based on column name/order and DTO being mapped to, so the (rather heavy) * code generation/compilation/instantiation only happens once for each query/dto, and is very fast on subsequent calls. *

- * By default this uses a plain HashMap for the cache, unbounded, and not thread-safe. There are overloaded constructors - * you can use to tell it to be threadSafe in which case it uses an unbounded ConcurrentHashMap, or to give it a maxEntries - * in which case it uses a LinkedHashMap and clears out old entries in LRU order keeping only maxEntries. Lastly you can - * send in your own custom Map implementation, CompilingResultSetMapper guarantees null will never be used for key or value. + * By default this uses a plain HashMap for the cache, unbounded, and not thread-safe. Use CacheUtil to get Maps for your + * preferred use case. You cansend in your own custom Map implementation, CompilingResultSetMapper guarantees null will + * never be used for key or value. * * @see CompilingRowToObjectMapper */ public class CompilingResultSetMapper extends ResultSetMapper { protected final Compiler compiler = new Compiler(); - protected final Map> cache; + protected final CompilingRowToObjectMapper.Cache cache; - /** - * CompilingResultSetMapper with optional maxEntries, expiring old ones in LRU fashion - * - * @param cal optional calendar for date/time values - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - * @param maxEntries max cached compiled entries to keep in cache, < 1 means unlimited - */ - public CompilingResultSetMapper(final Calendar cal, final int arrayMaxLength, final int maxEntries) { + public CompilingResultSetMapper(final Calendar cal, final int arrayMaxLength, final CompilingRowToObjectMapper.Cache cache) { super(cal, arrayMaxLength); - if (maxEntries > 0) { // we want a limited cache - final float loadFactor = 0.75f; // default for HashMaps - // if we set the initialCapacity this way, nothing should ever need re-sized - final int initialCapacity = ((int) Math.ceil(maxEntries / loadFactor)) + 1; - cache = new LinkedHashMap>(initialCapacity, loadFactor, true) { - @Override - protected boolean removeEldestEntry(final Map.Entry> eldest) { - return size() > maxEntries; - } - }; - } else - cache = new HashMap>(); + this.cache = cache == null ? new CompilingRowToObjectMapper.Cache() : cache; } - /** - * CompilingResultSetMapper with unlimited cache - * - * @param cal optional calendar for date/time values - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - */ - public CompilingResultSetMapper(final Calendar cal, final int arrayMaxLength) { - this(cal, arrayMaxLength, 0); // default unlimited cache + public CompilingResultSetMapper(final CompilingRowToObjectMapper.Cache cache) { + this.cache = cache == null ? new CompilingRowToObjectMapper.Cache() : cache; } - /** - * CompilingResultSetMapper with unlimited cache - * - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - */ - public CompilingResultSetMapper(final int arrayMaxLength) { - this(null, arrayMaxLength); + public CompilingResultSetMapper(final int arrayMaxLength, final CompilingRowToObjectMapper.Cache cache) { + super(arrayMaxLength); + this.cache = cache == null ? new CompilingRowToObjectMapper.Cache() : cache; } - /** - * CompilingResultSetMapper with unlimited cache - */ public CompilingResultSetMapper() { - this(-1); - } - - /** - * CompilingResultSetMapper with custom cache implementation - * - * @param cal optional calendar for date/time values - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - * @param cache any Map implementation for cache you wish, does not need to handle null keys or values - */ - public CompilingResultSetMapper(final Calendar cal, final int arrayMaxLength, final Map> cache) { - super(cal, arrayMaxLength); - if (cache == null) - throw new IllegalArgumentException("cache cannot be null"); - this.cache = cache; - } - - /** - * CompilingResultSetMapper with custom cache implementation - * - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - * @param cache any Map implementation for cache you wish, does not need to handle null keys or values - */ - public CompilingResultSetMapper(final int arrayMaxLength, final Map> cache) { - this(null, arrayMaxLength, cache); - } - - /** - * CompilingResultSetMapper with custom cache implementation - * - * @param cache any Map implementation for cache you wish, does not need to handle null keys or values - */ - public CompilingResultSetMapper(final Map> cache) { - this(-1, cache); - } - - /** - * CompilingResultSetMapper with optionally threadSafe cache implementation - * - * @param cal optional calendar for date/time values - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - * @param threadSafe true uses a thread-safe cache implementation (currently ConcurrentHashMap), false uses regular HashMap - */ - public CompilingResultSetMapper(final Calendar cal, final int arrayMaxLength, final boolean threadSafe) { - this(cal, arrayMaxLength, threadSafe ? - new ConcurrentHashMap>() - : - new HashMap>() - ); - } - - /** - * CompilingResultSetMapper with optionally threadSafe cache implementation - * - * @param arrayMaxLength max array/list/map length, a value of less than 1 indicates that all rows from the ResultSet should be included - * @param threadSafe true uses a thread-safe cache implementation (currently ConcurrentHashMap), false uses regular HashMap - */ - public CompilingResultSetMapper(final int arrayMaxLength, final boolean threadSafe) { - this(null, arrayMaxLength, threadSafe); - } - - /** - * CompilingResultSetMapper with optionally threadSafe cache implementation - * - * @param threadSafe true uses a thread-safe cache implementation (currently ConcurrentHashMap), false uses regular HashMap - */ - public CompilingResultSetMapper(final boolean threadSafe) { - this(-1, threadSafe); + this.cache = new CompilingRowToObjectMapper.Cache(); } @Override diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java index 53fbd3e..b91d466 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java @@ -1,15 +1,20 @@ package com.moparisthebest.jdbc; import com.moparisthebest.classgen.Compiler; +import com.sun.org.apache.xpath.internal.operations.Mod; import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Calendar; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Map a ResultSet row to an Object. This mapper generates/compiles/executes java code to perform the mapping. @@ -20,7 +25,8 @@ import java.util.Map; *

* Usage differences: * 1. Reflection can set non-public or final fields directly, direct java code cannot, so DTOs like that will result in - * a compilation and therefore mapping error. + * a compilation and therefore mapping error, unless the Cache sent in has allowReflection = true which will use reflection + * for these Fields in the generated code. */ public class CompilingRowToObjectMapper extends RowToObjectMapper { @@ -32,24 +38,28 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { protected String _calendarName = null; - public CompilingRowToObjectMapper(final Compiler compiler, final Map> cache, ResultSet resultSet, Class returnTypeClass, Calendar cal, Class mapValType, Class mapKeyType) { + protected int reflectionFieldIndex = -1; + protected boolean allowReflection = false; + + public CompilingRowToObjectMapper(final Compiler compiler, final Cache cache, ResultSet resultSet, Class returnTypeClass, Calendar cal, Class mapValType, Class mapKeyType) { this(compiler, cache, resultSet, returnTypeClass, cal, mapValType, mapKeyType, false); } - public CompilingRowToObjectMapper(final Compiler compiler, final Map> cache, ResultSet resultSet, Class returnTypeClass, Calendar cal, Class mapValType, Class mapKeyType, final boolean caseInsensitiveMap) { + public CompilingRowToObjectMapper(final Compiler compiler, final Cache cache, ResultSet resultSet, Class returnTypeClass, Calendar cal, Class mapValType, Class mapKeyType, final boolean caseInsensitiveMap) { super(resultSet, returnTypeClass, cal, mapValType, mapKeyType, caseInsensitiveMap); this.compiler = compiler; try { final CompilingRowToObjectMapper.ResultSetKey keys = new CompilingRowToObjectMapper.ResultSetKey(super.getKeysFromResultSet(), _returnTypeClass, _mapKeyType, cal != null); //System.out.printf("keys: %s\n", keys); @SuppressWarnings("unchecked") - final ResultSetToObject resultSetToObject = (ResultSetToObject) cache.get(keys); + final ResultSetToObject resultSetToObject = (ResultSetToObject) cache.cache.get(keys); if (resultSetToObject == null) { //System.out.printf("cache miss, keys: %s\n", keys); // generate and put into cache if(keys.hasCalendar) _calendarName = "cal"; - cache.put(keys, this.resultSetToObject = genClass()); + allowReflection = cache.allowReflection; + cache.cache.put(keys, this.resultSetToObject = genClass()); this.keys = null; this._fields = null; this._fieldTypes = null; @@ -109,6 +119,30 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { T toObject(final ResultSet rs, final Calendar cal) throws SQLException; } + public static class Cache { + private final Map> cache; + private final boolean allowReflection; + + public Cache(final Map> cache, final boolean allowReflection) { + if(cache == null) + throw new NullPointerException("cache cannot be null"); + this.cache = cache; + this.allowReflection = allowReflection; + } + + public Cache(final Map> cache) { + this(cache, false); + } + + public Cache() { + this(new HashMap>()); + } + + public Cache(final boolean allowReflection) { + this(new HashMap>(), allowReflection); + } + } + protected String typeFromName(final Class type) { if(type == null) return "Object"; @@ -185,7 +219,21 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { java.append("throw new com.moparisthebest.jdbc.MapperException(com.moparisthebest.jdbc.CompilingRowToObjectMapper.firstColumnError)"); } - java.append(footer); + if(reflectionFieldIndex == -1) { + java.append(footer); + } else { + // otherwise we have a reflection field array to set up... + java.append(";\n }\n\n"); + java.append("private static final java.lang.reflect.Field[] _fields = new java.lang.reflect.Field[]{\n"); + for(final AccessibleObject ao : _fields) + if(ao instanceof ReflectionAccessibleObject) { + final Field f = ((ReflectionAccessibleObject)ao).field; + java.append("com.moparisthebest.jdbc.util.ReflectionUtil.getAccessibleField(") + .append(f.getDeclaringClass().getCanonicalName()).append(".class, \"") + .append(f.getName()).append("\"),\n"); + } + java.append("};\n}\n"); + } //System.out.println(java); return compiler.compile(className, java); } @@ -287,19 +335,21 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { for (int i = 1; i < _fields.length; i++) { AccessibleObject f = _fields[i]; - - String enumName = null; - if (_fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM) { - enumName = (f instanceof Field ? ((Field) f).getType() : ((Method) f).getParameterTypes()[0]).getCanonicalName(); - } if (f instanceof Field) { // if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine java.append("ret.").append(((Field) f).getName()).append(" = "); - extractColumnValueString(java, i, _fieldTypes[i], enumName); + extractColumnValueString(java, i, _fieldTypes[i], + _fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM ? ((Field) f).getType().getCanonicalName() : null); java.append(";\n"); + } else if (f instanceof ReflectionAccessibleObject) { + java.append("com.moparisthebest.jdbc.util.ReflectionUtil.setValue(_fields[").append(String.valueOf(((ReflectionAccessibleObject)f).index)).append("], ret, "); + extractColumnValueString(java, i, _fieldTypes[i], + _fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM ? ((ReflectionAccessibleObject) f).field.getType().getCanonicalName() : null); + java.append(");\n"); } else { java.append("ret.").append(((Method) f).getName()).append("("); - extractColumnValueString(java, i, _fieldTypes[i], enumName); + extractColumnValueString(java, i, _fieldTypes[i], + _fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM ? ((Method) f).getParameterTypes()[0].getCanonicalName() : null); java.append(");\n"); } } @@ -308,6 +358,17 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { java.append("ret.finish(rs);\n"); } + @Override + protected AccessibleObject modField(final Field field, final int index) { + if(!allowReflection) + return field; + final int modifiers = field.getModifiers(); + if(Modifier.isFinal(modifiers) || Modifier.isPrivate(modifiers) || Modifier.isProtected(modifiers)) { + return new ReflectionAccessibleObject(field, ++reflectionFieldIndex); + } + return field; + } + public void extractColumnValueString(final Appendable java, final int index, final int resultType, final String enumName) throws IOException { extractColumnValueString(java, index, resultType, enumName, _calendarName); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/ReflectionAccessibleObject.java b/querymapper/src/main/java/com/moparisthebest/jdbc/ReflectionAccessibleObject.java new file mode 100644 index 0000000..19302fc --- /dev/null +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/ReflectionAccessibleObject.java @@ -0,0 +1,17 @@ +package com.moparisthebest.jdbc; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; + +/** + * Created by mopar on 6/19/17. + */ +class ReflectionAccessibleObject extends AccessibleObject { + final Field field; + final int index; + + public ReflectionAccessibleObject(final Field field, final int index) { + this.field = field; + this.index = index; + } +} diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java index 2e8f092..99d42fe 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java @@ -452,16 +452,22 @@ public class RowToObjectMapper extends AbstractRowMapper { + "names and public setter methods on the return class. Columns are also " + "stripped of '_' and compared if no match is found with them."); } - f.setAccessible(true); _fields[i] = f; if (f instanceof Field) { - _fieldTypes[i] = _tmf.getTypeId(((Field) f).getType()); + final Field field = (Field) f; + _fields[i] = modField(field, i); + _fieldTypes[i] = _tmf.getTypeId(field.getType()); } else { _fieldTypes[i] = _tmf.getTypeId(((Method) f).getParameterTypes()[0]); } } } + protected AccessibleObject modField(final Field field, final int index) { + field.setAccessible(true); + return field; + } + public static T fixNull(Class returnType) { return returnType.cast(_tmf.fixNull(returnType)); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/StaticCompilingResultSetMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/StaticCompilingResultSetMapper.java index 5a50dd1..b63827c 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/StaticCompilingResultSetMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/StaticCompilingResultSetMapper.java @@ -1,15 +1,15 @@ package com.moparisthebest.jdbc; +import com.moparisthebest.jdbc.util.CacheUtil; + import java.util.Calendar; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * Global for entire application, hopefully you know what you are doing. */ public class StaticCompilingResultSetMapper extends CompilingResultSetMapper { - private static final Map> cache = new ConcurrentHashMap>(); + private static final CompilingRowToObjectMapper.Cache cache = new CompilingRowToObjectMapper.Cache(CacheUtil.>getCache(true), true); public static final StaticCompilingResultSetMapper instance = new StaticCompilingResultSetMapper(); diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/util/CacheUtil.java b/querymapper/src/main/java/com/moparisthebest/jdbc/util/CacheUtil.java new file mode 100644 index 0000000..19f81f4 --- /dev/null +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/util/CacheUtil.java @@ -0,0 +1,35 @@ +package com.moparisthebest.jdbc.util; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by mopar on 6/19/17. + */ +public abstract class CacheUtil { + + public static Map getCache(final int maxEntries) { + if (maxEntries > 0) { // we want a limited cache + final float loadFactor = 0.75f; // default for HashMaps + // if we set the initialCapacity this way, nothing should ever need re-sized + final int initialCapacity = ((int) Math.ceil(maxEntries / loadFactor)) + 1; + return new LinkedHashMap(initialCapacity, loadFactor, true) { + @Override + protected boolean removeEldestEntry(final Map.Entry eldest) { + return size() > maxEntries; + } + }; + } else + return new HashMap(); + } + + public static Map getCache(final boolean threadSafe) { + return threadSafe ? + new ConcurrentHashMap() + : + new HashMap(); + } + +} diff --git a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java index b20ea19..088bf52 100644 --- a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java +++ b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java @@ -146,7 +146,7 @@ public class QueryMapperTest { { new ResultSetMapper() }, { new CachingResultSetMapper() }, { new CaseInsensitiveMapResultSetMapper() }, - { new CompilingResultSetMapper() }, + { new CompilingResultSetMapper(new CompilingRowToObjectMapper.Cache(true)) }, }); }