From 5fa28e87eaaeba0d178733c72fc090756b1db5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarre=C3=B1o?= Date: Mon, 2 Apr 2012 10:13:31 +0200 Subject: [PATCH] HHH-7179 Complete support for Natural Id caching for Infinispan --- .../internal/NaturalIdXrefDelegate.java | 3 +- .../infinispan/InfinispanRegionFactory.java | 20 +- .../access/TransactionalAccessDelegate.java | 9 +- .../naturalid/NaturalIdRegionImpl.java | 1 + .../naturalid/TransactionalAccess.java | 25 +- .../query/QueryResultsRegionImpl.java | 3 - .../InfinispanRegionFactoryTestCase.java | 2 +- .../BasicTransactionalTestCase.java | 397 ++++++++++++++---- .../cache/infinispan/functional/Citizen.java | 69 +++ .../functional/NaturalIdOnManyToOne.java | 44 ++ .../functional/SingleNodeTestCase.java | 2 +- .../cache/infinispan/functional/State.java | 32 ++ .../NaturalIdInvalidationTestCase.java | 278 ++++++++++++ 13 files changed, 765 insertions(+), 120 deletions(-) create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/Citizen.java create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NaturalIdOnManyToOne.java create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/State.java create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTestCase.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java index 6d0664271c..8221e56900 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.hibernate.pretty.MessageHelper; import org.jboss.logging.Logger; import org.hibernate.action.spi.AfterTransactionCompletionProcess; @@ -102,7 +103,7 @@ public class NaturalIdXrefDelegate { session().getTimestamp(), null ); - + if ( put && justAddedToLocalCache && factory.getStatistics().isStatisticsEnabled() ) { factory.getStatisticsImplementor() .naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() ); diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java index b43d0cf861..8d8811108f 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java @@ -29,7 +29,6 @@ import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.CacheException; import org.hibernate.cache.infinispan.collection.CollectionRegionImpl; import org.hibernate.cache.infinispan.entity.EntityRegionImpl; -import org.hibernate.cache.infinispan.impl.BaseRegion; import org.hibernate.cache.infinispan.impl.ClassLoaderAwareCache; import org.hibernate.cache.infinispan.query.QueryResultsRegionImpl; import org.hibernate.cache.infinispan.timestamp.TimestampTypeOverrides; @@ -37,8 +36,6 @@ import org.hibernate.cache.infinispan.timestamp.TimestampsRegionImpl; import org.hibernate.cache.infinispan.tm.HibernateTransactionManagerLookup; import org.hibernate.cache.infinispan.util.CacheAdapter; import org.hibernate.cache.infinispan.util.CacheAdapterImpl; -import org.hibernate.cache.infinispan.util.CacheCommandFactory; -import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.spi.CollectionRegion; import org.hibernate.cache.spi.EntityRegion; import org.hibernate.cache.spi.NaturalIdRegion; @@ -323,7 +320,7 @@ public class InfinispanRegionFactory implements RegionFactory { } } - protected HibernateTransactionManagerLookup createTransactionManagerLookup( + protected org.infinispan.transaction.lookup.TransactionManagerLookup createTransactionManagerLookup( Settings settings, Properties properties) { return new HibernateTransactionManagerLookup(settings, properties); } @@ -332,13 +329,19 @@ public class InfinispanRegionFactory implements RegionFactory { * {@inheritDoc} */ public void stop() { - log.debug("Clear region references and stop Infinispan cache manager"); - getCacheCommandFactory(manager.getCache()).clearRegions(regionNames); - regionNames.clear(); + log.debug("Stop region factory"); + stopCacheRegions(); stopCacheManager(); } + protected void stopCacheRegions() { + log.debug("Clear region references"); + getCacheCommandFactory(manager.getCache()).clearRegions(regionNames); + regionNames.clear(); + } + protected void stopCacheManager() { + log.debug("Stop cache manager"); manager.stop(); } @@ -384,6 +387,9 @@ public class InfinispanRegionFactory implements RegionFactory { TypeOverrides collectionOverrides = new TypeOverrides(); collectionOverrides.setCacheName(DEF_ENTITY_RESOURCE); typeOverrides.put(COLLECTION_KEY, collectionOverrides); + TypeOverrides naturalIdOverrides = new TypeOverrides(); + naturalIdOverrides.setCacheName(DEF_ENTITY_RESOURCE); + typeOverrides.put(NATURAL_ID_KEY, naturalIdOverrides); TypeOverrides timestampOverrides = new TimestampTypeOverrides(); timestampOverrides.setCacheName(DEF_TIMESTAMPS_RESOURCE); typeOverrides.put(TIMESTAMPS_KEY, timestampOverrides); diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TransactionalAccessDelegate.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TransactionalAccessDelegate.java index 02f5511135..16d01f1220 100755 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TransactionalAccessDelegate.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TransactionalAccessDelegate.java @@ -48,6 +48,7 @@ import org.hibernate.cache.spi.access.SoftLock; */ public class TransactionalAccessDelegate { private static final Log log = LogFactory.getLog(TransactionalAccessDelegate.class); + private static final boolean isTrace = log.isTraceEnabled(); protected final CacheAdapter cacheAdapter; protected final BaseRegion region; protected final PutFromLoadValidator putValidator; @@ -68,11 +69,15 @@ public class TransactionalAccessDelegate { } public boolean putFromLoad(Object key, Object value, long txTimestamp, Object version) throws CacheException { - if (!region.checkValid()) + if (!region.checkValid()) { + if (isTrace) log.tracef("Region %s not valid", region.getName()); return false; + } - if (!putValidator.acquirePutFromLoadLock(key)) + if (!putValidator.acquirePutFromLoadLock(key)) { + if (isTrace) log.tracef("Put from load lock not acquired for key %s", key); return false; + } try { cacheAdapter.putForExternalRead(key, value); diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java index 5414c44717..fbabe8603e 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java @@ -31,6 +31,7 @@ public class NaturalIdRegionImpl extends BaseTransactionalDataRegion implements } throw new CacheException("Unsupported access type [" + accessType.getExternalName() + "]"); } + public PutFromLoadValidator getPutFromLoadValidator() { return new PutFromLoadValidator(transactionManager); } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/TransactionalAccess.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/TransactionalAccess.java index fa4c8f4146..fd159eb69a 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/TransactionalAccess.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/TransactionalAccess.java @@ -19,24 +19,14 @@ class TransactionalAccess implements NaturalIdRegionAccessStrategy { this.delegate = new TransactionalAccessDelegate( region, region.getPutFromLoadValidator() ); } - @Override - public boolean afterInsert(Object key, Object value) throws CacheException { - return false; - } - @Override public boolean insert(Object key, Object value) throws CacheException { - return false; + return delegate.insert(key, value, null); } @Override public boolean update(Object key, Object value) throws CacheException { - return false; - } - - @Override - public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException { - return false; + return delegate.update(key, value, null, null); } @Override @@ -59,7 +49,6 @@ class TransactionalAccess implements NaturalIdRegionAccessStrategy { return delegate.get( key, txTimestamp ); } - @Override public boolean putFromLoad(Object key, Object value, long txTimestamp, Object version) throws CacheException { return delegate.putFromLoad( key, value, txTimestamp, version ); @@ -81,7 +70,6 @@ class TransactionalAccess implements NaturalIdRegionAccessStrategy { delegate.removeAll(); } - @Override public SoftLock lockItem(Object key, Object version) throws CacheException { return null; @@ -100,5 +88,14 @@ class TransactionalAccess implements NaturalIdRegionAccessStrategy { public void unlockRegion(SoftLock lock) throws CacheException { } + @Override + public boolean afterInsert(Object key, Object value) throws CacheException { + return false; + } + + @Override + public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException { + return false; + } } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java index 3b0d2b2c84..88c48d3c67 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java @@ -4,8 +4,6 @@ import java.util.Properties; import javax.transaction.Transaction; import javax.transaction.TransactionManager; -import org.infinispan.notifications.Listener; - import org.hibernate.cache.CacheException; import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion; import org.hibernate.cache.infinispan.util.CacheAdapter; @@ -18,7 +16,6 @@ import org.hibernate.cache.spi.RegionFactory; * @author Galder ZamarreƱo * @since 3.5 */ -@Listener public class QueryResultsRegionImpl extends BaseTransactionalDataRegion implements QueryResultsRegion { private boolean localOnly; diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/InfinispanRegionFactoryTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/InfinispanRegionFactoryTestCase.java index f43be510b0..f2601409ef 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/InfinispanRegionFactoryTestCase.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/InfinispanRegionFactoryTestCase.java @@ -536,7 +536,7 @@ public class InfinispanRegionFactoryTestCase { private InfinispanRegionFactory createRegionFactory(final EmbeddedCacheManager manager, Properties p) { final InfinispanRegionFactory factory = new InfinispanRegionFactory() { @Override - protected HibernateTransactionManagerLookup createTransactionManagerLookup(Settings settings, Properties properties) { + protected org.infinispan.transaction.lookup.TransactionManagerLookup createTransactionManagerLookup(Settings settings, Properties properties) { return new HibernateTransactionManagerLookup(null, null) { @Override public TransactionManager getTransactionManager() throws Exception { diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java index 6ea0baf61c..071f003099 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java @@ -24,10 +24,18 @@ package org.hibernate.test.cache.infinispan.functional; import java.io.Serializable; +import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import org.hibernate.Criteria; +import org.hibernate.NaturalIdLoadAccess; +import org.hibernate.cache.infinispan.access.PutFromLoadValidator; +import org.hibernate.criterion.Restrictions; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; +import org.junit.After; import org.junit.Test; import org.hibernate.Session; @@ -37,6 +45,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.stat.SecondLevelCacheStatistics; import org.hibernate.stat.Statistics; +import static junit.framework.Assert.assertNotNull; +import static org.infinispan.test.TestingUtil.withTx; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,106 +64,117 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { super.configure( cfg ); } - @Test + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Citizen.class, State.class, + NaturalIdOnManyToOne.class + }; + } + + @After + public void cleanupData() throws Exception { + super.cleanupCache(); + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = sessionFactory().openSession(); + s.beginTransaction(); + s.createQuery( "delete NaturalIdOnManyToOne" ).executeUpdate(); + s.createQuery( "delete Citizen" ).executeUpdate(); + s.createQuery( "delete State" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + return null; + } + }); + } + + @Test public void testEntityCache() throws Exception { - Statistics stats = sessionFactory().getStatistics(); + final Statistics stats = sessionFactory().getStatistics(); stats.clear(); - Item item = new Item( "chris", "Chris's Item" ); - beginTx(); - try { - Session s = openSession(); - s.getTransaction().begin(); - s.persist( item ); - s.getTransaction().commit(); - s.close(); - } - catch (Exception e) { - setRollbackOnlyTx( e ); - } - finally { - commitOrRollbackTx(); - } + final Item item = new Item( "chris", "Chris's Item" ); + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + s.getTransaction().begin(); + s.persist( item ); + s.getTransaction().commit(); + s.close(); + return null; + } + }); - log.info( "Entry persisted, let's load and delete it." ); + log.info("Entry persisted, let's load and delete it."); - beginTx(); - try { - Session s = openSession(); - Item found = (Item) s.load( Item.class, item.getId() ); - log.info( stats.toString() ); - assertEquals( item.getDescription(), found.getDescription() ); - assertEquals( 0, stats.getSecondLevelCacheMissCount() ); - assertEquals( 1, stats.getSecondLevelCacheHitCount() ); - s.delete( found ); - s.close(); - } - catch (Exception e) { - setRollbackOnlyTx( e ); - } - finally { - commitOrRollbackTx(); - } + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + Item found = (Item) s.load(Item.class, item.getId()); + log.info(stats.toString()); + assertEquals(item.getDescription(), found.getDescription()); + assertEquals(0, stats.getSecondLevelCacheMissCount()); + assertEquals(1, stats.getSecondLevelCacheHitCount()); + s.delete(found); + s.close(); + return null; + } + }); } @Test public void testCollectionCache() throws Exception { - Statistics stats = sessionFactory().getStatistics(); + final Statistics stats = sessionFactory().getStatistics(); stats.clear(); - Item item = new Item( "chris", "Chris's Item" ); - Item another = new Item( "another", "Owned Item" ); + final Item item = new Item( "chris", "Chris's Item" ); + final Item another = new Item( "another", "Owned Item" ); item.addItem( another ); - beginTx(); - try { - Session s = openSession(); - s.getTransaction().begin(); - s.persist( item ); - s.persist( another ); - s.getTransaction().commit(); - s.close(); - } - catch (Exception e) { - setRollbackOnlyTx( e ); - } - finally { - commitOrRollbackTx(); - } + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + s.getTransaction().begin(); + s.persist( item ); + s.persist( another ); + s.getTransaction().commit(); + s.close(); + return null; + } + }); - beginTx(); - try { - Session s = openSession(); - Item loaded = (Item) s.load( Item.class, item.getId() ); - assertEquals( 1, loaded.getItems().size() ); - s.close(); - } - catch (Exception e) { - setRollbackOnlyTx( e ); - } - finally { - commitOrRollbackTx(); - } + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + Item loaded = (Item) s.load( Item.class, item.getId() ); + assertEquals( 1, loaded.getItems().size() ); + s.close(); + return null; + } + }); - beginTx(); - try { - Session s = openSession(); - SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); - Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() ); - stats.logSummary(); - assertEquals( item.getName(), loadedWithCachedCollection.getName() ); - assertEquals( item.getItems().size(), loadedWithCachedCollection.getItems().size() ); - assertEquals( 1, cStats.getHitCount() ); - Map cacheEntries = cStats.getEntries(); - assertEquals( 1, cacheEntries.size() ); - s.close(); - } - catch (Exception e) { - setRollbackOnlyTx( e ); - } - finally { - commitOrRollbackTx(); - } + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); + Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() ); + stats.logSummary(); + assertEquals( item.getName(), loadedWithCachedCollection.getName() ); + assertEquals( item.getItems().size(), loadedWithCachedCollection.getItems().size() ); + assertEquals( 1, cStats.getHitCount() ); + Map cacheEntries = cStats.getEntries(); + assertEquals( 1, cacheEntries.size() ); + s.close(); + return null; + } + }); } @Test @@ -164,6 +185,7 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { VersionedItem item = null; Transaction txn = null; Session s = null; + beginTx(); try { s = openSession(); @@ -191,12 +213,12 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { try { s = openSession(); txn = s.beginTransaction(); - s.update( item ); + s.update(item); txn.commit(); - fail( "expected stale write to fail" ); + fail("expected stale write to fail"); } catch (Exception e) { - setRollbackOnlyTxExpected( e ); + setRollbackOnlyTxExpected(e); } finally { commitOrRollbackTx(); @@ -215,7 +237,7 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { Object entry = slcs.getEntries().get( item.getId() ); Long cachedVersionValue; cachedVersionValue = (Long) ((CacheEntry) entry).getVersion(); - assertEquals( initialVersion.longValue(), cachedVersionValue.longValue() ); + assertEquals(initialVersion.longValue(), cachedVersionValue.longValue()); beginTx(); try { @@ -243,7 +265,7 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { SecondLevelCacheStatistics slcs = stats.getSecondLevelCacheStatistics( Item.class.getName() ); sessionFactory().getCache().evictEntityRegion( Item.class.getName() ); - assertEquals( 0, slcs.getPutCount() ); + assertEquals(0, slcs.getPutCount()); assertEquals( 0, slcs.getElementCountInMemory() ); assertEquals( 0, slcs.getEntries().size() ); @@ -304,7 +326,7 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { // cleanup s = openSession(); t = s.beginTransaction(); - s.delete( i ); + s.delete(i); t.commit(); s.close(); } @@ -440,4 +462,197 @@ public class BasicTransactionalTestCase extends SingleNodeTestCase { assertEquals( 0, cacheEntries.size() ); } + @Test + public void testNaturalIdCached() throws Exception { + saveSomeCitizens(); + + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + State france = BasicTransactionalTestCase.this.getState(s, "Ile de France"); + Criteria criteria = s.createCriteria( Citizen.class ); + criteria.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", france ) ); + criteria.setCacheable( true ); + + BasicTransactionalTestCase.this.cleanupCache(); + + Statistics stats = sessionFactory().getStatistics(); + stats.setStatisticsEnabled( true ); + stats.clear(); + assertEquals( + "Cache hits should be empty", 0, stats + .getNaturalIdCacheHitCount() + ); + + // first query + List results = criteria.list(); + assertEquals( 1, results.size() ); + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 1, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() ); + + // query a second time - result should be cached in session + criteria.list(); + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 1, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() ); + + // cleanup + tx.rollback(); + s.close(); + return null; + } + }); + } + + @Test + public void testNaturalIdLoaderCached() throws Exception { + final Statistics stats = sessionFactory().getStatistics(); + stats.setStatisticsEnabled( true ); + stats.clear(); + + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() ); + + saveSomeCitizens(); + + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() ); + + //Try NaturalIdLoadAccess after insert + final Citizen citizen = withTx(tm, new Callable() { + @Override + public Citizen call() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + State france = BasicTransactionalTestCase.this.getState(s, "Ile de France"); + NaturalIdLoadAccess naturalIdLoader = s.byNaturalId(Citizen.class); + naturalIdLoader.using("ssn", "1234").using("state", france); + + //Not clearing naturalId caches, should be warm from entity loading + stats.clear(); + + // first query + Citizen citizen = (Citizen) naturalIdLoader.load(); + assertNotNull(citizen); + assertEquals("NaturalId Cache Hits", 1, stats.getNaturalIdCacheHitCount()); + assertEquals("NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount()); + assertEquals("NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount()); + assertEquals("NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount()); + + // cleanup + tx.rollback(); + s.close(); + return citizen; + } + }); + + // TODO: Clear caches manually via cache manager (it's faster!!) + this.cleanupCache(); + Thread.sleep(PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD + TimeUnit.SECONDS.toMillis(1)); + stats.setStatisticsEnabled( true ); + stats.clear(); + + //Try NaturalIdLoadAccess + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + + // first query + Citizen loadedCitizen = (Citizen) s.get( Citizen.class, citizen.getId() ); + assertNotNull( loadedCitizen ); + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 1, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() ); + + // cleanup + tx.rollback(); + s.close(); + return null; + } + }); + + // Try NaturalIdLoadAccess after load + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + State france = BasicTransactionalTestCase.this.getState(s, "Ile de France"); + NaturalIdLoadAccess naturalIdLoader = s.byNaturalId(Citizen.class); + naturalIdLoader.using( "ssn", "1234" ).using( "state", france ); + + //Not clearing naturalId caches, should be warm from entity loading + stats.setStatisticsEnabled( true ); + stats.clear(); + + // first query + Citizen loadedCitizen = (Citizen) naturalIdLoader.load(); + assertNotNull( loadedCitizen ); + assertEquals( "NaturalId Cache Hits", 1, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() ); + + // cleanup + tx.rollback(); + s.close(); + return null; + } + }); + + } + + private void saveSomeCitizens() throws Exception { + final Citizen c1 = new Citizen(); + c1.setFirstname( "Emmanuel" ); + c1.setLastname( "Bernard" ); + c1.setSsn( "1234" ); + + final State france = new State(); + france.setName( "Ile de France" ); + c1.setState( france ); + + final Citizen c2 = new Citizen(); + c2.setFirstname( "Gavin" ); + c2.setLastname( "King" ); + c2.setSsn( "000" ); + final State australia = new State(); + australia.setName( "Australia" ); + c2.setState( australia ); + + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + s.persist( australia ); + s.persist( france ); + s.persist( c1 ); + s.persist( c2 ); + tx.commit(); + s.close(); + return null; + } + }); + } + + private State getState(Session s, String name) { + Criteria criteria = s.createCriteria( State.class ); + criteria.add( Restrictions.eq("name", name) ); + criteria.setCacheable(true); + return (State) criteria.list().get( 0 ); + } + } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/Citizen.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/Citizen.java new file mode 100644 index 0000000000..531172ee92 --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/Citizen.java @@ -0,0 +1,69 @@ +//$Id$ +package org.hibernate.test.cache.infinispan.functional; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Emmanuel Bernard + */ +@Entity +@NaturalIdCache +public class Citizen { + @Id + @GeneratedValue + private Integer id; + private String firstname; + private String lastname; + @NaturalId + @ManyToOne + private State state; + @NaturalId + private String ssn; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public String getSsn() { + return ssn; + } + + public void setSsn(String ssn) { + this.ssn = ssn; + } +} diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NaturalIdOnManyToOne.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NaturalIdOnManyToOne.java new file mode 100644 index 0000000000..e205a9efa1 --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NaturalIdOnManyToOne.java @@ -0,0 +1,44 @@ +package org.hibernate.test.cache.infinispan.functional; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +@NaturalIdCache +/** + * Test case for NaturalId annotation - ANN-750 + * + * @author Emmanuel Bernard + * @author Hardy Ferentschik + */ +public class NaturalIdOnManyToOne { + + @Id + @GeneratedValue + int id; + + @NaturalId + @ManyToOne + Citizen citizen; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Citizen getCitizen() { + return citizen; + } + + public void setCitizen(Citizen citizen) { + this.citizen = citizen; + } +} diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/SingleNodeTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/SingleNodeTestCase.java index 287f6d887f..a270cd252f 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/SingleNodeTestCase.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/SingleNodeTestCase.java @@ -48,7 +48,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; */ public abstract class SingleNodeTestCase extends BaseCoreFunctionalTestCase { private static final Log log = LogFactory.getLog( SingleNodeTestCase.class ); - private TransactionManager tm; + protected TransactionManager tm; @Before public void prepare() { diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/State.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/State.java new file mode 100644 index 0000000000..229da0b682 --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/State.java @@ -0,0 +1,32 @@ +//$Id$ +package org.hibernate.test.cache.infinispan.functional; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Emmanuel Bernard + */ +@Entity +public class State { + @Id + @GeneratedValue + private Integer id; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTestCase.java new file mode 100644 index 0000000000..7fd73f7d41 --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTestCase.java @@ -0,0 +1,278 @@ +package org.hibernate.test.cache.infinispan.functional.cluster; + +import org.hibernate.Criteria; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.cache.spi.NaturalIdCacheKey; +import org.hibernate.criterion.Restrictions; +import org.hibernate.test.cache.infinispan.functional.Citizen; +import org.hibernate.test.cache.infinispan.functional.NaturalIdOnManyToOne; +import org.hibernate.test.cache.infinispan.functional.State; +import org.infinispan.Cache; +import org.infinispan.manager.CacheContainer; +import org.infinispan.notifications.Listener; +import org.infinispan.notifications.cachelistener.annotation.CacheEntryVisited; +import org.infinispan.notifications.cachelistener.event.CacheEntryVisitedEvent; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; +import org.jboss.util.collection.ConcurrentSet; +import org.junit.After; +import org.junit.Test; + +import javax.transaction.TransactionManager; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; + +import static org.infinispan.test.TestingUtil.tmpDirectory; +import static org.infinispan.test.TestingUtil.withTx; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * // TODO: Document this + * + * @author Galder ZamarreƱo + * @since // TODO + */ +public class NaturalIdInvalidationTestCase extends DualNodeTestCase { + + private static final Log log = LogFactory.getLog(NaturalIdInvalidationTestCase.class); + + private static final long SLEEP_TIME = 50l; + private static final Integer CUSTOMER_ID = new Integer( 1 ); + private static int test = 0; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Citizen.class, State.class, + NaturalIdOnManyToOne.class + }; + } + + @Test + public void testAll() throws Exception { + log.info( "*** testAll()" ); + + // Bind a listener to the "local" cache + // Our region factory makes its CacheManager available to us + CacheContainer localManager = ClusterAwareRegionFactory.getCacheManager(DualNodeTestCase.LOCAL); + Cache localNaturalIdCache = localManager.getCache(Citizen.class.getName() + "##NaturalId"); + MyListener localListener = new MyListener( "local" ); + localNaturalIdCache.addListener(localListener); + TransactionManager localTM = DualNodeJtaTransactionManagerImpl.getInstance(DualNodeTestCase.LOCAL); + + // Bind a listener to the "remote" cache + CacheContainer remoteManager = ClusterAwareRegionFactory.getCacheManager(DualNodeTestCase.REMOTE); + Cache remoteNaturalIdCache = remoteManager.getCache(Citizen.class.getName() + "##NaturalId"); + MyListener remoteListener = new MyListener( "remote" ); + remoteNaturalIdCache.addListener(remoteListener); + TransactionManager remoteTM = DualNodeJtaTransactionManagerImpl.getInstance(DualNodeTestCase.REMOTE); + + SessionFactory localFactory = sessionFactory(); + SessionFactory remoteFactory = secondNodeEnvironment().getSessionFactory(); + + try { + assertTrue(remoteListener.isEmpty()); + assertTrue(localListener.isEmpty()); + + saveSomeCitizens(localTM, localFactory); + + assertTrue(remoteListener.isEmpty()); + assertTrue(localListener.isEmpty()); + + // Sleep a bit to let async commit propagate. Really just to + // help keep the logs organized for debugging any issues + sleep( SLEEP_TIME ); + + log.debug("Find node 0"); + // This actually brings the collection into the cache + getCitizenWithCriteria(localTM, localFactory); + + sleep( SLEEP_TIME ); + // Now the collection is in the cache so, the 2nd "get" + // should read everything from the cache + log.debug( "Find(2) node 0" ); + localListener.clear(); + getCitizenWithCriteria(localTM, localFactory); + + // Check the read came from the cache + log.debug( "Check cache 0" ); + assertLoadedFromCache(localListener, "1234"); + + log.debug( "Find node 1" ); + // This actually brings the collection into the cache since invalidation is in use + getCitizenWithCriteria(remoteTM, remoteFactory); + + // Now the collection is in the cache so, the 2nd "get" + // should read everything from the cache + log.debug( "Find(2) node 1" ); + remoteListener.clear(); + getCitizenWithCriteria(remoteTM, remoteFactory); + + // Check the read came from the cache + log.debug( "Check cache 1" ); + assertLoadedFromCache(remoteListener, "1234"); + + // Modify customer in remote + remoteListener.clear(); + deleteCitizenWithCriteria(remoteTM, remoteFactory); + sleep(250); + + Set localKeys = localNaturalIdCache.keySet(); + assertEquals(1, localKeys.size()); + // Only key left is the one for the citizen *not* in France + localKeys.toString().contains("000"); + } + catch (Exception e) { + log.error("Error", e); + throw e; + } finally { + withTx(localTM, new Callable() { + @Override + public Void call() throws Exception { + Session s = sessionFactory().openSession(); + s.beginTransaction(); + s.createQuery( "delete NaturalIdOnManyToOne" ).executeUpdate(); + s.createQuery( "delete Citizen" ).executeUpdate(); + s.createQuery( "delete State" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + return null; + } + }); + } + } + + private void assertLoadedFromCache(MyListener localListener, String id) { + for (String visited : localListener.visited){ + if (visited.contains(id)) + return; + } + fail("Citizen (" + id + ") should have present in the cache"); + } + + private void saveSomeCitizens(TransactionManager tm, final SessionFactory sf) throws Exception { + final Citizen c1 = new Citizen(); + c1.setFirstname( "Emmanuel" ); + c1.setLastname( "Bernard" ); + c1.setSsn( "1234" ); + + final State france = new State(); + france.setName( "Ile de France" ); + c1.setState( france ); + + final Citizen c2 = new Citizen(); + c2.setFirstname( "Gavin" ); + c2.setLastname( "King" ); + c2.setSsn( "000" ); + final State australia = new State(); + australia.setName( "Australia" ); + c2.setState( australia ); + + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = sf.openSession(); + Transaction tx = s.beginTransaction(); + s.persist( australia ); + s.persist( france ); + s.persist( c1 ); + s.persist( c2 ); + tx.commit(); + s.close(); + return null; + } + }); + } + + private void getCitizenWithCriteria(TransactionManager tm, final SessionFactory sf) throws Exception { + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = sf.openSession(); + Transaction tx = s.beginTransaction(); + State france = getState(s, "Ile de France"); + Criteria criteria = s.createCriteria( Citizen.class ); + criteria.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", france ) ); + criteria.setCacheable( true ); + criteria.list(); + // cleanup + tx.commit(); + s.close(); + return null; + } + }); + } + + private void deleteCitizenWithCriteria(TransactionManager tm, final SessionFactory sf) throws Exception { + withTx(tm, new Callable() { + @Override + public Void call() throws Exception { + Session s = sf.openSession(); + Transaction tx = s.beginTransaction(); + State france = getState(s, "Ile de France"); + Criteria criteria = s.createCriteria( Citizen.class ); + criteria.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", france ) ); + criteria.setCacheable( true ); + Citizen c = (Citizen) criteria.uniqueResult(); + s.delete(c); + // cleanup + tx.commit(); + s.close(); + return null; + } + }); + } + + private State getState(Session s, String name) { + Criteria criteria = s.createCriteria( State.class ); + criteria.add( Restrictions.eq("name", name) ); + criteria.setCacheable(true); + return (State) criteria.list().get( 0 ); + } + + @Listener + public static class MyListener { + private static final Log log = LogFactory.getLog( MyListener.class ); + private Set visited = new ConcurrentSet(); + private final String name; + + public MyListener(String name) { + this.name = name; + } + + public void clear() { + visited.clear(); + } + + public boolean isEmpty() { + return visited.isEmpty(); + } + + @CacheEntryVisited + public void nodeVisited(CacheEntryVisitedEvent event) { + log.debug( event.toString() ); + if ( !event.isPre() ) { + NaturalIdCacheKey cacheKey = (NaturalIdCacheKey) event.getKey(); + visited.add(cacheKey.toString()); +// Integer primKey = (Integer) cacheKey.getKey(); +// String key = (String) cacheKey.getEntityOrRoleName() + '#' + primKey; +// log.debug( "MyListener[" + name + "] - Visiting key " + key ); +// // String name = fqn.toString(); +// String token = ".functional."; +// int index = key.indexOf( token ); +// if ( index > -1 ) { +// index += token.length(); +// key = key.substring( index ); +// log.debug( "MyListener[" + name + "] - recording visit to " + key ); +// visited.add( key ); +// } + } + } + } + +}