HHH-18230 disable exception when collection is unowned

I just feel like that's a bit too heavy-handed

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-06-05 12:29:33 +02:00
parent aa91138b6b
commit a6ef6d1b55
3 changed files with 70 additions and 51 deletions

View File

@ -24,6 +24,7 @@ import org.jboss.logging.Logger;
import java.util.Iterator;
import static java.util.Collections.emptyIterator;
import static org.hibernate.engine.internal.ForeignKeys.isTransient;
import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy;
@ -403,7 +404,17 @@ public class CascadingActions {
EventSource session,
CollectionType collectionType,
Object collection) {
return getLoadedElementsIterator( session, collectionType, collection );
if ( collectionType.isInverse( session.getSessionFactory() ) ) {
// For now, don't throw when an unowned collection
// contains references to transient/deleted objects.
// Strictly speaking, we should throw: but it just
// feels a bit too heavy-handed, especially in the
// case where the entity isn't transient but removed.
return emptyIterator();
}
else {
return getLoadedElementsIterator( session, collectionType, collection );
}
}
@Override

View File

@ -21,8 +21,9 @@ import java.util.TreeMap;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Incubating;
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.collection.spi.AbstractPersistentCollection;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CollectionEntry;
@ -42,12 +43,14 @@ import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.sql.results.graph.collection.LoadingCollectionEntry;
import org.jboss.logging.Logger;
import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
/**
* A type that handles Hibernate {@code PersistentCollection}s (including arrays).
*
@ -57,7 +60,7 @@ public abstract class CollectionType extends AbstractType implements Association
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, CollectionType.class.getName());
// private static final Object NOT_NULL_COLLECTION = new MarkerObject( "NOT NULL COLLECTION" );
@Internal
public static final Object UNFETCHED_COLLECTION = new MarkerObject( "UNFETCHED COLLECTION" );
private final String role;
@ -67,7 +70,6 @@ public abstract class CollectionType extends AbstractType implements Association
// TODO initialize it at constructor time
private volatile CollectionPersister persister;
public CollectionType(String role, String foreignKeyPropertyName) {
this.role = role;
this.foreignKeyPropertyName = foreignKeyPropertyName;
@ -86,11 +88,11 @@ public abstract class CollectionType extends AbstractType implements Association
public boolean contains(Object collection, Object childObject, SharedSessionContractImplementor session) {
// we do not have to worry about queued additions to uninitialized
// collections, since they can only occur for inverse collections!
Iterator<?> elems = getElementsIterator( collection );
final Iterator<?> elems = getElementsIterator( collection );
while ( elems.hasNext() ) {
Object element = elems.next();
// worrying about proxies is perhaps a little bit of overkill here...
final LazyInitializer li = HibernateProxy.extractLazyInitializer( element );
final LazyInitializer li = extractLazyInitializer( element );
if ( li != null ) {
if ( !li.isUninitialized() ) {
element = li.getImplementation();
@ -168,8 +170,7 @@ public abstract class CollectionType extends AbstractType implements Association
}
@Override
public String toLoggableString(Object value, SessionFactoryImplementor factory)
throws HibernateException {
public String toLoggableString(Object value, SessionFactoryImplementor factory) {
if ( value == null ) {
return "null";
}
@ -177,32 +178,34 @@ public abstract class CollectionType extends AbstractType implements Association
if ( !getReturnedClass().isInstance( value ) && !(value instanceof PersistentCollection) ) {
// its most likely the collection-key
final CollectionPersister persister = getPersister( factory );
if ( persister.getKeyType().getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + getPersister( factory ).getKeyType().toLoggableString( value, factory );
final Type keyType = persister.getKeyType();
if ( keyType.getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + keyType.toLoggableString( value, factory );
}
else {
// although it could also be the collection-id
if ( persister.getIdentifierType() != null
&& persister.getIdentifierType().getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + getPersister( factory ).getIdentifierType().toLoggableString( value, factory );
final Type identifierType = persister.getIdentifierType();
if ( identifierType != null
&& identifierType.getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + identifierType.toLoggableString( value, factory );
}
}
}
return renderLoggableString( value, factory );
}
protected String renderLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
protected String renderLoggableString(Object value, SessionFactoryImplementor factory) {
if ( !Hibernate.isInitialized( value ) ) {
return "<uninitialized>";
}
final List<String> list = new ArrayList<>();
Type elemType = getElementType( factory );
Iterator<?> itr = getElementsIterator( value );
final Type elemType = getElementType( factory );
final Iterator<?> itr = getElementsIterator( value );
while ( itr.hasNext() ) {
Object element = itr.next();
String string =
element == LazyPropertyInitializer.UNFETCHED_PROPERTY || !Hibernate.isInitialized(element)
final Object element = itr.next();
final String string =
element == UNFETCHED_PROPERTY || !Hibernate.isInitialized(element)
? "<uninitialized>"
: elemType.toLoggableString( element, factory );
list.add( string );
@ -249,6 +252,11 @@ public abstract class CollectionType extends AbstractType implements Association
return false;
}
@Internal @Incubating
public boolean isInverse(SessionFactoryImplementor factory) {
return getPersister( factory ).isInverse();
}
@Override
public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
@ -259,7 +267,10 @@ public abstract class CollectionType extends AbstractType implements Association
//session.getPersistenceContext().getCollectionEntry( (PersistentCollection) value ).getKey();
final Object key = getKeyOfOwner( owner, session );
return key == null ? null : getPersister( session ).getKeyType().disassemble( key, session, owner );
return key == null ? null
: getPersister( session )
.getKeyType()
.disassemble( key, session, owner );
}
@Override
@ -276,9 +287,10 @@ public abstract class CollectionType extends AbstractType implements Association
return null;
}
else {
final Object key = getPersister(session)
.getKeyType()
.assemble( cached, session, owner);
final Object key =
getPersister( session )
.getKeyType()
.assemble( cached, session, owner);
return resolveKey( key, session, owner );
}
}
@ -318,11 +330,9 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session)
throws HibernateException {
// collections don't dirty an un-versioned parent entity
// TODO: I don't really like this implementation; it would be better if
// this was handled by searchForDirtyCollections()
// TODO: I don't really like this implementation; it would be
// better if this was handled by searchForDirtyCollections()
return super.isDirty( old, current, session );
// return false;
@ -331,7 +341,7 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session)
throws HibernateException {
return isDirty(old, current, session);
return isDirty( old, current, session );
}
/**
@ -367,16 +377,13 @@ public abstract class CollectionType extends AbstractType implements Association
* @return The collection owner's key
*/
public Object getKeyOfOwner(Object owner, SharedSessionContractImplementor session) {
final PersistenceContext pc = session.getPersistenceContextInternal();
final EntityEntry entityEntry = pc.getEntry( owner );
final EntityEntry entityEntry = session.getPersistenceContextInternal().getEntry( owner );
if ( entityEntry == null ) {
// This just handles a particular case of component
// projection, perhaps get rid of it and throw an exception
return null;
}
if ( foreignKeyPropertyName == null ) {
else if ( foreignKeyPropertyName == null ) {
return entityEntry.getId();
}
else {
@ -386,14 +393,13 @@ public abstract class CollectionType extends AbstractType implements Association
// to decide to semiResolve(), trouble is that initializeEntity() reuses
// the same array for resolved and hydrated values
final Object loadedValue = entityEntry.getLoadedValue( foreignKeyPropertyName );
final Object id = loadedValue == null ?
entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName ) :
loadedValue;
final Object id = loadedValue == null
? entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName )
: loadedValue;
// NOTE VERY HACKISH WORKAROUND!!
// TODO: Fix this so it will work for non-POJO entity mode
Type keyType = getPersister( session ).getKeyType();
if ( !keyType.getReturnedClass().isInstance( id ) ) {
if ( !keyClass( session ).isInstance( id ) ) {
throw new UnsupportedOperationException( "Re-work support for semi-resolve" );
// id = keyType.semiResolve(
// entityEntry.getLoadedValue( foreignKeyPropertyName ),
@ -416,26 +422,28 @@ public abstract class CollectionType extends AbstractType implements Association
* otherwise, null is returned
*/
public Object getIdOfOwnerOrNull(Object key, SharedSessionContractImplementor session) {
Object ownerId = null;
if ( foreignKeyPropertyName == null ) {
ownerId = key;
return key;
}
else {
final CollectionPersister persister = getPersister( session );
Type keyType = persister.getKeyType();
EntityPersister ownerPersister = persister.getOwnerEntityPersister();
final EntityPersister ownerPersister = persister.getOwnerEntityPersister();
// TODO: Fix this so it will work for non-POJO entity mode
Class<?> ownerMappedClass = ownerPersister.getMappedClass();
if ( ownerMappedClass.isAssignableFrom( keyType.getReturnedClass() )
&& keyType.getReturnedClass().isInstance( key ) ) {
final Class<?> keyClass = keyClass( session );
if ( ownerPersister.getMappedClass().isAssignableFrom( keyClass )
&& keyClass.isInstance( key ) ) {
// the key is the owning entity itself, so get the ID from the key
ownerId = ownerPersister.getIdentifier( key, session );
return ownerPersister.getIdentifier( key, session );
}
else {
// TODO: check if key contains the owner ID
return null;
}
}
return ownerId;
}
private Class<?> keyClass(SharedSessionContractImplementor session) {
return getPersister( session ).getKeyType().getReturnedClass();
}
private Object resolveKey(Object key, SharedSessionContractImplementor session, Object owner) {
@ -675,7 +683,7 @@ public abstract class CollectionType extends AbstractType implements Association
// need to put the merged elements in a new collection
Object result = ( target == null ||
target == original ||
target == LazyPropertyInitializer.UNFETCHED_PROPERTY ||
target == UNFETCHED_PROPERTY ||
target instanceof PersistentCollection
&& ( (PersistentCollection<?>) target ).isWrapper( original ) )
? instantiateResult( original ) : target;

View File

@ -206,8 +206,8 @@ public class MultiCircleJpaCascadeTest {
assertTrue( ex.getCause() instanceof IllegalStateException );
IllegalStateException ise = (IllegalStateException) ex.getCause();
assertTyping( TransientObjectException.class, ise.getCause() );
String message = ise.getCause().getMessage();
assertTrue( message.contains("'org.hibernate.orm.test.jpa.cascade.multicircle.B'") );
// String message = ise.getCause().getMessage();
// assertTrue( message.contains("'org.hibernate.orm.test.jpa.cascade.multicircle.B'") );
}
finally {
entityManager.getTransaction().rollback();