/* * 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.infinispan.functional; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.hibernate.Cache; import org.hibernate.Criteria; import org.hibernate.Hibernate; import org.hibernate.NaturalIdLoadAccess; import org.hibernate.Session; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.criterion.Restrictions; import org.hibernate.stat.SecondLevelCacheStatistics; import org.hibernate.stat.Statistics; import org.hibernate.testing.TestForIssue; import org.hibernate.test.cache.infinispan.functional.entities.Citizen; import org.hibernate.test.cache.infinispan.functional.entities.Item; import org.hibernate.test.cache.infinispan.functional.entities.NaturalIdOnManyToOne; import org.hibernate.test.cache.infinispan.functional.entities.OtherItem; import org.hibernate.test.cache.infinispan.functional.entities.State; import org.hibernate.test.cache.infinispan.functional.entities.VersionedItem; import org.junit.After; import org.junit.Test; import org.infinispan.commons.util.ByRef; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * Functional entity transactional tests. * * @author Galder ZamarreƱo * @since 3.5 */ public class ReadWriteTest extends ReadOnlyTest { @Override public List<Object[]> getParameters() { return getParameters(true, true, false, true); } @Override protected Class<?>[] getAnnotatedClasses() { return new Class[] { Citizen.class, State.class, NaturalIdOnManyToOne.class }; } @After public void cleanupData() throws Exception { super.cleanupCache(); withTxSession(s -> { s.createQuery( "delete NaturalIdOnManyToOne" ).executeUpdate(); s.createQuery( "delete Citizen" ).executeUpdate(); s.createQuery( "delete State" ).executeUpdate(); }); } @Test public void testCollectionCache() throws Exception { final Statistics stats = sessionFactory().getStatistics(); stats.clear(); final Item item = new Item( "chris", "Chris's Item" ); final Item another = new Item( "another", "Owned Item" ); item.addItem( another ); withTxSession(s -> { s.persist( item ); s.persist( another ); }); // The collection has been removed, but we can't add it again immediately using putFromLoad TIME_SERVICE.advance(1); withTxSession(s -> { Item loaded = s.load( Item.class, item.getId() ); assertEquals( 1, loaded.getItems().size() ); }); SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); assertEquals( 1, cStats.getElementCountInMemory() ); withTxSession(s -> { 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() ); Item itemElement = loadedWithCachedCollection.getItems().iterator().next(); itemElement.setOwner( null ); loadedWithCachedCollection.getItems().clear(); s.delete( itemElement ); s.delete( loadedWithCachedCollection ); }); } @Test @TestForIssue( jiraKey = "HHH-9231" ) public void testAddNewOneToManyElementInitFlushLeaveCacheConsistent() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); ByRef<Long> itemId = new ByRef<>(null); saveItem(itemId); // create an element for item.itsms Item itemElement = new Item(); itemElement.setName( "element" ); itemElement.setDescription( "element item" ); withTxSession(s -> { Item item = s.get( Item.class, itemId.get() ); assertFalse( Hibernate.isInitialized( item.getItems() ) ); // Add an element to item.items (a Set); it will initialize the Set. item.addItem( itemElement ); assertTrue( Hibernate.isInitialized( item.getItems() ) ); s.persist( itemElement ); s.flush(); markRollbackOnly(s); }); withTxSession(s -> { Item item = s.get( Item.class, itemId.get() ); Hibernate.initialize( item.getItems() ); assertTrue( item.getItems().isEmpty() ); s.delete( item ); }); } @Test @TestForIssue( jiraKey = "HHH-9231" ) public void testAddNewOneToManyElementNoInitFlushLeaveCacheConsistent() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); ByRef<Long> itemId = new ByRef<>(null); saveItem(itemId); // create an element for item.bagOfItems Item itemElement = new Item(); itemElement.setName( "element" ); itemElement.setDescription( "element item" ); withTxSession(s -> { Item item = s.get( Item.class, itemId.get() ); assertFalse( Hibernate.isInitialized( item.getItems() ) ); // Add an element to item.bagOfItems (a bag); it will not initialize the bag. item.addItemToBag( itemElement ); assertFalse( Hibernate.isInitialized( item.getBagOfItems() ) ); s.persist( itemElement ); s.flush(); markRollbackOnly(s); }); withTxSession(s -> { Item item = s.get( Item.class, itemId.get() ); Hibernate.initialize( item.getItems() ); assertTrue( item.getItems().isEmpty() ); s.delete( item ); }); } @Test public void testAddNewOneToManyElementNoInitFlushInitLeaveCacheConsistent() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); ByRef<Long> itemId = new ByRef<>(null); saveItem(itemId); // create an element for item.bagOfItems Item itemElement = new Item(); itemElement.setName( "element" ); itemElement.setDescription( "element item" ); withTxSession(s -> { Item item = s.get(Item.class, itemId.get()); assertFalse(Hibernate.isInitialized(item.getBagOfItems())); // Add an element to item.bagOfItems (a bag); it will not initialize the bag. item.addItemToBag(itemElement); assertFalse(Hibernate.isInitialized(item.getBagOfItems())); s.persist(itemElement); s.flush(); // Now initialize the collection; it will contain the uncommitted itemElement. Hibernate.initialize(item.getBagOfItems()); markRollbackOnly(s); }); withTxSession(s -> { Item item = s.get(Item.class, itemId.get()); // Because of HHH-9231, the following will fail due to ObjectNotFoundException because the // collection will be read from the cache and it still contains the uncommitted element, // which cannot be found. Hibernate.initialize(item.getBagOfItems()); assertTrue(item.getBagOfItems().isEmpty()); s.delete(item); }); } protected void saveItem(ByRef<Long> itemId) throws Exception { withTxSession(s -> { Item item = new Item(); item.setName( "steve" ); item.setDescription( "steve's item" ); s.save( item ); itemId.set(item.getId()); }); } @Test public void testAddNewManyToManyPropertyRefNoInitFlushInitLeaveCacheConsistent() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); ByRef<Long> otherItemId = new ByRef<>(null); withTxSession(s -> { OtherItem otherItem = new OtherItem(); otherItem.setName( "steve" ); s.save( otherItem ); otherItemId.set(otherItem.getId()); }); // create an element for otherItem.bagOfItems Item item = new Item(); item.setName( "element" ); item.setDescription( "element Item" ); withTxSession(s -> { OtherItem otherItem = s.get( OtherItem.class, otherItemId.get() ); assertFalse( Hibernate.isInitialized( otherItem.getBagOfItems() ) ); // Add an element to otherItem.bagOfItems (a bag); it will not initialize the bag. otherItem.addItemToBag( item ); assertFalse( Hibernate.isInitialized( otherItem.getBagOfItems() ) ); s.persist( item ); s.flush(); // Now initialize the collection; it will contain the uncommitted itemElement. // The many-to-many uses a property-ref Hibernate.initialize( otherItem.getBagOfItems() ); markRollbackOnly(s); }); withTxSession(s -> { OtherItem otherItem = s.get( OtherItem.class, otherItemId.get() ); // Because of HHH-9231, the following will fail due to ObjectNotFoundException because the // collection will be read from the cache and it still contains the uncommitted element, // which cannot be found. Hibernate.initialize( otherItem.getBagOfItems() ); assertTrue( otherItem.getBagOfItems().isEmpty() ); s.delete( otherItem ); }); } @Test public void testStaleWritesLeaveCacheConsistent() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); ByRef<VersionedItem> itemRef = new ByRef<>(null); withTxSession(s -> { VersionedItem item = new VersionedItem(); item.setName( "steve" ); item.setDescription( "steve's item" ); s.save( item ); itemRef.set(item); }); final VersionedItem item = itemRef.get(); Long initialVersion = item.getVersion(); // manually revert the version property item.setVersion( new Long( item.getVersion().longValue() - 1 ) ); try { withTxSession(s -> s.update(item)); fail("expected stale write to fail"); } catch (Exception e) { log.debug("Rollback was expected", e); } // check the version value in the cache... SecondLevelCacheStatistics slcs = stats.getSecondLevelCacheStatistics( VersionedItem.class.getName() ); Object entry = slcs.getEntries().get( item.getId() ); Long cachedVersionValue; cachedVersionValue = (Long) ((CacheEntry) entry).getVersion(); assertEquals(initialVersion.longValue(), cachedVersionValue.longValue()); withTxSession(s -> { VersionedItem item2 = s.load( VersionedItem.class, item.getId() ); s.delete( item2 ); }); } @Test @TestForIssue( jiraKey = "HHH-5690") public void testPersistEntityFlushRollbackNotInEntityCache() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics slcs = stats.getSecondLevelCacheStatistics( Item.class.getName() ); ByRef<Long> itemId = new ByRef<>(null); withTxSession(s -> { Item item = new Item(); item.setName("steve"); item.setDescription("steve's item"); s.persist(item); s.flush(); itemId.set(item.getId()); // assertNotNull( slcs.getEntries().get( item.getId() ) ); markRollbackOnly(s); }); // item should not be in entity cache. assertEquals( Collections.EMPTY_MAP, slcs.getEntries() ); withTxSession(s -> { Item item = s.get( Item.class, itemId.get() ); assertNull( item ); }); } @Test @TestForIssue( jiraKey = "HHH-5690") public void testPersistEntityFlushEvictGetRollbackNotInEntityCache() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics slcs = stats.getSecondLevelCacheStatistics( Item.class.getName() ); ByRef<Long> itemId = new ByRef<>(null); withTxSession(s -> { Item item = new Item(); item.setName("steve"); item.setDescription("steve's item"); s.persist(item); s.flush(); itemId.set(item.getId()); // item is cached on insert. // assertNotNull( slcs.getEntries().get( item.getId() ) ); s.evict(item); assertEquals(slcs.getHitCount(), 0); item = s.get(Item.class, item.getId()); assertNotNull(item); // assertEquals( slcs.getHitCount(), 1 ); // assertNotNull( slcs.getEntries().get( item.getId() ) ); markRollbackOnly(s); }); // item should not be in entity cache. //slcs = stats.getSecondLevelCacheStatistics( Item.class.getName() ); assertEquals(Collections.EMPTY_MAP, slcs.getEntries() ); withTxSession(s -> { Item item = s.get(Item.class, itemId.get()); assertNull(item); }); } @Test public void testQueryCacheInvalidation() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); SecondLevelCacheStatistics slcs = stats.getSecondLevelCacheStatistics( Item.class.getName() ); sessionFactory().getCache().evictEntityRegion( Item.class.getName() ); TIME_SERVICE.advance(1); assertEquals(0, slcs.getPutCount()); assertEquals( 0, slcs.getElementCountInMemory() ); assertEquals( 0, slcs.getEntries().size() ); ByRef<Long> idRef = new ByRef<>(null); withTxSession(s -> { Item item = new Item(); item.setName( "widget" ); item.setDescription( "A really top-quality, full-featured widget." ); s.persist( item ); idRef.set( item.getId() ); }); assertEquals( 1, slcs.getPutCount() ); assertEquals( 1, slcs.getElementCountInMemory() ); assertEquals( 1, slcs.getEntries().size() ); withTxSession(s -> { Item item = s.get( Item.class, idRef.get() ); assertEquals( slcs.getHitCount(), 1 ); assertEquals( slcs.getMissCount(), 0 ); item.setDescription( "A bog standard item" ); }); assertEquals( slcs.getPutCount(), 2 ); CacheEntry entry = (CacheEntry) slcs.getEntries().get( idRef.get() ); Serializable[] ser = entry.getDisassembledState(); assertTrue( ser[0].equals( "widget" ) ); assertTrue( ser[1].equals( "A bog standard item" ) ); withTxSession(s -> { Item item = s.load(Item.class, idRef.get()); s.delete(item); }); } @Test public void testQueryCache() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); Item item = new Item( "chris", "Chris's Item" ); withTxSession(s -> s.persist( item )); // Delay added to guarantee that query cache results won't be considered // as not up to date due to persist session and query results from first // query happening simultaneously. TIME_SERVICE.advance(1); withTxSession(s -> s.createQuery( "from Item" ).setCacheable( true ).list()); withTxSession(s -> { s.createQuery( "from Item" ).setCacheable( true ).list(); assertEquals( 1, stats.getQueryCacheHitCount() ); s.createQuery( "delete from Item" ).executeUpdate(); }); } @Test public void testQueryCacheHitInSameTransaction() throws Exception { Statistics stats = sessionFactory().getStatistics(); stats.clear(); Item item = new Item( "galder", "Galder's Item" ); withTxSession(s -> s.persist( item )); // Delay added to guarantee that query cache results won't be considered // as not up to date due to persist session and query results from first // query happening simultaneously. TIME_SERVICE.advance(1); withTxSession(s -> { s.createQuery("from Item").setCacheable(true).list(); s.createQuery("from Item").setCacheable(true).list(); assertEquals(1, stats.getQueryCacheHitCount()); }); withTxSession(s -> s.createQuery( "delete from Item" ).executeUpdate()); } @Test public void testNaturalIdCached() throws Exception { saveSomeCitizens(); // Clear the cache before the transaction begins cleanupCache(); TIME_SERVICE.advance(1); withTxSession(s -> { State france = ReadWriteTest.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 ); 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 markRollbackOnly(s); }); } @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 = withTxSessionApply(s -> { State france = ReadWriteTest.this.getState(s, "Ile de France"); NaturalIdLoadAccess<Citizen> 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 c = naturalIdLoader.load(); assertNotNull(c); 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 markRollbackOnly(s); return c; }); // TODO: Clear caches manually via cache manager (it's faster!!) cleanupCache(); TIME_SERVICE.advance(1); stats.setStatisticsEnabled( true ); stats.clear(); //Try NaturalIdLoadAccess withTxSession(s -> { // 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 markRollbackOnly(s); }); // Try NaturalIdLoadAccess after load withTxSession(s -> { State france = ReadWriteTest.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 markRollbackOnly(s); }); } @Test public void testEntityCacheContentsAfterEvictAll() throws Exception { final List<Citizen> citizens = saveSomeCitizens(); withTxSession(s -> { Cache cache = s.getSessionFactory().getCache(); Statistics stats = sessionFactory().getStatistics(); SecondLevelCacheStatistics slcStats = stats.getSecondLevelCacheStatistics(Citizen.class.getName()); assertTrue("2lc entity cache is expected to contain Citizen id = " + citizens.get(0).getId(), cache.containsEntity(Citizen.class, citizens.get(0).getId())); assertTrue("2lc entity cache is expected to contain Citizen id = " + citizens.get(1).getId(), cache.containsEntity(Citizen.class, citizens.get(1).getId())); assertEquals(2, slcStats.getPutCount()); cache.evictEntityRegions(); TIME_SERVICE.advance(1); assertEquals(0, slcStats.getElementCountInMemory()); assertFalse("2lc entity cache is expected to not contain Citizen id = " + citizens.get(0).getId(), cache.containsEntity(Citizen.class, citizens.get(0).getId())); assertFalse("2lc entity cache is expected to not contain Citizen id = " + citizens.get(1).getId(), cache.containsEntity(Citizen.class, citizens.get(1).getId())); Citizen citizen = s.load(Citizen.class, citizens.get(0).getId()); assertNotNull(citizen); assertNotNull(citizen.getFirstname()); // proxy gets resolved assertEquals(1, slcStats.getMissCount()); // cleanup markRollbackOnly(s); }); } @Test public void testMultipleEvictAll() throws Exception { final List<Citizen> citizens = saveSomeCitizens(); withTxSession(s -> { Cache cache = s.getSessionFactory().getCache(); cache.evictEntityRegions(); cache.evictEntityRegions(); }); withTxSession(s -> { Cache cache = s.getSessionFactory().getCache(); cache.evictEntityRegions(); s.delete(s.load(Citizen.class, citizens.get(0).getId())); s.delete(s.load(Citizen.class, citizens.get(1).getId())); }); } private List<Citizen> 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 ); withTxSession(s -> { s.persist( australia ); s.persist( france ); s.persist( c1 ); s.persist( c2 ); }); List<Citizen> citizens = new ArrayList<>(2); citizens.add(c1); citizens.add(c2); return citizens; } 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 ); } }