blob: 89ef96d9668bec232b4f4e05d4ce87b9634f5352 [file] [log] [blame]
/*
* 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.openjpa.persistence.cache.jpa;
import java.util.List;
import javax.persistence.Cache;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.EntityManager;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.cache.jpa.model.CacheEntity;
import org.apache.openjpa.persistence.cache.jpa.model.CacheableEntity;
import org.apache.openjpa.persistence.cache.jpa.model.NegatedCachableEntity;
import org.apache.openjpa.persistence.cache.jpa.model.NegatedUncacheableEntity;
import org.apache.openjpa.persistence.cache.jpa.model.UncacheableEntity;
import org.apache.openjpa.persistence.cache.jpa.model.UnspecifiedEntity;
import org.apache.openjpa.persistence.cache.jpa.model.XmlCacheableEntity;
import org.apache.openjpa.persistence.cache.jpa.model.XmlUncacheableEntity;
public abstract class AbstractCacheModeTestCase extends AbstractCacheTestCase {
@Override
public abstract OpenJPAEntityManagerFactorySPI getEntityManagerFactory();
public abstract List<String> getSql();
protected abstract Class<?>[] getExpectedNotInCache();
protected abstract Class<?>[] getExpectedInCache();
// =======================================================================
// Asserts
// =======================================================================
/**
* Assert whether the cache contains the expected results.
*
* @param cache
* The JPA Cache to verify
* @param expectCacheables
* Whether entities with @Cacheable(true) should be in the cache
* (almost always true)
* @param expectUncacheables
* Whether entities with @Cacheable(false) should be in the cache
* (almost always false)
* @param expectUnspecified
* Whether entities with no @Cacheable annotation should be in
* the cache (varies per config).
*/
protected void assertCacheContents(Cache cache, boolean expectCacheables, boolean expectUncacheables,
boolean expectUnspecified) {
assertCacheables(cache, expectCacheables);
assertUncacheables(cache, expectUncacheables);
assertUnspecified(cache, expectUnspecified);
}
/**
* Assert whether the cacheable types are in the cache. This method exits on
* the first cache 'miss'.
*
* @param cache
* JPA Cache to verify
* @param expected
* If true the cacheable types should be in the cache, if false
* they should not be.
*/
protected void assertCacheables(Cache cache, boolean expected) {
assertCached(cache, CacheableEntity.class, 1, expected);
assertCached(cache, NegatedUncacheableEntity.class, 1, expected);
assertCached(cache, XmlCacheableEntity.class, 1, expected);
}
/**
* Assert whether the uncacheable types are in the cache. This method exits
* on the first cache 'miss'.
*
* @param cache
* JPA Cache to verify
* @param expected
* If true the uncacheable types should be in the cache, if false
* they should not be.
*/
protected void assertUncacheables(Cache cache, boolean expected) {
assertCached(cache, UncacheableEntity.class, 1, expected);
assertCached(cache, XmlUncacheableEntity.class, 1, expected);
assertCached(cache, NegatedCachableEntity.class, 1, expected);
}
/**
* Assert whether the unspecified types are in the cache. This method exits
* on the first cache 'miss'.
*
* @param cache
* JPA Cache to verify
* @param expected
* If true the unspecified types should be in the cache, if false
* they should not be.
*/
protected void assertUnspecified(Cache cache, boolean expected) {
assertCached(cache, UnspecifiedEntity.class, 1, expected);
}
/**
* Assert that no sql is executed when running the supplied Action.
*
* @param act
* Action to execute.
*/
public void assertNoSql(Action act) {
assertSqlInc(act, 0);
}
/**
* Assert that <literal>expectedSqls</literal> SQL statements are executed
* when running <literal>act</literal>
*
* @param act
* Action to run.
* @param expectedSqls
* Number of SQL statements that should be executed.
*/
public void assertSqlInc(Action act, int expectedSqls) {
int before = getSql().size();
act.run();
assertEquals(before + expectedSqls, getSql().size());
}
// =======================================================================
// Utility classes
// =======================================================================
/**
* Basic 'runnable' interface used to run a set of commands, then analyze
* the number of SQL statements that result.
*/
public interface Action {
void run();
}
// =======================================================================
// Test utilities
// =======================================================================
public boolean getCacheEnabled() {
return true;
}
// =======================================================================
// Common test methods.
// =======================================================================
/**
* Ensure that each call the em.find generates an SQL statement when
* CacheRetrieveMode.BYPASS is used.
*/
public void testReadModeByass() {
assertSqlInc(new Action() {
@Override
public void run() {
EntityManager em = getEntityManagerFactory().createEntityManager();
em.setProperty(RETRIEVE_MODE_PROP, CacheRetrieveMode.BYPASS);
for (Class<?> cls : persistentTypes) {
em.find(cls, 1);
}
em.close();
}
}, persistentTypes.length);
}
/**
* <p>
* Ensure that each entity in getExpectedInCache():
* <ul>
* <li>is in the cache</li>
* <li>does not go to the database for a find operation</li>
* <li>is not null</li>
* </ul>
* </p>
* <p>
* and
* </p>
* <p>
* Ensure that each entity in getExpectedNotInCache() :
* <ul>
* <li>is not in the cache</li>
* <li>results in a single SQL statement when em.find() is called</li>
* <li>is not null</li>
* </ul>
* </p>
*
*/
public void testRetrieveModeUse() {
if (getCacheEnabled()) {
assertNoSql(new Action() {
@Override
public void run() {
EntityManager em = getEntityManagerFactory().createEntityManager();
em.setProperty(RETRIEVE_MODE_PROP, CacheRetrieveMode.USE);
for (Class<?> cls : getExpectedInCache()) {
assertCached(getEntityManagerFactory().getCache(), cls, 1, true);
assertNotNull(em.find(cls, 1));
}
em.close();
}
});
assertSqlInc(new Action() {
@Override
public void run() {
EntityManager em = getEntityManagerFactory().createEntityManager();
em.setProperty(RETRIEVE_MODE_PROP, CacheRetrieveMode.USE);
for (Class<?> cls : getExpectedNotInCache()) {
assertCached(getEntityManagerFactory().getCache(), cls, 1, false);
assertNotNull(em.find(cls, 1));
}
em.close();
}
}, getExpectedNotInCache().length);
}
}
public void updateAndFind(Class<? extends CacheEntity> classToUpdate, int idToUpdate,
Class<? extends CacheEntity> classToFind, int idToFind,
CacheStoreMode storeMode, CacheRetrieveMode retrieveMode) {
EntityManager em = getEntityManagerFactory().createEntityManager();
if (storeMode != null) {
em.setProperty(STORE_MODE_PROP, storeMode);
}
if (retrieveMode != null) {
em.setProperty(RETRIEVE_MODE_PROP, retrieveMode);
}
em.getTransaction().begin();
CacheEntity ce1 = em.find(classToUpdate, idToUpdate);
CacheEntity ce2 = em.find(classToFind, idToFind);
assertNotNull(ce1);
assertNotNull(ce2);
ce1.setName(ce1.getName() + "UPD");
em.getTransaction().commit();
em.close();
}
/**
* <p>
* Test logic to validate different CacheStoreModes. It should behave
* identically for all shared-cache-modes except NONE which never caches
* anything.
* </p>
* <p>
* This method only tests setting the store mode on the EntityManager
* itself.
* </p>
* <p>
* The first transaction updates CacheableEntity::1 with CacheStoreMode
* tran1StoreMode, calls find for CacheableEntity::1 and
* XmlCacheableEntity::1. This will never trigger a cache refresh since the
* data is up to date - but it could trigger additional SQL
* </p>
* <p>
* The second transaction updates XmlCacheableEntity::1 with CacheStoreMode
* tran2StoreMode, calls find for CacheableEntity::1 and
* XmlCacheableEntity::1. In this case if tran2StoreMode ==
* CacheStoreMode.REFRESH we may update the cache with the state of
* CacheableEntity::1.
* </p>
*
* @param tran1StoreMode
* CacheStoreMode to use in transaction 1.
* @param tran2StoreMode
* cacheStoreMode to use in transaction 2.
* @param cacheUpdatedForTran1
* Whether the cache will contain an updated version of
* CacheableEntity::1
* @param cacheUpdatedForTran2
* Whether the cache will contain an updated version of
* XmlCacheableEntity::1
* @param version
* Expected starting version of for both entities
*/
public void entityManagerStoreModeTest(CacheStoreMode tran1StoreMode, CacheStoreMode tran2StoreMode,
boolean cacheUpdatedForTran1, boolean cacheUpdatedForTran2, int version) {
updateAndFind(CacheableEntity.class, 1, XmlCacheableEntity.class, 1, tran1StoreMode, null);
updateAndFind(XmlCacheableEntity.class, 1, CacheableEntity.class, 1, tran2StoreMode, null);
// get entities from the cache and ensure their versions are as
// expected.
EntityManager em = getEntityManagerFactory().createEntityManager();
em = getEntityManagerFactory().createEntityManager();
CacheableEntity ceFromEM = em.find(CacheableEntity.class, 1);
XmlCacheableEntity xceFromEM = em.find(XmlCacheableEntity.class, 1);
em.close();
assertEquals(cacheUpdatedForTran1 ? version + 1 : version, ceFromEM.getVersion());
assertEquals(cacheUpdatedForTran2 ? version + 1 : version, xceFromEM.getVersion());
// get the data from the database. Version should always have been
// updated in this case.
em = getEntityManagerFactory().createEntityManager();
em.setProperty(RETRIEVE_MODE_PROP, CacheRetrieveMode.BYPASS);
CacheableEntity ceFromDB =
(CacheableEntity) em.createNativeQuery("Select * from CacheableEntity where ID = 1", CacheableEntity.class)
.getSingleResult();
XmlCacheableEntity xceFromDB =
(XmlCacheableEntity) em.createNativeQuery("Select * from XmlCacheableEntity where ID = 1",
XmlCacheableEntity.class).getSingleResult();
assertEquals(version + 1, ceFromDB.getVersion());
assertEquals(version + 1, xceFromDB.getVersion());
em.close();
}
/**
* Execute the defaultStoreModeTest with
*/
public void testStoreModeUseBypass() throws Exception {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.USE, CacheStoreMode.BYPASS, true, false, 1);
}
}
public void testStoreModeUseUse() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.USE, CacheStoreMode.USE, true, true, 1);
}
}
public void testRefresh() {
if (getCacheEnabled()) {
OpenJPAEntityManagerSPI em = getEntityManagerFactory().createEntityManager();
CacheableEntity e1 = em.find(CacheableEntity.class, 1);
XmlCacheableEntity e2 = em.find(XmlCacheableEntity.class, 1);
assertNotNull(e1);
assertNotNull(e2);
int e1Version = e1.getVersion();
int e2Version = e2.getVersion();
String e1Sql = "UPDATE CacheableEntity SET VERSN=?1 WHERE ID=?2";
String e2Sql = "UPDATE XmlCacheableEntity SET VERSN=?1 WHERE ID=?2";
em.getTransaction().begin();
assertEquals(1, em.createNativeQuery(e1Sql).setParameter(1, e1Version + 1).setParameter(2, e1.getId())
.executeUpdate());
assertEquals(1, em.createNativeQuery(e2Sql).setParameter(1, e2Version + 1).setParameter(2, e2.getId())
.executeUpdate());
em.getTransaction().commit();
em.refresh(e1);
em.refresh(e2);
assertEquals(e1Version + 1, e1.getVersion());
assertEquals(e2Version + 1, e2.getVersion());
em.close();
}
}
public void testStoreModeUseRefresh() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.USE, CacheStoreMode.REFRESH, true, true, 1);
}
}
public void entityManagerStoreModeTest() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.BYPASS, CacheStoreMode.BYPASS, false, false, 1);
}
}
public void testStoreModeBypassUse() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.BYPASS, CacheStoreMode.USE, false, true, 1);
}
}
public void testStoreModeBypassRefresh() {
if (getCacheEnabled()) {
// REFRESH picks up the changes from the database, even though the
// first update was done with BYPASS
entityManagerStoreModeTest(CacheStoreMode.BYPASS, CacheStoreMode.REFRESH, true, true, 1);
}
}
public void testStoreModeRefreshUse() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.REFRESH, CacheStoreMode.USE, true, true, 1);
}
}
public void testStoreModeRefreshBypass() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.REFRESH, CacheStoreMode.BYPASS, true, false, 1);
}
}
public void testStoreModeRefreshRefresh() {
if (getCacheEnabled()) {
entityManagerStoreModeTest(CacheStoreMode.REFRESH, CacheStoreMode.REFRESH, true, true, 1);
}
}
public void testResultsFromQueryAreInCache() {
if (getCacheEnabled()) {
// clear cache
getEntityManagerFactory().getStoreCache().evictAll();
getEntityManagerFactory().getQueryResultCache().evictAll();
EntityManager em = getEntityManagerFactory().createEntityManager();
String query;
for(Class<?> cls : persistentTypes) {
query = "Select e from " + getAlias(cls) + " e";
List<?> res = em.createQuery(query).getResultList();
assertNotNull(String.format("Expected to find some results when running query %s",query), res);
assertTrue(String.format("Expected more than 0 results running query %s",query),res.size() != 0 ) ;
}
for(Class<?> cls : getExpectedInCache()) {
assertCached(getEntityManagerFactory().getCache(), cls, 1, true);
}
for(Class<?> cls : getExpectedNotInCache()) {
assertCached(getEntityManagerFactory().getCache(), cls, 1, false);
}
em.close();
}
}
public void testResultsFromFindAreInCache() {
if (getCacheEnabled()) {
// clear cache
getEntityManagerFactory().getStoreCache().evictAll();
getEntityManagerFactory().getQueryResultCache().evictAll();
EntityManager em = getEntityManagerFactory().createEntityManager();
for(Class<?> cls : persistentTypes) {
assertNotNull(String.format("Expected to find %s::%d from database or from cache", cls, 1),
em.find(cls, 1));
}
for(Class<?> cls : getExpectedInCache()) {
assertCached(getEntityManagerFactory().getCache(), cls, 1, true);
}
for(Class<?> cls : getExpectedNotInCache()) {
assertCached(getEntityManagerFactory().getCache(), cls, 1, false);
}
em.close();
}
}
private String getAlias(Class<?> cls) {
ClassMapping mapping =
(ClassMapping) getEntityManagerFactory().getConfiguration().getMetaDataRepositoryInstance().getMetaData(
cls, null, true);
return mapping.getTypeAlias();
}
}