HHH-9231 : Uncommitted data can remain in transactional collection cache after rollback if collection is initialized after flush

This commit is contained in:
Gail Badner 2014-07-07 18:39:52 -07:00
parent 4851ab01df
commit f9a49efd17
4 changed files with 204 additions and 2 deletions

View File

@ -128,6 +128,7 @@ public class BasicTransactionalTestCase extends AbstractFunctionalTestCase {
@Override
public Void call() throws Exception {
Session s = openSession();
s.getTransaction().begin();
SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" );
Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() );
stats.logSummary();
@ -136,6 +137,12 @@ public class BasicTransactionalTestCase extends AbstractFunctionalTestCase {
assertEquals( 1, cStats.getHitCount() );
Map cacheEntries = cStats.getEntries();
assertEquals( 1, cacheEntries.size() );
Item itemElement = loadedWithCachedCollection.getItems().iterator().next();
itemElement.setOwner( null );
loadedWithCachedCollection.getItems().clear();
s.delete( itemElement );
s.delete( loadedWithCachedCollection );
s.getTransaction().commit();
s.close();
return null;
}
@ -389,6 +396,91 @@ public class BasicTransactionalTestCase extends AbstractFunctionalTestCase {
}
}
@Test
public void testAddNewManyToManyPropertyRefNoInitFlushInitLeaveCacheConsistent() throws Exception {
Statistics stats = sessionFactory().getStatistics();
stats.clear();
SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" );
OtherItem otherItem = null;
Transaction txn = null;
Session s = null;
beginTx();
try {
s = openSession();
txn = s.beginTransaction();
otherItem = new OtherItem();
otherItem.setName( "steve" );
s.save( otherItem );
txn.commit();
s.close();
}
catch (Exception e) {
setRollbackOnlyTx( e );
}
finally {
commitOrRollbackTx();
}
// create an element for otherItem.bagOfItems
Item item = new Item();
item.setName( "element" );
item.setDescription( "element Item" );
beginTx();
try {
s = openSession();
txn = s.beginTransaction();
otherItem = (OtherItem) s.get( OtherItem.class, otherItem.getId() );
assertFalse( Hibernate.isInitialized( otherItem.getBagOfItems() ) );
// Add an element to otherItem.bagOfItems (a bag); it will not initialize the bag.
otherItem.addItemToBag( item );
assertFalse( Hibernate.isInitialized( otherItem.getBagOfItems() ) );
s.persist( item );
s.flush();
// Now initialize the collection; it will contain the uncommitted itemElement.
// The many-to-many uses a property-ref
Hibernate.initialize( otherItem.getBagOfItems() );
setRollbackOnlyTx();
}
catch (Exception e) {
setRollbackOnlyTxExpected(e);
}
finally {
commitOrRollbackTx();
if ( s != null && s.isOpen() ) {
try {
s.close();
}
catch (Throwable ignore) {
}
}
}
beginTx();
try {
// cleanup
s = openSession();
txn = s.beginTransaction();
otherItem = (OtherItem) s.get( OtherItem.class, otherItem.getId() );
// Because of HHH-9231, the following will fail due to ObjectNotFoundException because the
// collection will be read from the cache and it still contains the uncommitted element,
// which cannot be found.
Hibernate.initialize( otherItem.getBagOfItems() );
assertTrue( otherItem.getBagOfItems().isEmpty() );
s.delete( otherItem );
txn.commit();
s.close();
}
catch (Exception e) {
setRollbackOnlyTx( e );
}
finally {
commitOrRollbackTx();
}
}
@Test
public void testStaleWritesLeaveCacheConsistent() throws Exception {
Statistics stats = sessionFactory().getStatistics();

View File

@ -38,6 +38,7 @@ public class Item {
private Set<Item> items = new HashSet<Item>( );
private Item bagOwner;
private List<Item> bagOfItems = new ArrayList<Item>( );
private Set<OtherItem> otherItems = new HashSet<OtherItem>( );
public Item() {}
@ -111,4 +112,18 @@ public class Item {
item.setBagOwner( this );
getBagOfItems().add( item );
}
public Set<OtherItem> getOtherItems() {
return otherItems;
}
public void setOtherItems(Set<OtherItem> otherItems) {
this.otherItems = otherItems;
}
public void addOtherItem(OtherItem otherItem) {
getOtherItems().add( otherItem );
otherItem.getBagOfItems().add( this );
}
}

View File

@ -0,0 +1,77 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat, Inc. and/or it's affiliates 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. and/or it's affiliates.
*
* 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.cache.infinispan.functional;
import java.util.ArrayList;
import java.util.List;
/**
* @author Gail Badner
*/
public class OtherItem {
private Long id;
private String name;
private Item favoriteItem;
private List<Item> bagOfItems = new ArrayList<Item>();
public OtherItem() {
}
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;
}
public Item getFavoriteItem() {
return favoriteItem;
}
public void setFavoriteItem(Item favoriteItem) {
this.favoriteItem = favoriteItem;
}
public List<Item> getBagOfItems() {
return bagOfItems;
}
public void setBagOfItems(List<Item> bagOfItems) {
this.bagOfItems = bagOfItems;
}
public void addItemToBag(Item item) {
bagOfItems.add( item );
item.getOtherItems().add( this );
}
}

View File

@ -28,7 +28,7 @@
<id name="id">
<generator class="increment" />
</id>
<property name="name" not-null="true" />
<property name="name" not-null="true" column="item_name" unique="true"/>
<property name="description" not-null="true" />
<many-to-one name="owner" column="owner_id" class="Item" />
<many-to-one name="bagOwner" column="bagowner_id" class="Item" />
@ -40,9 +40,27 @@
<key column="bagowner_id" />
<one-to-many class="Item" />
</bag>
<set name="otherItems" table="items_otheritems">
<key property-ref="name" column="item_name"/>
<many-to-many class="OtherItem" column="other_item_id"/>
</set>
</class>
<class name="VersionedItem" table="VersionedItems">
<class name="OtherItem" table="OtherItems">
<id name="id">
<generator class="increment" />
</id>
<many-to-one name="favoriteItem" property-ref="name"/>
<property name="name" not-null="true" column="other_item_name" unique="true" />
<bag name="bagOfItems" inverse="true" table="items_otheritems">
<key column="other_item_id"/>
<many-to-many class="Item" property-ref="name" column="item_name" />
</bag>
</class>
<class name="VersionedItem" table="VersionedItems">
<id name="id">
<generator class="increment" />
</id>