diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index fe096fb..3159160 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -143,6 +143,35 @@ public abstract class DepotRepository return _ctx.invoke(new FindOneQuery(_ctx, type, clauses)); } + /** + * Converts the supplied set of raw keys into {@link Key} records so they can easily be passed + * to {@link #loadAll}. We'd just accept a collection of raw keys, but that would have the same + * type erasure as the loadAll version that accepts a collection of keys. Alas. + */ + protected List> makeKeys ( + Class type, Collection> primaryKeys) + throws DatabaseException + { + // convert the raw keys into real key records + DepotMarshaller marsh = _ctx.getMarshaller(type); + List> keys = new ArrayList>(); + for (Comparable key : primaryKeys) { + keys.add(marsh.makePrimaryKey(key)); + } + return keys; + } + + /** + * Loads up all persistent records that match the supplied set of primary keys. + */ + protected List loadAll (Class type, Collection> keys) + throws DatabaseException + { + // if we have precisely one query clause and it's a KeySet, then we can skip the key lookup + // phase and go right to phase two of our two phase query process + return _ctx.invoke(new FindAllQuery.WithKeys(_ctx, type, keys)); + } + /** * A varargs version of {@link #findAll(Class,Collection)}. */ @@ -182,8 +211,8 @@ public abstract class DepotRepository throws DatabaseException { DepotMarshaller marsh = _ctx.getMarshaller(type); - boolean useExplicit = !skipCache || - (marsh.getTableName() == null) || !marsh.hasPrimaryKey() || !_ctx.isUsingCache(); + boolean useExplicit = !skipCache || (marsh.getTableName() == null) || + !marsh.hasPrimaryKey() || !_ctx.isUsingCache(); // queries on @Computed records or the presence of FieldOverrides use the simple algorithm for (QueryClause clause : clauses) { diff --git a/src/java/com/samskivert/jdbc/depot/FindAllQuery.java b/src/java/com/samskivert/jdbc/depot/FindAllQuery.java index 653e98e..7d2cf9d 100644 --- a/src/java/com/samskivert/jdbc/depot/FindAllQuery.java +++ b/src/java/com/samskivert/jdbc/depot/FindAllQuery.java @@ -111,60 +111,45 @@ public abstract class FindAllQuery JDBCUtil.close(stmt); } - // if we're fetching a huge number of records, we have to do it in multiple queries - if (fetchKeys.size() > In.MAX_KEYS) { - int keyCount = fetchKeys.size(); - do { - Set> keys = new HashSet>(); - Iterator> iter = fetchKeys.iterator(); - for (int ii = 0; ii < Math.min(keyCount, In.MAX_KEYS); ii++) { - keys.add(iter.next()); - iter.remove(); - } - keyCount -= keys.size(); - loadRecords(conn, keys, entities); - } while (keyCount > 0); + return loadAndResolve(conn, allKeys, fetchKeys, entities); + } + } - } else if (fetchKeys.size() > 0) { - loadRecords(conn, fetchKeys, entities); - } - - List result = new ArrayList(); - for (Key key : allKeys) { - T value = entities.get(key); - if (value != null) { - result.add(value); - } - } - return result; + /** + * The two-pass collection query implementation. {@see DepotRepository#findAll} for details. + */ + public static class WithKeys extends FindAllQuery + { + public WithKeys (PersistenceContext ctx, Class type, Collection> keys) + throws DatabaseException + { + super(ctx, type); + _keys = keys; } - protected void loadRecords (Connection conn, Set> keys, Map, T> entities) + public List invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { - _builder.newQuery(new SelectClause(_type, _marsh.getFieldNames(), - new KeySet(_type, keys))); - PreparedStatement stmt = _builder.prepare(conn); - try { - ResultSet rs = stmt.executeQuery(); - int cnt = 0, dups = 0; - while (rs.next()) { - T obj = _marsh.createObject(rs); - if (entities.put(_marsh.getPrimaryKey(obj), obj) != null) { - dups++; + Map, T> entities = new HashMap, T>(); + Set> fetchKeys = new HashSet>(); + for (Key key : _keys) { + // TODO: All this cache fiddling needs to move to PersistenceContext? + CacheAdapter.CachedValue hit = _ctx.cacheLookup(key); + if (hit != null) { + T value = hit.getValue(); + if (value != null) { + @SuppressWarnings("unchecked") T newValue = (T) value.clone(); + entities.put(key, newValue); + continue; } - cnt++; } - if (cnt != keys.size()) { - log.warning("Row count mismatch in second pass [query=" + stmt + - ", wanted=" + keys.size() + ", got=" + cnt + - ", dups=" + dups + "]"); - } - - } finally { - JDBCUtil.close(stmt); + fetchKeys.add(key); } + + return loadAndResolve(conn, _keys, fetchKeys, entities); } + + protected Collection> _keys; } /** @@ -241,6 +226,65 @@ public abstract class FindAllQuery return result; } + protected void loadRecords (Connection conn, Set> keys, Map, T> entities) + throws SQLException + { + _builder.newQuery(new SelectClause(_type, _marsh.getFieldNames(), + new KeySet(_type, keys))); + PreparedStatement stmt = _builder.prepare(conn); + try { + ResultSet rs = stmt.executeQuery(); + int cnt = 0, dups = 0; + while (rs.next()) { + T obj = _marsh.createObject(rs); + if (entities.put(_marsh.getPrimaryKey(obj), obj) != null) { + dups++; + } + cnt++; + } + if (cnt != keys.size()) { + log.warning("Row count mismatch in second pass [query=" + stmt + + ", wanted=" + keys.size() + ", got=" + cnt + + ", dups=" + dups + "]"); + } + + } finally { + JDBCUtil.close(stmt); + } + } + + protected List loadAndResolve (Connection conn, Collection> allKeys, + Set> fetchKeys, Map, T> entities) + throws SQLException + { + // if we're fetching a huge number of records, we have to do it in multiple queries + if (fetchKeys.size() > In.MAX_KEYS) { + int keyCount = fetchKeys.size(); + do { + Set> keys = new HashSet>(); + Iterator> iter = fetchKeys.iterator(); + for (int ii = 0; ii < Math.min(keyCount, In.MAX_KEYS); ii++) { + keys.add(iter.next()); + iter.remove(); + } + keyCount -= keys.size(); + loadRecords(conn, keys, entities); + } while (keyCount > 0); + + } else if (fetchKeys.size() > 0) { + loadRecords(conn, fetchKeys, entities); + } + + List result = new ArrayList(); + for (Key key : allKeys) { + T value = entities.get(key); + if (value != null) { + result.add(value); + } + } + return result; + } + protected PersistenceContext _ctx; protected SQLBuilder _builder; protected DepotMarshaller _marsh;