HHH-9231 : Uncommitted data can remain in transactional collection cache after rollback if collection is initialized after flush
This commit is contained in:
parent
4851ab01df
commit
f9a49efd17
|
@ -128,6 +128,7 @@ public class BasicTransactionalTestCase extends AbstractFunctionalTestCase {
|
||||||
@Override
|
@Override
|
||||||
public Void call() throws Exception {
|
public Void call() throws Exception {
|
||||||
Session s = openSession();
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" );
|
SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" );
|
||||||
Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() );
|
Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() );
|
||||||
stats.logSummary();
|
stats.logSummary();
|
||||||
|
@ -136,6 +137,12 @@ public class BasicTransactionalTestCase extends AbstractFunctionalTestCase {
|
||||||
assertEquals( 1, cStats.getHitCount() );
|
assertEquals( 1, cStats.getHitCount() );
|
||||||
Map cacheEntries = cStats.getEntries();
|
Map cacheEntries = cStats.getEntries();
|
||||||
assertEquals( 1, cacheEntries.size() );
|
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();
|
s.close();
|
||||||
return null;
|
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
|
@Test
|
||||||
public void testStaleWritesLeaveCacheConsistent() throws Exception {
|
public void testStaleWritesLeaveCacheConsistent() throws Exception {
|
||||||
Statistics stats = sessionFactory().getStatistics();
|
Statistics stats = sessionFactory().getStatistics();
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class Item {
|
||||||
private Set<Item> items = new HashSet<Item>( );
|
private Set<Item> items = new HashSet<Item>( );
|
||||||
private Item bagOwner;
|
private Item bagOwner;
|
||||||
private List<Item> bagOfItems = new ArrayList<Item>( );
|
private List<Item> bagOfItems = new ArrayList<Item>( );
|
||||||
|
private Set<OtherItem> otherItems = new HashSet<OtherItem>( );
|
||||||
|
|
||||||
public Item() {}
|
public Item() {}
|
||||||
|
|
||||||
|
@ -111,4 +112,18 @@ public class Item {
|
||||||
item.setBagOwner( this );
|
item.setBagOwner( this );
|
||||||
getBagOfItems().add( item );
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
77
hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/OtherItem.java
vendored
Executable file
77
hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/OtherItem.java
vendored
Executable 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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@
|
||||||
<id name="id">
|
<id name="id">
|
||||||
<generator class="increment" />
|
<generator class="increment" />
|
||||||
</id>
|
</id>
|
||||||
<property name="name" not-null="true" />
|
<property name="name" not-null="true" column="item_name" unique="true"/>
|
||||||
<property name="description" not-null="true" />
|
<property name="description" not-null="true" />
|
||||||
<many-to-one name="owner" column="owner_id" class="Item" />
|
<many-to-one name="owner" column="owner_id" class="Item" />
|
||||||
<many-to-one name="bagOwner" column="bagowner_id" class="Item" />
|
<many-to-one name="bagOwner" column="bagowner_id" class="Item" />
|
||||||
|
@ -40,8 +40,26 @@
|
||||||
<key column="bagowner_id" />
|
<key column="bagowner_id" />
|
||||||
<one-to-many class="Item" />
|
<one-to-many class="Item" />
|
||||||
</bag>
|
</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>
|
||||||
|
|
||||||
|
|
||||||
|
<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">
|
<class name="VersionedItem" table="VersionedItems">
|
||||||
<id name="id">
|
<id name="id">
|
||||||
<generator class="increment" />
|
<generator class="increment" />
|
||||||
|
|
Loading…
Reference in New Issue