HHH-3282 : DB2Dialect#supportsLobValueChangePropogation == false

git-svn-id: https://svn.jboss.org/repos/hibernate/core/branches/Branch_3_2@14660 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2008-05-14 14:06:47 +00:00
parent e94b243e72
commit c5cee56f1e
7 changed files with 351 additions and 41 deletions

View File

@ -55,7 +55,7 @@
The methods on <literal>Session</literal> are: <literal>enableFilter(String filterName)</literal>,
<literal>getEnabledFilter(String filterName)</literal>, and <literal>disableFilter(String filterName)</literal>.
By default, filters are <emphasis>not</emphasis> enabled for a given session; they must be explcitly
enabled through use of the <literal>Session.enabledFilter()</literal> method, which returns an
enabled through use of the <literal>Session.enableFilter()</literal> method, which returns an
instance of the <literal>Filter</literal> interface. Using the simple filter defined above, this
would look like:
</para>

View File

@ -51,6 +51,7 @@ public class QueryKey implements Serializable {
}
public boolean equals(Object other) {
if ( !( other instanceof QueryKey ) ) return false;
QueryKey that = (QueryKey) other;
if ( !sqlQueryString.equals(that.sqlQueryString) ) return false;
if ( !EqualsHelper.equals(firstRow, that.firstRow) || !EqualsHelper.equals(maxRows, that.maxRows) ) return false;

View File

@ -362,4 +362,8 @@ public class DB2Dialect extends Dialect {
public boolean supportsEmptyInList() {
return false;
}
public boolean supportsLobValueChangePropogation() {
return false;
}
}

View File

@ -2,23 +2,27 @@
package org.hibernate.event.def;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.ObjectDeletedException;
import org.hibernate.StaleObjectStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.WrongClassException;
import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.Status;
import org.hibernate.event.EventSource;
import org.hibernate.event.MergeEvent;
import org.hibernate.event.MergeEventListener;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey;
import org.hibernate.intercept.FieldInterceptionHelper;
import org.hibernate.intercept.FieldInterceptor;
import org.hibernate.persister.entity.EntityPersister;
@ -43,14 +47,33 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
return IdentityMap.invert( (Map) anything );
}
/**
/**
* Handle the given merge event.
*
* @param event The merge event to be handled.
* @throws HibernateException
*/
public void onMerge(MergeEvent event) throws HibernateException {
onMerge( event, IdentityMap.instantiate(10) );
Map copyCache = IdentityMap.instantiate(10);
onMerge( event, copyCache );
for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
Object entity = it.next();
if ( entity instanceof HibernateProxy ) {
entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
}
EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
if ( entry == null ) {
throw new TransientObjectException(
"object references an unsaved transient instance - save the transient instance before merging: " +
event.getSession().guessEntityName( entity )
);
// TODO: cache the entity name somewhere so that it is available to this exception
// entity name will not be available for non-POJO entities
}
if ( entry.getStatus() != Status.MANAGED ) {
throw new AssertionFailure( "Merged entity does not have status set to MANAGED; "+entry+" status="+entry.getStatus() );
}
}
}
/**
@ -82,7 +105,8 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
entity = original;
}
if ( copyCache.containsKey(entity) ) {
if ( copyCache.containsKey(entity) &&
source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
log.trace("already merged");
event.setResult(entity);
}
@ -126,7 +150,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
entityIsPersistent(event, copyCache);
break;
default: //DELETED
throw new ObjectDeletedException(
throw new ObjectDeletedException(
"deleted instance passed to merge",
null,
getLoggableName( event.getEntityName(), entity )
@ -137,7 +161,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
}
}
protected void entityIsPersistent(MergeEvent event, Map copyCache) {
log.trace("ignoring persistent instance");
@ -168,10 +192,15 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source.getEntityMode() ) :
null;
final Object copy = persister.instantiate( id, source.getEntityMode() ); //TODO: should this be Session.instantiate(Persister, ...)?
copyCache.put(entity, copy); //before cascade!
if ( copyCache.containsKey( entity ) ) {
persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
}
else {
copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
//TODO: should this be Session.instantiate(Persister, ...)?
}
final Object copy = copyCache.get( entity );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);

View File

@ -250,13 +250,23 @@ public abstract class EntityType extends AbstractType implements AssociationType
if ( original == target ) {
return target;
}
Object id = getIdentifier( original, session );
if ( id == null ) {
throw new AssertionFailure("cannot copy a reference to an object with a null id");
if ( session.getContextEntityIdentifier( original ) == null &&
ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
final Object copy = session.getFactory().getEntityPersister( associatedEntityName )
.instantiate( null, session.getEntityMode() );
//TODO: should this be Session.instantiate(Persister, ...)?
copyCache.put( original, copy );
return copy;
}
else {
Object id = getIdentifier( original, session );
if ( id == null ) {
throw new AssertionFailure("non-transient entity has a null id");
}
id = getIdentifierOrUniqueKeyType( session.getFactory() )
.replace(id, null, session, owner, copyCache);
return resolve( id, session, owner );
}
id = getIdentifierOrUniqueKeyType( session.getFactory() )
.replace(id, null, session, owner, copyCache);
return resolve( id, session, owner );
}
}

View File

@ -2,14 +2,13 @@
package org.hibernate.test.cascade;
import java.util.Collections;
import junit.framework.Test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.TransientObjectException;
import org.hibernate.junit.functional.FunctionalTestCase;
import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
import org.hibernate.proxy.HibernateProxy;
/**
* @author <a href="mailto:ovidiu@feodorov.com">Ovidiu Feodorov</a>
@ -33,15 +32,23 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
return new FunctionalTestClassTestSuite( MultiPathCascadeTest.class );
}
public void testMultiPathMergeDetachedFailureExpected() throws Exception
protected void cleanupTest() {
Session s = openSession();
s.beginTransaction();
s.createQuery( "delete from A" );
s.createQuery( "delete from G" );
s.createQuery( "delete from H" );
}
public void testMultiPathMergeModifiedDetached() throws Exception
{
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData("Anna");
s.save(a);
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
@ -50,22 +57,22 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
s = openSession();
s.beginTransaction();
s.merge(a);
a = ( A ) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
}
public void testMultiPathUpdateDetached() throws Exception
public void testMultiPathMergeModifiedDetachedIntoProxy() throws Exception
{
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData("Anna");
s.save(a);
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
@ -74,7 +81,33 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
s = openSession();
s.beginTransaction();
s.update(a);
A aLoaded = ( A ) s.load( A.class, new Long( a.getId() ) );
assertTrue( aLoaded instanceof HibernateProxy );
assertSame( aLoaded, s.merge( a ) );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
}
public void testMultiPathUpdateModifiedDetached() throws Exception
{
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
s.update( a );
s.getTransaction().commit();
s.close();
@ -88,8 +121,8 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData("Anna");
s.save(a);
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
@ -104,24 +137,168 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
verifyModifications( a.getId() );
}
public void testMultiPathMergeNonCascadedTransientEntityInCollection() throws Exception
{
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = ( A ) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
// add a new (transient) G to collection in h
// there is no cascade from H to the collection, so this should fail when merged
assertEquals( 1, a.getHs().size() );
H h = ( H ) a.getHs().iterator().next();
G gNew = new G();
gNew.setData( "Gail" );
gNew.getHs().add( h );
h.getGs().add( gNew );
s = openSession();
s.beginTransaction();
try {
s.merge( a );
s.merge( h );
fail( "should have thrown TransientObjectException" );
}
catch ( TransientObjectException ex ) {
// expected
}
finally {
s.getTransaction().rollback();
}
s.close();
}
public void testMultiPathMergeNonCascadedTransientEntityInOneToOne() throws Exception
{
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = ( A ) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
// change the one-to-one association from g to be a new (transient) A
// there is no cascade from G to A, so this should fail when merged
G g = a.getG();
a.setG( null );
A aNew = new A();
aNew.setData( "Alice" );
g.setA( aNew );
aNew.setG( g );
s = openSession();
s.beginTransaction();
try {
s.merge( a );
s.merge( g );
fail( "should have thrown TransientObjectException" );
}
catch ( TransientObjectException ex ) {
// expected
}
finally {
s.getTransaction().rollback();
}
s.close();
}
public void testMultiPathMergeNonCascadedTransientEntityInManyToOne() throws Exception
{
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = ( A ) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
// change the many-to-one association from h to be a new (transient) A
// there is no cascade from H to A, so this should fail when merged
assertEquals( 1, a.getHs().size() );
H h = ( H ) a.getHs().iterator().next();
a.getHs().remove( h );
A aNew = new A();
aNew.setData( "Alice" );
aNew.addH( h );
s = openSession();
s.beginTransaction();
try {
s.merge( a );
s.merge( h );
fail( "should have thrown TransientObjectException" );
}
catch ( TransientObjectException ex ) {
// expected
}
finally {
s.getTransaction().rollback();
}
s.close();
}
private void modifyEntity(A a) {
// create a *circular* graph in detached entity
a.setData("Anthony");
G g = new G();
g.setData("Giovanni");
g.setData( "Giovanni" );
H h = new H();
h.setData("Hellen");
h.setData( "Hellen" );
a.setG(g);
g.setA(a);
a.setG( g );
g.setA( a );
a.getHs().add(h);
h.setA(a);
a.getHs().add( h );
h.setA( a );
g.getHs().add(h);
h.getGs().add(g);
g.getHs().add( h );
h.getGs().add( g );
}
private void verifyModifications(long aId) {
@ -157,4 +334,4 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
s.close();
}
}
}

View File

@ -4,6 +4,7 @@ package org.hibernate.test.ops;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import junit.framework.Test;
@ -561,6 +562,94 @@ public class MergeTest extends AbstractOperationTestCase {
// cleanup();
}
public void testMergeManagedUninitializedCollection() {
Session s = openSession();
Transaction tx = s.beginTransaction();
NumberedNode root = new NumberedNode( "root" );
root.addChild( new NumberedNode( "child" ) );
s.persist(root);
tx.commit();
s.close();
clearCounts();
NumberedNode newRoot = new NumberedNode( "root" );
newRoot.setId( root.getId() );
s = openSession();
tx = s.beginTransaction();
root = ( NumberedNode ) s.get( NumberedNode.class, new Long( root.getId() ) );
Set managedChildren = root.getChildren();
assertFalse( Hibernate.isInitialized( managedChildren ) );
newRoot.setChildren( managedChildren );
assertSame( root, s.merge( newRoot ) );
assertSame( managedChildren, root.getChildren() );
assertFalse( Hibernate.isInitialized( managedChildren ) );
tx.commit();
assertInsertCount(0);
assertUpdateCount(0);
assertDeleteCount(0);
tx = s.beginTransaction();
assertEquals(
s.createCriteria(NumberedNode.class)
.setProjection( Projections.rowCount() )
.uniqueResult(),
new Integer(2)
);
tx.commit();
s.close();
// cleanup();
}
public void testMergeManagedInitializedCollection() {
Session s = openSession();
Transaction tx = s.beginTransaction();
NumberedNode root = new NumberedNode( "root" );
root.addChild( new NumberedNode( "child" ) );
s.persist(root);
tx.commit();
s.close();
clearCounts();
NumberedNode newRoot = new NumberedNode( "root" );
newRoot.setId( root.getId() );
s = openSession();
tx = s.beginTransaction();
root = ( NumberedNode ) s.get( NumberedNode.class, new Long( root.getId() ) );
Set managedChildren = root.getChildren();
Hibernate.initialize( managedChildren );
assertTrue( Hibernate.isInitialized( managedChildren ) );
newRoot.setChildren( managedChildren );
assertSame( root, s.merge( newRoot ) );
assertSame( managedChildren, root.getChildren() );
assertTrue( Hibernate.isInitialized( managedChildren ) );
tx.commit();
assertInsertCount(0);
assertUpdateCount(0);
assertDeleteCount(0);
tx = s.beginTransaction();
assertEquals(
s.createCriteria(NumberedNode.class)
.setProjection( Projections.rowCount() )
.uniqueResult(),
new Integer(2)
);
tx.commit();
s.close();
// cleanup();
}
public void testRecursiveMergeTransient() {
Session s = openSession();
Transaction tx = s.beginTransaction();