This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 34aaa780edd30f1d30c0c7a5f21f41cb3864f54f
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Aug 30 13:44:28 2026 -0400

    Query cleanup... Unwinding CacheableQuery
    
    also some reformatting
---
 .../org/apache/cayenne/query/CacheableQuery.java   |    5 -
 .../org/apache/cayenne/query/FluentSelect.java     |   83 +-
 .../org/apache/cayenne/query/MappedSelect.java     |   16 +-
 .../java/org/apache/cayenne/query/SelectById.java  | 1473 ++++++++++----------
 .../cayenne/access/DataContextQueryCachingIT.java  |    6 +-
 5 files changed, 821 insertions(+), 762 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/query/CacheableQuery.java 
b/cayenne/src/main/java/org/apache/cayenne/query/CacheableQuery.java
index 970f98a8c..b2fd54b1a 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/CacheableQuery.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/CacheableQuery.java
@@ -19,9 +19,6 @@
 
 package org.apache.cayenne.query;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /**
  * Shared functionality for cacheable queries.
  *
@@ -29,8 +26,6 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class CacheableQuery implements Query {
 
-    protected static final Logger LOGGER = 
LoggerFactory.getLogger(CacheableQuery.class);
-
     abstract protected BaseQueryMetadata getBaseMetaData();
 
     /**
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java 
b/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java
index 73d2517d7..1b1c2ef9d 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java
@@ -41,7 +41,7 @@ import org.apache.cayenne.map.ObjEntity;
  *
  * @since 4.0
  */
-public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends 
CacheableQuery implements Select<T> {
+public abstract class FluentSelect<T, S extends FluentSelect<T, S>> implements 
Select<T> {
 
     // root
     protected Class<?> entityType;
@@ -63,6 +63,8 @@ public abstract class FluentSelect<T, S extends 
FluentSelect<T, S>> extends Cach
 
     protected abstract ObjectSelectMetadata createMetadata();
 
+    protected abstract ObjectSelectMetadata getBaseMetaData();
+
     protected Object resolveRoot(EntityResolver resolver) {
         Object root;
         if (entityType != null) {
@@ -346,8 +348,8 @@ public abstract class FluentSelect<T, S extends 
FluentSelect<T, S>> extends Cach
     }
 
     public S cacheStrategy(QueryCacheStrategy strategy) {
-        setCacheStrategy(strategy);
-        setCacheGroup(null);
+        getBaseMetaData().setCacheStrategy(strategy);
+        getBaseMetaData().setCacheGroup(null);
         return castSelf();
     }
 
@@ -356,7 +358,7 @@ public abstract class FluentSelect<T, S extends 
FluentSelect<T, S>> extends Cach
     }
 
     public S cacheGroup(String cacheGroup) {
-        setCacheGroup(cacheGroup);
+        getBaseMetaData().setCacheGroup(cacheGroup);
         return castSelf();
     }
 
@@ -408,6 +410,79 @@ public abstract class FluentSelect<T, S extends 
FluentSelect<T, S>> extends Cach
         return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
     }
 
+    /**
+     * @since 3.0
+     */
+    public QueryCacheStrategy getCacheStrategy() {
+        return getBaseMetaData().getCacheStrategy();
+    }
+
+    /**
+     * @since 4.0
+     */
+    public String getCacheGroup() {
+        return getBaseMetaData().getCacheGroup();
+    }
+
+    /**
+     * Note that unlike {@link #cacheStrategy(QueryCacheStrategy)} this method 
preserves the
+     * existing cache group. To reset it, use {@link 
#cacheStrategy(QueryCacheStrategy, String)}.
+     *
+     * @since 3.0
+     * @deprecated use {@link #cacheStrategy(QueryCacheStrategy, String)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public void setCacheStrategy(QueryCacheStrategy strategy) {
+        getBaseMetaData().setCacheStrategy(strategy);
+    }
+
+    /**
+     * @since 4.0
+     * @deprecated use {@link #cacheGroup(String)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public void setCacheGroup(String cacheGroup) {
+        getBaseMetaData().setCacheGroup(cacheGroup);
+    }
+
+    /**
+     * @since 4.0
+     * @deprecated use {@link #localCache()}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public void useLocalCache() {
+        getBaseMetaData().setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+    }
+
+    /**
+     * @since 4.0
+     * @deprecated use {@link #localCache(String)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public void useLocalCache(String cacheGroup) {
+        getBaseMetaData().setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+        getBaseMetaData().setCacheGroup(cacheGroup);
+    }
+
+    /**
+     * @since 4.0
+     * @deprecated use {@link #sharedCache()}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public void useSharedCache() {
+        getBaseMetaData().setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+    }
+
+    /**
+     * @since 4.0
+     * @deprecated use {@link #sharedCache(String)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public void useSharedCache(String cacheGroup) {
+        getBaseMetaData().setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+        getBaseMetaData().setCacheGroup(cacheGroup);
+    }
+
     public int getStatementFetchSize() {
         return getBaseMetaData().getStatementFetchSize();
     }
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/MappedSelect.java 
b/cayenne/src/main/java/org/apache/cayenne/query/MappedSelect.java
index f87518113..e6062bc25 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/MappedSelect.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/MappedSelect.java
@@ -18,15 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
-import java.sql.Statement;
-import java.util.List;
-import java.util.Map;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.QueryDescriptor;
 
+import java.sql.Statement;
+import java.util.Map;
+
 /**
  * A query that represents a named parameterized selecting query stored in the 
mapping. The
  * actual query is resolved during execution.
@@ -147,17 +146,12 @@ public class MappedSelect<T> extends AbstractMappedQuery 
implements Select<T> {
     public MappedSelect<T> param(String name, Object value) {
         return (MappedSelect<T>) super.param(name, value);
     }
-
-
-
+    
     @Override
     public T selectFirst(ObjectContext context) {
         return context.selectFirst(limit(1));
     }
 
-
-
-
     @Override
     protected Query createReplacementQuery(EntityResolver resolver) {
         QueryDescriptor descriptor = resolver.getQueryDescriptor(queryName);
@@ -191,7 +185,7 @@ public class MappedSelect<T> extends AbstractMappedQuery 
implements Select<T> {
                     selectQuery.pageSize(pageSize);
                 }
                 if (cacheStrategyOverride != null) {
-                    selectQuery.setCacheStrategy(cacheStrategyOverride);
+                    selectQuery.cacheStrategy(cacheStrategyOverride, 
selectQuery.getCacheGroup());
                 }
             }
             case QueryDescriptor.SQL_TEMPLATE -> {
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/SelectById.java 
b/cayenne/src/main/java/org/apache/cayenne/query/SelectById.java
index d5e11659e..532942134 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/SelectById.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/SelectById.java
@@ -38,748 +38,743 @@ import static org.apache.cayenne.exp.ExpressionFactory.*;
 
 /**
  * A query to select objects by id.
- * 
+ *
  * @since 4.0
  */
 public class SelectById<T> extends IndirectQuery implements Select<T> {
 
-       private static final long serialVersionUID = -6589464349051607583L;
-
-       final QueryRoot root;
-       final IdSpec idSpec;
-       final boolean fetchingDataRows;
-
-       QueryCacheStrategy cacheStrategy;
-       String cacheGroup;
-       PrefetchTreeNode prefetches;
-
-       /* Object query factory methods */
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryId(Class<T> entityType, Object id) 
{
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new SingleScalarIdSpec(id);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryMap(Class<T> entityType, 
Map<String, ?> id) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new SingleMapIdSpec(checkIdMap(id));
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryObjectId(Class<T> entityType, 
ObjectId id) {
-               checkObjectId(id);
-               QueryRoot root = new ByEntityNameResolver(id.getEntityName());
-               IdSpec idSpec = new SingleMapIdSpec(id.getIdSnapshot());
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryIds(Class<T> entityType, Object... 
ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(Arrays.asList(ids));
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryIdsCollection(Class<T> entityType, 
Collection<Object> ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(ids);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       @SafeVarargs
-       public static <T> SelectById<T> queryMaps(Class<T> entityType, 
Map<String, ?>... ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-
-               for(Map<String, ?> id : ids) {
-                       checkIdMap(id);
-               }
-
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = MultiMapIdSpec.ofMap(ids);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryMapsCollection(Class<T> 
entityType, Collection<Map<String, ?>> ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-
-               ids.forEach(SelectById::checkIdMap);
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = MultiMapIdSpec.ofMapCollection(ids);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryObjectIds(Class<T> entityType, 
ObjectId... ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               String entityName = ids[0].getEntityName();
-               for(ObjectId id : ids) {
-                       checkObjectId(id, entityName);
-               }
-
-               QueryRoot root = new ByEntityNameResolver(entityName);
-               IdSpec idSpec = MultiMapIdSpec.ofObjectId(ids);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static <T> SelectById<T> queryObjectIdsCollection(Class<T> 
entityType, Collection<ObjectId> ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               String entityName = ids.iterator().next().getEntityName();
-               for(ObjectId id : ids) {
-                       checkObjectId(id, entityName);
-               }
-
-               QueryRoot root = new ByEntityNameResolver(entityName);
-               IdSpec idSpec = MultiMapIdSpec.ofObjectIdCollection(ids);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /* Deprecated since 5.0 factory methods */
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryId(Class, Object)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static <T> SelectById<T> query(Class<T> entityType, Object id) {
-               return queryId(entityType, id);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryMap(Class, Map)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static <T> SelectById<T> query(Class<T> entityType, Map<String, 
?> id) {
-               return queryMap(entityType, id);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryObjectId(Class, ObjectId)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static <T> SelectById<T> query(Class<T> entityType, ObjectId id) 
{
-               return queryObjectId(entityType, id);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryIds(Class, Object...)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static <T> SelectById<T> query(Class<T> entityType, Object 
firstId, Object... otherIds) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(firstId, otherIds);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryIdsCollection(Class, Collection)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static <T> SelectById<T> query(Class<T> entityType, 
Collection<Object> ids) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(ids);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryMaps(Class, Map[])}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       @SafeVarargs
-       public static <T> SelectById<T> query(Class<T> entityType, Map<String, 
?> firstId, Map<String, ?>... otherIds) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = MultiMapIdSpec.ofMap(firstId, otherIds);
-               return new SelectById<>(root, idSpec);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #queryObjectIds(Class, ObjectId...)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static <T> SelectById<T> query(Class<T> entityType, ObjectId 
firstId, ObjectId... otherIds) {
-               checkObjectId(firstId);
-               for(ObjectId id : otherIds) {
-                       checkObjectId(id, firstId.getEntityName());
-               }
-
-               QueryRoot root = new 
ByEntityNameResolver(firstId.getEntityName());
-               IdSpec idSpec = MultiMapIdSpec.ofObjectId(firstId, otherIds);
-               return new SelectById<>(root, idSpec);
-       }
-
-
-       /* DataRow factory methods */
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryId(Class<?> entityType, 
Object id) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new SingleScalarIdSpec(id);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryMap(Class<?> entityType, 
Map<String, ?> id) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new SingleMapIdSpec(checkIdMap(id));
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryObjectId(ObjectId id) {
-               checkObjectId(id);
-               QueryRoot root = new ByEntityNameResolver(id.getEntityName());
-               IdSpec idSpec = new SingleMapIdSpec(id.getIdSnapshot());
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryIds(Class<?> entityType, 
Object... ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(Arrays.asList(ids));
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryIdsCollection(Class<?> 
entityType, Collection<Object> ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(ids);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       @SafeVarargs
-       public static SelectById<DataRow> dataRowQueryMaps(Class<?> entityType, 
Map<String, ?>... ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-
-               for(Map<String, ?> id : ids) {
-                       checkIdMap(id);
-               }
-
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = MultiMapIdSpec.ofMap(ids);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryMapsCollection(Class<?> 
entityType, Collection<Map<String, ?>> ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-
-               ids.forEach(SelectById::checkIdMap);
-
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = MultiMapIdSpec.ofMapCollection(ids);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> dataRowQueryObjectIds(ObjectId... 
ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               String entityName = ids[0].getEntityName();
-               for(ObjectId id : ids) {
-                       checkObjectId(id, entityName);
-               }
-
-               QueryRoot root = new ByEntityNameResolver(entityName);
-               IdSpec idSpec = MultiMapIdSpec.ofObjectId(ids);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 5.0
-        */
-       public static SelectById<DataRow> 
dataRowQueryObjectIdsCollection(Collection<ObjectId> ids) {
-               if(ids == null) {
-                       throw new CayenneRuntimeException("Null ids");
-               }
-               String entityName = ids.iterator().next().getEntityName();
-               for(ObjectId id : ids) {
-                       checkObjectId(id, entityName);
-               }
-
-               QueryRoot root = new ByEntityNameResolver(entityName);
-               IdSpec idSpec = MultiMapIdSpec.ofObjectIdCollection(ids);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /* Deprecated since 5.0 DataRow factory methods */
-
-       /**
-        * @deprecated use {@link #dataRowQueryId(Class, Object)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static SelectById<DataRow> dataRowQuery(Class<?> entityType, 
Object id) {
-               return dataRowQueryId(entityType, id);
-       }
-
-       /**
-        * @deprecated use {@link #dataRowQueryMap(Class, Map)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static SelectById<DataRow> dataRowQuery(Class<?> entityType, 
Map<String, ?> id) {
-               return dataRowQueryMap(entityType, id);
-       }
-
-       /**
-        * @deprecated use {@link #dataRowQueryObjectId(ObjectId)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static SelectById<DataRow> dataRowQuery(ObjectId id) {
-               return dataRowQueryObjectId(id);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #dataRowQueryIds(Class, Object...)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static SelectById<DataRow> dataRowQuery(Class<?> entityType, 
Object firstId, Object... otherIds) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = new MultiScalarIdSpec(firstId, otherIds);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #dataRowQueryMaps(Class, Map[])}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       @SafeVarargs
-       public static SelectById<DataRow> dataRowQuery(Class<?> entityType, 
Map<String, ?> firstId, Map<String, ?>... otherIds) {
-               QueryRoot root = new ByEntityTypeResolver(entityType);
-               IdSpec idSpec = MultiMapIdSpec.ofMap(firstId, otherIds);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       /**
-        * @since 4.2
-        * @deprecated use {@link #dataRowQueryObjectIds(ObjectId...)}
-        */
-       @Deprecated(since = "5.0", forRemoval = true)
-       public static SelectById<DataRow> dataRowQuery(ObjectId firstId, 
ObjectId... otherIds) {
-               checkObjectId(firstId);
-               for(ObjectId id : otherIds) {
-                       checkObjectId(id, firstId.getEntityName());
-               }
-
-               QueryRoot root = new 
ByEntityNameResolver(firstId.getEntityName());
-               IdSpec idSpec = MultiMapIdSpec.ofObjectId(firstId, otherIds);
-               return new SelectById<>(root, idSpec, true);
-       }
-
-       protected SelectById(QueryRoot root, IdSpec idSpec, boolean 
fetchingDataRows) {
-               this.root = root;
-               this.idSpec = idSpec;
-               this.fetchingDataRows = fetchingDataRows;
-       }
-
-       protected SelectById(QueryRoot root, IdSpec idSpec) {
-               this(root, idSpec, false);
-       }
-
-
-
-       @Override
-       public T selectFirst(ObjectContext context) {
-               return context.selectFirst(this);
-       }
-
-
-
-
-       /**
-        * Instructs Cayenne to look for query results in the "local" cache when
-        * running the query. This is a short-hand notation for:
-        *
-        * <pre>
-        * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
-        * </pre>
-        */
-       public SelectById<T> localCache(String cacheGroup) {
-               return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, 
cacheGroup);
-       }
-
-       /**
-        * Instructs Cayenne to look for query results in the "local" cache when
-        * running the query. This is a short-hand notation for:
-        *
-        * <pre>
-        * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-        * </pre>
-        */
-       public SelectById<T> localCache() {
-               return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-       }
-
-       /**
-        * Instructs Cayenne to look for query results in the "shared" cache 
when
-        * running the query. This is a short-hand notation for:
-        *
-        * <pre>
-        * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
-        * </pre>
-        */
-       public SelectById<T> sharedCache(String cacheGroup) {
-               return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, 
cacheGroup);
-       }
-
-       /**
-        * Instructs Cayenne to look for query results in the "shared" cache 
when
-        * running the query. This is a short-hand notation for:
-        *
-        * <pre>
-        * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-        * </pre>
-        */
-       public SelectById<T> sharedCache() {
-               return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-       }
-
-       public QueryCacheStrategy getCacheStrategy() {
-               return cacheStrategy;
-       }
-
-       public SelectById<T> cacheStrategy(QueryCacheStrategy strategy) {
-               if (this.cacheStrategy != strategy) {
-                       this.cacheStrategy = strategy;
-                       this.replacementQuery = null;
-               }
-
-               return this;
-       }
-
-       public SelectById<T> cacheStrategy(QueryCacheStrategy strategy, String 
cacheGroup) {
-               return cacheStrategy(strategy).cacheGroup(cacheGroup);
-       }
-
-       public String getCacheGroup() {
-               return cacheGroup;
-       }
-
-       public SelectById<T> cacheGroup(String cacheGroup) {
-               this.cacheGroup = cacheGroup;
-               this.replacementQuery = null;
-               return this;
-       }
-
-       public boolean isFetchingDataRows() {
-               return fetchingDataRows;
-       }
-
-       /**
-        * Merges prefetch into the query prefetch tree.
-        * 
-        * @return this object
-        */
-       public SelectById<T> prefetch(PrefetchTreeNode prefetch) {
-
-               if (prefetch == null) {
-                       return this;
-               }
-
-               if (prefetches == null) {
-                       prefetches = new PrefetchTreeNode();
-               }
-
-               prefetches.merge(prefetch);
-               return this;
-       }
-
-       /**
-        * Merges a prefetch path with specified semantics into the query 
prefetch
-        * tree.
-        * 
-        * @return this object
-        */
-       public SelectById<T> prefetch(String path, int semantics) {
-
-               if (path == null) {
-                       return this;
-               }
-
-               if (prefetches == null) {
-                       prefetches = new PrefetchTreeNode();
-               }
-
-               prefetches.addPath(path).setSemantics(semantics);
-               return this;
-       }
-
-       public PrefetchTreeNode getPrefetches() {
-               return prefetches;
-       }
-
-       @Override
-       protected Query createReplacementQuery(EntityResolver resolver) {
-               ObjEntity entity = root.resolve(resolver);
-
-               ObjectSelect<?> query = new ObjectSelect<>()
-                               .entityName(entity.getName())
-                               .where(idSpec.getQualifier(entity))
-                               .cacheStrategy(cacheStrategy, cacheGroup);
-               if(prefetches != null) {
-                       query.prefetch(prefetches);
-               }
-               if(fetchingDataRows) {
-                       query.fetchDataRows();
-               }
-               return query;
-       }
-
-       private static String resolveSinglePkName(ObjEntity entity) {
-               Collection<String> pkAttributes = entity.getPrimaryKeyNames();
-               if(pkAttributes.size() == 1) {
-                       return pkAttributes.iterator().next();
-               }
-               throw new CayenneRuntimeException("PK contains %d columns, 
expected 1.",  pkAttributes.size());
-       }
-
-       private static void checkObjectId(ObjectId id) {
-               if (id.isTemporary() && !id.isReplacementIdAttached()) {
-                       throw new CayenneRuntimeException("Can't build a query 
for a temporary id: %s", id);
-               }
-       }
-
-       private static void checkObjectId(ObjectId id, String entityName) {
-               checkObjectId(id);
-               if(!entityName.equals(id.getEntityName())) {
-                       throw new CayenneRuntimeException("Can't build a query 
with mixed object types for given ObjectIds");
-               }
-       }
-
-       private static Map<String, ?> checkIdMap(Map<String, ?> id) {
-               if(id == null || id.isEmpty()) {
-                       throw new CayenneRuntimeException("Null or empty id 
map");
-               }
-
-               return id;
-       }
-
-       @SafeVarargs
-       private static <E, R> Collection<R> foldArguments(Function<E, R> 
mapper, E first, E... other) {
-               List<R> result = new ArrayList<>(1 + other.length);
-               result.add(mapper.apply(first));
-               for(E next : other) {
-                       result.add(mapper.apply(next));
-               }
-               return result;
-       }
-
-       @SafeVarargs
-       private static <E, R> Collection<R> foldArguments(Function<E, R> 
mapper, E... other) {
-               List<R> result = new ArrayList<>(other.length);
-               for(E next : other) {
-                       result.add(mapper.apply(next));
-               }
-               return result;
-       }
-
-       private static <E, R> Collection<R> foldArguments(Function<E, R> 
mapper, Collection<E> other) {
-               List<R> result = new ArrayList<>(other.size());
-               for(E next : other) {
-                       result.add(mapper.apply(next));
-               }
-               return result;
-       }
-
-       protected interface QueryRoot extends Serializable {
-               ObjEntity resolve(EntityResolver resolver);
-       }
-
-       protected interface IdSpec extends Serializable{
-               Expression getQualifier(ObjEntity entity);
-       }
-
-       protected static class SingleScalarIdSpec implements IdSpec {
-
-               private final Object id;
-
-               protected SingleScalarIdSpec(Object id) {
-                       this.id = id;
-               }
-
-               @Override
-               public Expression getQualifier(ObjEntity entity) {
-                       return matchDbExp(resolveSinglePkName(entity), id);
-               }
-       }
-
-       protected static class MultiScalarIdSpec implements IdSpec {
-
-               private final Collection<Object> ids;
-
-               protected MultiScalarIdSpec(Object firstId, Object... otherIds) 
{
-                       this.ids = foldArguments(Function.identity(), firstId, 
otherIds);
-               }
-
-               protected MultiScalarIdSpec(Collection<Object> ids) {
-                       this.ids = ids;
-               }
-
-               @Override
-               public Expression getQualifier(ObjEntity entity) {
-                       return inDbExp(resolveSinglePkName(entity), ids);
-               }
-       }
-
-       protected static class SingleMapIdSpec implements IdSpec {
-
-               private final Map<String, ?> id;
-
-               protected SingleMapIdSpec(Map<String, ?> id) {
-                       this.id = id;
-               }
-
-               @Override
-               public Expression getQualifier(ObjEntity entity) {
-                       Expression expression = matchAllDbExp(id, 
Expression.EQUAL_TO);
-                       return expression == null ? expFalse() : expression;
-               }
-       }
-
-       protected static class MultiMapIdSpec implements IdSpec {
-
-               private final Collection<Map<String, ?>> ids;
-
-               @SafeVarargs
+    private static final long serialVersionUID = -6589464349051607583L;
+
+    final QueryRoot root;
+    final IdSpec idSpec;
+    final boolean fetchingDataRows;
+
+    QueryCacheStrategy cacheStrategy;
+    String cacheGroup;
+    PrefetchTreeNode prefetches;
+
+    /* Object query factory methods */
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryId(Class<T> entityType, Object id) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new SingleScalarIdSpec(id);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryMap(Class<T> entityType, Map<String, 
?> id) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new SingleMapIdSpec(checkIdMap(id));
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryObjectId(Class<T> entityType, 
ObjectId id) {
+        checkObjectId(id);
+        QueryRoot root = new ByEntityNameResolver(id.getEntityName());
+        IdSpec idSpec = new SingleMapIdSpec(id.getIdSnapshot());
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryIds(Class<T> entityType, Object... 
ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(Arrays.asList(ids));
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryIdsCollection(Class<T> entityType, 
Collection<Object> ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(ids);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    @SafeVarargs
+    public static <T> SelectById<T> queryMaps(Class<T> entityType, Map<String, 
?>... ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+
+        for (Map<String, ?> id : ids) {
+            checkIdMap(id);
+        }
+
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = MultiMapIdSpec.ofMap(ids);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryMapsCollection(Class<T> entityType, 
Collection<Map<String, ?>> ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+
+        ids.forEach(SelectById::checkIdMap);
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = MultiMapIdSpec.ofMapCollection(ids);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryObjectIds(Class<T> entityType, 
ObjectId... ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        String entityName = ids[0].getEntityName();
+        for (ObjectId id : ids) {
+            checkObjectId(id, entityName);
+        }
+
+        QueryRoot root = new ByEntityNameResolver(entityName);
+        IdSpec idSpec = MultiMapIdSpec.ofObjectId(ids);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static <T> SelectById<T> queryObjectIdsCollection(Class<T> 
entityType, Collection<ObjectId> ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        String entityName = ids.iterator().next().getEntityName();
+        for (ObjectId id : ids) {
+            checkObjectId(id, entityName);
+        }
+
+        QueryRoot root = new ByEntityNameResolver(entityName);
+        IdSpec idSpec = MultiMapIdSpec.ofObjectIdCollection(ids);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /* Deprecated since 5.0 factory methods */
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryId(Class, Object)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static <T> SelectById<T> query(Class<T> entityType, Object id) {
+        return queryId(entityType, id);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryMap(Class, Map)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static <T> SelectById<T> query(Class<T> entityType, Map<String, ?> 
id) {
+        return queryMap(entityType, id);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryObjectId(Class, ObjectId)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static <T> SelectById<T> query(Class<T> entityType, ObjectId id) {
+        return queryObjectId(entityType, id);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryIds(Class, Object...)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static <T> SelectById<T> query(Class<T> entityType, Object firstId, 
Object... otherIds) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(firstId, otherIds);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryIdsCollection(Class, Collection)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static <T> SelectById<T> query(Class<T> entityType, 
Collection<Object> ids) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(ids);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryMaps(Class, Map[])}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    @SafeVarargs
+    public static <T> SelectById<T> query(Class<T> entityType, Map<String, ?> 
firstId, Map<String, ?>... otherIds) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = MultiMapIdSpec.ofMap(firstId, otherIds);
+        return new SelectById<>(root, idSpec);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #queryObjectIds(Class, ObjectId...)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static <T> SelectById<T> query(Class<T> entityType, ObjectId 
firstId, ObjectId... otherIds) {
+        checkObjectId(firstId);
+        for (ObjectId id : otherIds) {
+            checkObjectId(id, firstId.getEntityName());
+        }
+
+        QueryRoot root = new ByEntityNameResolver(firstId.getEntityName());
+        IdSpec idSpec = MultiMapIdSpec.ofObjectId(firstId, otherIds);
+        return new SelectById<>(root, idSpec);
+    }
+
+
+    /* DataRow factory methods */
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryId(Class<?> entityType, 
Object id) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new SingleScalarIdSpec(id);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryMap(Class<?> entityType, 
Map<String, ?> id) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new SingleMapIdSpec(checkIdMap(id));
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryObjectId(ObjectId id) {
+        checkObjectId(id);
+        QueryRoot root = new ByEntityNameResolver(id.getEntityName());
+        IdSpec idSpec = new SingleMapIdSpec(id.getIdSnapshot());
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryIds(Class<?> entityType, 
Object... ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(Arrays.asList(ids));
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryIdsCollection(Class<?> 
entityType, Collection<Object> ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(ids);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    @SafeVarargs
+    public static SelectById<DataRow> dataRowQueryMaps(Class<?> entityType, 
Map<String, ?>... ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+
+        for (Map<String, ?> id : ids) {
+            checkIdMap(id);
+        }
+
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = MultiMapIdSpec.ofMap(ids);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryMapsCollection(Class<?> 
entityType, Collection<Map<String, ?>> ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+
+        ids.forEach(SelectById::checkIdMap);
+
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = MultiMapIdSpec.ofMapCollection(ids);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> dataRowQueryObjectIds(ObjectId... ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        String entityName = ids[0].getEntityName();
+        for (ObjectId id : ids) {
+            checkObjectId(id, entityName);
+        }
+
+        QueryRoot root = new ByEntityNameResolver(entityName);
+        IdSpec idSpec = MultiMapIdSpec.ofObjectId(ids);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public static SelectById<DataRow> 
dataRowQueryObjectIdsCollection(Collection<ObjectId> ids) {
+        if (ids == null) {
+            throw new CayenneRuntimeException("Null ids");
+        }
+        String entityName = ids.iterator().next().getEntityName();
+        for (ObjectId id : ids) {
+            checkObjectId(id, entityName);
+        }
+
+        QueryRoot root = new ByEntityNameResolver(entityName);
+        IdSpec idSpec = MultiMapIdSpec.ofObjectIdCollection(ids);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /* Deprecated since 5.0 DataRow factory methods */
+
+    /**
+     * @deprecated use {@link #dataRowQueryId(Class, Object)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static SelectById<DataRow> dataRowQuery(Class<?> entityType, Object 
id) {
+        return dataRowQueryId(entityType, id);
+    }
+
+    /**
+     * @deprecated use {@link #dataRowQueryMap(Class, Map)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static SelectById<DataRow> dataRowQuery(Class<?> entityType, 
Map<String, ?> id) {
+        return dataRowQueryMap(entityType, id);
+    }
+
+    /**
+     * @deprecated use {@link #dataRowQueryObjectId(ObjectId)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static SelectById<DataRow> dataRowQuery(ObjectId id) {
+        return dataRowQueryObjectId(id);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #dataRowQueryIds(Class, Object...)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static SelectById<DataRow> dataRowQuery(Class<?> entityType, Object 
firstId, Object... otherIds) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = new MultiScalarIdSpec(firstId, otherIds);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #dataRowQueryMaps(Class, Map[])}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    @SafeVarargs
+    public static SelectById<DataRow> dataRowQuery(Class<?> entityType, 
Map<String, ?> firstId, Map<String, ?>... otherIds) {
+        QueryRoot root = new ByEntityTypeResolver(entityType);
+        IdSpec idSpec = MultiMapIdSpec.ofMap(firstId, otherIds);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    /**
+     * @since 4.2
+     * @deprecated use {@link #dataRowQueryObjectIds(ObjectId...)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    public static SelectById<DataRow> dataRowQuery(ObjectId firstId, 
ObjectId... otherIds) {
+        checkObjectId(firstId);
+        for (ObjectId id : otherIds) {
+            checkObjectId(id, firstId.getEntityName());
+        }
+
+        QueryRoot root = new ByEntityNameResolver(firstId.getEntityName());
+        IdSpec idSpec = MultiMapIdSpec.ofObjectId(firstId, otherIds);
+        return new SelectById<>(root, idSpec, true);
+    }
+
+    protected SelectById(QueryRoot root, IdSpec idSpec, boolean 
fetchingDataRows) {
+        this.root = root;
+        this.idSpec = idSpec;
+        this.fetchingDataRows = fetchingDataRows;
+    }
+
+    protected SelectById(QueryRoot root, IdSpec idSpec) {
+        this(root, idSpec, false);
+    }
+
+    @Override
+    public T selectFirst(ObjectContext context) {
+        return context.selectFirst(this);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
+     * </pre>
+     */
+    public SelectById<T> localCache(String cacheGroup) {
+        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+     * </pre>
+     */
+    public SelectById<T> localCache() {
+        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
+     * </pre>
+     */
+    public SelectById<T> sharedCache(String cacheGroup) {
+        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+     * </pre>
+     */
+    public SelectById<T> sharedCache() {
+        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+    }
+
+    public QueryCacheStrategy getCacheStrategy() {
+        return cacheStrategy;
+    }
+
+    public SelectById<T> cacheStrategy(QueryCacheStrategy strategy) {
+        if (this.cacheStrategy != strategy) {
+            this.cacheStrategy = strategy;
+            this.replacementQuery = null;
+        }
+
+        return this;
+    }
+
+    public SelectById<T> cacheStrategy(QueryCacheStrategy strategy, String 
cacheGroup) {
+        return cacheStrategy(strategy).cacheGroup(cacheGroup);
+    }
+
+    public String getCacheGroup() {
+        return cacheGroup;
+    }
+
+    public SelectById<T> cacheGroup(String cacheGroup) {
+        this.cacheGroup = cacheGroup;
+        this.replacementQuery = null;
+        return this;
+    }
+
+    public boolean isFetchingDataRows() {
+        return fetchingDataRows;
+    }
+
+    /**
+     * Merges prefetch into the query prefetch tree.
+     *
+     * @return this object
+     */
+    public SelectById<T> prefetch(PrefetchTreeNode prefetch) {
+
+        if (prefetch == null) {
+            return this;
+        }
+
+        if (prefetches == null) {
+            prefetches = new PrefetchTreeNode();
+        }
+
+        prefetches.merge(prefetch);
+        return this;
+    }
+
+    /**
+     * Merges a prefetch path with specified semantics into the query prefetch
+     * tree.
+     *
+     * @return this object
+     */
+    public SelectById<T> prefetch(String path, int semantics) {
+
+        if (path == null) {
+            return this;
+        }
+
+        if (prefetches == null) {
+            prefetches = new PrefetchTreeNode();
+        }
+
+        prefetches.addPath(path).setSemantics(semantics);
+        return this;
+    }
+
+    public PrefetchTreeNode getPrefetches() {
+        return prefetches;
+    }
+
+    @Override
+    protected Query createReplacementQuery(EntityResolver resolver) {
+        ObjEntity entity = root.resolve(resolver);
+
+        ObjectSelect<?> query = new ObjectSelect<>()
+                .entityName(entity.getName())
+                .where(idSpec.getQualifier(entity))
+                .cacheStrategy(cacheStrategy, cacheGroup);
+        if (prefetches != null) {
+            query.prefetch(prefetches);
+        }
+        if (fetchingDataRows) {
+            query.fetchDataRows();
+        }
+        return query;
+    }
+
+    private static String resolveSinglePkName(ObjEntity entity) {
+        Collection<String> pkAttributes = entity.getPrimaryKeyNames();
+        if (pkAttributes.size() == 1) {
+            return pkAttributes.iterator().next();
+        }
+        throw new CayenneRuntimeException("PK contains %d columns, expected 
1.", pkAttributes.size());
+    }
+
+    private static void checkObjectId(ObjectId id) {
+        if (id.isTemporary() && !id.isReplacementIdAttached()) {
+            throw new CayenneRuntimeException("Can't build a query for a 
temporary id: %s", id);
+        }
+    }
+
+    private static void checkObjectId(ObjectId id, String entityName) {
+        checkObjectId(id);
+        if (!entityName.equals(id.getEntityName())) {
+            throw new CayenneRuntimeException("Can't build a query with mixed 
object types for given ObjectIds");
+        }
+    }
+
+    private static Map<String, ?> checkIdMap(Map<String, ?> id) {
+        if (id == null || id.isEmpty()) {
+            throw new CayenneRuntimeException("Null or empty id map");
+        }
+
+        return id;
+    }
+
+    @SafeVarargs
+    private static <E, R> Collection<R> foldArguments(Function<E, R> mapper, E 
first, E... other) {
+        List<R> result = new ArrayList<>(1 + other.length);
+        result.add(mapper.apply(first));
+        for (E next : other) {
+            result.add(mapper.apply(next));
+        }
+        return result;
+    }
+
+    @SafeVarargs
+    private static <E, R> Collection<R> foldArguments(Function<E, R> mapper, 
E... other) {
+        List<R> result = new ArrayList<>(other.length);
+        for (E next : other) {
+            result.add(mapper.apply(next));
+        }
+        return result;
+    }
+
+    private static <E, R> Collection<R> foldArguments(Function<E, R> mapper, 
Collection<E> other) {
+        List<R> result = new ArrayList<>(other.size());
+        for (E next : other) {
+            result.add(mapper.apply(next));
+        }
+        return result;
+    }
+
+    protected interface QueryRoot extends Serializable {
+        ObjEntity resolve(EntityResolver resolver);
+    }
+
+    protected interface IdSpec extends Serializable {
+        Expression getQualifier(ObjEntity entity);
+    }
+
+    protected static class SingleScalarIdSpec implements IdSpec {
+
+        private final Object id;
+
+        protected SingleScalarIdSpec(Object id) {
+            this.id = id;
+        }
+
+        @Override
+        public Expression getQualifier(ObjEntity entity) {
+            return matchDbExp(resolveSinglePkName(entity), id);
+        }
+    }
+
+    protected static class MultiScalarIdSpec implements IdSpec {
+
+        private final Collection<Object> ids;
+
+        protected MultiScalarIdSpec(Object firstId, Object... otherIds) {
+            this.ids = foldArguments(Function.identity(), firstId, otherIds);
+        }
+
+        protected MultiScalarIdSpec(Collection<Object> ids) {
+            this.ids = ids;
+        }
+
+        @Override
+        public Expression getQualifier(ObjEntity entity) {
+            return inDbExp(resolveSinglePkName(entity), ids);
+        }
+    }
+
+    protected static class SingleMapIdSpec implements IdSpec {
+
+        private final Map<String, ?> id;
+
+        protected SingleMapIdSpec(Map<String, ?> id) {
+            this.id = id;
+        }
+
+        @Override
+        public Expression getQualifier(ObjEntity entity) {
+            Expression expression = matchAllDbExp(id, Expression.EQUAL_TO);
+            return expression == null ? expFalse() : expression;
+        }
+    }
+
+    protected static class MultiMapIdSpec implements IdSpec {
+
+        private final Collection<Map<String, ?>> ids;
+
+        @SafeVarargs
         static MultiMapIdSpec ofMap(Map<String, ?> firstId, Map<String, ?>... 
otherIds) {
-                       return new 
MultiMapIdSpec(foldArguments(Function.identity(), firstId, otherIds));
-               }
-
-               @SafeVarargs
-               static MultiMapIdSpec ofMap(Map<String, ?>... ids) {
-                       return new 
MultiMapIdSpec(foldArguments(Function.identity(), ids));
-               }
-
-               static MultiMapIdSpec ofObjectId(ObjectId firstId, ObjectId... 
otherIds) {
-                       return new 
MultiMapIdSpec(foldArguments(ObjectId::getIdSnapshot, firstId, otherIds));
-               }
-
-               static MultiMapIdSpec ofObjectId(ObjectId... ids) {
-                       return new 
MultiMapIdSpec(foldArguments(ObjectId::getIdSnapshot, ids));
-               }
-
-               static MultiMapIdSpec ofObjectIdCollection(Collection<ObjectId> 
ids) {
-                       return new 
MultiMapIdSpec(foldArguments(ObjectId::getIdSnapshot, ids));
-               }
-
-               static MultiMapIdSpec ofMapCollection(Collection<Map<String, 
?>> ids) {
-                       return new MultiMapIdSpec(ids);
-               }
-
-               protected MultiMapIdSpec(Collection<Map<String, ?>> ids) {
-                       this.ids = ids;
-               }
-
-               @Override
-               public Expression getQualifier(ObjEntity entity) {
-                       List<Expression> expressions = new ArrayList<>();
-                       for(Map<String, ?> id : ids) {
-                               Expression expression = matchAllDbExp(id, 
Expression.EQUAL_TO);
-                               if(expression != null) {
-                                       expressions.add(expression);
-                               }
-                       }
-
-                       return expressions.isEmpty() ? expFalse() : 
or(expressions);
-               }
-       }
-
-       private static class ByEntityTypeResolver implements QueryRoot {
-               private final Class<?> entityType;
-
-               public ByEntityTypeResolver(Class<?> entityType) {
-                       this.entityType = entityType;
-               }
-
-               @Override
-               public ObjEntity resolve(EntityResolver resolver) {
-                       return resolver.getObjEntity(entityType);
-               }
-       }
-
-       private static class ByEntityNameResolver implements QueryRoot {
-               private final String entityName;
-
-               public ByEntityNameResolver(String entityName) {
-                       this.entityName = entityName;
-               }
-
-               @Override
-               public ObjEntity resolve(EntityResolver resolver) {
-                       return resolver.getObjEntity(entityName);
-               }
-       }
+            return new MultiMapIdSpec(foldArguments(Function.identity(), 
firstId, otherIds));
+        }
+
+        @SafeVarargs
+        static MultiMapIdSpec ofMap(Map<String, ?>... ids) {
+            return new MultiMapIdSpec(foldArguments(Function.identity(), ids));
+        }
+
+        static MultiMapIdSpec ofObjectId(ObjectId firstId, ObjectId... 
otherIds) {
+            return new MultiMapIdSpec(foldArguments(ObjectId::getIdSnapshot, 
firstId, otherIds));
+        }
+
+        static MultiMapIdSpec ofObjectId(ObjectId... ids) {
+            return new MultiMapIdSpec(foldArguments(ObjectId::getIdSnapshot, 
ids));
+        }
+
+        static MultiMapIdSpec ofObjectIdCollection(Collection<ObjectId> ids) {
+            return new MultiMapIdSpec(foldArguments(ObjectId::getIdSnapshot, 
ids));
+        }
+
+        static MultiMapIdSpec ofMapCollection(Collection<Map<String, ?>> ids) {
+            return new MultiMapIdSpec(ids);
+        }
+
+        protected MultiMapIdSpec(Collection<Map<String, ?>> ids) {
+            this.ids = ids;
+        }
+
+        @Override
+        public Expression getQualifier(ObjEntity entity) {
+            List<Expression> expressions = new ArrayList<>();
+            for (Map<String, ?> id : ids) {
+                Expression expression = matchAllDbExp(id, Expression.EQUAL_TO);
+                if (expression != null) {
+                    expressions.add(expression);
+                }
+            }
+
+            return expressions.isEmpty() ? expFalse() : or(expressions);
+        }
+    }
+
+    private static class ByEntityTypeResolver implements QueryRoot {
+        private final Class<?> entityType;
+
+        public ByEntityTypeResolver(Class<?> entityType) {
+            this.entityType = entityType;
+        }
+
+        @Override
+        public ObjEntity resolve(EntityResolver resolver) {
+            return resolver.getObjEntity(entityType);
+        }
+    }
+
+    private static class ByEntityNameResolver implements QueryRoot {
+        private final String entityName;
+
+        public ByEntityNameResolver(String entityName) {
+            this.entityName = entityName;
+        }
+
+        @Override
+        public ObjEntity resolve(EntityResolver resolver) {
+            return resolver.getObjEntity(entityName);
+        }
+    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextQueryCachingIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextQueryCachingIT.java
index 608f06efc..57514bf0a 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextQueryCachingIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextQueryCachingIT.java
@@ -103,7 +103,7 @@ public class DataContextQueryCachingIT {
             List<?> rows2 = mockupDataRows(4);
             engine.reset();
             engine.addExpectedResult(select, rows2);
-            select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
+            select.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
             List<?> freshResultRows = context.performQuery(select);
             assertEquals(1, engine.getRunCount());
             assertEquals(rows2, freshResultRows);
@@ -141,7 +141,7 @@ public class DataContextQueryCachingIT {
             List<?> rows2 = mockupDataRows(5);
             engine.reset();
             engine.addExpectedResult(select, rows2);
-            select.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE_REFRESH);
+            select.cacheStrategy(QueryCacheStrategy.SHARED_CACHE_REFRESH);
             List<?> freshResultRows = context.performQuery(select);
             assertEquals(1, engine.getRunCount());
             assertEquals(rows2, freshResultRows);
@@ -178,7 +178,7 @@ public class DataContextQueryCachingIT {
             List<?> rows2 = mockupDataRows(4);
             engine.reset();
             engine.addExpectedResult(select, rows2);
-            select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
+            select.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
             List<?> freshResultRows = context.performQuery(select);
 
             assertEquals(1, engine.getRunCount());

Reply via email to