HHH-7179 Complete support for Natural Id caching for Infinispan

This commit is contained in:
Galder Zamarreño 2012-04-02 10:13:31 +02:00
parent 2afa747ef9
commit 5fa28e87ea
13 changed files with 765 additions and 120 deletions

View File

@ -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() );

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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<Void>() {
@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<Void>() {
@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<Void>() {
@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<Void>() {
@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<Void>() {
@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<Void>() {
@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<Void>() {
@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<Citizen>() {
@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<Void>() {
@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<Void>() {
@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<Void>() {
@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 );
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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<Void>() {
@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<Void>() {
@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<Void >() {
@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<Void >() {
@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<String> visited = new ConcurrentSet<String>();
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 );
// }
}
}
}
}