-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HHH-7316 : Collection removal actions added by DefaultAutoFlushEventL…
…istener.onAutoFlush() are not removed when flush is not needed
- Loading branch information
Showing
3 changed files
with
355 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
291 changes: 291 additions & 0 deletions
291
...nate-core/src/matrix/java/org/hibernate/test/flush/TestAutoFlushBeforeQueryExecution.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
/* | ||
* 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.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
import org.junit.Test; | ||
|
||
import org.hibernate.Hibernate; | ||
import org.hibernate.Session; | ||
import org.hibernate.Transaction; | ||
import org.hibernate.cfg.Configuration; | ||
import org.hibernate.collection.internal.PersistentSet; | ||
import org.hibernate.engine.spi.ActionQueue; | ||
import org.hibernate.engine.spi.PersistenceContext; | ||
import org.hibernate.engine.spi.SessionFactoryImplementor; | ||
import org.hibernate.engine.spi.SessionImplementor; | ||
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.internal.SessionImpl; | ||
import org.hibernate.metamodel.source.MetadataImplementor; | ||
import org.hibernate.service.BootstrapServiceRegistryBuilder; | ||
import org.hibernate.service.spi.SessionFactoryServiceRegistry; | ||
import org.hibernate.testing.TestForIssue; | ||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
/** | ||
* @author Gail Badner | ||
*/ | ||
@TestForIssue( jiraKey = "HHH-6960" ) | ||
public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCase { | ||
|
||
@Test | ||
public void testAutoflushIsRequired() { | ||
Session s = openSession(); | ||
Transaction txn = s.beginTransaction(); | ||
Publisher publisher = new Publisher(); | ||
publisher.setName( "name" ); | ||
s.save( publisher ); | ||
assertTrue( "autoflush entity create", s.createQuery( "from Publisher p" ).list().size() == 1 ); | ||
publisher.setName( "name" ); | ||
assertTrue( "autoflush entity update", s.createQuery( "from Publisher p where p.name='name'" ).list().size() == 1 ); | ||
txn.commit(); | ||
s.close(); | ||
|
||
s = openSession(); | ||
txn = s.beginTransaction(); | ||
publisher = (Publisher) s.get( Publisher.class, publisher.getId() ); | ||
assertTrue( publisher.getAuthors().isEmpty() ); | ||
|
||
final PersistenceContext persistenceContext = ( (SessionImplementor) s ).getPersistenceContext(); | ||
final ActionQueue actionQueue = ( (SessionImpl) s ).getActionQueue(); | ||
assertEquals( 1, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
Author author1 = new Author( ); | ||
author1.setPublisher( publisher ); | ||
publisher.getAuthors().add( author1 ); | ||
assertTrue( | ||
"autoflush collection update", | ||
s.createQuery( "select a from Publisher p join p.authors a" ).list().size() == 1 | ||
); | ||
assertEquals( 2, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 2, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( author1.getBooks() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
author1.setPublisher( null ); | ||
s.delete( author1 ); | ||
publisher.getAuthors().clear(); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
assertTrue( "autoflush collection update", | ||
s.createQuery( "select a from Publisher p join p.authors a" ).list().size() == 0 | ||
); | ||
assertEquals( 1, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
Set<Author> authorsOld = publisher.getAuthors(); | ||
publisher.setAuthors( new HashSet<Author>() ); | ||
Author author2 = new Author( ); | ||
author2.setName( "author2" ); | ||
author2.setPublisher( publisher ); | ||
publisher.getAuthors().add( author2 ); | ||
List results = s.createQuery( "select a from Publisher p join p.authors a" ).list(); | ||
assertEquals( 1, results.size() ); | ||
assertEquals( 2, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 2, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author2.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( author2.getBooks() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
s.delete(publisher); | ||
assertTrue( "autoflush delete", s.createQuery( "from Publisher p" ).list().size()==0 ); | ||
txn.commit(); | ||
s.close(); | ||
} | ||
|
||
@Test | ||
public void testAutoflushIsNotRequiredWithUnrelatedCollectionChange() { | ||
Session s = openSession(); | ||
Transaction txn = s.beginTransaction(); | ||
Publisher publisher = new Publisher(); | ||
publisher.setName( "name" ); | ||
s.save( publisher ); | ||
assertTrue( "autoflush entity create", s.createQuery( "from Publisher p" ).list().size() == 1 ); | ||
publisher.setName( "name" ); | ||
assertTrue( "autoflush entity update", s.createQuery( "from Publisher p where p.name='name'" ).list().size() == 1 ); | ||
UnrelatedEntity unrelatedEntity = new UnrelatedEntity( ); | ||
s.save( unrelatedEntity ); | ||
txn.commit(); | ||
s.close(); | ||
|
||
s = openSession(); | ||
txn = s.beginTransaction(); | ||
unrelatedEntity = (UnrelatedEntity) s.get( UnrelatedEntity.class, unrelatedEntity.getId() ); | ||
publisher = (Publisher) s.get( Publisher.class, publisher.getId() ); | ||
assertTrue( publisher.getAuthors().isEmpty() ); | ||
|
||
final PersistenceContext persistenceContext = ( (SessionImplementor) s ).getPersistenceContext(); | ||
final ActionQueue actionQueue = ( (SessionImpl) s ).getActionQueue(); | ||
assertEquals( 1, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
Author author1 = new Author( ); | ||
author1.setPublisher( publisher ); | ||
publisher.getAuthors().add( author1 ); | ||
assertTrue( s.createQuery( "from UnrelatedEntity" ).list().size() == 1 ); | ||
assertEquals( 2, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
author1.setPublisher( null ); | ||
s.delete( author1 ); | ||
publisher.getAuthors().clear(); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
assertTrue( s.createQuery( "from UnrelatedEntity" ).list().size() == 1 ); | ||
assertEquals( 2, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
Set<Author> authorsOld = publisher.getAuthors(); | ||
publisher.setAuthors( new HashSet<Author>() ); | ||
Author author2 = new Author( ); | ||
author2.setName( "author2" ); | ||
author2.setPublisher( publisher ); | ||
publisher.getAuthors().add( author2 ); | ||
List results = s.createQuery( "from UnrelatedEntity" ).list(); | ||
assertEquals( 1, results.size() ); | ||
assertEquals( 4, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author2.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( authorsOld ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( authorsOld ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
s.flush(); | ||
assertEquals( 2, persistenceContext.getCollectionEntries().size() ); | ||
assertEquals( 2, persistenceContext.getCollectionsByKey().size() ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionEntries().containsKey( author2.getBooks() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); | ||
assertTrue( persistenceContext.getCollectionsByKey().values().contains( author2.getBooks() ) ); | ||
assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); | ||
|
||
s.delete(publisher); | ||
assertTrue( "autoflush delete", s.createQuery( "from UnrelatedEntity" ).list().size()==1 ); | ||
s.delete( unrelatedEntity ); | ||
txn.commit(); | ||
s.close(); | ||
} | ||
|
||
@Override | ||
protected Class<?>[] getAnnotatedClasses() { | ||
return new Class<?>[] { Author.class, Book.class, Publisher.class, UnrelatedEntity.class }; | ||
} | ||
|
||
@Override | ||
protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { | ||
super.prepareBootstrapRegistryBuilder( builder ); | ||
builder.with( | ||
new Integrator() { | ||
|
||
@Override | ||
public void integrate( | ||
Configuration configuration, | ||
SessionFactoryImplementor sessionFactory, | ||
SessionFactoryServiceRegistry serviceRegistry) { | ||
integrate( serviceRegistry ); | ||
} | ||
|
||
@Override | ||
public void integrate( | ||
MetadataImplementor metadata, | ||
SessionFactoryImplementor sessionFactory, | ||
SessionFactoryServiceRegistry serviceRegistry) { | ||
integrate( serviceRegistry ); | ||
} | ||
|
||
private void integrate(SessionFactoryServiceRegistry serviceRegistry) { | ||
serviceRegistry.getService( EventListenerRegistry.class ) | ||
.getEventListenerGroup( EventType.PRE_UPDATE ) | ||
.appendListener( InitializingPreUpdateEventListener.INSTANCE ); | ||
} | ||
|
||
@Override | ||
public void disintegrate( | ||
SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { | ||
} | ||
} | ||
); | ||
} | ||
|
||
public static class InitializingPreUpdateEventListener implements PreUpdateEventListener { | ||
public static final InitializingPreUpdateEventListener INSTANCE = new InitializingPreUpdateEventListener(); | ||
|
||
private boolean executed = false; | ||
private boolean foundAny = false; | ||
|
||
@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; | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
hibernate-core/src/matrix/java/org/hibernate/test/flush/UnrelatedEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Hibernate, Relational Persistence for Idiomatic Java | ||
* | ||
* Copyright (c) 2012, 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 javax.persistence.Entity; | ||
import javax.persistence.GeneratedValue; | ||
import javax.persistence.Id; | ||
|
||
import org.hibernate.annotations.GenericGenerator; | ||
|
||
/** | ||
* @author Gail Badner | ||
*/ | ||
@Entity | ||
public class UnrelatedEntity { | ||
private Long id; | ||
private String name; | ||
|
||
public UnrelatedEntity() { | ||
} | ||
|
||
@Id | ||
@GeneratedValue( generator = "increment" ) | ||
@GenericGenerator( name = "increment", strategy = "increment" ) | ||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public void setId(Long id) { | ||
this.id = id; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public void setName(String name) { | ||
this.name = name; | ||
} | ||
} |