diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java index 42e9fef2e2ad..8325b338afe5 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java @@ -163,6 +163,7 @@ protected boolean readSize() { session.getPersistenceContextInternal().getCollectionEntry( this ); if ( entry != null ) { final CollectionPersister persister = entry.getLoadedPersister(); + checkPersister( this, persister ); if ( persister.isExtraLazy() ) { // TODO: support for extra-lazy collections was // dropped so this code should be obsolete @@ -331,6 +332,7 @@ protected Boolean readIndexExistence(final Object index) { () -> { final CollectionEntry entry = session.getPersistenceContextInternal().getCollectionEntry( this ); final CollectionPersister persister = entry.getLoadedPersister(); + checkPersister( this, persister ); if ( persister.isExtraLazy() ) { if ( hasQueuedOperations() ) { session.flush(); @@ -353,6 +355,7 @@ protected Boolean readElementExistence(final Object element) { () -> { final CollectionEntry entry = session.getPersistenceContextInternal().getCollectionEntry( this ); final CollectionPersister persister = entry.getLoadedPersister(); + checkPersister( this, persister ); if ( persister.isExtraLazy() ) { if ( hasQueuedOperations() ) { session.flush(); @@ -399,6 +402,7 @@ public Object doWork() { session.getPersistenceContextInternal() .getCollectionEntry( AbstractPersistentCollection.this ); final CollectionPersister persister = entry.getLoadedPersister(); + checkPersister( AbstractPersistentCollection.this, persister ); isExtraLazy = persister.isExtraLazy(); if ( isExtraLazy ) { if ( hasQueuedOperations() ) { @@ -646,12 +650,22 @@ private void throwLazyInitializationExceptionIfNotConnected() { } private void throwLazyInitializationException(String message) { + throwLazyInitializationException( role, message); + } + + private static void throwLazyInitializationException(String role, String message) { throw new LazyInitializationException( String.format( "Cannot lazily initialize collection%s (%s)", role == null ? "" : " of role '" + role + "'", message ) ); } + public static void checkPersister(PersistentCollection collection, CollectionPersister persister) { + if ( !collection.wasInitialized() && persister == null ) { + throwLazyInitializationException( null, "collection is being removed" ); + } + } + protected final void setInitialized() { this.initializing = false; this.initialized = true; diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java index fe012a81719c..f1d2ef160b67 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java @@ -17,6 +17,7 @@ import org.hibernate.sql.results.internal.ResultsHelper; import org.hibernate.stat.spi.StatisticsImplementor; +import static org.hibernate.collection.spi.AbstractPersistentCollection.checkPersister; import static org.hibernate.loader.internal.CacheLoadHelper.initializeCollectionFromCache; import static org.hibernate.pretty.MessageHelper.collectionInfoString; @@ -41,6 +42,7 @@ public void onInitializeCollection(InitializeCollectionEvent event) throws Hiber } if ( !collection.wasInitialized() ) { final CollectionPersister loadedPersister = ce.getLoadedPersister(); + checkPersister(collection, loadedPersister); final Object loadedKey = ce.getLoadedKey(); if ( LOG.isTraceEnabled() ) { LOG.trace( "Initializing collection " diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/events/PreDeleteEventListenerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/events/PreDeleteEventListenerTest.java new file mode 100644 index 000000000000..cc13fda72c0e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/events/PreDeleteEventListenerTest.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.events; + + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; +import org.hibernate.HibernateException; +import org.hibernate.event.spi.EventType; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static jakarta.persistence.FetchType.LAZY; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Felix König + * @author Jan Schatteman + */ +@DomainModel ( + annotatedClasses = { + PreDeleteEventListenerTest.Parent.class, PreDeleteEventListenerTest.Child.class + } +) +@SessionFactory +@Jira( value = "https://hibernate.atlassian.net/browse/HHH-19631" ) +public class PreDeleteEventListenerTest { + + @Test + void testAccessUninitializedCollectionInListener(SessionFactoryScope scope) { + + scope.getSessionFactory().getEventListenerRegistry().appendListeners( EventType.PRE_DELETE, + event -> { + Parent parent = ((Parent) event.getEntity()); + // dummy access + assertThrows( HibernateException.class, () -> parent.getChildren().size() ); + return false; + }, event -> { + Parent parent = ((Parent) event.getEntity()); + // dummy access + assertThrows( HibernateException.class, () -> parent.getChildren().contains(new Child()) ); + return false; + } ); + + scope.inTransaction( session -> session.persist(new Parent()) ); + + scope.inTransaction(session -> { + var parent = session.createSelectionQuery("select p from Parent p", Parent.class).getSingleResult(); + // triggers pre-delete event + session.remove(parent); + session.flush(); + }); + } + + @Entity(name= "Parent") + public static class Parent { + @Id String id = UUID.randomUUID().toString(); + @ManyToMany(fetch = LAZY) + Set children = new HashSet<>(); + public Set getChildren() { + return children; + } + } + + @Entity(name= "Child") + public static class Child { + @Id + String childId = UUID.randomUUID().toString(); + } + +}