diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index e21bc42a41..cd4155f5b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -299,30 +299,34 @@ public abstract class AbstractFlushingEventListener implements Serializable { } /** - * Execute all SQL and second-level cache updates, in a - * special order so that foreign-key constraints cannot - * be violated: - *
    + * Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot + * be violated:
      *
    1. Inserts, in the order they were performed *
    2. Updates *
    3. Deletion of collection elements *
    4. Insertion of collection elements *
    5. Deletes, in the order they were performed *
    + * + * @param session The session being flushed */ - protected void performExecutions(EventSource session) throws HibernateException { - + protected void performExecutions(EventSource session) { LOG.trace( "Executing flush" ); + // IMPL NOTE : here we alter the flushing flag of the persistence context to allow + // during-flush callbacks more leniency in regards to initializing proxies and + // lazy collections during their processing. + // For more information, see HHH-2763 try { session.getTransactionCoordinator().getJdbcCoordinator().flushBeginning(); - // we need to lock the collection caches before - // executing entity inserts/updates in order to - // account for bidi associations + session.getPersistenceContext().setFlushing( true ); + // we need to lock the collection caches before executing entity inserts/updates in order to + // account for bi-directional associations session.getActionQueue().prepareActions(); session.getActionQueue().executeActions(); } finally { + session.getPersistenceContext().setFlushing( false ); session.getTransactionCoordinator().getJdbcCoordinator().flushEnding(); } } diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/flush/InitializingPreUpdateEventListener.java b/hibernate-core/src/matrix/java/org/hibernate/test/flush/InitializingPreUpdateEventListener.java deleted file mode 100644 index c23bd599fe..0000000000 --- a/hibernate-core/src/matrix/java/org/hibernate/test/flush/InitializingPreUpdateEventListener.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2011, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.test.flush; - -import java.util.Collection; - -import org.hibernate.event.spi.PreUpdateEvent; -import org.hibernate.event.spi.PreUpdateEventListener; - -/** - * @author Steve Ebersole - */ -public class InitializingPreUpdateEventListener implements PreUpdateEventListener { - @Override - public boolean onPreUpdate(PreUpdateEvent event) { - final Object entity = event.getEntity(); - final Object[] oldValues = event.getOldState(); - final String[] properties = event.getPersister().getPropertyNames(); - - // Iterate through all fields of the updated object - for ( int i = 0; i < properties.length; i++ ) { - if (oldValues != null && oldValues[i] instanceof Collection ) { - final Collection col = (Collection) oldValues[i]; - // force initialization of collection to illustrate HHH-2763 - for ( Object element : col ) { - element.toString(); - } - } - } - return true; - } -} diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/flush/TestCollectionInitializingDuringFlush.java b/hibernate-core/src/matrix/java/org/hibernate/test/flush/TestCollectionInitializingDuringFlush.java index 84f625b595..1afd53d668 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/flush/TestCollectionInitializingDuringFlush.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/flush/TestCollectionInitializingDuringFlush.java @@ -23,26 +23,68 @@ */ package org.hibernate.test.flush; +import java.util.Collection; + import org.junit.Test; +import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.PreUpdateEvent; +import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.integrator.spi.Integrator; import org.hibernate.metamodel.source.MetadataImplementor; import org.hibernate.service.BootstrapServiceRegistryBuilder; import org.hibernate.service.spi.SessionFactoryServiceRegistry; -import org.hibernate.testing.FailureExpected; + import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + /** * @author Steve Ebersole */ @TestForIssue( jiraKey = "HHH-2763" ) public class TestCollectionInitializingDuringFlush extends BaseCoreFunctionalTestCase { + @Test + public void testInitializationDuringFlush() { + assertFalse( InitializingPreUpdateEventListener.INSTANCE.executed ); + assertFalse( InitializingPreUpdateEventListener.INSTANCE.foundAny ); + + Session s = openSession(); + s.beginTransaction(); + Publisher publisher = new Publisher( "acme" ); + Author author = new Author( "john" ); + author.setPublisher( publisher ); + publisher.getAuthors().add( author ); + author.getBooks().add( new Book( "Reflections on a Wimpy Kid", author ) ); + s.save( author ); + s.getTransaction().commit(); + s.clear(); + + s = openSession(); + s.beginTransaction(); + publisher = (Publisher) s.get( Publisher.class, publisher.getId() ); + publisher.setName( "random nally" ); + s.flush(); + s.getTransaction().commit(); + s.clear(); + + s = openSession(); + s.beginTransaction(); + s.delete( author ); + s.getTransaction().commit(); + s.clear(); + + assertTrue( InitializingPreUpdateEventListener.INSTANCE.executed ); + assertTrue( InitializingPreUpdateEventListener.INSTANCE.foundAny ); + } + @Override protected Class[] getAnnotatedClasses() { return new Class[] { Author.class, Book.class, Publisher.class }; @@ -73,7 +115,7 @@ public class TestCollectionInitializingDuringFlush extends BaseCoreFunctionalTes private void integrate(SessionFactoryServiceRegistry serviceRegistry) { serviceRegistry.getService( EventListenerRegistry.class ) .getEventListenerGroup( EventType.PRE_UPDATE ) - .appendListener( new InitializingPreUpdateEventListener() ); + .appendListener( InitializingPreUpdateEventListener.INSTANCE ); } @Override @@ -84,32 +126,30 @@ public class TestCollectionInitializingDuringFlush extends BaseCoreFunctionalTes ); } - @Test - @FailureExpected( jiraKey = "HHH-2763" ) - public void testInitializationDuringFlush() { - Session s = openSession(); - s.beginTransaction(); - Publisher publisher = new Publisher( "acme" ); - Author author = new Author( "john" ); - author.setPublisher( publisher ); - publisher.getAuthors().add( author ); - author.getBooks().add( new Book( "Reflections on a Wimpy Kid", author ) ); - s.save( author ); - s.getTransaction().commit(); - s.clear(); + public static class InitializingPreUpdateEventListener implements PreUpdateEventListener { + public static final InitializingPreUpdateEventListener INSTANCE = new InitializingPreUpdateEventListener(); - s = openSession(); - s.beginTransaction(); - publisher = (Publisher) s.get( Publisher.class, publisher.getId() ); - publisher.setName( "random nally" ); - s.flush(); - s.getTransaction().commit(); - s.clear(); + private boolean executed = false; + private boolean foundAny = false; - s = openSession(); - s.beginTransaction(); - s.delete( author ); - s.getTransaction().commit(); - s.clear(); + @Override + public boolean onPreUpdate(PreUpdateEvent event) { + executed = true; + + final Object[] oldValues = event.getOldState(); + final String[] properties = event.getPersister().getPropertyNames(); + + // Iterate through all fields of the updated object + for ( int i = 0; i < properties.length; i++ ) { + if ( oldValues != null && oldValues[i] != null ) { + if ( ! Hibernate.isInitialized( oldValues[i] ) ) { + // force any proxies and/or collections to initialize to illustrate HHH-2763 + foundAny = true; + Hibernate.initialize( oldValues ); + } + } + } + return true; + } } }