| /* |
| * 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(); |
| } |
| } |