You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/11/28 15:19:29 UTC

cayenne git commit: CAY-1958 SelectById - a new full-featured select query to get objects by id

Repository: cayenne
Updated Branches:
  refs/heads/master 047a29477 -> 5331b8c5c


CAY-1958 SelectById - a new full-featured select query to get objects by id

* support for prefetches


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/5331b8c5
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/5331b8c5
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/5331b8c5

Branch: refs/heads/master
Commit: 5331b8c5cad581ecfe71bed41f7d671b548bee7d
Parents: 047a294
Author: aadamchik <aa...@apache.org>
Authored: Fri Nov 28 17:07:51 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Fri Nov 28 17:19:21 2014 +0300

----------------------------------------------------------------------
 .../apache/cayenne/query/SelectByIdTest.java    |   8 +-
 .../org/apache/cayenne/query/SelectById.java    |  66 ++++++
 .../org/apache/cayenne/query/SelectByIdIT.java  | 201 ----------------
 .../apache/cayenne/query/SelectByIdTest.java    |  78 +++++++
 .../apache/cayenne/query/SelectById_RunIT.java  | 227 +++++++++++++++++++
 5 files changed, 375 insertions(+), 205 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/5331b8c5/cayenne-client/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/test/java/org/apache/cayenne/query/SelectByIdTest.java b/cayenne-client/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
index 087c84c..84b2e06 100644
--- a/cayenne-client/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
+++ b/cayenne-client/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
@@ -18,15 +18,15 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.remote.hessian.service.HessianUtil;
 import org.apache.cayenne.testdo.testmap.Artist;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
 public class SelectByIdTest {
 
 	@Test

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5331b8c5/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
index 00ee205..92060ad 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
@@ -53,6 +53,7 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 	boolean fetchingDataRows;
 	QueryCacheStrategy cacheStrategy;
 	String[] cacheGroups;
+	PrefetchTreeNode prefetches;
 
 	public static <T> SelectById<T> query(Class<T> entityType, Object id) {
 		SelectById<T> q = new SelectById<T>();
@@ -185,6 +186,70 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 		return fetchingDataRows;
 	}
 
+	/**
+	 * Resets internal prefetches to the new value, which is a single prefetch
+	 * with specified semantics.
+	 * 
+	 * @return this object
+	 */
+	public SelectById<T> prefetch(String path, int semantics) {
+		this.prefetches = PrefetchTreeNode.withPath(path, semantics);
+		return this;
+	}
+
+	/**
+	 * Resets internal prefetches to the new value.
+	 * 
+	 * @return this object
+	 */
+	public SelectById<T> prefetch(PrefetchTreeNode prefetch) {
+		this.prefetches = prefetch;
+		return this;
+	}
+
+	/**
+	 * Merges prefetch into the query prefetch tree.
+	 * 
+	 * @return this object
+	 */
+	public SelectById<T> addPrefetch(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> addPrefetch(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) {
 
@@ -201,6 +266,7 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 		// optimally - object cache may have an object, but query cache will not
 		query.setCacheGroups(cacheGroups);
 		query.setCacheStrategy(cacheStrategy);
+		query.setPrefetchTree(prefetches);
 
 		return query;
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5331b8c5/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIT.java
deleted file mode 100644
index bc18f5d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIT.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.query;
-
-import org.apache.cayenne.DataRow;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.test.jdbc.DBHelper;
-import org.apache.cayenne.test.jdbc.TableHelper;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.testdo.testmap.Painting;
-import org.apache.cayenne.unit.di.DataChannelInterceptor;
-import org.apache.cayenne.unit.di.UnitTestClosure;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Before;
-import org.junit.Test;
-
-import static java.util.Collections.singletonMap;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class SelectByIdIT extends ServerCase {
-
-	@Inject
-	private DataChannelInterceptor interceptor;
-
-	@Inject
-	private DBHelper dbHelper;
-
-	private TableHelper tArtist;
-
-	@Inject
-	private ObjectContext context;
-
-	@Inject
-	private EntityResolver resolver;
-
-	@Before
-	public void setUp() throws Exception {
-		tArtist = new TableHelper(dbHelper, "ARTIST");
-		tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
-	}
-
-	private void createTwoArtists() throws Exception {
-		tArtist.insert(2, "artist2");
-		tArtist.insert(3, "artist3");
-	}
-
-	@Test
-	public void testIntPk() throws Exception {
-		createTwoArtists();
-
-		Artist a3 = SelectById.query(Artist.class, 3).selectOne(context);
-		assertNotNull(a3);
-		assertEquals("artist3", a3.getArtistName());
-
-		Artist a2 = SelectById.query(Artist.class, 2).selectOne(context);
-		assertNotNull(a2);
-		assertEquals("artist2", a2.getArtistName());
-	}
-
-	@Test
-	public void testMapPk() throws Exception {
-		createTwoArtists();
-
-		Artist a3 = SelectById.query(Artist.class, singletonMap(Artist.ARTIST_ID_PK_COLUMN, 3)).selectOne(context);
-		assertNotNull(a3);
-		assertEquals("artist3", a3.getArtistName());
-
-		Artist a2 = SelectById.query(Artist.class, singletonMap(Artist.ARTIST_ID_PK_COLUMN, 2)).selectOne(context);
-		assertNotNull(a2);
-		assertEquals("artist2", a2.getArtistName());
-	}
-
-	@Test
-	public void testObjectIdPk() throws Exception {
-		createTwoArtists();
-
-		ObjectId oid3 = new ObjectId("Artist", Artist.ARTIST_ID_PK_COLUMN, 3);
-		Artist a3 = SelectById.query(Artist.class, oid3).selectOne(context);
-		assertNotNull(a3);
-		assertEquals("artist3", a3.getArtistName());
-
-		ObjectId oid2 = new ObjectId("Artist", Artist.ARTIST_ID_PK_COLUMN, 2);
-		Artist a2 = SelectById.query(Artist.class, oid2).selectOne(context);
-		assertNotNull(a2);
-		assertEquals("artist2", a2.getArtistName());
-	}
-
-	@Test
-	public void testDataRowIntPk() throws Exception {
-		createTwoArtists();
-
-		DataRow a3 = SelectById.dataRowQuery(Artist.class, 3).selectOne(context);
-		assertNotNull(a3);
-		assertEquals("artist3", a3.get("ARTIST_NAME"));
-
-		DataRow a2 = SelectById.dataRowQuery(Artist.class, 2).selectOne(context);
-		assertNotNull(a2);
-		assertEquals("artist2", a2.get("ARTIST_NAME"));
-	}
-
-	@Test
-	public void testMetadataCacheKey() throws Exception {
-		SelectById<Painting> q1 = SelectById.query(Painting.class, 4).useLocalCache();
-		QueryMetadata md1 = q1.getMetaData(resolver);
-		assertNotNull(md1);
-		assertNotNull(md1.getCacheKey());
-
-		SelectById<Painting> q2 = SelectById.query(Painting.class, singletonMap(Painting.PAINTING_ID_PK_COLUMN, 4))
-				.useLocalCache();
-		QueryMetadata md2 = q2.getMetaData(resolver);
-		assertNotNull(md2);
-		assertNotNull(md2.getCacheKey());
-
-		// this query is just a different form of q1, so should hit the same
-		// cache entry
-		assertEquals(md1.getCacheKey(), md2.getCacheKey());
-
-		SelectById<Painting> q3 = SelectById.query(Painting.class, 5).useLocalCache();
-		QueryMetadata md3 = q3.getMetaData(resolver);
-		assertNotNull(md3);
-		assertNotNull(md3.getCacheKey());
-		assertNotEquals(md1.getCacheKey(), md3.getCacheKey());
-
-		SelectById<Artist> q4 = SelectById.query(Artist.class, 4).useLocalCache();
-		QueryMetadata md4 = q4.getMetaData(resolver);
-		assertNotNull(md4);
-		assertNotNull(md4.getCacheKey());
-		assertNotEquals(md1.getCacheKey(), md4.getCacheKey());
-
-		SelectById<Painting> q5 = SelectById.query(Painting.class,
-				new ObjectId("Painting", Painting.PAINTING_ID_PK_COLUMN, 4)).useLocalCache();
-		QueryMetadata md5 = q5.getMetaData(resolver);
-		assertNotNull(md5);
-		assertNotNull(md5.getCacheKey());
-
-		// this query is just a different form of q1, so should hit the same
-		// cache entry
-		assertEquals(md1.getCacheKey(), md5.getCacheKey());
-	}
-
-	@Test
-	public void testLocalCache() throws Exception {
-		createTwoArtists();
-
-		final Artist[] a3 = new Artist[1];
-
-		assertEquals(1, interceptor.runWithQueryCounter(new UnitTestClosure() {
-
-			@Override
-			public void execute() {
-				a3[0] = SelectById.query(Artist.class, 3).useLocalCache("g1").selectOne(context);
-				assertNotNull(a3[0]);
-				assertEquals("artist3", a3[0].getArtistName());
-			}
-		}));
-
-		interceptor.runWithQueriesBlocked(new UnitTestClosure() {
-
-			@Override
-			public void execute() {
-				Artist a3cached = SelectById.query(Artist.class, 3).useLocalCache("g1").selectOne(context);
-				assertSame(a3[0], a3cached);
-			}
-		});
-
-		context.performGenericQuery(new RefreshQuery("g1"));
-
-		assertEquals(1, interceptor.runWithQueryCounter(new UnitTestClosure() {
-
-			@Override
-			public void execute() {
-				SelectById.query(Artist.class, 3).useLocalCache("g1").selectOne(context);
-			}
-		}));
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5331b8c5/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdTest.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
new file mode 100644
index 0000000..4dca1c8
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdTest.java
@@ -0,0 +1,78 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.query;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.junit.Test;
+
+public class SelectByIdTest {
+
+	@Test
+	public void testPrefetch() {
+
+		PrefetchTreeNode root = PrefetchTreeNode.withPath("a.b", PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+
+		SelectById<Artist> q = SelectById.query(Artist.class, 6);
+		q.prefetch(root);
+
+		assertSame(root, q.getPrefetches());
+	}
+
+	@Test
+	public void testPrefetch_Path() {
+
+		SelectById<Artist> q = SelectById.query(Artist.class, 7);
+		q.prefetch("a.b", PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+		PrefetchTreeNode root1 = q.getPrefetches();
+
+		assertNotNull(root1);
+		assertNotNull(root1.getNode("a.b"));
+
+		q.prefetch("a.c", PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+		PrefetchTreeNode root2 = q.getPrefetches();
+
+		assertNotNull(root2);
+		assertNotNull(root2.getNode("a.c"));
+		assertNull(root2.getNode("a.b"));
+		assertNotSame(root1, root2);
+	}
+
+	@Test
+	public void testAddPrefetch() {
+
+		PrefetchTreeNode root = PrefetchTreeNode.withPath("a.b", PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+
+		SelectById<Artist> q = SelectById.query(Artist.class, 8);
+		q.prefetch(root);
+
+		assertSame(root, q.getPrefetches());
+
+		PrefetchTreeNode subRoot = PrefetchTreeNode.withPath("a.b.c", PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+		q.addPrefetch(subRoot);
+
+		assertSame(root, q.getPrefetches());
+
+		assertNotNull(root.getNode("a.b.c"));
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5331b8c5/cayenne-server/src/test/java/org/apache/cayenne/query/SelectById_RunIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectById_RunIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectById_RunIT.java
new file mode 100644
index 0000000..ab67596
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectById_RunIT.java
@@ -0,0 +1,227 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.query;
+
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.sql.Types;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.DataChannelInterceptor;
+import org.apache.cayenne.unit.di.UnitTestClosure;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class SelectById_RunIT extends ServerCase {
+
+	@Inject
+	private DataChannelInterceptor interceptor;
+
+	@Inject
+	private DBHelper dbHelper;
+
+	private TableHelper tArtist;
+	private TableHelper tPainting;
+
+	@Inject
+	private ObjectContext context;
+
+	@Inject
+	private EntityResolver resolver;
+
+	@Before
+	public void setUp() throws Exception {
+		tArtist = new TableHelper(dbHelper, "ARTIST").setColumns("ARTIST_ID", "ARTIST_NAME");
+		tPainting = new TableHelper(dbHelper, "PAINTING").setColumns("PAINTING_ID", "ARTIST_ID", "PAINTING_TITLE")
+				.setColumnTypes(Types.INTEGER, Types.BIGINT, Types.VARCHAR);
+	}
+
+	private void createTwoArtists() throws Exception {
+		tArtist.insert(2, "artist2");
+		tArtist.insert(3, "artist3");
+	}
+
+	@Test
+	public void testIntPk() throws Exception {
+		createTwoArtists();
+
+		Artist a3 = SelectById.query(Artist.class, 3).selectOne(context);
+		assertNotNull(a3);
+		assertEquals("artist3", a3.getArtistName());
+
+		Artist a2 = SelectById.query(Artist.class, 2).selectOne(context);
+		assertNotNull(a2);
+		assertEquals("artist2", a2.getArtistName());
+	}
+
+	@Test
+	public void testMapPk() throws Exception {
+		createTwoArtists();
+
+		Artist a3 = SelectById.query(Artist.class, singletonMap(Artist.ARTIST_ID_PK_COLUMN, 3)).selectOne(context);
+		assertNotNull(a3);
+		assertEquals("artist3", a3.getArtistName());
+
+		Artist a2 = SelectById.query(Artist.class, singletonMap(Artist.ARTIST_ID_PK_COLUMN, 2)).selectOne(context);
+		assertNotNull(a2);
+		assertEquals("artist2", a2.getArtistName());
+	}
+
+	@Test
+	public void testObjectIdPk() throws Exception {
+		createTwoArtists();
+
+		ObjectId oid3 = new ObjectId("Artist", Artist.ARTIST_ID_PK_COLUMN, 3);
+		Artist a3 = SelectById.query(Artist.class, oid3).selectOne(context);
+		assertNotNull(a3);
+		assertEquals("artist3", a3.getArtistName());
+
+		ObjectId oid2 = new ObjectId("Artist", Artist.ARTIST_ID_PK_COLUMN, 2);
+		Artist a2 = SelectById.query(Artist.class, oid2).selectOne(context);
+		assertNotNull(a2);
+		assertEquals("artist2", a2.getArtistName());
+	}
+
+	@Test
+	public void testDataRowIntPk() throws Exception {
+		createTwoArtists();
+
+		DataRow a3 = SelectById.dataRowQuery(Artist.class, 3).selectOne(context);
+		assertNotNull(a3);
+		assertEquals("artist3", a3.get("ARTIST_NAME"));
+
+		DataRow a2 = SelectById.dataRowQuery(Artist.class, 2).selectOne(context);
+		assertNotNull(a2);
+		assertEquals("artist2", a2.get("ARTIST_NAME"));
+	}
+
+	@Test
+	public void testMetadataCacheKey() throws Exception {
+		SelectById<Painting> q1 = SelectById.query(Painting.class, 4).useLocalCache();
+		QueryMetadata md1 = q1.getMetaData(resolver);
+		assertNotNull(md1);
+		assertNotNull(md1.getCacheKey());
+
+		SelectById<Painting> q2 = SelectById.query(Painting.class, singletonMap(Painting.PAINTING_ID_PK_COLUMN, 4))
+				.useLocalCache();
+		QueryMetadata md2 = q2.getMetaData(resolver);
+		assertNotNull(md2);
+		assertNotNull(md2.getCacheKey());
+
+		// this query is just a different form of q1, so should hit the same
+		// cache entry
+		assertEquals(md1.getCacheKey(), md2.getCacheKey());
+
+		SelectById<Painting> q3 = SelectById.query(Painting.class, 5).useLocalCache();
+		QueryMetadata md3 = q3.getMetaData(resolver);
+		assertNotNull(md3);
+		assertNotNull(md3.getCacheKey());
+		assertNotEquals(md1.getCacheKey(), md3.getCacheKey());
+
+		SelectById<Artist> q4 = SelectById.query(Artist.class, 4).useLocalCache();
+		QueryMetadata md4 = q4.getMetaData(resolver);
+		assertNotNull(md4);
+		assertNotNull(md4.getCacheKey());
+		assertNotEquals(md1.getCacheKey(), md4.getCacheKey());
+
+		SelectById<Painting> q5 = SelectById.query(Painting.class,
+				new ObjectId("Painting", Painting.PAINTING_ID_PK_COLUMN, 4)).useLocalCache();
+		QueryMetadata md5 = q5.getMetaData(resolver);
+		assertNotNull(md5);
+		assertNotNull(md5.getCacheKey());
+
+		// this query is just a different form of q1, so should hit the same
+		// cache entry
+		assertEquals(md1.getCacheKey(), md5.getCacheKey());
+	}
+
+	@Test
+	public void testLocalCache() throws Exception {
+		createTwoArtists();
+
+		final Artist[] a3 = new Artist[1];
+
+		assertEquals(1, interceptor.runWithQueryCounter(new UnitTestClosure() {
+
+			@Override
+			public void execute() {
+				a3[0] = SelectById.query(Artist.class, 3).useLocalCache("g1").selectOne(context);
+				assertNotNull(a3[0]);
+				assertEquals("artist3", a3[0].getArtistName());
+			}
+		}));
+
+		interceptor.runWithQueriesBlocked(new UnitTestClosure() {
+
+			@Override
+			public void execute() {
+				Artist a3cached = SelectById.query(Artist.class, 3).useLocalCache("g1").selectOne(context);
+				assertSame(a3[0], a3cached);
+			}
+		});
+
+		context.performGenericQuery(new RefreshQuery("g1"));
+
+		assertEquals(1, interceptor.runWithQueryCounter(new UnitTestClosure() {
+
+			@Override
+			public void execute() {
+				SelectById.query(Artist.class, 3).useLocalCache("g1").selectOne(context);
+			}
+		}));
+	}
+
+	@Test
+	public void testPrefetch() throws Exception {
+		createTwoArtists();
+		tPainting.insert(45, 3, "One");
+		tPainting.insert(48, 3, "Two");
+
+		final Artist a3 = SelectById.query(Artist.class, 3).prefetch(Artist.PAINTING_ARRAY.joint()).selectOne(context);
+
+		interceptor.runWithQueriesBlocked(new UnitTestClosure() {
+
+			@Override
+			public void execute() {
+				assertNotNull(a3);
+				assertEquals("artist3", a3.getArtistName());
+				assertEquals(2, a3.getPaintingArray().size());
+				
+				a3.getPaintingArray().get(0).getPaintingTitle();
+				a3.getPaintingArray().get(1).getPaintingTitle();
+			}
+		});
+	}
+}