/*****************************************************************
* 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.access;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.cache.MapQueryCache;
import org.apache.cayenne.cache.QueryCache;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.query.QueryCacheStrategy;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.query.SelectQuery;
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.unit.di.server.CayenneProjects;
import org.apache.cayenne.unit.di.server.ServerCase;
import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
public class DataContextQueryCachingIT extends ServerCase {
@Inject
protected DataContext context;
@Inject
protected DBHelper dbHelper;
protected TableHelper tArtist;
protected TableHelper tPainting;
protected QueryCache oldCache;
protected DataDomain domain;
protected DataNode getNode() {
return this.domain.getDataNodes().iterator().next();
}
@Before
public void setUp() throws Exception {
tArtist = new TableHelper(dbHelper, "ARTIST");
tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
tPainting = new TableHelper(dbHelper, "PAINTING");
tPainting.setColumns(
"PAINTING_ID",
"PAINTING_TITLE",
"ARTIST_ID",
"ESTIMATED_PRICE");
domain = context.getParentDataDomain();
oldCache = domain.getQueryCache();
domain.setQueryCache(new MapQueryCache(50));
context.setQueryCache(new MapQueryCache(50));
}
@After
public void tearDown() throws Exception {
domain.setQueryCache(oldCache);
}
protected void createInsertDataSet() throws Exception {
tArtist.insert(33001, "aaa");
tPainting.insert(33001, "P", 33001, 4000);
}
protected void createUpdateDataSet1() throws Exception {
tArtist.update().set("ARTIST_NAME", "bbb").where("ARTIST_ID", 33001).execute();
}
protected void createUpdateDataSet2() throws Exception {
tArtist.update().set("ARTIST_NAME", "ccc").where("ARTIST_ID", 33001).execute();
}
@Test
public void testLocalCacheDataRowsRefresh() throws Exception {
SelectQuery<Artist> select = new SelectQuery<>(Artist.class);
select.setFetchingDataRows(true);
select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
MockDataNode engine = MockDataNode.interceptNode(domain, getNode());
try {
// first run, no cache yet
List<?> rows1 = mockupDataRows(2);
engine.reset();
engine.addExpectedResult(select, rows1);
List<?> resultRows = context.performQuery(select);
assertEquals(1, engine.getRunCount());
assertEquals(rows1, resultRows);
QueryMetadata cacheKey = select.getMetaData(context.getEntityResolver());
assertNull(context.getParentDataDomain().getQueryCache().get(cacheKey));
assertEquals(rows1, context.getQueryCache().get(cacheKey));
// second run, must refresh the cache
List<?> rows2 = mockupDataRows(4);
engine.reset();
engine.addExpectedResult(select, rows2);
select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
List<?> freshResultRows = context.performQuery(select);
assertEquals(1, engine.getRunCount());
assertEquals(rows2, freshResultRows);
assertNull(context.getParentDataDomain().getQueryCache().get(cacheKey));
assertEquals(rows2, context.getQueryCache().get(cacheKey));
}
finally {
engine.stopInterceptNode();
}
}
@Test
public void testSharedCacheDataRowsRefresh() throws Exception {
SelectQuery<Artist> select = new SelectQuery<>(Artist.class);
select.setFetchingDataRows(true);
select.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
MockDataNode engine = MockDataNode.interceptNode(domain, getNode());
try {
// first run, no cache yet
List<?> rows1 = mockupDataRows(2);
engine.reset();
engine.addExpectedResult(select, rows1);
List<?> resultRows = context.performQuery(select);
assertEquals(1, engine.getRunCount());
assertEquals(rows1, resultRows);
QueryMetadata cacheKey = select.getMetaData(context.getEntityResolver());
assertEquals(rows1, context.getParentDataDomain().getQueryCache().get(
cacheKey));
assertNull(context.getQueryCache().get(cacheKey));
// second run, must refresh the cache
List<?> rows2 = mockupDataRows(5);
engine.reset();
engine.addExpectedResult(select, rows2);
select.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE_REFRESH);
List<?> freshResultRows = context.performQuery(select);
assertEquals(1, engine.getRunCount());
assertEquals(rows2, freshResultRows);
assertEquals(rows2, context.getParentDataDomain().getQueryCache().get(
cacheKey));
assertNull(context.getQueryCache().get(cacheKey));
}
finally {
engine.stopInterceptNode();
}
}
@Test
public void testLocalCacheDataObjectsRefresh() throws Exception {
SelectQuery<Artist> select = new SelectQuery<>(Artist.class);
select.setFetchingDataRows(false);
select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
MockDataNode engine = MockDataNode.interceptNode(domain, getNode());
try {
// first run, no cache yet
List<?> rows1 = mockupDataRows(2);
engine.reset();
engine.addExpectedResult(select, rows1);
List<?> resultRows = context.performQuery(select);
assertEquals(1, engine.getRunCount());
assertEquals(2, resultRows.size());
assertTrue(resultRows.get(0) instanceof DataObject);
QueryMetadata cacheKey = select.getMetaData(context.getEntityResolver());
assertNull(context.getParentDataDomain().getQueryCache().get(cacheKey));
assertEquals(resultRows, context.getQueryCache().get(cacheKey));
// second run, must refresh the cache
List<?> rows2 = mockupDataRows(4);
engine.reset();
engine.addExpectedResult(select, rows2);
select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
List<?> freshResultRows = context.performQuery(select);
assertEquals(1, engine.getRunCount());
assertEquals(4, freshResultRows.size());
assertTrue(resultRows.get(0) instanceof DataObject);
assertNull(context.getParentDataDomain().getQueryCache().get(cacheKey));
assertEquals(freshResultRows, context.getQueryCache().get(cacheKey));
}
finally {
engine.stopInterceptNode();
}
}
@Test
public void testLocalCacheRefreshObjectsRefresh() throws Exception {
createInsertDataSet();
SelectQuery<Artist> select = new SelectQuery<>(Artist.class);
select.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
// no cache yet...
List<Artist> objects1 = context.performQuery(select);
assertEquals(1, objects1.size());
Artist a1 = objects1.get(0);
assertEquals("aaa", a1.getArtistName());
// cache, but force refresh
createUpdateDataSet1();
List<?> objects2 = context.performQuery(select);
assertEquals(1, objects2.size());
Artist a2 = (Artist) objects2.get(0);
assertSame(a1, a2);
assertEquals("bbb", a2.getArtistName());
}
private List<?> mockupDataRows(int len) {
List<Object> rows = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
DataRow a = new DataRow(3);
a.put("ARTIST_ID", i + 1);
a.put("ARTIST_NAME", "A-" + (i + 1));
rows.add(a);
}
return rows;
}
}