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>, The methods on <literal>Session</literal> are: <literal>enableFilter(String filterName)</literal>,
<literal>getEnabledFilter(String filterName)</literal>, and <literal>disableFilter(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 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 instance of the <literal>Filter</literal> interface. Using the simple filter defined above, this
would look like: would look like:
</para> </para>

View File

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

View File

@ -2,23 +2,27 @@
package org.hibernate.event.def; package org.hibernate.event.def;
import java.io.Serializable; import java.io.Serializable;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.ObjectDeletedException; import org.hibernate.ObjectDeletedException;
import org.hibernate.StaleObjectStateException; import org.hibernate.StaleObjectStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.WrongClassException; import org.hibernate.WrongClassException;
import org.hibernate.engine.Cascade; import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction; 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.EventSource;
import org.hibernate.event.MergeEvent; import org.hibernate.event.MergeEvent;
import org.hibernate.event.MergeEventListener; 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.FieldInterceptionHelper;
import org.hibernate.intercept.FieldInterceptor; import org.hibernate.intercept.FieldInterceptor;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -43,14 +47,33 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
return IdentityMap.invert( (Map) anything ); return IdentityMap.invert( (Map) anything );
} }
/** /**
* Handle the given merge event. * Handle the given merge event.
* *
* @param event The merge event to be handled. * @param event The merge event to be handled.
* @throws HibernateException * @throws HibernateException
*/ */
public void onMerge(MergeEvent event) 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; entity = original;
} }
if ( copyCache.containsKey(entity) ) { if ( copyCache.containsKey(entity) &&
source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
log.trace("already merged"); log.trace("already merged");
event.setResult(entity); event.setResult(entity);
} }
@ -126,7 +150,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
entityIsPersistent(event, copyCache); entityIsPersistent(event, copyCache);
break; break;
default: //DELETED default: //DELETED
throw new ObjectDeletedException( throw new ObjectDeletedException(
"deleted instance passed to merge", "deleted instance passed to merge",
null, null,
getLoggableName( event.getEntityName(), entity ) getLoggableName( event.getEntityName(), entity )
@ -137,7 +161,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
} }
} }
protected void entityIsPersistent(MergeEvent event, Map copyCache) { protected void entityIsPersistent(MergeEvent event, Map copyCache) {
log.trace("ignoring persistent instance"); log.trace("ignoring persistent instance");
@ -168,10 +192,15 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
final Serializable id = persister.hasIdentifierProperty() ? final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source.getEntityMode() ) : persister.getIdentifier( entity, source.getEntityMode() ) :
null; null;
if ( copyCache.containsKey( entity ) ) {
final Object copy = persister.instantiate( id, source.getEntityMode() ); //TODO: should this be Session.instantiate(Persister, ...)? persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
copyCache.put(entity, copy); //before cascade! }
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 // cascade first, so that all unsaved objects get their
// copy created before we actually copy // copy created before we actually copy
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); //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 ) { if ( original == target ) {
return target; return target;
} }
Object id = getIdentifier( original, session ); if ( session.getContextEntityIdentifier( original ) == null &&
if ( id == null ) { ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
throw new AssertionFailure("cannot copy a reference to an object with a null id"); 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; package org.hibernate.test.cascade;
import java.util.Collections;
import junit.framework.Test; import junit.framework.Test;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.TransientObjectException;
import org.hibernate.junit.functional.FunctionalTestCase; import org.hibernate.junit.functional.FunctionalTestCase;
import org.hibernate.junit.functional.FunctionalTestClassTestSuite; import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
import org.hibernate.proxy.HibernateProxy;
/** /**
* @author <a href="mailto:ovidiu@feodorov.com">Ovidiu Feodorov</a> * @author <a href="mailto:ovidiu@feodorov.com">Ovidiu Feodorov</a>
@ -33,15 +32,23 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
return new FunctionalTestClassTestSuite( MultiPathCascadeTest.class ); 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 // persist a simple A in the database
Session s = openSession(); Session s = openSession();
s.beginTransaction(); s.beginTransaction();
A a = new A(); A a = new A();
a.setData("Anna"); a.setData( "Anna" );
s.save(a); s.save( a );
s.getTransaction().commit(); s.getTransaction().commit();
s.close(); s.close();
@ -50,22 +57,22 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
s = openSession(); s = openSession();
s.beginTransaction(); s.beginTransaction();
s.merge(a); a = ( A ) s.merge( a );
s.getTransaction().commit(); s.getTransaction().commit();
s.close(); s.close();
verifyModifications( a.getId() ); verifyModifications( a.getId() );
} }
public void testMultiPathUpdateDetached() throws Exception public void testMultiPathMergeModifiedDetachedIntoProxy() throws Exception
{ {
// persist a simple A in the database // persist a simple A in the database
Session s = openSession(); Session s = openSession();
s.beginTransaction(); s.beginTransaction();
A a = new A(); A a = new A();
a.setData("Anna"); a.setData( "Anna" );
s.save(a); s.save( a );
s.getTransaction().commit(); s.getTransaction().commit();
s.close(); s.close();
@ -74,7 +81,33 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
s = openSession(); s = openSession();
s.beginTransaction(); 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.getTransaction().commit();
s.close(); s.close();
@ -88,8 +121,8 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
Session s = openSession(); Session s = openSession();
s.beginTransaction(); s.beginTransaction();
A a = new A(); A a = new A();
a.setData("Anna"); a.setData( "Anna" );
s.save(a); s.save( a );
s.getTransaction().commit(); s.getTransaction().commit();
s.close(); s.close();
@ -104,24 +137,168 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
verifyModifications( a.getId() ); 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) { private void modifyEntity(A a) {
// create a *circular* graph in detached entity // create a *circular* graph in detached entity
a.setData("Anthony"); a.setData("Anthony");
G g = new G(); G g = new G();
g.setData("Giovanni"); g.setData( "Giovanni" );
H h = new H(); H h = new H();
h.setData("Hellen"); h.setData( "Hellen" );
a.setG(g); a.setG( g );
g.setA(a); g.setA( a );
a.getHs().add(h); a.getHs().add( h );
h.setA(a); h.setA( a );
g.getHs().add(h); g.getHs().add( h );
h.getGs().add(g); h.getGs().add( g );
} }
private void verifyModifications(long aId) { private void verifyModifications(long aId) {
@ -157,4 +334,4 @@ public class MultiPathCascadeTest extends FunctionalTestCase {
s.close(); s.close();
} }
} }

View File

@ -4,6 +4,7 @@ package org.hibernate.test.ops;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set;
import junit.framework.Test; import junit.framework.Test;
@ -561,6 +562,94 @@ public class MergeTest extends AbstractOperationTestCase {
// cleanup(); // 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() { public void testRecursiveMergeTransient() {
Session s = openSession(); Session s = openSession();
Transaction tx = s.beginTransaction(); Transaction tx = s.beginTransaction();