From 0385b1436a754c9feb6c7319bdd4a0ef92ad514f Mon Sep 17 00:00:00 2001 From: Janario Oliveira Date: Mon, 7 Mar 2016 22:29:52 -0300 Subject: [PATCH] HHH-10602 - Retrieve cached value with enable_lazy_load_no_trans throws an exception --- .../AbstractPersistentCollection.java | 41 ++-- .../org/hibernate/type/CollectionType.java | 41 ++-- .../cache/CacheLazyLoadNoTransTest.java | 189 ++++++++++++++++++ 3 files changed, 236 insertions(+), 35 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ondemandload/cache/CacheLazyLoadNoTransTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 7ef2512008..dc87b3b9df 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -56,6 +56,7 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractPersistentCollection.class ); private transient SessionImplementor session; + private boolean isTempSession = false; private boolean initialized; private transient List operationQueue; private transient boolean directlyAccessible; @@ -190,14 +191,11 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers } private T withTemporarySessionIfNeeded(LazyInitializationWork lazyInitializationWork) { - SessionImplementor originalSession = null; - boolean isTempSession = false; - boolean isJTA = false; + SessionImplementor tempSession = null; if ( session == null ) { if ( allowLoadOutsideTransaction ) { - session = openTemporarySessionForLoading(); - isTempSession = true; + tempSession = openTemporarySessionForLoading(); } else { throwLazyInitializationException( "could not initialize proxy - no Session" ); @@ -205,9 +203,7 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers } else if ( !session.isOpen() ) { if ( allowLoadOutsideTransaction ) { - originalSession = session; - session = openTemporarySessionForLoading(); - isTempSession = true; + tempSession = openTemporarySessionForLoading(); } else { throwLazyInitializationException( "could not initialize proxy - the owning Session was closed" ); @@ -215,16 +211,23 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers } else if ( !session.isConnected() ) { if ( allowLoadOutsideTransaction ) { - originalSession = session; - session = openTemporarySessionForLoading(); - isTempSession = true; + tempSession = openTemporarySessionForLoading(); } else { throwLazyInitializationException( "could not initialize proxy - the owning Session is disconnected" ); } } - if ( isTempSession ) { + + SessionImplementor originalSession = null; + boolean isJTA = false; + + if ( tempSession != null ) { + isTempSession = true; + originalSession = session; + session = tempSession; + + isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); if ( !isJTA ) { @@ -246,18 +249,20 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers return lazyInitializationWork.doWork(); } finally { - if ( isTempSession ) { + if ( tempSession != null ) { // make sure the just opened temp session gets closed! + isTempSession = false; + session = originalSession; + try { if ( !isJTA ) { - ( (Session) session ).getTransaction().commit(); + ( (Session) tempSession ).getTransaction().commit(); } - ( (Session) session ).close(); + ( (Session) tempSession ).close(); } catch (Exception e) { LOG.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); } - session = originalSession; } } } @@ -601,7 +606,9 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers public final boolean unsetSession(SessionImplementor currentSession) { prepareForPossibleLoadingOutsideTransaction(); if ( currentSession == this.session ) { - this.session = null; + if ( !isTempSession ) { + this.session = null; + } return true; } else { diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 079fd9cca2..cdfa27c3b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -731,25 +731,30 @@ public abstract class CollectionType extends AbstractType implements Association collection = persistenceContext.useUnownedCollection( new CollectionKey(persister, key, entityMode) ); if ( collection == null ) { - // create a new collection wrapper, to be initialized later - collection = instantiate( session, persister, key ); - - collection.setOwner(owner); - - persistenceContext.addUninitializedCollection( persister, collection, key ); - - // some collections are not lazy: - if ( initializeImmediately() ) { - session.initializeCollection( collection, false ); + + collection = persistenceContext.getCollection( new CollectionKey(persister, key, entityMode) ); + + if ( collection == null ) { + // create a new collection wrapper, to be initialized later + collection = instantiate( session, persister, key ); + + collection.setOwner( owner ); + + persistenceContext.addUninitializedCollection( persister, collection, key ); + + // some collections are not lazy: + if ( initializeImmediately() ) { + session.initializeCollection( collection, false ); + } + else if ( !persister.isLazy() ) { + persistenceContext.addNonLazyCollection( collection ); + } + + if ( hasHolder() ) { + session.getPersistenceContext().addCollectionHolder( collection ); + } } - else if ( !persister.isLazy() ) { - persistenceContext.addNonLazyCollection( collection ); - } - - if ( hasHolder() ) { - session.getPersistenceContext().addCollectionHolder( collection ); - } - + } if ( LOG.isTraceEnabled() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/ondemandload/cache/CacheLazyLoadNoTransTest.java b/hibernate-core/src/test/java/org/hibernate/test/ondemandload/cache/CacheLazyLoadNoTransTest.java new file mode 100644 index 0000000000..1434c5aec2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ondemandload/cache/CacheLazyLoadNoTransTest.java @@ -0,0 +1,189 @@ +/* + * 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.ondemandload.cache; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.Cacheable; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.persister.collection.CollectionPersister; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Janario Oliveira + */ +public class CacheLazyLoadNoTransTest extends BaseNonConfigCoreFunctionalTestCase { + + @SuppressWarnings("unchecked") + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + + settings.put( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + settings.put( Environment.USE_SECOND_LEVEL_CACHE, "true" ); + settings.put( Environment.USE_QUERY_CACHE, "true" ); + settings.put( Environment.CACHE_PROVIDER_CONFIG, "true" ); + } + + @Test + public void testOneToMany() { + Customer customer = new Customer(); + Item item1 = new Item( customer ); + Item item2 = new Item( customer ); + customer.boughtItems.add( item1 ); + customer.boughtItems.add( item2 ); + persist( customer ); + + //init cache + assertFalse( isCached( customer.id, Customer.class, "boughtItems" ) ); + customer = find( Customer.class, customer.id ); + assertEquals( 2, customer.boughtItems.size() ); + + //read from cache + assertTrue( isCached( customer.id, Customer.class, "boughtItems" ) ); + customer = find( Customer.class, customer.id ); + assertEquals( 2, customer.boughtItems.size() ); + } + + @Test + public void testManyToMany() { + Application application = new Application(); + persist( application ); + Customer customer = new Customer(); + customer.applications.add( application ); + application.customers.add( customer ); + persist( customer ); + + //init cache + assertFalse( isCached( customer.id, Customer.class, "applications" ) ); + assertFalse( isCached( application.id, Application.class, "customers" ) ); + + customer = find( Customer.class, customer.id ); + assertEquals( 1, customer.applications.size() ); + application = find( Application.class, application.id ); + assertEquals( 1, application.customers.size() ); + + assertTrue( isCached( customer.id, Customer.class, "applications" ) ); + assertTrue( isCached( application.id, Application.class, "customers" ) ); + + //read from cache + customer = find( Customer.class, customer.id ); + assertEquals( 1, customer.applications.size() ); + application = find( Application.class, application.id ); + assertEquals( 1, application.customers.size() ); + } + + private void persist(Object entity) { + Session session = openSession(); + session.beginTransaction(); + session.persist( entity ); + session.getTransaction().commit(); + session.close(); + } + + private E find(Class entityClass, int id) { + Session session; + session = openSession(); + E customer = session.get( entityClass, id ); + session.close(); + return customer; + } + + private boolean isCached(Serializable id, Class entityClass, String attr) { + Session session = openSession(); + CollectionPersister persister = sessionFactory().getCollectionPersister( entityClass.getName() + "." + attr ); + CollectionRegionAccessStrategy cache = persister.getCacheAccessStrategy(); + Object key = cache.generateCacheKey( id, persister, sessionFactory(), session.getTenantIdentifier() ); + Object cachedValue = cache.get( + ( (SessionImplementor) session ), + key, + ( (SessionImplementor) session ).getTimestamp() + ); + session.close(); + return cachedValue != null; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Application.class, Customer.class, Item.class}; + } + + @Entity + @Table(name = "application") + @Cacheable + public static class Application { + @Id + @GeneratedValue + private Integer id; + + @ManyToMany(mappedBy = "applications") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + private List customers = new ArrayList<>(); + } + + @Entity + @Table(name = "customer") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class Customer { + @Id + @GeneratedValue + private Integer id; + + @ManyToMany + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + private List applications = new ArrayList<>(); + + @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + private List boughtItems = new ArrayList<>(); + } + + @Entity + @Table(name = "item") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class Item { + @Id + @GeneratedValue + private Integer id; + @ManyToOne + @JoinColumn(name = "customer_id") + private Customer customer; + + protected Item() { + } + + public Item(Customer customer) { + this.customer = customer; + } + } +}