HHH-7097 Cache naturalId mapping on load event

This commit is contained in:
Eric Dalquist 2012-02-21 11:15:21 -06:00
parent 13f4c830e1
commit c0b66d5298
4 changed files with 184 additions and 117 deletions

View File

@ -43,6 +43,7 @@ import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.Status;
import org.hibernate.engine.spi.PersistenceContext.CachedNaturalIdValueSource;
import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.EventType;
@ -443,6 +444,22 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
source source
); );
if (entity != null && persister.hasNaturalIdentifier()) {
final int[] naturalIdentifierProperties = persister.getNaturalIdentifierProperties();
final Object[] naturalId = new Object[naturalIdentifierProperties.length];
for ( int i = 0; i < naturalIdentifierProperties.length; i++ ) {
naturalId[i] = persister.getPropertyValue( entity, naturalIdentifierProperties[i] );
}
event.getSession().getPersistenceContext().cacheNaturalIdResolution(
persister,
event.getEntityId(),
naturalId,
CachedNaturalIdValueSource.LOAD
);
}
if ( event.isAssociationFetch() && source.getFactory().getStatistics().isStatisticsEnabled() ) { if ( event.isAssociationFetch() && source.getFactory().getStatistics().isStatisticsEnabled() ) {
source.getFactory().getStatisticsImplementor().fetchEntity( event.getEntityClassName() ); source.getFactory().getStatisticsImplementor().fetchEntity( event.getEntityClassName() );
} }

View File

@ -52,6 +52,13 @@ import static org.junit.Assert.assertTrue;
public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase { public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase {
private static final Logger log = Logger.getLogger( NaturalIdOnSingleManyToOneTest.class ); private static final Logger log = Logger.getLogger( NaturalIdOnSingleManyToOneTest.class );
@Override
protected void cleanupTest() throws Exception {
this.cleanupCache();
this.deleteAllData();
}
@Test @Test
public void testMappingProperties() { public void testMappingProperties() {
log.warn("Commented out test"); log.warn("Commented out test");
@ -109,37 +116,17 @@ public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase {
// first query // first query
List results = criteria.list(); List results = criteria.list();
assertEquals( 1, results.size() ); assertEquals( 1, results.size() );
assertEquals( assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() );
assertEquals(
"First query should be a miss", 1, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 1, stats
.getNaturalIdCachePutCount()
);
assertEquals(
"Query count should be one", 1, stats
.getNaturalIdQueryExecutionCount()
);
// query a second time - result should be in session cache // query a second time - result should be in session cache
criteria.list(); criteria.list();
assertEquals( assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() );
assertEquals(
"Second query should not be a miss", 1, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query count should be one", 1, stats
.getNaturalIdQueryExecutionCount()
);
// cleanup // cleanup
tx.rollback(); tx.rollback();

View File

@ -48,6 +48,13 @@ import org.junit.Test;
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class NaturalIdTest extends BaseCoreFunctionalTestCase { public class NaturalIdTest extends BaseCoreFunctionalTestCase {
@Override
protected void cleanupTest() throws Exception {
this.cleanupCache();
this.deleteAllData();
}
@Test @Test
public void testMappingProperties() { public void testMappingProperties() {
ClassMetadata metaData = sessionFactory().getClassMetadata( ClassMetadata metaData = sessionFactory().getClassMetadata(
@ -67,12 +74,12 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
Session s = openSession(); Session s = openSession();
Transaction tx = s.beginTransaction(); Transaction tx = s.beginTransaction();
State france = ( State ) s.load( State.class, 2 ); State france = this.getState( s, "Ile de France" );
Criteria criteria = s.createCriteria( Citizen.class ); Criteria criteria = s.createCriteria( Citizen.class );
criteria.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", france ) ); criteria.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", france ) );
criteria.setCacheable( true ); criteria.setCacheable( true );
s.getSessionFactory().getCache().evictNaturalIdRegions(); this.cleanupCache();
Statistics stats = sessionFactory().getStatistics(); Statistics stats = sessionFactory().getStatistics();
stats.setStatisticsEnabled( true ); stats.setStatisticsEnabled( true );
@ -85,37 +92,17 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
// first query // first query
List results = criteria.list(); List results = criteria.list();
assertEquals( 1, results.size() ); assertEquals( 1, results.size() );
assertEquals( assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() );
assertEquals(
"First query should be a miss", 1, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 1, stats
.getNaturalIdCachePutCount()
);
assertEquals(
"Query execution count should be one", 1, stats
.getNaturalIdQueryExecutionCount()
);
// query a second time - result should be cached in session // query a second time - result should be cached in session
criteria.list(); criteria.list();
assertEquals( assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() );
assertEquals(
"Second query should not be a miss", 1, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query execution count should be one", 1, stats
.getNaturalIdQueryExecutionCount()
);
// cleanup // cleanup
tx.rollback(); tx.rollback();
@ -128,35 +115,27 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
Session s = openSession(); Session s = openSession();
Transaction tx = s.beginTransaction(); Transaction tx = s.beginTransaction();
State france = ( State ) s.load( State.class, 2 ); State france = this.getState( s, "Ile de France" );
final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class ); final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class );
naturalIdLoader.using( "ssn", "1234" ).using( "state", france ); naturalIdLoader.using( "ssn", "1234" ).using( "state", france );
//NaturalId cache gets populated during entity loading, need to clear it out //NaturalId cache gets populated during entity loading, need to clear it out
sessionFactory().getCache().evictNaturalIdRegions(); this.cleanupCache();
Statistics stats = sessionFactory().getStatistics(); Statistics stats = sessionFactory().getStatistics();
stats.setStatisticsEnabled( true ); stats.setStatisticsEnabled( true );
stats.clear(); stats.clear();
assertEquals( assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() );
// first query // first query
Citizen citizen = (Citizen)naturalIdLoader.load(); Citizen citizen = (Citizen)naturalIdLoader.load();
assertNotNull( citizen ); assertNotNull( citizen );
assertEquals( assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() );
assertEquals(
"First load should be a miss", 1, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 1, stats
.getNaturalIdCachePutCount()
);
// cleanup // cleanup
tx.rollback(); tx.rollback();
@ -165,51 +144,88 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
@Test @Test
public void testNaturalIdLoaderCached() { public void testNaturalIdLoaderCached() {
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(); saveSomeCitizens();
Statistics stats = sessionFactory().getStatistics(); assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() );
assertEquals( assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() );
"Cache hits should be empty", 0, stats assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() );
);
assertEquals(
"First load should be a miss", 1, stats //Try NaturalIdLoadAccess after insert
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 3, stats
.getNaturalIdCachePutCount()
);
Session s = openSession(); Session s = openSession();
Transaction tx = s.beginTransaction(); Transaction tx = s.beginTransaction();
State france = ( State ) s.load( State.class, 2 ); State france = this.getState( s, "Ile de France" );
final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class ); 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", 1, stats.getNaturalIdCachePutCount() );
assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() );
// cleanup
tx.rollback();
s.close();
//Try NaturalIdLoadAccess
s = openSession();
tx = s.beginTransaction();
this.cleanupCache();
stats.setStatisticsEnabled( true );
stats.clear();
// first query
citizen = (Citizen) s.get( Citizen.class, citizen.getId() );
assertNotNull( citizen );
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();
//Try NaturalIdLoadAccess after load
s = openSession();
tx = s.beginTransaction();
france = this.getState( s, "Ile de France" );
naturalIdLoader = s.byNaturalId( Citizen.class );
naturalIdLoader.using( "ssn", "1234" ).using( "state", france ); naturalIdLoader.using( "ssn", "1234" ).using( "state", france );
//Not clearing naturalId caches, should be warm from entity loading //Not clearing naturalId caches, should be warm from entity loading
stats.setStatisticsEnabled( true ); stats.setStatisticsEnabled( true );
stats.clear(); stats.clear();
assertEquals(
"Cache hits should be empty", 0, stats
.getNaturalIdCacheHitCount()
);
// first query // first query
Citizen citizen = (Citizen)naturalIdLoader.load(); citizen = (Citizen)naturalIdLoader.load();
assertNotNull( citizen ); assertNotNull( citizen );
assertEquals( assertEquals( "NaturalId Cache Hits", 1, stats.getNaturalIdCacheHitCount() );
"Cache hits should be empty", 1, stats assertEquals( "NaturalId Cache Misses", 0, stats.getNaturalIdCacheMissCount() );
.getNaturalIdCacheHitCount() assertEquals( "NaturalId Cache Puts", 1, stats.getNaturalIdCachePutCount() );
); assertEquals( "NaturalId Cache Queries", 0, stats.getNaturalIdQueryExecutionCount() );
assertEquals(
"First load should be a miss", 0, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 0, stats
.getNaturalIdCachePutCount()
);
// cleanup // cleanup
tx.rollback(); tx.rollback();
@ -222,7 +238,7 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
Session s = openSession(); Session s = openSession();
Transaction tx = s.beginTransaction(); Transaction tx = s.beginTransaction();
State france = ( State ) s.load( State.class, 2 ); State france = this.getState( s, "Ile de France" );
Criteria criteria = s.createCriteria( Citizen.class ); Criteria criteria = s.createCriteria( Citizen.class );
criteria.add( criteria.add(
Restrictions.naturalId().set( "ssn", "1234" ).set( Restrictions.naturalId().set( "ssn", "1234" ).set(
@ -232,7 +248,7 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
); );
criteria.setCacheable( false ); criteria.setCacheable( false );
s.getSessionFactory().getCache().evictNaturalIdRegions(); this.cleanupCache();
Statistics stats = sessionFactory().getStatistics(); Statistics stats = sessionFactory().getStatistics();
stats.setStatisticsEnabled( true ); stats.setStatisticsEnabled( true );
@ -310,6 +326,13 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
s.close(); s.close();
} }
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 );
}
@Override @Override
protected void configure(Configuration cfg) { protected void configure(Configuration cfg) {
cfg.setProperty( "hibernate.cache.use_query_cache", "true" ); cfg.setProperty( "hibernate.cache.use_query_cache", "true" );

View File

@ -23,20 +23,27 @@
*/ */
package org.hibernate.testing.junit4; package org.hibernate.testing.junit4;
import static org.junit.Assert.fail;
import java.io.InputStream; import java.io.InputStream;
import java.sql.Blob; import java.sql.Blob;
import java.sql.Clob; import java.sql.Clob;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import org.hibernate.Criteria;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Interceptor; import org.hibernate.Interceptor;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
@ -44,6 +51,7 @@ import org.hibernate.cfg.Mappings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.jdbc.AbstractReturningWork; import org.hibernate.jdbc.AbstractReturningWork;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
@ -51,6 +59,7 @@ import org.hibernate.mapping.Collection;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SimpleValue;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metamodel.MetadataSources; import org.hibernate.metamodel.MetadataSources;
import org.hibernate.metamodel.source.MetadataImplementor; import org.hibernate.metamodel.source.MetadataImplementor;
import org.hibernate.service.BootstrapServiceRegistry; import org.hibernate.service.BootstrapServiceRegistry;
@ -58,20 +67,15 @@ import org.hibernate.service.BootstrapServiceRegistryBuilder;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder; import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.config.spi.ConfigurationService; import org.hibernate.service.config.spi.ConfigurationService;
import org.hibernate.service.internal.BootstrapServiceRegistryImpl;
import org.hibernate.service.internal.StandardServiceRegistryImpl; import org.hibernate.service.internal.StandardServiceRegistryImpl;
import org.junit.After;
import org.junit.Before;
import org.hibernate.testing.AfterClassOnce; import org.hibernate.testing.AfterClassOnce;
import org.hibernate.testing.BeforeClassOnce; import org.hibernate.testing.BeforeClassOnce;
import org.hibernate.testing.OnExpectedFailure; import org.hibernate.testing.OnExpectedFailure;
import org.hibernate.testing.OnFailure; import org.hibernate.testing.OnFailure;
import org.hibernate.testing.SkipLog; import org.hibernate.testing.SkipLog;
import org.hibernate.testing.cache.CachingRegionFactory; import org.hibernate.testing.cache.CachingRegionFactory;
import org.junit.After;
import static org.junit.Assert.fail; import org.junit.Before;
/** /**
* Applies functional testing logic for core Hibernate testing on top of {@link BaseUnitTestCase} * Applies functional testing logic for core Hibernate testing on top of {@link BaseUnitTestCase}
@ -419,12 +423,48 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
assertAllDataRemoved(); assertAllDataRemoved();
} }
protected void deleteAllData() {
// Get all the entities the session factory knows about
final Map<String, ClassMetadata> allClassMetadata = this.sessionFactory().getAllClassMetadata();
Set<ClassMetadata> entityTypes = new LinkedHashSet<ClassMetadata>(allClassMetadata.values());
do {
final Set<ClassMetadata> failedEntitieTypes = new HashSet<ClassMetadata>();
for (final ClassMetadata entityType : entityTypes) {
final String entityClassName = entityType.getEntityName();
final Session s = openSession();
final Transaction tx = s.beginTransaction();
try {
final Criteria criteria = s.createCriteria( entityClassName );
final List<?> entities = criteria.list();
for (final Object entity : entities) {
s.delete( entity);
}
tx.commit();
}
catch (ConstraintViolationException e) {
failedEntitieTypes.add(entityType);
tx.rollback();
}
finally {
s.close();
}
}
entityTypes = failedEntitieTypes;
} while (!entityTypes.isEmpty());
}
protected void cleanupCache() { protected void cleanupCache() {
if ( sessionFactory != null ) { if ( sessionFactory != null ) {
sessionFactory.getCache().evictCollectionRegions(); sessionFactory.getCache().evictCollectionRegions();
sessionFactory.getCache().evictDefaultQueryRegion(); sessionFactory.getCache().evictDefaultQueryRegion();
sessionFactory.getCache().evictEntityRegions(); sessionFactory.getCache().evictEntityRegions();
sessionFactory.getCache().evictQueryRegions(); sessionFactory.getCache().evictQueryRegions();
sessionFactory.getCache().evictNaturalIdRegions();
} }
} }