diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index 6ca71fe98d..41ad608df2 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -13,18 +13,22 @@ import java.util.Map; import org.hibernate.HibernateException; import org.hibernate.PersistentObjectException; import org.hibernate.UnresolvableObjectException; +import org.hibernate.action.spi.AfterTransactionCompletionProcess; +import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; +import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.RefreshEvent; import org.hibernate.event.spi.RefreshEventListener; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.CollectionType; @@ -136,17 +140,31 @@ public class DefaultRefreshEventListener implements RefreshEventListener { } if ( persister.hasCache() ) { + Object previousVersion = null; + if ( persister.isVersionPropertyGenerated() ) { + // we need to grab the version value from the entity, otherwise + // we have issues with generated-version entities that may have + // multiple actions queued during the same flush + previousVersion = persister.getVersion( object ); + } final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy(); - Object ck = cache.generateCacheKey( + final Object ck = cache.generateCacheKey( id, persister, source.getFactory(), source.getTenantIdentifier() ); - cache.evict( ck ); + final SoftLock lock = cache.lockItem( source, ck, previousVersion ); + source.getActionQueue().registerProcess( new AfterTransactionCompletionProcess() { + @Override + public void doAfterTransactionCompletion(boolean success, SessionImplementor session) { + cache.unlockItem( session, ck, lock ); + } + } ); + cache.remove( source, ck ); } - evictCachedCollections( persister, id, source.getFactory() ); + evictCachedCollections( persister, id, source ); String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile(); source.getLoadQueryInfluencers().setInternalFetchProfile( "refresh" ); @@ -168,19 +186,36 @@ public class DefaultRefreshEventListener implements RefreshEventListener { } - private void evictCachedCollections(EntityPersister persister, Serializable id, SessionFactoryImplementor factory) { - evictCachedCollections( persister.getPropertyTypes(), id, factory ); + private void evictCachedCollections(EntityPersister persister, Serializable id, EventSource source) { + evictCachedCollections( persister.getPropertyTypes(), id, source ); } - private void evictCachedCollections(Type[] types, Serializable id, SessionFactoryImplementor factory) + private void evictCachedCollections(Type[] types, Serializable id, EventSource source) throws HibernateException { for ( Type type : types ) { if ( type.isCollectionType() ) { - factory.getCache().evictCollection( ( (CollectionType) type ).getRole(), id ); + CollectionPersister collectionPersister = source.getFactory().getCollectionPersister( ( (CollectionType) type ).getRole() ); + if ( collectionPersister.hasCache() ) { + final CollectionRegionAccessStrategy cache = collectionPersister.getCacheAccessStrategy(); + final Object ck = cache.generateCacheKey( + id, + collectionPersister, + source.getFactory(), + source.getTenantIdentifier() + ); + final SoftLock lock = cache.lockItem( source, ck, null ); + source.getActionQueue().registerProcess( new AfterTransactionCompletionProcess() { + @Override + public void doAfterTransactionCompletion(boolean success, SessionImplementor session) { + cache.unlockItem( session, ck, lock ); + } + } ); + cache.remove( source, ck ); + } } else if ( type.isComponentType() ) { CompositeType actype = (CompositeType) type; - evictCachedCollections( actype.getSubtypes(), id, factory ); + evictCachedCollections( actype.getSubtypes(), id, source ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/InsertedDataTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/InsertedDataTest.java index ede717953d..a596a106ee 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cache/InsertedDataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/InsertedDataTest.java @@ -13,10 +13,14 @@ import org.junit.Test; import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.persister.entity.Lockable; + import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * Tests for handling of data just inserted during a transaction being read from the database @@ -164,7 +168,9 @@ public class InsertedDataTest extends BaseCoreFunctionalTestCase { s.close(); Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); - assertEquals( 0, cacheMap.size() ); + assertEquals( 1, cacheMap.size() ); + Object lock = cacheMap.values().iterator().next(); + assertEquals( "org.hibernate.testing.cache.AbstractReadWriteAccessStrategy$Lock", lock.getClass().getName() ); s = openSession(); s.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/RefreshUpdatedDataTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/RefreshUpdatedDataTest.java new file mode 100644 index 0000000000..778dec6f3c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/RefreshUpdatedDataTest.java @@ -0,0 +1,345 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cache; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Zhenlei Huang + */ +@TestForIssue(jiraKey = "HHH-10649") +@RequiresDialect(value = {H2Dialect.class}) +public class RefreshUpdatedDataTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ReadWriteCacheableItem.class, + ReadWriteVersionedCacheableItem.class, + NonStrictReadWriteCacheableItem.class, + NonStrictReadWriteVersionedCacheableItem.class, + }; + } + + @Override + protected void configure(Configuration cfg) { + super.configure( cfg ); + Properties properties = Environment.getProperties(); + if ( H2Dialect.class.getName().equals( properties.get( Environment.DIALECT ) ) ) { + cfg.setProperty( Environment.URL, "jdbc:h2:mem:db-mvcc;MVCC=true" ); + } + cfg.setProperty( Environment.CACHE_REGION_PREFIX, "" ); + cfg.setProperty( Environment.GENERATE_STATISTICS, "true" ); + cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" ); + cfg.setProperty( Environment.CACHE_PROVIDER_CONFIG, "true" ); + } + + @Test + public void testUpdateAndFlushThenRefresh() { + // prepare data + Session s = openSession(); + s.beginTransaction(); + + final String BEFORE = "before"; + + ReadWriteCacheableItem readWriteCacheableItem = new ReadWriteCacheableItem( BEFORE ); + readWriteCacheableItem.getTags().add( "Hibernate" ); + readWriteCacheableItem.getTags().add( "ORM" ); + s.persist( readWriteCacheableItem ); + + ReadWriteVersionedCacheableItem readWriteVersionedCacheableItem = new ReadWriteVersionedCacheableItem( BEFORE ); + readWriteVersionedCacheableItem.getTags().add( "Hibernate" ); + readWriteVersionedCacheableItem.getTags().add( "ORM" ); + s.persist( readWriteVersionedCacheableItem ); + + NonStrictReadWriteCacheableItem nonStrictReadWriteCacheableItem = new NonStrictReadWriteCacheableItem( BEFORE ); + nonStrictReadWriteCacheableItem.getTags().add( "Hibernate" ); + nonStrictReadWriteCacheableItem.getTags().add( "ORM" ); + s.persist( nonStrictReadWriteCacheableItem ); + + NonStrictReadWriteVersionedCacheableItem nonStrictReadWriteVersionedCacheableItem = new NonStrictReadWriteVersionedCacheableItem( BEFORE ); + nonStrictReadWriteVersionedCacheableItem.getTags().add( "Hibernate" ); + nonStrictReadWriteVersionedCacheableItem.getTags().add( "ORM" ); + s.persist( nonStrictReadWriteVersionedCacheableItem ); + + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.beginTransaction(); + + final String AFTER = "after"; + + ReadWriteCacheableItem readWriteCacheableItem1 = s1.get( ReadWriteCacheableItem.class, readWriteCacheableItem.getId() ); + readWriteCacheableItem1.setName( AFTER ); + readWriteCacheableItem1.getTags().remove("ORM"); + + ReadWriteVersionedCacheableItem readWriteVersionedCacheableItem1 = s1.get( ReadWriteVersionedCacheableItem.class, readWriteVersionedCacheableItem.getId() ); + readWriteVersionedCacheableItem1.setName( AFTER ); + readWriteVersionedCacheableItem1.getTags().remove("ORM"); + + NonStrictReadWriteCacheableItem nonStrictReadWriteCacheableItem1 = s1.get( NonStrictReadWriteCacheableItem.class, nonStrictReadWriteCacheableItem.getId() ); + nonStrictReadWriteCacheableItem1.setName( AFTER ); + nonStrictReadWriteCacheableItem1.getTags().remove("ORM"); + + NonStrictReadWriteVersionedCacheableItem nonStrictReadWriteVersionedCacheableItem1 = s1.get( NonStrictReadWriteVersionedCacheableItem.class, nonStrictReadWriteVersionedCacheableItem.getId() ); + nonStrictReadWriteVersionedCacheableItem1.setName( AFTER ); + nonStrictReadWriteVersionedCacheableItem1.getTags().remove("ORM"); + + s1.flush(); + s1.refresh( readWriteCacheableItem1 ); + s1.refresh( readWriteVersionedCacheableItem1 ); + s1.refresh( nonStrictReadWriteCacheableItem1 ); + s1.refresh( nonStrictReadWriteVersionedCacheableItem1 ); + + assertEquals( AFTER, readWriteCacheableItem1.getName() ); + assertEquals( 1, readWriteCacheableItem1.getTags().size() ); + assertEquals( AFTER, readWriteVersionedCacheableItem1.getName() ); + assertEquals( 1, readWriteVersionedCacheableItem1.getTags().size() ); + assertEquals( AFTER, nonStrictReadWriteCacheableItem1.getName() ); + assertEquals( 1, nonStrictReadWriteCacheableItem1.getTags().size() ); + assertEquals( AFTER, nonStrictReadWriteVersionedCacheableItem1.getName() ); + assertEquals( 1, nonStrictReadWriteVersionedCacheableItem1.getTags().size() ); + + // open another session + Session s2 = sessionFactory().openSession(); + try { + s2.beginTransaction(); + ReadWriteCacheableItem readWriteCacheableItem2 = s2.get( ReadWriteCacheableItem.class, readWriteCacheableItem.getId() ); + ReadWriteVersionedCacheableItem readWriteVersionedCacheableItem2 = s2.get( ReadWriteVersionedCacheableItem.class, readWriteVersionedCacheableItem.getId() ); + NonStrictReadWriteCacheableItem nonStrictReadWriteCacheableItem2 = s2.get( NonStrictReadWriteCacheableItem.class, nonStrictReadWriteCacheableItem.getId() ); + NonStrictReadWriteVersionedCacheableItem nonStrictReadWriteVersionedCacheableItem2 = s2.get( NonStrictReadWriteVersionedCacheableItem.class, nonStrictReadWriteVersionedCacheableItem.getId() ); + + assertEquals( BEFORE, readWriteCacheableItem2.getName() ); + assertEquals( 2, readWriteCacheableItem2.getTags().size() ); + assertEquals( BEFORE, readWriteVersionedCacheableItem2.getName() ); + assertEquals( 2, readWriteVersionedCacheableItem2.getTags().size() ); + + //READ_UNCOMMITTED because there is no locking to prevent collections from being cached in the first Session + + assertEquals( BEFORE, nonStrictReadWriteCacheableItem2.getName() ); + assertEquals( 1, nonStrictReadWriteCacheableItem2.getTags().size()); + assertEquals( BEFORE, nonStrictReadWriteVersionedCacheableItem2.getName() ); + assertEquals( 1, nonStrictReadWriteVersionedCacheableItem2.getTags().size() ); + + s2.getTransaction().commit(); + } + finally { + if ( s2.getTransaction().getStatus().canRollback() ) { + s2.getTransaction().rollback(); + } + s2.close(); + } + + s1.getTransaction().rollback(); + s1.close(); + + s = openSession(); + s.beginTransaction(); + s.delete( readWriteCacheableItem ); + s.delete( readWriteVersionedCacheableItem ); + s.delete( nonStrictReadWriteCacheableItem ); + s.delete( nonStrictReadWriteVersionedCacheableItem ); + s.getTransaction().commit(); + s.close(); + } + + @Entity(name = "ReadWriteCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item") + public static class ReadWriteCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + public ReadWriteCacheableItem() { + } + + public ReadWriteCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } + + @Entity(name = "ReadWriteVersionedCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item") + public static class ReadWriteVersionedCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + @Version + private int version; + + public ReadWriteVersionedCacheableItem() { + } + + public ReadWriteVersionedCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } + + @Entity(name = "NonStrictReadWriteCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "item") + public static class NonStrictReadWriteCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + public NonStrictReadWriteCacheableItem() { + } + + public NonStrictReadWriteCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } + + @Entity(name = "NonStrictReadWriteVersionedCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "item") + public static class NonStrictReadWriteVersionedCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + @Version + private int version; + + public NonStrictReadWriteVersionedCacheableItem() { + } + + public NonStrictReadWriteVersionedCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } +} diff --git a/hibernate-ehcache/src/test/java/org/hibernate/test/cache/ehcache/functional/InsertedDataTest.java b/hibernate-ehcache/src/test/java/org/hibernate/test/cache/ehcache/functional/InsertedDataTest.java new file mode 100644 index 0000000000..2908976c8c --- /dev/null +++ b/hibernate-ehcache/src/test/java/org/hibernate/test/cache/ehcache/functional/InsertedDataTest.java @@ -0,0 +1,277 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cache.ehcache.functional; + +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Tests for handling of data just inserted during a transaction being read from the database + * and placed into cache. Initially these cases went through putFromRead which causes problems because it + * loses the context of that data having just been read. + * + * @author Steve Ebersole + */ +public class InsertedDataTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {CacheableItem.class}; + } + + @Override + @SuppressWarnings("unchecked") + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.CACHE_REGION_PREFIX, "" ); + settings.put( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.configure( "hibernate-config/hibernate.cfg.xml" ); + } + + @Test + public void testInsert() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.getTransaction().commit(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 1, cacheMap.size() ); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete CacheableItem" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testInsertWithRollback() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + s.getTransaction().rollback(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 0, cacheMap.size() ); + } + + @Test + public void testInsertThenUpdate() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + item.setName( "new data" ); + s.getTransaction().commit(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 1, cacheMap.size() ); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete CacheableItem" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testInsertThenUpdateThenRollback() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + item.setName( "new data" ); + s.getTransaction().rollback(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 0, cacheMap.size() ); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete CacheableItem" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testInsertWithRefresh() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + s.refresh( item ); + s.getTransaction().commit(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 1, cacheMap.size() ); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete CacheableItem" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testInsertWithRefreshThenRollback() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + s.refresh( item ); + s.getTransaction().rollback(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 1, cacheMap.size() ); + Object lock = cacheMap.values().iterator().next(); + assertEquals( "org.hibernate.cache.ehcache.internal.strategy.AbstractReadWriteEhcacheAccessStrategy$Lock", lock.getClass().getName() ); + + s = openSession(); + s.beginTransaction(); + item = (CacheableItem) s.get( CacheableItem.class, item.getId() ); + s.getTransaction().commit(); + s.close(); + + assertNull( "it should be null", item ); + } + + @Test + public void testInsertWithClear() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + s.clear(); + s.getTransaction().commit(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 1, cacheMap.size() ); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete CacheableItem" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testInsertWithClearThenRollback() { + sessionFactory().getCache().evictEntityRegions(); + sessionFactory().getStatistics().clear(); + + Session s = openSession(); + s.beginTransaction(); + CacheableItem item = new CacheableItem( "data" ); + s.save( item ); + s.flush(); + s.clear(); + item = (CacheableItem) s.get( CacheableItem.class, item.getId() ); + s.getTransaction().rollback(); + s.close(); + + Map cacheMap = sessionFactory().getStatistics().getSecondLevelCacheStatistics( "item" ).getEntries(); + assertEquals( 0, cacheMap.size() ); + + s = openSession(); + s.beginTransaction(); + item = (CacheableItem) s.get( CacheableItem.class, item.getId() ); + s.getTransaction().commit(); + s.close(); + + assertNull( "it should be null", item ); + } + + @Entity(name = "CacheableItem") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item") + public static class CacheableItem { + private Long id; + private String name; + + public CacheableItem() { + } + + public CacheableItem(String name) { + this.name = name; + } + + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-ehcache/src/test/java/org/hibernate/test/cache/ehcache/functional/RefreshUpdatedDataTest.java b/hibernate-ehcache/src/test/java/org/hibernate/test/cache/ehcache/functional/RefreshUpdatedDataTest.java new file mode 100644 index 0000000000..0f763e3dcb --- /dev/null +++ b/hibernate-ehcache/src/test/java/org/hibernate/test/cache/ehcache/functional/RefreshUpdatedDataTest.java @@ -0,0 +1,342 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cache.ehcache.functional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Zhenlei Huang + */ +@TestForIssue(jiraKey = "HHH-10649") +public class RefreshUpdatedDataTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ReadWriteCacheableItem.class, + ReadWriteVersionedCacheableItem.class, + NonStrictReadWriteCacheableItem.class, + NonStrictReadWriteVersionedCacheableItem.class, + }; + } + + @Override + @SuppressWarnings("unchecked") + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.configure( "hibernate-config/hibernate.cfg.xml" ); + } + + @Test + public void testUpdateAndFlushThenRefresh() { + // prepare data + Session s = openSession(); + s.beginTransaction(); + + final String BEFORE = "before"; + + ReadWriteCacheableItem readWriteCacheableItem = new ReadWriteCacheableItem( BEFORE ); + readWriteCacheableItem.getTags().add( "Hibernate" ); + readWriteCacheableItem.getTags().add( "ORM" ); + s.persist( readWriteCacheableItem ); + + ReadWriteVersionedCacheableItem readWriteVersionedCacheableItem = new ReadWriteVersionedCacheableItem( BEFORE ); + readWriteVersionedCacheableItem.getTags().add( "Hibernate" ); + readWriteVersionedCacheableItem.getTags().add( "ORM" ); + s.persist( readWriteVersionedCacheableItem ); + + NonStrictReadWriteCacheableItem nonStrictReadWriteCacheableItem = new NonStrictReadWriteCacheableItem( BEFORE ); + nonStrictReadWriteCacheableItem.getTags().add( "Hibernate" ); + nonStrictReadWriteCacheableItem.getTags().add( "ORM" ); + s.persist( nonStrictReadWriteCacheableItem ); + + NonStrictReadWriteVersionedCacheableItem nonStrictReadWriteVersionedCacheableItem = new NonStrictReadWriteVersionedCacheableItem( BEFORE ); + nonStrictReadWriteVersionedCacheableItem.getTags().add( "Hibernate" ); + nonStrictReadWriteVersionedCacheableItem.getTags().add( "ORM" ); + s.persist( nonStrictReadWriteVersionedCacheableItem ); + + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.beginTransaction(); + + final String AFTER = "after"; + + ReadWriteCacheableItem readWriteCacheableItem1 = s1.get( ReadWriteCacheableItem.class, readWriteCacheableItem.getId() ); + readWriteCacheableItem1.setName( AFTER ); + readWriteCacheableItem1.getTags().remove("ORM"); + + ReadWriteVersionedCacheableItem readWriteVersionedCacheableItem1 = s1.get( ReadWriteVersionedCacheableItem.class, readWriteVersionedCacheableItem.getId() ); + readWriteVersionedCacheableItem1.setName( AFTER ); + readWriteVersionedCacheableItem1.getTags().remove("ORM"); + + NonStrictReadWriteCacheableItem nonStrictReadWriteCacheableItem1 = s1.get( NonStrictReadWriteCacheableItem.class, nonStrictReadWriteCacheableItem.getId() ); + nonStrictReadWriteCacheableItem1.setName( AFTER ); + nonStrictReadWriteCacheableItem1.getTags().remove("ORM"); + + NonStrictReadWriteVersionedCacheableItem nonStrictReadWriteVersionedCacheableItem1 = s1.get( NonStrictReadWriteVersionedCacheableItem.class, nonStrictReadWriteVersionedCacheableItem.getId() ); + nonStrictReadWriteVersionedCacheableItem1.setName( AFTER ); + nonStrictReadWriteVersionedCacheableItem1.getTags().remove("ORM"); + + s1.flush(); + s1.refresh( readWriteCacheableItem1 ); + s1.refresh( readWriteVersionedCacheableItem1 ); + s1.refresh( nonStrictReadWriteCacheableItem1 ); + s1.refresh( nonStrictReadWriteVersionedCacheableItem1 ); + + assertEquals( AFTER, readWriteCacheableItem1.getName() ); + assertEquals( 1, readWriteCacheableItem1.getTags().size() ); + assertEquals( AFTER, readWriteVersionedCacheableItem1.getName() ); + assertEquals( 1, readWriteVersionedCacheableItem1.getTags().size() ); + assertEquals( AFTER, nonStrictReadWriteCacheableItem1.getName() ); + assertEquals( 1, nonStrictReadWriteCacheableItem1.getTags().size() ); + assertEquals( AFTER, nonStrictReadWriteVersionedCacheableItem1.getName() ); + assertEquals( 1, nonStrictReadWriteVersionedCacheableItem1.getTags().size() ); + + // open another session + Session s2 = sessionFactory().openSession(); + try { + s2.beginTransaction(); + ReadWriteCacheableItem readWriteCacheableItem2 = s2.get( ReadWriteCacheableItem.class, readWriteCacheableItem.getId() ); + ReadWriteVersionedCacheableItem readWriteVersionedCacheableItem2 = s2.get( ReadWriteVersionedCacheableItem.class, readWriteVersionedCacheableItem.getId() ); + NonStrictReadWriteCacheableItem nonStrictReadWriteCacheableItem2 = s2.get( NonStrictReadWriteCacheableItem.class, nonStrictReadWriteCacheableItem.getId() ); + NonStrictReadWriteVersionedCacheableItem nonStrictReadWriteVersionedCacheableItem2 = s2.get( NonStrictReadWriteVersionedCacheableItem.class, nonStrictReadWriteVersionedCacheableItem.getId() ); + + assertEquals( BEFORE, readWriteCacheableItem2.getName() ); + assertEquals( 2, readWriteCacheableItem2.getTags().size() ); + assertEquals( BEFORE, readWriteVersionedCacheableItem2.getName() ); + assertEquals( 2, readWriteVersionedCacheableItem2.getTags().size() ); + + //READ_UNCOMMITTED because there is no locking to prevent collections from being cached in the first Session + + assertEquals( BEFORE, nonStrictReadWriteCacheableItem2.getName() ); + assertEquals( 1, nonStrictReadWriteCacheableItem2.getTags().size()); + assertEquals( BEFORE, nonStrictReadWriteVersionedCacheableItem2.getName() ); + assertEquals( 1, nonStrictReadWriteVersionedCacheableItem2.getTags().size() ); + + s2.getTransaction().commit(); + } + finally { + if ( s2.getTransaction().getStatus().canRollback() ) { + s2.getTransaction().rollback(); + } + s2.close(); + } + + s1.getTransaction().rollback(); + s1.close(); + + s = openSession(); + s.beginTransaction(); + s.delete( readWriteCacheableItem ); + s.delete( readWriteVersionedCacheableItem ); + s.delete( nonStrictReadWriteCacheableItem ); + s.delete( nonStrictReadWriteVersionedCacheableItem ); + s.getTransaction().commit(); + s.close(); + } + + @Entity(name = "ReadWriteCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item") + public static class ReadWriteCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + public ReadWriteCacheableItem() { + } + + public ReadWriteCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } + + @Entity(name = "ReadWriteVersionedCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item") + public static class ReadWriteVersionedCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + @Version + private int version; + + public ReadWriteVersionedCacheableItem() { + } + + public ReadWriteVersionedCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } + + @Entity(name = "NonStrictReadWriteCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "item") + public static class NonStrictReadWriteCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + public NonStrictReadWriteCacheableItem() { + } + + public NonStrictReadWriteCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } + + @Entity(name = "NonStrictReadWriteVersionedCacheableItem") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "item") + public static class NonStrictReadWriteVersionedCacheableItem { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @ElementCollection + private List tags = new ArrayList<>(); + + @Version + private int version; + + public NonStrictReadWriteVersionedCacheableItem() { + } + + public NonStrictReadWriteVersionedCacheableItem(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + } +}