HHH-8159 - Apply fixups indicated by analysis tools
This commit is contained in:
parent
fc02da1c12
commit
021401835c
|
@ -47,29 +47,29 @@ import org.jboss.logging.Logger;
|
|||
* @author Gavin King
|
||||
*/
|
||||
public final class Collections {
|
||||
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, Collections.class.getName());
|
||||
|
||||
private Collections() {
|
||||
}
|
||||
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
|
||||
CoreMessageLogger.class,
|
||||
Collections.class.getName()
|
||||
);
|
||||
|
||||
/**
|
||||
* record the fact that this collection was dereferenced
|
||||
*
|
||||
* @param coll The collection to be updated by un-reachability.
|
||||
* @param session The session
|
||||
*/
|
||||
@SuppressWarnings( {"JavaDoc"})
|
||||
public static void processUnreachableCollection(PersistentCollection coll, SessionImplementor session) {
|
||||
if ( coll.getOwner()==null ) {
|
||||
processNeverReferencedCollection(coll, session);
|
||||
processNeverReferencedCollection( coll, session );
|
||||
}
|
||||
else {
|
||||
processDereferencedCollection(coll, session);
|
||||
processDereferencedCollection( coll, session );
|
||||
}
|
||||
}
|
||||
|
||||
private static void processDereferencedCollection(PersistentCollection coll, SessionImplementor session) {
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||
CollectionEntry entry = persistenceContext.getCollectionEntry(coll);
|
||||
final CollectionEntry entry = persistenceContext.getCollectionEntry( coll );
|
||||
final CollectionPersister loadedPersister = entry.getLoadedPersister();
|
||||
|
||||
if ( loadedPersister != null && LOG.isDebugEnabled() ) {
|
||||
|
@ -82,15 +82,15 @@ public final class Collections {
|
|||
}
|
||||
|
||||
// do a check
|
||||
boolean hasOrphanDelete = loadedPersister != null && loadedPersister.hasOrphanDelete();
|
||||
if (hasOrphanDelete) {
|
||||
final boolean hasOrphanDelete = loadedPersister != null && loadedPersister.hasOrphanDelete();
|
||||
if ( hasOrphanDelete ) {
|
||||
Serializable ownerId = loadedPersister.getOwnerEntityPersister().getIdentifier( coll.getOwner(), session );
|
||||
if ( ownerId == null ) {
|
||||
// the owning entity may have been deleted and its identifier unset due to
|
||||
// identifier-rollback; in which case, try to look up its identifier from
|
||||
// the persistence context
|
||||
if ( session.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
|
||||
EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
|
||||
final EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
|
||||
if ( ownerEntry != null ) {
|
||||
ownerId = ownerEntry.getId();
|
||||
}
|
||||
|
@ -99,15 +99,15 @@ public final class Collections {
|
|||
throw new AssertionFailure( "Unable to determine collection owner identifier for orphan-delete processing" );
|
||||
}
|
||||
}
|
||||
EntityKey key = session.generateEntityKey( ownerId, loadedPersister.getOwnerEntityPersister() );
|
||||
Object owner = persistenceContext.getEntity(key);
|
||||
final EntityKey key = session.generateEntityKey( ownerId, loadedPersister.getOwnerEntityPersister() );
|
||||
final Object owner = persistenceContext.getEntity( key );
|
||||
if ( owner == null ) {
|
||||
throw new AssertionFailure(
|
||||
"collection owner not associated with session: " +
|
||||
loadedPersister.getRole()
|
||||
);
|
||||
}
|
||||
EntityEntry e = persistenceContext.getEntry(owner);
|
||||
final EntityEntry e = persistenceContext.getEntry( owner );
|
||||
//only collections belonging to deleted entities are allowed to be dereferenced in the case of orphan delete
|
||||
if ( e != null && e.getStatus() != Status.DELETED && e.getStatus() != Status.GONE ) {
|
||||
throw new HibernateException(
|
||||
|
@ -118,23 +118,27 @@ public final class Collections {
|
|||
}
|
||||
|
||||
// do the work
|
||||
entry.setCurrentPersister(null);
|
||||
entry.setCurrentKey(null);
|
||||
entry.setCurrentPersister( null );
|
||||
entry.setCurrentKey( null );
|
||||
prepareCollectionForUpdate( coll, entry, session.getFactory() );
|
||||
|
||||
}
|
||||
|
||||
private static void processNeverReferencedCollection(PersistentCollection coll, SessionImplementor session)
|
||||
throws HibernateException {
|
||||
|
||||
throws HibernateException {
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||
CollectionEntry entry = persistenceContext.getCollectionEntry(coll);
|
||||
final CollectionEntry entry = persistenceContext.getCollectionEntry( coll );
|
||||
|
||||
if ( LOG.isDebugEnabled() ) {
|
||||
LOG.debugf( "Found collection with unloaded owner: %s",
|
||||
LOG.debugf(
|
||||
"Found collection with unloaded owner: %s",
|
||||
MessageHelper.collectionInfoString(
|
||||
entry.getLoadedPersister(), coll,
|
||||
entry.getLoadedKey(), session ) );
|
||||
entry.getLoadedPersister(),
|
||||
coll,
|
||||
entry.getLoadedKey(),
|
||||
session
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
entry.setCurrentPersister( entry.getLoadedPersister() );
|
||||
|
@ -154,13 +158,11 @@ public final class Collections {
|
|||
*/
|
||||
public static void processReachableCollection(
|
||||
PersistentCollection collection,
|
||||
CollectionType type,
|
||||
Object entity,
|
||||
SessionImplementor session) {
|
||||
|
||||
collection.setOwner(entity);
|
||||
|
||||
CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(collection);
|
||||
CollectionType type,
|
||||
Object entity,
|
||||
SessionImplementor session) {
|
||||
collection.setOwner( entity );
|
||||
final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( collection );
|
||||
|
||||
if ( ce == null ) {
|
||||
// refer to comment in StatefulPersistenceContext.addCollection()
|
||||
|
@ -175,30 +177,35 @@ public final class Collections {
|
|||
if ( ce.isReached() ) {
|
||||
// We've been here before
|
||||
throw new HibernateException(
|
||||
"Found shared references to a collection: " +
|
||||
type.getRole()
|
||||
"Found shared references to a collection: " + type.getRole()
|
||||
);
|
||||
}
|
||||
ce.setReached(true);
|
||||
ce.setReached( true );
|
||||
|
||||
SessionFactoryImplementor factory = session.getFactory();
|
||||
CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
|
||||
ce.setCurrentPersister(persister);
|
||||
ce.setCurrentKey( type.getKeyOfOwner(entity, session) ); //TODO: better to pass the id in as an argument?
|
||||
final SessionFactoryImplementor factory = session.getFactory();
|
||||
final CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
|
||||
ce.setCurrentPersister( persister );
|
||||
//TODO: better to pass the id in as an argument?
|
||||
ce.setCurrentKey( type.getKeyOfOwner( entity, session ) );
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
if (collection.wasInitialized()) LOG.debugf("Collection found: %s, was: %s (initialized)",
|
||||
MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session),
|
||||
MessageHelper.collectionInfoString(ce.getLoadedPersister(), collection,
|
||||
ce.getLoadedKey(),
|
||||
session));
|
||||
else LOG.debugf("Collection found: %s, was: %s (uninitialized)",
|
||||
MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session),
|
||||
MessageHelper.collectionInfoString(ce.getLoadedPersister(), collection, ce.getLoadedKey(), session));
|
||||
}
|
||||
if ( LOG.isDebugEnabled() ) {
|
||||
if ( collection.wasInitialized() ) {
|
||||
LOG.debugf(
|
||||
"Collection found: %s, was: %s (initialized)",
|
||||
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
|
||||
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), session )
|
||||
);
|
||||
}
|
||||
else {
|
||||
LOG.debugf(
|
||||
"Collection found: %s, was: %s (uninitialized)",
|
||||
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
|
||||
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), session )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
prepareCollectionForUpdate( collection, ce, factory );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,9 +216,8 @@ public final class Collections {
|
|||
@SuppressWarnings( {"JavaDoc"})
|
||||
private static void prepareCollectionForUpdate(
|
||||
PersistentCollection collection,
|
||||
CollectionEntry entry,
|
||||
SessionFactoryImplementor factory) {
|
||||
|
||||
CollectionEntry entry,
|
||||
SessionFactoryImplementor factory) {
|
||||
if ( entry.isProcessed() ) {
|
||||
throw new AssertionFailure( "collection was processed twice by flush()" );
|
||||
}
|
||||
|
@ -219,37 +225,33 @@ public final class Collections {
|
|||
|
||||
final CollectionPersister loadedPersister = entry.getLoadedPersister();
|
||||
final CollectionPersister currentPersister = entry.getCurrentPersister();
|
||||
if ( loadedPersister != null || currentPersister != null ) { // it is or was referenced _somewhere_
|
||||
if ( loadedPersister != null || currentPersister != null ) {
|
||||
// it is or was referenced _somewhere_
|
||||
|
||||
boolean ownerChanged = loadedPersister != currentPersister || // if either its role changed,
|
||||
!currentPersister
|
||||
.getKeyType().isEqual( // or its key changed
|
||||
entry.getLoadedKey(),
|
||||
entry.getCurrentKey(),
|
||||
factory
|
||||
);
|
||||
|
||||
if (ownerChanged) {
|
||||
// if either its role changed, or its key changed
|
||||
final boolean ownerChanged = loadedPersister != currentPersister
|
||||
|| !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory );
|
||||
|
||||
if ( ownerChanged ) {
|
||||
// do a check
|
||||
final boolean orphanDeleteAndRoleChanged = loadedPersister != null &&
|
||||
currentPersister != null &&
|
||||
loadedPersister.hasOrphanDelete();
|
||||
final boolean orphanDeleteAndRoleChanged =
|
||||
loadedPersister != null && currentPersister != null && loadedPersister.hasOrphanDelete();
|
||||
|
||||
if (orphanDeleteAndRoleChanged) {
|
||||
throw new HibernateException(
|
||||
"Don't change the reference to a collection with cascade=\"all-delete-orphan\": " +
|
||||
loadedPersister.getRole()
|
||||
"Don't change the reference to a collection with delete-orphan enabled : "
|
||||
+ loadedPersister.getRole()
|
||||
);
|
||||
}
|
||||
|
||||
// do the work
|
||||
if ( currentPersister != null ) {
|
||||
entry.setDorecreate( true ); // we will need to create new entries
|
||||
entry.setDorecreate( true );
|
||||
}
|
||||
|
||||
if ( loadedPersister != null ) {
|
||||
entry.setDoremove( true ); // we will need to remove ye olde entries
|
||||
// we will need to remove ye olde entries
|
||||
entry.setDoremove( true );
|
||||
if ( entry.isDorecreate() ) {
|
||||
LOG.trace( "Forcing collection initialization" );
|
||||
collection.forceInitialization();
|
||||
|
@ -260,8 +262,12 @@ public final class Collections {
|
|||
// the collection's elements have changed
|
||||
entry.setDoupdate( true );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow instantiation
|
||||
*/
|
||||
private Collections() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import java.util.Map;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
|
@ -56,22 +55,31 @@ public class EntityEntryContext {
|
|||
|
||||
private transient ManagedEntity head;
|
||||
private transient ManagedEntity tail;
|
||||
private transient int count = 0;
|
||||
private transient int count;
|
||||
|
||||
private transient IdentityHashMap<Object,ManagedEntity> nonEnhancedEntityXref;
|
||||
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
private transient Map.Entry<Object,EntityEntry>[] reentrantSafeEntries = new Map.Entry[0];
|
||||
private transient boolean dirty = false;
|
||||
private transient boolean dirty;
|
||||
|
||||
/**
|
||||
* Constructs a EntityEntryContext
|
||||
*/
|
||||
public EntityEntryContext() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the entity and entry to this context, associating them together
|
||||
*
|
||||
* @param entity The entity
|
||||
* @param entityEntry The entry
|
||||
*/
|
||||
public void addEntityEntry(Object entity, EntityEntry entityEntry) {
|
||||
// IMPORTANT!!!!!
|
||||
// add is called more than once of some entities. In such cases the first
|
||||
// call is simply setting up a "marker" to avoid infinite looping from reentrancy
|
||||
//
|
||||
|
||||
// any addition (even the double one described above) should invalidate the cross-ref array
|
||||
dirty = true;
|
||||
|
||||
|
@ -127,11 +135,26 @@ public class EntityEntryContext {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this entity exist in this context, associated with an EntityEntry?
|
||||
*
|
||||
* @param entity The entity to check
|
||||
*
|
||||
* @return {@code true} if it is associated with this context
|
||||
*/
|
||||
public boolean hasEntityEntry(Object entity) {
|
||||
return getEntityEntry( entity ) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated EntityEntry for the entity
|
||||
*
|
||||
* @param entity The entity to retrieve the EntityEntry for
|
||||
*
|
||||
* @return The associated EntityEntry
|
||||
*/
|
||||
public EntityEntry getEntityEntry(Object entity) {
|
||||
// essentially resolve the entity to a ManagedEntity...
|
||||
final ManagedEntity managedEntity;
|
||||
if ( ManagedEntity.class.isInstance( entity ) ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
|
@ -143,14 +166,23 @@ public class EntityEntryContext {
|
|||
managedEntity = nonEnhancedEntityXref.get( entity );
|
||||
}
|
||||
|
||||
// and get/return the EntityEntry from the ManagedEntry
|
||||
return managedEntity == null
|
||||
? null
|
||||
: managedEntity.$$_hibernate_getEntityEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entity from the context, returning the EntityEntry which was associated with it
|
||||
*
|
||||
* @param entity The entity to remove
|
||||
*
|
||||
* @return Tjee EntityEntry
|
||||
*/
|
||||
public EntityEntry removeEntityEntry(Object entity) {
|
||||
dirty = true;
|
||||
|
||||
// again, resolve the entity to a ManagedEntity (which may not be possible for non-enhanced)...
|
||||
final ManagedEntity managedEntity;
|
||||
if ( ManagedEntity.class.isInstance( entity ) ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
|
@ -162,16 +194,18 @@ public class EntityEntryContext {
|
|||
managedEntity = nonEnhancedEntityXref.remove( entity );
|
||||
}
|
||||
|
||||
// if we could not resolve it, just return (it was not associated with this context)
|
||||
if ( managedEntity == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// prepare for re-linking...
|
||||
ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
|
||||
ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
|
||||
final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
|
||||
final ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
|
||||
managedEntity.$$_hibernate_setPreviousManagedEntity( null );
|
||||
managedEntity.$$_hibernate_setNextManagedEntity( null );
|
||||
|
||||
// re-link
|
||||
count--;
|
||||
|
||||
if ( count == 0 ) {
|
||||
|
@ -203,11 +237,20 @@ public class EntityEntryContext {
|
|||
}
|
||||
}
|
||||
|
||||
EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
|
||||
// finally clean out the ManagedEntity and return the associated EntityEntry
|
||||
final EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
|
||||
managedEntity.$$_hibernate_setEntityEntry( null );
|
||||
return theEntityEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main bugaboo with IdentityMap that warranted this class in the first place.
|
||||
*
|
||||
* Return an array of all the entity/EntityEntry pairs in this context. The array is to make sure
|
||||
* that the iterators built off of it are safe from concurrency/reentrancy
|
||||
*
|
||||
* @return The safe array
|
||||
*/
|
||||
public Map.Entry<Object, EntityEntry>[] reentrantSafeEntityEntries() {
|
||||
if ( dirty ) {
|
||||
reentrantSafeEntries = new EntityEntryCrossRefImpl[count];
|
||||
|
@ -225,6 +268,9 @@ public class EntityEntryContext {
|
|||
return reentrantSafeEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this context of all managed entities
|
||||
*/
|
||||
public void clear() {
|
||||
dirty = true;
|
||||
|
||||
|
@ -250,6 +296,9 @@ public class EntityEntryContext {
|
|||
reentrantSafeEntries = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Down-grade locks to NONE for all entities in this context
|
||||
*/
|
||||
public void downgradeLocks() {
|
||||
if ( head == null ) {
|
||||
return;
|
||||
|
@ -263,6 +312,13 @@ public class EntityEntryContext {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JDK serialization hook for serializing
|
||||
*
|
||||
* @param oos The stream to write ourselves to
|
||||
*
|
||||
* @throws IOException Indicates an IO exception accessing the given stream
|
||||
*/
|
||||
public void serialize(ObjectOutputStream oos) throws IOException {
|
||||
log.tracef( "Starting serialization of [%s] EntityEntry entries", count );
|
||||
oos.writeInt( count );
|
||||
|
@ -281,7 +337,17 @@ public class EntityEntryContext {
|
|||
}
|
||||
}
|
||||
|
||||
public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPersistenceContext rtn) throws IOException, ClassNotFoundException {
|
||||
/**
|
||||
* JDK serialization hook for deserializing
|
||||
*
|
||||
* @param ois The stream to read ourselves from
|
||||
* @param rtn The persistence context we belong to
|
||||
*
|
||||
* @throws IOException Indicates an IO exception accessing the given stream
|
||||
* @throws ClassNotFoundException Problem reading stream data
|
||||
*/
|
||||
public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPersistenceContext rtn)
|
||||
throws IOException, ClassNotFoundException {
|
||||
final int count = ois.readInt();
|
||||
log.tracef( "Starting deserialization of [%s] EntityEntry entries", count );
|
||||
|
||||
|
@ -332,6 +398,9 @@ public class EntityEntryContext {
|
|||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapper for entity classes which do not implement ManagedEntity
|
||||
*/
|
||||
private static class ManagedEntityImpl implements ManagedEntity {
|
||||
private final Object entityInstance;
|
||||
private EntityEntry entityEntry;
|
||||
|
@ -378,6 +447,28 @@ public class EntityEntryContext {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in building the {@link #reentrantSafeEntityEntries()} entries
|
||||
*/
|
||||
public static interface EntityEntryCrossRef extends Map.Entry<Object,EntityEntry> {
|
||||
/**
|
||||
* The entity
|
||||
*
|
||||
* @return The entity
|
||||
*/
|
||||
public Object getEntity();
|
||||
|
||||
/**
|
||||
* The associated EntityEntry
|
||||
*
|
||||
* @return The EntityEntry associated with the entity in this context
|
||||
*/
|
||||
public EntityEntry getEntityEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the EntityEntryCrossRef interface
|
||||
*/
|
||||
private static class EntityEntryCrossRefImpl implements EntityEntryCrossRef {
|
||||
private final Object entity;
|
||||
private EntityEntry entityEntry;
|
||||
|
@ -414,9 +505,4 @@ public class EntityEntryContext {
|
|||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface EntityEntryCrossRef extends Map.Entry<Object,EntityEntry> {
|
||||
public Object getEntity();
|
||||
public EntityEntry getEntityEntry();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,15 +45,23 @@ import org.hibernate.type.Type;
|
|||
*/
|
||||
public final class ForeignKeys {
|
||||
|
||||
private ForeignKeys() {}
|
||||
|
||||
/**
|
||||
* Delegate for handling nullifying ("null"ing-out) non-cascaded associations
|
||||
*/
|
||||
public static class Nullifier {
|
||||
|
||||
private final boolean isDelete;
|
||||
private final boolean isEarlyInsert;
|
||||
private final SessionImplementor session;
|
||||
private final Object self;
|
||||
|
||||
/**
|
||||
* Constructs a Nullifier
|
||||
*
|
||||
* @param self The entity
|
||||
* @param isDelete Are we in the middle of a delete action?
|
||||
* @param isEarlyInsert Is this an early insert (INSERT generated id strategy)?
|
||||
* @param session The session
|
||||
*/
|
||||
public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SessionImplementor session) {
|
||||
this.isDelete = isDelete;
|
||||
this.isEarlyInsert = isEarlyInsert;
|
||||
|
@ -62,48 +70,51 @@ public final class ForeignKeys {
|
|||
}
|
||||
|
||||
/**
|
||||
* Nullify all references to entities that have not yet
|
||||
* been inserted in the database, where the foreign key
|
||||
* points toward that entity
|
||||
* Nullify all references to entities that have not yet been inserted in the database, where the foreign key
|
||||
* points toward that entity.
|
||||
*
|
||||
* @param values The entity attribute values
|
||||
* @param types The entity attribute types
|
||||
*/
|
||||
public void nullifyTransientReferences(final Object[] values, final Type[] types)
|
||||
throws HibernateException {
|
||||
throws HibernateException {
|
||||
for ( int i = 0; i < types.length; i++ ) {
|
||||
values[i] = nullifyTransientReferences( values[i], types[i] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null if the argument is an "unsaved" entity (ie.
|
||||
* one with no existing database row), or the input argument
|
||||
* otherwise. This is how Hibernate avoids foreign key constraint
|
||||
* violations.
|
||||
* Return null if the argument is an "unsaved" entity (ie. one with no existing database row), or the
|
||||
* input argument otherwise. This is how Hibernate avoids foreign key constraint violations.
|
||||
*
|
||||
* @param value An entity attribute value
|
||||
* @param type An entity attribute type
|
||||
*/
|
||||
private Object nullifyTransientReferences(final Object value, final Type type)
|
||||
throws HibernateException {
|
||||
throws HibernateException {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
else if ( type.isEntityType() ) {
|
||||
EntityType entityType = (EntityType) type;
|
||||
final EntityType entityType = (EntityType) type;
|
||||
if ( entityType.isOneToOne() ) {
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
String entityName = entityType.getAssociatedEntityName();
|
||||
return isNullifiable(entityName, value) ? null : value;
|
||||
final String entityName = entityType.getAssociatedEntityName();
|
||||
return isNullifiable( entityName, value ) ? null : value;
|
||||
}
|
||||
}
|
||||
else if ( type.isAnyType() ) {
|
||||
return isNullifiable(null, value) ? null : value;
|
||||
return isNullifiable( null, value ) ? null : value;
|
||||
}
|
||||
else if ( type.isComponentType() ) {
|
||||
CompositeType actype = (CompositeType) type;
|
||||
Object[] subvalues = actype.getPropertyValues(value, session);
|
||||
Type[] subtypes = actype.getSubtypes();
|
||||
final CompositeType actype = (CompositeType) type;
|
||||
final Object[] subvalues = actype.getPropertyValues( value, session );
|
||||
final Type[] subtypes = actype.getSubtypes();
|
||||
boolean substitute = false;
|
||||
for ( int i = 0; i < subvalues.length; i++ ) {
|
||||
Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] );
|
||||
final Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] );
|
||||
if ( replacement != subvalues[i] ) {
|
||||
substitute = true;
|
||||
subvalues[i] = replacement;
|
||||
|
@ -123,16 +134,21 @@ public final class ForeignKeys {
|
|||
/**
|
||||
* Determine if the object already exists in the database,
|
||||
* using a "best guess"
|
||||
*
|
||||
* @param entityName The name of the entity
|
||||
* @param object The entity instance
|
||||
*/
|
||||
private boolean isNullifiable(final String entityName, Object object)
|
||||
throws HibernateException {
|
||||
|
||||
if (object==LazyPropertyInitializer.UNFETCHED_PROPERTY) return false; //this is kinda the best we can do...
|
||||
throws HibernateException {
|
||||
if ( object == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
|
||||
// this is the best we can do...
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( object instanceof HibernateProxy ) {
|
||||
// if its an uninitialized proxy it can't be transient
|
||||
LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
|
||||
if ( li.getImplementation(session) == null ) {
|
||||
final LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
|
||||
if ( li.getImplementation( session ) == null ) {
|
||||
return false;
|
||||
// ie. we never have to null out a reference to
|
||||
// an uninitialized proxy
|
||||
|
@ -147,12 +163,8 @@ public final class ForeignKeys {
|
|||
// unless we are using native id generation, in which
|
||||
// case we definitely need to nullify
|
||||
if ( object == self ) {
|
||||
return isEarlyInsert || (
|
||||
isDelete &&
|
||||
session.getFactory()
|
||||
.getDialect()
|
||||
.hasSelfReferentialForeignKeyBug()
|
||||
);
|
||||
return isEarlyInsert
|
||||
|| ( isDelete && session.getFactory().getDialect().hasSelfReferentialForeignKeyBug() );
|
||||
}
|
||||
|
||||
// See if the entity is already bound to this session, if not look at the
|
||||
|
@ -160,12 +172,12 @@ public final class ForeignKeys {
|
|||
// id is not "unsaved" (that is, we rely on foreign keys to keep
|
||||
// database integrity)
|
||||
|
||||
EntityEntry entityEntry = session.getPersistenceContext().getEntry(object);
|
||||
if ( entityEntry==null ) {
|
||||
return isTransient(entityName, object, null, session);
|
||||
final EntityEntry entityEntry = session.getPersistenceContext().getEntry( object );
|
||||
if ( entityEntry == null ) {
|
||||
return isTransient( entityName, object, null, session );
|
||||
}
|
||||
else {
|
||||
return entityEntry.isNullifiable(isEarlyInsert, session);
|
||||
return entityEntry.isNullifiable( isEarlyInsert, session );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -174,68 +186,102 @@ public final class ForeignKeys {
|
|||
|
||||
/**
|
||||
* Is this instance persistent or detached?
|
||||
* If <tt>assumed</tt> is non-null, don't hit the database to make the
|
||||
* determination, instead assume that value; the client code must be
|
||||
* prepared to "recover" in the case that this assumed result is incorrect.
|
||||
* <p/>
|
||||
* If <tt>assumed</tt> is non-null, don't hit the database to make the determination, instead assume that
|
||||
* value; the client code must be prepared to "recover" in the case that this assumed result is incorrect.
|
||||
*
|
||||
* @param entityName The name of the entity
|
||||
* @param entity The entity instance
|
||||
* @param assumed The assumed return value, if avoiding database hit is desired
|
||||
* @param session The session
|
||||
*
|
||||
* @return {@code true} if the given entity is not transient (meaning it is either detached/persistent)
|
||||
*/
|
||||
public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session)
|
||||
throws HibernateException {
|
||||
if (entity instanceof HibernateProxy) return true;
|
||||
if ( session.getPersistenceContext().isEntryFor(entity) ) return true;
|
||||
return !isTransient(entityName, entity, assumed, session);
|
||||
@SuppressWarnings("SimplifiableIfStatement")
|
||||
public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
|
||||
if ( entity instanceof HibernateProxy ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( session.getPersistenceContext().isEntryFor( entity ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo : shouldnt assumed be revered here?
|
||||
|
||||
return !isTransient( entityName, entity, assumed, session );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this instance, which we know is not persistent, actually transient?
|
||||
* If <tt>assumed</tt> is non-null, don't hit the database to make the
|
||||
* determination, instead assume that value; the client code must be
|
||||
* prepared to "recover" in the case that this assumed result is incorrect.
|
||||
* <p/>
|
||||
* If <tt>assumed</tt> is non-null, don't hit the database to make the determination, instead assume that
|
||||
* value; the client code must be prepared to "recover" in the case that this assumed result is incorrect.
|
||||
*
|
||||
* @param entityName The name of the entity
|
||||
* @param entity The entity instance
|
||||
* @param assumed The assumed return value, if avoiding database hit is desired
|
||||
* @param session The session
|
||||
*
|
||||
* @return {@code true} if the given entity is transient (unsaved)
|
||||
*/
|
||||
public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session)
|
||||
throws HibernateException {
|
||||
|
||||
if (entity== LazyPropertyInitializer.UNFETCHED_PROPERTY) {
|
||||
@SuppressWarnings("UnnecessaryUnboxing")
|
||||
public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
|
||||
if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
|
||||
// an unfetched association can only point to
|
||||
// an entity that already exists in the db
|
||||
return false;
|
||||
}
|
||||
|
||||
// let the interceptor inspect the instance to decide
|
||||
Boolean isUnsaved = session.getInterceptor().isTransient(entity);
|
||||
if (isUnsaved!=null) return isUnsaved.booleanValue();
|
||||
Boolean isUnsaved = session.getInterceptor().isTransient( entity );
|
||||
if ( isUnsaved != null ) {
|
||||
return isUnsaved.booleanValue();
|
||||
}
|
||||
|
||||
// let the persister inspect the instance to decide
|
||||
EntityPersister persister = session.getEntityPersister(entityName, entity);
|
||||
isUnsaved = persister.isTransient(entity, session);
|
||||
if (isUnsaved!=null) return isUnsaved.booleanValue();
|
||||
final EntityPersister persister = session.getEntityPersister( entityName, entity );
|
||||
isUnsaved = persister.isTransient( entity, session );
|
||||
if ( isUnsaved != null ) {
|
||||
return isUnsaved.booleanValue();
|
||||
}
|
||||
|
||||
// we use the assumed value, if there is one, to avoid hitting
|
||||
// the database
|
||||
if (assumed!=null) return assumed.booleanValue();
|
||||
if ( assumed != null ) {
|
||||
return assumed.booleanValue();
|
||||
}
|
||||
|
||||
// hit the database, after checking the session cache for a snapshot
|
||||
Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot(
|
||||
final Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot(
|
||||
persister.getIdentifier( entity, session ),
|
||||
persister
|
||||
);
|
||||
return snapshot==null;
|
||||
return snapshot == null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identifier of the persistent or transient object, or throw
|
||||
* an exception if the instance is "unsaved"
|
||||
*
|
||||
* <p/>
|
||||
* Used by OneToOneType and ManyToOneType to determine what id value should
|
||||
* be used for an object that may or may not be associated with the session.
|
||||
* This does a "best guess" using any/all info available to use (not just the
|
||||
* EntityEntry).
|
||||
*
|
||||
* @param entityName The name of the entity
|
||||
* @param object The entity instance
|
||||
* @param session The session
|
||||
*
|
||||
* @return The identifier
|
||||
*
|
||||
* @throws TransientObjectException if the entity is transient (does not yet have an identifier)
|
||||
*/
|
||||
public static Serializable getEntityIdentifierIfNotUnsaved(
|
||||
final String entityName,
|
||||
final Object object,
|
||||
final SessionImplementor session)
|
||||
throws HibernateException {
|
||||
final SessionImplementor session) throws TransientObjectException {
|
||||
if ( object == null ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -245,10 +291,10 @@ public final class ForeignKeys {
|
|||
// context-entity-identifier returns null explicitly if the entity
|
||||
// is not associated with the persistence context; so make some
|
||||
// deeper checks...
|
||||
if ( isTransient(entityName, object, Boolean.FALSE, session) ) {
|
||||
if ( isTransient( entityName, object, Boolean.FALSE, session ) ) {
|
||||
throw new TransientObjectException(
|
||||
"object references an unsaved transient instance - save the transient instance before flushing: " +
|
||||
(entityName == null ? session.guessEntityName( object ) : entityName)
|
||||
(entityName == null ? session.guessEntityName( object ) : entityName)
|
||||
);
|
||||
}
|
||||
id = session.getEntityPersister( entityName, object ).getIdentifier( object, session );
|
||||
|
@ -265,9 +311,9 @@ public final class ForeignKeys {
|
|||
* @param entityName - the entity name
|
||||
* @param entity - the entity instance
|
||||
* @param values - insertable properties of the object (including backrefs),
|
||||
* possibly with substitutions
|
||||
* possibly with substitutions
|
||||
* @param isEarlyInsert - true if the entity needs to be executed as soon as possible
|
||||
* (e.g., to generate an ID)
|
||||
* (e.g., to generate an ID)
|
||||
* @param session - the session
|
||||
*
|
||||
* @return the transient unsaved entity dependencies that are non-nullable,
|
||||
|
@ -278,18 +324,16 @@ public final class ForeignKeys {
|
|||
Object entity,
|
||||
Object[] values,
|
||||
boolean isEarlyInsert,
|
||||
SessionImplementor session
|
||||
) {
|
||||
Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
|
||||
SessionImplementor session) {
|
||||
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
|
||||
final EntityPersister persister = session.getEntityPersister( entityName, entity );
|
||||
final String[] propertyNames = persister.getPropertyNames();
|
||||
final Type[] types = persister.getPropertyTypes();
|
||||
final boolean[] nullability = persister.getPropertyNullability();
|
||||
NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();
|
||||
final NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();
|
||||
for ( int i = 0; i < types.length; i++ ) {
|
||||
collectNonNullableTransientEntities(
|
||||
nullifier,
|
||||
i,
|
||||
values[i],
|
||||
propertyNames[i],
|
||||
types[i],
|
||||
|
@ -303,7 +347,6 @@ public final class ForeignKeys {
|
|||
|
||||
private static void collectNonNullableTransientEntities(
|
||||
Nullifier nullifier,
|
||||
int i,
|
||||
Object value,
|
||||
String propertyName,
|
||||
Type type,
|
||||
|
@ -311,33 +354,32 @@ public final class ForeignKeys {
|
|||
SessionImplementor session,
|
||||
NonNullableTransientDependencies nonNullableTransientEntities) {
|
||||
if ( value == null ) {
|
||||
return; // EARLY RETURN
|
||||
return;
|
||||
}
|
||||
if ( type.isEntityType() ) {
|
||||
EntityType entityType = (EntityType) type;
|
||||
if ( ! isNullable &&
|
||||
! entityType.isOneToOne() &&
|
||||
nullifier.isNullifiable( entityType.getAssociatedEntityName(), value ) ) {
|
||||
|
||||
if ( type.isEntityType() ) {
|
||||
final EntityType entityType = (EntityType) type;
|
||||
if ( !isNullable
|
||||
&& !entityType.isOneToOne()
|
||||
&& nullifier.isNullifiable( entityType.getAssociatedEntityName(), value ) ) {
|
||||
nonNullableTransientEntities.add( propertyName, value );
|
||||
}
|
||||
}
|
||||
else if ( type.isAnyType() ) {
|
||||
if ( ! isNullable &&
|
||||
nullifier.isNullifiable( null, value ) ) {
|
||||
if ( !isNullable && nullifier.isNullifiable( null, value ) ) {
|
||||
nonNullableTransientEntities.add( propertyName, value );
|
||||
}
|
||||
}
|
||||
else if ( type.isComponentType() ) {
|
||||
CompositeType actype = (CompositeType) type;
|
||||
boolean[] subValueNullability = actype.getPropertyNullability();
|
||||
final CompositeType actype = (CompositeType) type;
|
||||
final boolean[] subValueNullability = actype.getPropertyNullability();
|
||||
if ( subValueNullability != null ) {
|
||||
String[] subPropertyNames = actype.getPropertyNames();
|
||||
Object[] subvalues = actype.getPropertyValues(value, session);
|
||||
Type[] subtypes = actype.getSubtypes();
|
||||
final String[] subPropertyNames = actype.getPropertyNames();
|
||||
final Object[] subvalues = actype.getPropertyValues( value, session );
|
||||
final Type[] subtypes = actype.getSubtypes();
|
||||
for ( int j = 0; j < subvalues.length; j++ ) {
|
||||
collectNonNullableTransientEntities(
|
||||
nullifier,
|
||||
j,
|
||||
subvalues[j],
|
||||
subPropertyNames[j],
|
||||
subtypes[j],
|
||||
|
@ -349,4 +391,11 @@ public final class ForeignKeys {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow instantiation
|
||||
*/
|
||||
private ForeignKeys() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,9 +36,6 @@ import org.hibernate.type.AssociationType;
|
|||
* @author Gavin King
|
||||
*/
|
||||
public final class JoinHelper {
|
||||
|
||||
private JoinHelper() {}
|
||||
|
||||
/**
|
||||
* Get the aliased columns of the owning entity which are to
|
||||
* be used in the join
|
||||
|
@ -48,9 +45,8 @@ public final class JoinHelper {
|
|||
String alias,
|
||||
int property,
|
||||
OuterJoinLoadable lhsPersister,
|
||||
Mapping mapping
|
||||
) {
|
||||
return getAliasedLHSColumnNames(type, alias, property, 0, lhsPersister, mapping);
|
||||
Mapping mapping) {
|
||||
return getAliasedLHSColumnNames( type, alias, property, 0, lhsPersister, mapping );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,9 +57,8 @@ public final class JoinHelper {
|
|||
AssociationType type,
|
||||
int property,
|
||||
OuterJoinLoadable lhsPersister,
|
||||
Mapping mapping
|
||||
) {
|
||||
return getLHSColumnNames(type, property, 0, lhsPersister, mapping);
|
||||
Mapping mapping) {
|
||||
return getLHSColumnNames( type, property, 0, lhsPersister, mapping );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +85,8 @@ public final class JoinHelper {
|
|||
);
|
||||
}
|
||||
else {
|
||||
return ( (PropertyMapping) lhsPersister ).toColumns(columnQualifier, propertyName); //bad cast
|
||||
//bad cast
|
||||
return ( (PropertyMapping) lhsPersister ).toColumns( columnQualifier, propertyName );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,41 +108,57 @@ public final class JoinHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the columns of the owning entity which are to
|
||||
* be used in the join
|
||||
* Get the columns of the owning entity which are to be used in the join
|
||||
*
|
||||
* @param type The type representing the join
|
||||
* @param property The property index for the association type
|
||||
* @param begin ?
|
||||
* @param lhsPersister The persister for the left-hand-side of the join
|
||||
* @param mapping The mapping object (typically the SessionFactory)
|
||||
*
|
||||
* @return The columns for the left-hand-side of the join
|
||||
*/
|
||||
public static String[] getLHSColumnNames(
|
||||
AssociationType type,
|
||||
int property,
|
||||
int begin,
|
||||
OuterJoinLoadable lhsPersister,
|
||||
Mapping mapping
|
||||
) {
|
||||
Mapping mapping) {
|
||||
if ( type.useLHSPrimaryKey() ) {
|
||||
//return lhsPersister.getSubclassPropertyColumnNames(property);
|
||||
return lhsPersister.getIdentifierColumnNames();
|
||||
}
|
||||
else {
|
||||
String propertyName = type.getLHSPropertyName();
|
||||
if (propertyName==null) {
|
||||
final String propertyName = type.getLHSPropertyName();
|
||||
if ( propertyName == null ) {
|
||||
//slice, to get the columns for this component
|
||||
//property
|
||||
return ArrayHelper.slice(
|
||||
property < 0
|
||||
? lhsPersister.getIdentifierColumnNames()
|
||||
: lhsPersister.getSubclassPropertyColumnNames(property),
|
||||
: lhsPersister.getSubclassPropertyColumnNames( property ),
|
||||
begin,
|
||||
type.getColumnSpan(mapping)
|
||||
);
|
||||
type.getColumnSpan( mapping )
|
||||
);
|
||||
}
|
||||
else {
|
||||
//property-refs for associations defined on a
|
||||
//component are not supported, so no need to slice
|
||||
return lhsPersister.getPropertyColumnNames(propertyName);
|
||||
return lhsPersister.getPropertyColumnNames( propertyName );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the name of the table that is the left-hand-side of the join. Usually this is the
|
||||
* name of the main table from the left-hand-side persister. But that is not the case with property-refs.
|
||||
*
|
||||
* @param type The type representing the join
|
||||
* @param propertyIndex The property index for the type
|
||||
* @param lhsPersister The persister for the left-hand-side of the join
|
||||
*
|
||||
* @return The table name
|
||||
*/
|
||||
public static String getLHSTableName(
|
||||
AssociationType type,
|
||||
int propertyIndex,
|
||||
|
@ -155,17 +167,17 @@ public final class JoinHelper {
|
|||
return lhsPersister.getTableName();
|
||||
}
|
||||
else {
|
||||
String propertyName = type.getLHSPropertyName();
|
||||
if (propertyName==null) {
|
||||
final String propertyName = type.getLHSPropertyName();
|
||||
if ( propertyName == null ) {
|
||||
//if there is no property-ref, assume the join
|
||||
//is to the subclass table (ie. the table of the
|
||||
//subclass that the association belongs to)
|
||||
return lhsPersister.getSubclassPropertyTableName(propertyIndex);
|
||||
return lhsPersister.getSubclassPropertyTableName( propertyIndex );
|
||||
}
|
||||
else {
|
||||
//handle a property-ref
|
||||
String propertyRefTable = lhsPersister.getPropertyTableName(propertyName);
|
||||
if (propertyRefTable==null) {
|
||||
String propertyRefTable = lhsPersister.getPropertyTableName( propertyName );
|
||||
if ( propertyRefTable == null ) {
|
||||
//it is possible that the tree-walking in OuterJoinLoader can get to
|
||||
//an association defined by a subclass, in which case the property-ref
|
||||
//might refer to a property defined on a subclass of the current class
|
||||
|
@ -173,7 +185,7 @@ public final class JoinHelper {
|
|||
//assumes that the property-ref refers to a property of the subclass
|
||||
//table that the association belongs to (a reasonable guess)
|
||||
//TODO: fix this, add: OuterJoinLoadable.getSubclassPropertyTableName(String propertyName)
|
||||
propertyRefTable = lhsPersister.getSubclassPropertyTableName(propertyIndex);
|
||||
propertyRefTable = lhsPersister.getSubclassPropertyTableName( propertyIndex );
|
||||
}
|
||||
return propertyRefTable;
|
||||
}
|
||||
|
@ -181,17 +193,24 @@ public final class JoinHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the columns of the associated table which are to
|
||||
* be used in the join
|
||||
* Get the columns of the associated table which are to be used in the join
|
||||
*
|
||||
* @param type The type
|
||||
* @param factory The SessionFactory
|
||||
*
|
||||
* @return The columns for the right-hand-side of the join
|
||||
*/
|
||||
public static String[] getRHSColumnNames(AssociationType type, SessionFactoryImplementor factory) {
|
||||
String uniqueKeyPropertyName = type.getRHSUniqueKeyPropertyName();
|
||||
Joinable joinable = type.getAssociatedJoinable(factory);
|
||||
if (uniqueKeyPropertyName==null) {
|
||||
final String uniqueKeyPropertyName = type.getRHSUniqueKeyPropertyName();
|
||||
final Joinable joinable = type.getAssociatedJoinable( factory );
|
||||
if ( uniqueKeyPropertyName == null ) {
|
||||
return joinable.getKeyColumnNames();
|
||||
}
|
||||
else {
|
||||
return ( (OuterJoinLoadable) joinable ).getPropertyColumnNames(uniqueKeyPropertyName);
|
||||
return ( (OuterJoinLoadable) joinable ).getPropertyColumnNames( uniqueKeyPropertyName );
|
||||
}
|
||||
}
|
||||
|
||||
private JoinHelper() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ package org.hibernate.engine.internal;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -39,46 +38,368 @@ import org.hibernate.sql.QueryJoinFragment;
|
|||
import org.hibernate.type.AssociationType;
|
||||
|
||||
/**
|
||||
* A sequence of {@link Join} delegates to make it "easier" to work with joins. The "easier" part is obviously
|
||||
* subjective ;)
|
||||
* <p/>
|
||||
* Additionally JoinSequence is a directed graph of other JoinSequence instances, as represented by the
|
||||
* {@link #next} ({@link #setNext(JoinSequence)}) pointer.
|
||||
*
|
||||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
*
|
||||
* @see JoinFragment
|
||||
*/
|
||||
public class JoinSequence {
|
||||
|
||||
private final SessionFactoryImplementor factory;
|
||||
private final List<Join> joins = new ArrayList<Join>();
|
||||
private boolean useThetaStyle = false;
|
||||
|
||||
private final StringBuilder conditions = new StringBuilder();
|
||||
private final List<Join> joins = new ArrayList<Join>();
|
||||
|
||||
private boolean useThetaStyle;
|
||||
private String rootAlias;
|
||||
private Joinable rootJoinable;
|
||||
private Selector selector;
|
||||
private JoinSequence next;
|
||||
private boolean isFromPart = false;
|
||||
private boolean isFromPart;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append( "JoinSequence{" );
|
||||
if ( rootJoinable != null ) {
|
||||
buf.append( rootJoinable )
|
||||
.append( '[' )
|
||||
.append( rootAlias )
|
||||
.append( ']' );
|
||||
}
|
||||
for ( int i = 0; i < joins.size(); i++ ) {
|
||||
buf.append( "->" ).append( joins.get( i ) );
|
||||
}
|
||||
return buf.append( '}' ).toString();
|
||||
/**
|
||||
* Constructs a JoinSequence
|
||||
*
|
||||
* @param factory The SessionFactory
|
||||
*/
|
||||
public JoinSequence(SessionFactoryImplementor factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public final class Join {
|
||||
/**
|
||||
* Retrieve a JoinSequence that represents just the FROM clause parts
|
||||
*
|
||||
* @return The JoinSequence that represents just the FROM clause parts
|
||||
*/
|
||||
public JoinSequence getFromPart() {
|
||||
final JoinSequence fromPart = new JoinSequence( factory );
|
||||
fromPart.joins.addAll( this.joins );
|
||||
fromPart.useThetaStyle = this.useThetaStyle;
|
||||
fromPart.rootAlias = this.rootAlias;
|
||||
fromPart.rootJoinable = this.rootJoinable;
|
||||
fromPart.selector = this.selector;
|
||||
fromPart.next = this.next == null ? null : this.next.getFromPart();
|
||||
fromPart.isFromPart = true;
|
||||
return fromPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a full, although shallow, copy.
|
||||
*
|
||||
* @return The copy
|
||||
*/
|
||||
public JoinSequence copy() {
|
||||
final JoinSequence copy = new JoinSequence( factory );
|
||||
copy.joins.addAll( this.joins );
|
||||
copy.useThetaStyle = this.useThetaStyle;
|
||||
copy.rootAlias = this.rootAlias;
|
||||
copy.rootJoinable = this.rootJoinable;
|
||||
copy.selector = this.selector;
|
||||
copy.next = this.next == null ? null : this.next.copy();
|
||||
copy.isFromPart = this.isFromPart;
|
||||
copy.conditions.append( this.conditions.toString() );
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a join to this sequence
|
||||
*
|
||||
* @param associationType The type of the association representing the join
|
||||
* @param alias The RHS alias for the join
|
||||
* @param joinType The type of join (INNER, etc)
|
||||
* @param referencingKey The LHS columns for the join condition
|
||||
*
|
||||
* @return The Join memento
|
||||
*
|
||||
* @throws MappingException Generally indicates a problem resolving the associationType to a {@link Joinable}
|
||||
*/
|
||||
public JoinSequence addJoin(
|
||||
AssociationType associationType,
|
||||
String alias,
|
||||
JoinType joinType,
|
||||
String[] referencingKey) throws MappingException {
|
||||
joins.add( new Join( factory, associationType, alias, joinType, referencingKey ) );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JoinFragment
|
||||
*
|
||||
* @return The JoinFragment
|
||||
*
|
||||
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
|
||||
*/
|
||||
public JoinFragment toJoinFragment() throws MappingException {
|
||||
return toJoinFragment( Collections.EMPTY_MAP, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JoinFragment
|
||||
*
|
||||
* @param enabledFilters The filters associated with the originating session to properly define join conditions
|
||||
* @param includeExtraJoins Should {@link #addExtraJoins} to called. Honestly I do not understand the full
|
||||
* ramifications of this argument
|
||||
*
|
||||
* @return The JoinFragment
|
||||
*
|
||||
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
|
||||
*/
|
||||
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeExtraJoins) throws MappingException {
|
||||
return toJoinFragment( enabledFilters, includeExtraJoins, null, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JoinFragment
|
||||
*
|
||||
* @param enabledFilters The filters associated with the originating session to properly define join conditions
|
||||
* @param includeExtraJoins Should {@link #addExtraJoins} to called. Honestly I do not understand the full
|
||||
* ramifications of this argument
|
||||
* @param withClauseFragment The with clause (which represents additional join restrictions) fragment
|
||||
* @param withClauseJoinAlias The
|
||||
*
|
||||
* @return The JoinFragment
|
||||
*
|
||||
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
|
||||
*/
|
||||
public JoinFragment toJoinFragment(
|
||||
Map enabledFilters,
|
||||
boolean includeExtraJoins,
|
||||
String withClauseFragment,
|
||||
String withClauseJoinAlias) throws MappingException {
|
||||
final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
|
||||
if ( rootJoinable != null ) {
|
||||
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
|
||||
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters );
|
||||
// JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it
|
||||
// can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track
|
||||
// of that fact.
|
||||
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
|
||||
if ( includeExtraJoins ) {
|
||||
//TODO: not quite sure about the full implications of this!
|
||||
addExtraJoins( joinFragment, rootAlias, rootJoinable, true );
|
||||
}
|
||||
}
|
||||
|
||||
Joinable last = rootJoinable;
|
||||
|
||||
for ( Join join : joins ) {
|
||||
final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters );
|
||||
String condition;
|
||||
if ( last != null
|
||||
&& isManyToManyRoot( last )
|
||||
&& ((QueryableCollection) last).getElementType() == join.getAssociationType() ) {
|
||||
// the current join represents the join between a many-to-many association table
|
||||
// and its "target" table. Here we need to apply any additional filters
|
||||
// defined specifically on the many-to-many
|
||||
final String manyToManyFilter = ( (QueryableCollection) last ).getManyToManyFilterFragment(
|
||||
join.getAlias(),
|
||||
enabledFilters
|
||||
);
|
||||
condition = "".equals( manyToManyFilter )
|
||||
? on
|
||||
: "".equals( on ) ? manyToManyFilter : on + " and " + manyToManyFilter;
|
||||
}
|
||||
else {
|
||||
condition = on;
|
||||
}
|
||||
|
||||
if ( withClauseFragment != null ) {
|
||||
if ( join.getAlias().equals( withClauseJoinAlias ) ) {
|
||||
condition += " and " + withClauseFragment;
|
||||
}
|
||||
}
|
||||
|
||||
joinFragment.addJoin(
|
||||
join.getJoinable().getTableName(),
|
||||
join.getAlias(),
|
||||
join.getLHSColumns(),
|
||||
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
|
||||
join.joinType,
|
||||
condition
|
||||
);
|
||||
|
||||
//TODO: not quite sure about the full implications of this!
|
||||
if ( includeExtraJoins ) {
|
||||
addExtraJoins(
|
||||
joinFragment,
|
||||
join.getAlias(),
|
||||
join.getJoinable(),
|
||||
join.joinType == JoinType.INNER_JOIN
|
||||
);
|
||||
}
|
||||
last = join.getJoinable();
|
||||
}
|
||||
|
||||
if ( next != null ) {
|
||||
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeExtraJoins ) );
|
||||
}
|
||||
|
||||
joinFragment.addCondition( conditions.toString() );
|
||||
|
||||
if ( isFromPart ) {
|
||||
joinFragment.clearWherePart();
|
||||
}
|
||||
|
||||
return joinFragment;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SimplifiableIfStatement")
|
||||
private boolean isManyToManyRoot(Joinable joinable) {
|
||||
if ( joinable != null && joinable.isCollection() ) {
|
||||
return ( (QueryableCollection) joinable ).isManyToMany();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addExtraJoins(JoinFragment joinFragment, String alias, Joinable joinable, boolean innerJoin) {
|
||||
final boolean include = isIncluded( alias );
|
||||
joinFragment.addJoins(
|
||||
joinable.fromJoinFragment( alias, innerJoin, include ),
|
||||
joinable.whereJoinFragment( alias, innerJoin, include )
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isIncluded(String alias) {
|
||||
return selector != null && selector.includeSubclasses( alias );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a condition to this sequence.
|
||||
*
|
||||
* @param condition The condition
|
||||
*
|
||||
* @return {@link this}, for method chaining
|
||||
*/
|
||||
public JoinSequence addCondition(String condition) {
|
||||
if ( condition.trim().length() != 0 ) {
|
||||
if ( !condition.startsWith( " and " ) ) {
|
||||
conditions.append( " and " );
|
||||
}
|
||||
conditions.append( condition );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a condition to this sequence. Typical usage here might be:
|
||||
* <pre>
|
||||
* addCondition( "a", {"c1", "c2"}, "?" )
|
||||
* </pre>
|
||||
* to represent:
|
||||
* <pre>
|
||||
* "... a.c1 = ? and a.c2 = ? ..."
|
||||
* </pre>
|
||||
*
|
||||
* @param alias The alias to apply to the columns
|
||||
* @param columns The columns to add checks for
|
||||
* @param condition The conditions to check against the columns
|
||||
*
|
||||
* @return {@link this}, for method chaining
|
||||
*/
|
||||
public JoinSequence addCondition(String alias, String[] columns, String condition) {
|
||||
for ( String column : columns ) {
|
||||
conditions.append( " and " )
|
||||
.append( alias )
|
||||
.append( '.' )
|
||||
.append( column )
|
||||
.append( condition );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root of this JoinSequence. In SQL terms, this would be the driving table.
|
||||
*
|
||||
* @param joinable The entity/collection that is the root of this JoinSequence
|
||||
* @param alias The alias associated with that joinable.
|
||||
*
|
||||
* @return {@link this}, for method chaining
|
||||
*/
|
||||
public JoinSequence setRoot(Joinable joinable, String alias) {
|
||||
this.rootAlias = alias;
|
||||
this.rootJoinable = joinable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the next join sequence
|
||||
*
|
||||
* @param next The next JoinSequence in the directed graph
|
||||
*
|
||||
* @return {@code this}, for method chaining
|
||||
*/
|
||||
public JoinSequence setNext(JoinSequence next) {
|
||||
this.next = next;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Selector to use to determine how subclass joins should be applied.
|
||||
*
|
||||
* @param selector The selector to apply
|
||||
*
|
||||
* @return {@code this}, for method chaining
|
||||
*/
|
||||
public JoinSequence setSelector(Selector selector) {
|
||||
this.selector = selector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this JoinSequence use theta-style joining (both a FROM and WHERE component) in the rendered SQL?
|
||||
*
|
||||
* @param useThetaStyle {@code true} indicates that theta-style joins should be used.
|
||||
*
|
||||
* @return {@code this}, for method chaining
|
||||
*/
|
||||
public JoinSequence setUseThetaStyle(boolean useThetaStyle) {
|
||||
this.useThetaStyle = useThetaStyle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isThetaStyle() {
|
||||
return useThetaStyle;
|
||||
}
|
||||
|
||||
public Join getFirstJoin() {
|
||||
return joins.get( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass join selector
|
||||
*/
|
||||
public static interface Selector {
|
||||
/**
|
||||
* Should subclasses be included in the rendered join sequence?
|
||||
*
|
||||
* @param alias The alias
|
||||
*
|
||||
* @return {@code true} if the subclass joins should be included
|
||||
*/
|
||||
public boolean includeSubclasses(String alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a join
|
||||
*/
|
||||
public static final class Join {
|
||||
private final AssociationType associationType;
|
||||
private final Joinable joinable;
|
||||
private final JoinType joinType;
|
||||
private final String alias;
|
||||
private final String[] lhsColumns;
|
||||
|
||||
Join(AssociationType associationType, String alias, JoinType joinType, String[] lhsColumns)
|
||||
throws MappingException {
|
||||
Join(
|
||||
SessionFactoryImplementor factory,
|
||||
AssociationType associationType,
|
||||
String alias,
|
||||
JoinType joinType,
|
||||
String[] lhsColumns) throws MappingException {
|
||||
this.associationType = associationType;
|
||||
this.joinable = associationType.getAssociatedJoinable( factory );
|
||||
this.alias = alias;
|
||||
|
@ -107,195 +428,24 @@ public class JoinSequence {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public String toString() {
|
||||
return joinable.toString() + '[' + alias + ']';
|
||||
}
|
||||
}
|
||||
|
||||
public JoinSequence(SessionFactoryImplementor factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public JoinSequence getFromPart() {
|
||||
JoinSequence fromPart = new JoinSequence( factory );
|
||||
fromPart.joins.addAll( this.joins );
|
||||
fromPart.useThetaStyle = this.useThetaStyle;
|
||||
fromPart.rootAlias = this.rootAlias;
|
||||
fromPart.rootJoinable = this.rootJoinable;
|
||||
fromPart.selector = this.selector;
|
||||
fromPart.next = this.next == null ? null : this.next.getFromPart();
|
||||
fromPart.isFromPart = true;
|
||||
return fromPart;
|
||||
}
|
||||
|
||||
public JoinSequence copy() {
|
||||
JoinSequence copy = new JoinSequence( factory );
|
||||
copy.joins.addAll( this.joins );
|
||||
copy.useThetaStyle = this.useThetaStyle;
|
||||
copy.rootAlias = this.rootAlias;
|
||||
copy.rootJoinable = this.rootJoinable;
|
||||
copy.selector = this.selector;
|
||||
copy.next = this.next == null ? null : this.next.copy();
|
||||
copy.isFromPart = this.isFromPart;
|
||||
copy.conditions.append( this.conditions.toString() );
|
||||
return copy;
|
||||
}
|
||||
|
||||
public JoinSequence addJoin(AssociationType associationType, String alias, JoinType joinType, String[] referencingKey)
|
||||
throws MappingException {
|
||||
joins.add( new Join( associationType, alias, joinType, referencingKey ) );
|
||||
return this;
|
||||
}
|
||||
|
||||
public JoinFragment toJoinFragment() throws MappingException {
|
||||
return toJoinFragment( Collections.EMPTY_MAP, true );
|
||||
}
|
||||
|
||||
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeExtraJoins) throws MappingException {
|
||||
return toJoinFragment( enabledFilters, includeExtraJoins, null, null );
|
||||
}
|
||||
|
||||
public JoinFragment toJoinFragment(
|
||||
Map enabledFilters,
|
||||
boolean includeExtraJoins,
|
||||
String withClauseFragment,
|
||||
String withClauseJoinAlias) throws MappingException {
|
||||
QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
buf.append( "JoinSequence{" );
|
||||
if ( rootJoinable != null ) {
|
||||
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
|
||||
String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters );
|
||||
// JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it
|
||||
// can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track
|
||||
// of that fact.
|
||||
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
|
||||
if (includeExtraJoins) { //TODO: not quite sure about the full implications of this!
|
||||
addExtraJoins( joinFragment, rootAlias, rootJoinable, true );
|
||||
}
|
||||
buf.append( rootJoinable )
|
||||
.append( '[' )
|
||||
.append( rootAlias )
|
||||
.append( ']' );
|
||||
}
|
||||
|
||||
Joinable last = rootJoinable;
|
||||
|
||||
for ( Join join: joins ) {
|
||||
String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters );
|
||||
String condition = null;
|
||||
if ( last != null &&
|
||||
isManyToManyRoot( last ) &&
|
||||
( ( QueryableCollection ) last ).getElementType() == join.getAssociationType() ) {
|
||||
// the current join represents the join between a many-to-many association table
|
||||
// and its "target" table. Here we need to apply any additional filters
|
||||
// defined specifically on the many-to-many
|
||||
String manyToManyFilter = ( ( QueryableCollection ) last )
|
||||
.getManyToManyFilterFragment( join.getAlias(), enabledFilters );
|
||||
condition = "".equals( manyToManyFilter )
|
||||
? on
|
||||
: "".equals( on )
|
||||
? manyToManyFilter
|
||||
: on + " and " + manyToManyFilter;
|
||||
}
|
||||
else {
|
||||
condition = on;
|
||||
}
|
||||
if ( withClauseFragment != null ) {
|
||||
if ( join.getAlias().equals( withClauseJoinAlias ) ) {
|
||||
condition += " and " + withClauseFragment;
|
||||
}
|
||||
}
|
||||
joinFragment.addJoin(
|
||||
join.getJoinable().getTableName(),
|
||||
join.getAlias(),
|
||||
join.getLHSColumns(),
|
||||
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
|
||||
join.joinType,
|
||||
condition
|
||||
);
|
||||
if (includeExtraJoins) { //TODO: not quite sure about the full implications of this!
|
||||
addExtraJoins( joinFragment, join.getAlias(), join.getJoinable(), join.joinType == JoinType.INNER_JOIN );
|
||||
}
|
||||
last = join.getJoinable();
|
||||
for ( Join join : joins ) {
|
||||
buf.append( "->" ).append( join );
|
||||
}
|
||||
if ( next != null ) {
|
||||
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeExtraJoins ) );
|
||||
}
|
||||
joinFragment.addCondition( conditions.toString() );
|
||||
if ( isFromPart ) joinFragment.clearWherePart();
|
||||
return joinFragment;
|
||||
}
|
||||
|
||||
private boolean isManyToManyRoot(Joinable joinable) {
|
||||
if ( joinable != null && joinable.isCollection() ) {
|
||||
QueryableCollection persister = ( QueryableCollection ) joinable;
|
||||
return persister.isManyToMany();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isIncluded(String alias) {
|
||||
return selector != null && selector.includeSubclasses( alias );
|
||||
}
|
||||
|
||||
private void addExtraJoins(JoinFragment joinFragment, String alias, Joinable joinable, boolean innerJoin) {
|
||||
boolean include = isIncluded( alias );
|
||||
joinFragment.addJoins( joinable.fromJoinFragment( alias, innerJoin, include ),
|
||||
joinable.whereJoinFragment( alias, innerJoin, include ) );
|
||||
}
|
||||
|
||||
public JoinSequence addCondition(String condition) {
|
||||
if ( condition.trim().length() != 0 ) {
|
||||
if ( !condition.startsWith( " and " ) ) conditions.append( " and " );
|
||||
conditions.append( condition );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public JoinSequence addCondition(String alias, String[] columns, String condition) {
|
||||
for ( int i = 0; i < columns.length; i++ ) {
|
||||
conditions.append( " and " )
|
||||
.append( alias )
|
||||
.append( '.' )
|
||||
.append( columns[i] )
|
||||
.append( condition );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public JoinSequence setRoot(Joinable joinable, String alias) {
|
||||
this.rootAlias = alias;
|
||||
this.rootJoinable = joinable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JoinSequence setNext(JoinSequence next) {
|
||||
this.next = next;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JoinSequence setSelector(Selector s) {
|
||||
this.selector = s;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JoinSequence setUseThetaStyle(boolean useThetaStyle) {
|
||||
this.useThetaStyle = useThetaStyle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isThetaStyle() {
|
||||
return useThetaStyle;
|
||||
}
|
||||
|
||||
public int getJoinCount() {
|
||||
return joins.size();
|
||||
}
|
||||
|
||||
public Iterator iterateJoins() {
|
||||
return joins.iterator();
|
||||
}
|
||||
|
||||
public Join getFirstJoin() {
|
||||
return joins.get( 0 );
|
||||
}
|
||||
|
||||
public static interface Selector {
|
||||
public boolean includeSubclasses(String alias);
|
||||
return buf.append( '}' ).toString();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue