From 8f4450c43349eb8f96d613fab6ec6cdac57352b5 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 22 Jun 2021 10:55:43 +0200 Subject: [PATCH 1/3] HHH-14597 Test and fix for NPE while trying to delete cascade to-one association within element collection --- .../hibernate/engine/internal/Cascade.java | 37 ++++++++----- .../ElementCollectionOrphanTest.java | 53 +++++++++++++++++++ .../elementcollection/EnrollableClass.java | 34 ++++++++++++ .../elementcollection/EnrolledClassSeat.java | 31 +++++++++++ .../orphan/elementcollection/Student.java | 43 +++++++++++++++ .../StudentEnrolledClass.java | 31 +++++++++++ .../orphan/elementcollection/student.hbm.xml | 45 ++++++++++++++++ 7 files changed, 261 insertions(+), 13 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/ElementCollectionOrphanTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrollableClass.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrolledClassSeat.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/Student.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/StudentEnrolledClass.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/student.hbm.xml diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index 107a3eff8f..762ab776f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -232,10 +232,12 @@ public final class Cascade { } } else if ( type.isComponentType() ) { - if ( componentPath == null ) { + if ( componentPath == null && propertyName != null ) { componentPath = new ArrayList<>(); } - componentPath.add( propertyName ); + if ( componentPath != null ) { + componentPath.add( propertyName ); + } cascadeComponent( action, cascadePoint, @@ -246,7 +248,9 @@ public final class Cascade { (CompositeType) type, anything ); - componentPath.remove( componentPath.size() - 1 ); + if ( componentPath != null ) { + componentPath.remove( componentPath.size() - 1 ); + } } } @@ -293,17 +297,24 @@ public final class Cascade { // Since the loadedState in the EntityEntry is a flat domain type array // We first have to extract the component object and then ask the component type // recursively to give us the value of the sub-property of that object - loadedValue = entry.getLoadedValue( componentPath.get( 0 ) ); - ComponentType componentType = (ComponentType) entry.getPersister().getPropertyType( componentPath.get( 0 ) ); - if ( componentPath.size() != 1 ) { - for ( int i = 1; i < componentPath.size(); i++ ) { - final int subPropertyIndex = componentType.getPropertyIndex( componentPath.get( i ) ); - loadedValue = componentType.getPropertyValue( loadedValue, subPropertyIndex ); - componentType = (ComponentType) componentType.getSubtypes()[subPropertyIndex]; + final Type propertyType = entry.getPersister().getPropertyType( componentPath.get(0) ); + if ( propertyType instanceof ComponentType ) { + loadedValue = entry.getLoadedValue( componentPath.get( 0 ) ); + ComponentType componentType = (ComponentType) propertyType; + if ( componentPath.size() != 1 ) { + for ( int i = 1; i < componentPath.size(); i++ ) { + final int subPropertyIndex = componentType.getPropertyIndex( componentPath.get( i ) ); + loadedValue = componentType.getPropertyValue( loadedValue, subPropertyIndex ); + componentType = (ComponentType) componentType.getSubtypes()[subPropertyIndex]; + } } - } - loadedValue = componentType.getPropertyValue( loadedValue, componentType.getPropertyIndex( propertyName ) ); + loadedValue = componentType.getPropertyValue( loadedValue, componentType.getPropertyIndex( propertyName ) ); + } + else { + // Association is probably defined in an element collection, so we can't do orphan removals + loadedValue = null; + } } // orphaned if the association was nulled (child == null) or receives a new value while the @@ -538,7 +549,7 @@ public final class Cascade { itr.next(), elemType, style, - null, + collectionType.getRole().substring( collectionType.getRole().lastIndexOf('.') + 1 ), anything, isCascadeDeleteEnabled ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/ElementCollectionOrphanTest.java b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/ElementCollectionOrphanTest.java new file mode 100644 index 0000000000..83de4af020 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/ElementCollectionOrphanTest.java @@ -0,0 +1,53 @@ +package org.hibernate.test.orphan.elementcollection; + +import java.util.Collections; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Before; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-14597") +public class ElementCollectionOrphanTest extends BaseCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "orphan/elementcollection/student.hbm.xml" }; + } + + @Test + public void setCompositeElementTest() { + TransactionUtil.doInHibernate( + this::sessionFactory, + session -> { + EnrollableClass aClass = new EnrollableClass(); + aClass.setId("123"); + aClass.setName("Math"); + session.save(aClass); + + Student aStudent = new Student(); + aStudent.setId("s1"); + aStudent.setFirstName("John"); + aStudent.setLastName("Smith"); + + EnrolledClassSeat seat = new EnrolledClassSeat(); + seat.setId("seat1"); + seat.setRow(10); + seat.setColumn(5); + + StudentEnrolledClass enrClass = new StudentEnrolledClass(); + enrClass.setEnrolledClass(aClass); + enrClass.setClassStartTime(130); + enrClass.setSeat(seat); + aStudent.setEnrolledClasses(Collections.singleton(enrClass)); + session.save(aStudent); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrollableClass.java b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrollableClass.java new file mode 100644 index 0000000000..5ae6e94d60 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrollableClass.java @@ -0,0 +1,34 @@ +package org.hibernate.test.orphan.elementcollection; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "ENROLLABLECLASS") +public class EnrollableClass { + + @Id + @Column(name = "id") + private String id; + + @Column(name = "name") + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrolledClassSeat.java b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrolledClassSeat.java new file mode 100644 index 0000000000..3a1d2654d0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/EnrolledClassSeat.java @@ -0,0 +1,31 @@ +package org.hibernate.test.orphan.elementcollection; + +public class EnrolledClassSeat { + private String id; + private int row; + private int column; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getRow() { + return row; + } + + public void setRow(int row) { + this.row = row; + } + + public int getColumn() { + return column; + } + + public void setColumn(int column) { + this.column = column; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/Student.java b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/Student.java new file mode 100644 index 0000000000..541184233f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/Student.java @@ -0,0 +1,43 @@ +package org.hibernate.test.orphan.elementcollection; + +import java.util.Set; + +public class Student { + + private String id; + private String firstName; + private String lastName; + private Set< StudentEnrolledClass > enrolledClasses; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Set getEnrolledClasses() { + return enrolledClasses; + } + + public void setEnrolledClasses(Set enrolledClasses) { + this.enrolledClasses = enrolledClasses; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/StudentEnrolledClass.java b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/StudentEnrolledClass.java new file mode 100644 index 0000000000..199c865000 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/StudentEnrolledClass.java @@ -0,0 +1,31 @@ +package org.hibernate.test.orphan.elementcollection; + +public class StudentEnrolledClass { + private EnrollableClass enrolledClass; + private int classStartTime; + private EnrolledClassSeat seat; + + public EnrollableClass getEnrolledClass() { + return enrolledClass; + } + + public void setEnrolledClass(EnrollableClass enrolledClass) { + this.enrolledClass = enrolledClass; + } + + public int getClassStartTime() { + return classStartTime; + } + + public void setClassStartTime(int classStartTime) { + this.classStartTime = classStartTime; + } + + public EnrolledClassSeat getSeat() { + return seat; + } + + public void setSeat(EnrolledClassSeat seat) { + this.seat = seat; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/student.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/student.hbm.xml new file mode 100644 index 0000000000..60af248813 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/orphan/elementcollection/student.hbm.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 179c1d1da0f18ae86a84e0731974e3016f924265 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 5 Jul 2018 12:54:02 +0100 Subject: [PATCH 2/3] HHH-4808 Add test for issue --- .../LazyLoadingConnectionCloseTest.java | 365 ++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/connections/LazyLoadingConnectionCloseTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/connections/LazyLoadingConnectionCloseTest.java b/hibernate-core/src/test/java/org/hibernate/test/connections/LazyLoadingConnectionCloseTest.java new file mode 100644 index 0000000000..b3e322f620 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/connections/LazyLoadingConnectionCloseTest.java @@ -0,0 +1,365 @@ +/* + * 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.connections; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Query; +import javax.sql.DataSource; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.connection.BaseDataSource; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static junit.framework.TestCase.assertTrue; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; + +/** + * @author Selaron + */ +@TestForIssue(jiraKey = "HHH-4808") +public class LazyLoadingConnectionCloseTest extends BaseEntityManagerFunctionalTestCase { + + private ConnectionProviderDecorator connectionProvider; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class, ChildEntity.class }; + } + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + + config.put( AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT ); + + config.put( AvailableSettings.AUTOCOMMIT, "false" ); + + connectionProvider = new ConnectionProviderDecorator( getDataSource() ); + config.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + return config; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + final SimpleEntity entity = new SimpleEntity(); + entity.setId( 1L ); + entity.setName( "TheParent" ); + + final ChildEntity c1 = new ChildEntity(); + c1.setId( 1L ); + c1.setParent( entity ); + c1.setName( "child1" ); + + final ChildEntity c2 = new ChildEntity(); + c2.setId( 2L ); + c2.setParent( entity ); + c2.setName( "child2" ); + + entityManager.persist( entity ); + entityManager.persist( c1 ); + entityManager.persist( c2 ); + } ); + } + + /** + * Tests connections get closed after transaction commit. + */ + @Test + public void testConnectionCloseAfterTx() { + connectionProvider.clear(); + EntityManager entityManager = getOrCreateEntityManager(); + + try { + entityManager.getTransaction().begin(); + try { + + final Query qry = entityManager.createQuery( "FROM SimpleEntity" ); + final List entities = qry.getResultList(); + final SimpleEntity entity = entities.get( 0 ); + assertEquals( 1, connectionProvider.getCurrentOpenConnections() ); + } + catch (Exception e) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + finally { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().commit(); + } + } + assertTrue( connectionProvider.areAllConnectionClosed() ); + } + finally { + entityManager.close(); + } + + } + + /** + * Tests connections get closed after lazy collection initialization. + */ + @Test + public void testConnectionCloseAfterLazyCollectionInit() { + connectionProvider.clear(); + EntityManager entityManager = getOrCreateEntityManager(); + + try { + final Query qry = entityManager.createQuery( "FROM SimpleEntity" ); + final List entities = qry.getResultList(); + final SimpleEntity entity = entities.get( 0 ); + + // assert no connection is open + assertTrue( connectionProvider.areAllConnectionClosed() ); + + final int oldOpenedConnections = connectionProvider.getTotalOpenedConnectionCount(); + final Set lazyChildren = entity.getChildren(); + + // this will initialize the collection and such trigger a query + lazyChildren.stream().findAny(); + + // assert a connection had been opened + Assert.assertTrue( oldOpenedConnections < connectionProvider.getTotalOpenedConnectionCount() ); + + // assert there's no remaining connection left. + assertTrue( connectionProvider.areAllConnectionClosed() ); + + } + finally { + entityManager.close(); + } + } + + /** + * Tests connections get closed after transaction commit. + */ + @Test + public void testConnectionCloseAfterLazyPojoPropertyInit() { + connectionProvider.clear(); + EntityManager entityManager = getOrCreateEntityManager(); + + try { + final Query qry = entityManager.createQuery( "FROM ChildEntity" ); + final List entities = qry.getResultList(); + final ChildEntity entity = entities.get( 0 ); + + // assert no connection is open + assertTrue( connectionProvider.areAllConnectionClosed() ); + + final int oldOpenedConnections = connectionProvider.getTotalOpenedConnectionCount(); + + final SimpleEntity parent = entity.getParent(); + // this will initialize the collection and such trigger a query + parent.getName(); + // assert a connection had been opened + Assert.assertTrue( oldOpenedConnections < connectionProvider.getTotalOpenedConnectionCount() ); + + + // assert there's no remaining connection left. + assertTrue( connectionProvider.areAllConnectionClosed() ); + } + finally { + entityManager.close(); + } + } + + /** + * Tests connections get closed after transaction commit. + */ + @Test + public void testConnectionCloseAfterQueryWithoutTx() { + connectionProvider.clear(); + EntityManager entityManager = getOrCreateEntityManager(); + + try { + + final int oldOpenedConnections = connectionProvider.getTotalOpenedConnectionCount(); + final List childrenByQuery = entityManager.createQuery( "FROM ChildEntity" ).getResultList(); + assertTrue( childrenByQuery.size() > 0 ); + + // assert a connection had been opened + assertTrue( oldOpenedConnections < connectionProvider.getTotalOpenedConnectionCount() ); + // assert there's no remaining connection left. + assertTrue( connectionProvider.areAllConnectionClosed() ); + } + finally { + entityManager.close(); + } + } + + @Entity(name = "SimpleEntity") + public static class SimpleEntity { + private Long id; + + private String name; + + Set children = new HashSet<>(); + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent") + @LazyCollection(LazyCollectionOption.EXTRA) + @Fetch(FetchMode.SELECT) + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + } + + @Entity(name = "ChildEntity") + public static class ChildEntity { + private Long id; + + private String name; + + private SimpleEntity parent; + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.PROXY) + public SimpleEntity getParent() { + return parent; + } + + public void setParent(final SimpleEntity parent) { + this.parent = parent; + } + } + + private BaseDataSource getDataSource() { + final Properties connectionProps = new Properties(); + connectionProps.put( "user", Environment.getProperties().getProperty( Environment.USER ) ); + connectionProps.put( "password", Environment.getProperties().getProperty( Environment.PASS ) ); + + final String url = Environment.getProperties().getProperty( Environment.URL ); + return new BaseDataSource() { + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection( url, connectionProps ); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return DriverManager.getConnection( url, connectionProps ); + } + }; + } + + public static class ConnectionProviderDecorator extends UserSuppliedConnectionProviderImpl { + + private final DataSource dataSource; + + private int connectionCount; + private int openConnections; + + private Connection connection; + + public ConnectionProviderDecorator(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public Connection getConnection() throws SQLException { + connectionCount++; + openConnections++; + connection = spy( dataSource.getConnection() ); + return connection; + } + + @Override + public void closeConnection(Connection connection) throws SQLException { + connection.close(); + openConnections--; + } + + public int getTotalOpenedConnectionCount() { + return this.connectionCount; + } + + public int getCurrentOpenConnections() { + return openConnections; + } + + public boolean areAllConnectionClosed() { + return openConnections == 0; + } + + public void clear() { + connectionCount = 0; + openConnections = 0; + } + } + +} From 3ea0484122aaef172a4728a1179a1c2192204117 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 5 Jul 2018 11:07:38 +0100 Subject: [PATCH 3/3] HHH-4808 SessionImpl.initializeCollection() does not release JDBC connection (if outside of a transaction) --- .../AbstractPersistentCollection.java | 12 ++++-- .../org/hibernate/internal/SessionImpl.java | 2 +- .../proxy/AbstractLazyInitializer.java | 37 +++++++++++-------- 3 files changed, 32 insertions(+), 19 deletions(-) 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 eee4af75d1..d8587afa48 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 @@ -251,7 +251,7 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers // be created even if a current session and transaction are // open (ex: session.clear() was used). We must prevent // multiple transactions. - ( (Session) session ).beginTransaction(); + session.beginTransaction(); } session.getPersistenceContextInternal().addUninitializedDetachedCollection( @@ -265,20 +265,26 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers } finally { if ( tempSession != null ) { + // make sure the just opened temp session gets closed! isTempSession = false; session = originalSession; try { if ( !isJTA ) { - ( (Session) tempSession ).getTransaction().commit(); + tempSession.getTransaction().commit(); } - ( (Session) tempSession ).close(); + tempSession.close(); } catch (Exception e) { LOG.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); } } + else { + if ( !session.isTransactionInProgress() ) { + session.getJdbcCoordinator().afterTransaction(); + } + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index e54aabe092..6dbb43f3e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -3380,7 +3380,7 @@ public class SessionImpl if ( getLoadQueryInfluencers().getEffectiveEntityGraph().getSemantic() == GraphSemantic.FETCH ) { setEnforcingFetchGraph( true ); } - + return loadAccess.load( (Serializable) primaryKey ); } catch ( EntityNotFoundException ignored ) { diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index 2e7de16b14..9d481a2d71 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -163,22 +163,29 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { @Override public final void initialize() throws HibernateException { if ( !initialized ) { - if ( allowLoadOutsideTransaction ) { - permissiveInitialization(); + try { + if ( allowLoadOutsideTransaction ) { + permissiveInitialization(); + } + else if ( session == null ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" ); + } + else if ( !session.isOpenOrWaitingForAutoClose() ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session was closed" ); + } + else if ( !session.isConnected() ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session is disconnected" ); + } + else { + target = session.immediateLoad( entityName, id ); + initialized = true; + checkTargetState( session ); + } } - else if ( session == null ) { - throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" ); - } - else if ( !session.isOpenOrWaitingForAutoClose() ) { - throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session was closed" ); - } - else if ( !session.isConnected() ) { - throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session is disconnected" ); - } - else { - target = session.immediateLoad( entityName, id ); - initialized = true; - checkTargetState(session); + finally { + if ( session != null && !session.isTransactionInProgress() ) { + session.getJdbcCoordinator().afterTransaction(); + } } } else {