HHH-10649 - When 2LC enabled, flush session and then refresh entity cause dirty read in another session / transaction

This commit is contained in:
Zhenlei Huang 2016-03-28 03:16:04 +08:00 committed by Vlad Mihalcea
parent a68a6c6770
commit cbdab9d87f
5 changed files with 1015 additions and 10 deletions

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> getTags() {
return tags;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> getTags() {
return tags;
}
}
}