HHH-8159 - Apply fixups indicated by analysis tools

This commit is contained in:
Steve Ebersole 2013-04-26 14:10:54 -05:00
parent fc02da1c12
commit 021401835c
5 changed files with 753 additions and 443 deletions

View File

@ -47,29 +47,29 @@
* @author Gavin King * @author Gavin King
*/ */
public final class Collections { public final class Collections {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, Collections.class.getName()); private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
private Collections() { Collections.class.getName()
} );
/** /**
* record the fact that this collection was dereferenced * record the fact that this collection was dereferenced
* *
* @param coll The collection to be updated by un-reachability. * @param coll The collection to be updated by un-reachability.
* @param session The session
*/ */
@SuppressWarnings( {"JavaDoc"})
public static void processUnreachableCollection(PersistentCollection coll, SessionImplementor session) { public static void processUnreachableCollection(PersistentCollection coll, SessionImplementor session) {
if ( coll.getOwner()==null ) { if ( coll.getOwner()==null ) {
processNeverReferencedCollection(coll, session); processNeverReferencedCollection( coll, session );
} }
else { else {
processDereferencedCollection(coll, session); processDereferencedCollection( coll, session );
} }
} }
private static void processDereferencedCollection(PersistentCollection coll, SessionImplementor session) { private static void processDereferencedCollection(PersistentCollection coll, SessionImplementor session) {
final PersistenceContext persistenceContext = session.getPersistenceContext(); final PersistenceContext persistenceContext = session.getPersistenceContext();
CollectionEntry entry = persistenceContext.getCollectionEntry(coll); final CollectionEntry entry = persistenceContext.getCollectionEntry( coll );
final CollectionPersister loadedPersister = entry.getLoadedPersister(); final CollectionPersister loadedPersister = entry.getLoadedPersister();
if ( loadedPersister != null && LOG.isDebugEnabled() ) { if ( loadedPersister != null && LOG.isDebugEnabled() ) {
@ -82,15 +82,15 @@ private static void processDereferencedCollection(PersistentCollection coll, Ses
} }
// do a check // do a check
boolean hasOrphanDelete = loadedPersister != null && loadedPersister.hasOrphanDelete(); final boolean hasOrphanDelete = loadedPersister != null && loadedPersister.hasOrphanDelete();
if (hasOrphanDelete) { if ( hasOrphanDelete ) {
Serializable ownerId = loadedPersister.getOwnerEntityPersister().getIdentifier( coll.getOwner(), session ); Serializable ownerId = loadedPersister.getOwnerEntityPersister().getIdentifier( coll.getOwner(), session );
if ( ownerId == null ) { if ( ownerId == null ) {
// the owning entity may have been deleted and its identifier unset due to // 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 // identifier-rollback; in which case, try to look up its identifier from
// the persistence context // the persistence context
if ( session.getFactory().getSettings().isIdentifierRollbackEnabled() ) { if ( session.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() ); final EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
if ( ownerEntry != null ) { if ( ownerEntry != null ) {
ownerId = ownerEntry.getId(); ownerId = ownerEntry.getId();
} }
@ -99,15 +99,15 @@ private static void processDereferencedCollection(PersistentCollection coll, Ses
throw new AssertionFailure( "Unable to determine collection owner identifier for orphan-delete processing" ); throw new AssertionFailure( "Unable to determine collection owner identifier for orphan-delete processing" );
} }
} }
EntityKey key = session.generateEntityKey( ownerId, loadedPersister.getOwnerEntityPersister() ); final EntityKey key = session.generateEntityKey( ownerId, loadedPersister.getOwnerEntityPersister() );
Object owner = persistenceContext.getEntity(key); final Object owner = persistenceContext.getEntity( key );
if ( owner == null ) { if ( owner == null ) {
throw new AssertionFailure( throw new AssertionFailure(
"collection owner not associated with session: " + "collection owner not associated with session: " +
loadedPersister.getRole() 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 //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 ) { if ( e != null && e.getStatus() != Status.DELETED && e.getStatus() != Status.GONE ) {
throw new HibernateException( throw new HibernateException(
@ -118,23 +118,27 @@ private static void processDereferencedCollection(PersistentCollection coll, Ses
} }
// do the work // do the work
entry.setCurrentPersister(null); entry.setCurrentPersister( null );
entry.setCurrentKey(null); entry.setCurrentKey( null );
prepareCollectionForUpdate( coll, entry, session.getFactory() ); prepareCollectionForUpdate( coll, entry, session.getFactory() );
} }
private static void processNeverReferencedCollection(PersistentCollection coll, SessionImplementor session) private static void processNeverReferencedCollection(PersistentCollection coll, SessionImplementor session)
throws HibernateException { throws HibernateException {
final PersistenceContext persistenceContext = session.getPersistenceContext(); final PersistenceContext persistenceContext = session.getPersistenceContext();
CollectionEntry entry = persistenceContext.getCollectionEntry(coll); final CollectionEntry entry = persistenceContext.getCollectionEntry( coll );
if ( LOG.isDebugEnabled() ) { if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Found collection with unloaded owner: %s", LOG.debugf(
"Found collection with unloaded owner: %s",
MessageHelper.collectionInfoString( MessageHelper.collectionInfoString(
entry.getLoadedPersister(), coll, entry.getLoadedPersister(),
entry.getLoadedKey(), session ) ); coll,
entry.getLoadedKey(),
session
)
);
} }
entry.setCurrentPersister( entry.getLoadedPersister() ); entry.setCurrentPersister( entry.getLoadedPersister() );
@ -154,13 +158,11 @@ private static void processNeverReferencedCollection(PersistentCollection coll,
*/ */
public static void processReachableCollection( public static void processReachableCollection(
PersistentCollection collection, PersistentCollection collection,
CollectionType type, CollectionType type,
Object entity, Object entity,
SessionImplementor session) { SessionImplementor session) {
collection.setOwner( entity );
collection.setOwner(entity); final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( collection );
CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(collection);
if ( ce == null ) { if ( ce == null ) {
// refer to comment in StatefulPersistenceContext.addCollection() // refer to comment in StatefulPersistenceContext.addCollection()
@ -175,30 +177,35 @@ public static void processReachableCollection(
if ( ce.isReached() ) { if ( ce.isReached() ) {
// We've been here before // We've been here before
throw new HibernateException( throw new HibernateException(
"Found shared references to a collection: " + "Found shared references to a collection: " + type.getRole()
type.getRole()
); );
} }
ce.setReached(true); ce.setReached( true );
SessionFactoryImplementor factory = session.getFactory(); final SessionFactoryImplementor factory = session.getFactory();
CollectionPersister persister = factory.getCollectionPersister( type.getRole() ); final CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
ce.setCurrentPersister(persister); ce.setCurrentPersister( persister );
ce.setCurrentKey( type.getKeyOfOwner(entity, session) ); //TODO: better to pass the id in as an argument? //TODO: better to pass the id in as an argument?
ce.setCurrentKey( type.getKeyOfOwner( entity, session ) );
if (LOG.isDebugEnabled()) { if ( LOG.isDebugEnabled() ) {
if (collection.wasInitialized()) LOG.debugf("Collection found: %s, was: %s (initialized)", if ( collection.wasInitialized() ) {
MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session), LOG.debugf(
MessageHelper.collectionInfoString(ce.getLoadedPersister(), collection, "Collection found: %s, was: %s (initialized)",
ce.getLoadedKey(), MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
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)); 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 ); prepareCollectionForUpdate( collection, ce, factory );
} }
/** /**
@ -209,9 +216,8 @@ public static void processReachableCollection(
@SuppressWarnings( {"JavaDoc"}) @SuppressWarnings( {"JavaDoc"})
private static void prepareCollectionForUpdate( private static void prepareCollectionForUpdate(
PersistentCollection collection, PersistentCollection collection,
CollectionEntry entry, CollectionEntry entry,
SessionFactoryImplementor factory) { SessionFactoryImplementor factory) {
if ( entry.isProcessed() ) { if ( entry.isProcessed() ) {
throw new AssertionFailure( "collection was processed twice by flush()" ); throw new AssertionFailure( "collection was processed twice by flush()" );
} }
@ -219,37 +225,33 @@ private static void prepareCollectionForUpdate(
final CollectionPersister loadedPersister = entry.getLoadedPersister(); final CollectionPersister loadedPersister = entry.getLoadedPersister();
final CollectionPersister currentPersister = entry.getCurrentPersister(); 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, // if either its role changed, or its key changed
!currentPersister final boolean ownerChanged = loadedPersister != currentPersister
.getKeyType().isEqual( // or its key changed || !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory );
entry.getLoadedKey(),
entry.getCurrentKey(),
factory
);
if (ownerChanged) {
if ( ownerChanged ) {
// do a check // do a check
final boolean orphanDeleteAndRoleChanged = loadedPersister != null && final boolean orphanDeleteAndRoleChanged =
currentPersister != null && loadedPersister != null && currentPersister != null && loadedPersister.hasOrphanDelete();
loadedPersister.hasOrphanDelete();
if (orphanDeleteAndRoleChanged) { if (orphanDeleteAndRoleChanged) {
throw new HibernateException( throw new HibernateException(
"Don't change the reference to a collection with cascade=\"all-delete-orphan\": " + "Don't change the reference to a collection with delete-orphan enabled : "
loadedPersister.getRole() + loadedPersister.getRole()
); );
} }
// do the work // do the work
if ( currentPersister != null ) { if ( currentPersister != null ) {
entry.setDorecreate( true ); // we will need to create new entries entry.setDorecreate( true );
} }
if ( loadedPersister != null ) { 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() ) { if ( entry.isDorecreate() ) {
LOG.trace( "Forcing collection initialization" ); LOG.trace( "Forcing collection initialization" );
collection.forceInitialization(); collection.forceInitialization();
@ -260,8 +262,12 @@ else if ( collection.isDirty() ) {
// the collection's elements have changed // the collection's elements have changed
entry.setDoupdate( true ); entry.setDoupdate( true );
} }
} }
}
/**
* Disallow instantiation
*/
private Collections() {
} }
} }

View File

@ -31,7 +31,6 @@
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.hibernate.AssertionFailure;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.ManagedEntity;
@ -56,22 +55,31 @@ public class EntityEntryContext {
private transient ManagedEntity head; private transient ManagedEntity head;
private transient ManagedEntity tail; private transient ManagedEntity tail;
private transient int count = 0; private transient int count;
private transient IdentityHashMap<Object,ManagedEntity> nonEnhancedEntityXref; private transient IdentityHashMap<Object,ManagedEntity> nonEnhancedEntityXref;
@SuppressWarnings( {"unchecked"}) @SuppressWarnings( {"unchecked"})
private transient Map.Entry<Object,EntityEntry>[] reentrantSafeEntries = new Map.Entry[0]; 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() { 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) { public void addEntityEntry(Object entity, EntityEntry entityEntry) {
// IMPORTANT!!!!! // IMPORTANT!!!!!
// add is called more than once of some entities. In such cases the first // 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 // 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 // any addition (even the double one described above) should invalidate the cross-ref array
dirty = true; dirty = true;
@ -127,11 +135,26 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) {
} }
} }
/**
* 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) { public boolean hasEntityEntry(Object entity) {
return getEntityEntry( entity ) != null; 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) { public EntityEntry getEntityEntry(Object entity) {
// essentially resolve the entity to a ManagedEntity...
final ManagedEntity managedEntity; final ManagedEntity managedEntity;
if ( ManagedEntity.class.isInstance( entity ) ) { if ( ManagedEntity.class.isInstance( entity ) ) {
managedEntity = (ManagedEntity) entity; managedEntity = (ManagedEntity) entity;
@ -143,14 +166,23 @@ else if ( nonEnhancedEntityXref == null ) {
managedEntity = nonEnhancedEntityXref.get( entity ); managedEntity = nonEnhancedEntityXref.get( entity );
} }
// and get/return the EntityEntry from the ManagedEntry
return managedEntity == null return managedEntity == null
? null ? null
: managedEntity.$$_hibernate_getEntityEntry(); : 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) { public EntityEntry removeEntityEntry(Object entity) {
dirty = true; dirty = true;
// again, resolve the entity to a ManagedEntity (which may not be possible for non-enhanced)...
final ManagedEntity managedEntity; final ManagedEntity managedEntity;
if ( ManagedEntity.class.isInstance( entity ) ) { if ( ManagedEntity.class.isInstance( entity ) ) {
managedEntity = (ManagedEntity) entity; managedEntity = (ManagedEntity) entity;
@ -162,16 +194,18 @@ else if ( nonEnhancedEntityXref == null ) {
managedEntity = nonEnhancedEntityXref.remove( entity ); managedEntity = nonEnhancedEntityXref.remove( entity );
} }
// if we could not resolve it, just return (it was not associated with this context)
if ( managedEntity == null ) { if ( managedEntity == null ) {
return null; return null;
} }
// prepare for re-linking... // prepare for re-linking...
ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity(); final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity(); final ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
managedEntity.$$_hibernate_setPreviousManagedEntity( null ); managedEntity.$$_hibernate_setPreviousManagedEntity( null );
managedEntity.$$_hibernate_setNextManagedEntity( null ); managedEntity.$$_hibernate_setNextManagedEntity( null );
// re-link
count--; count--;
if ( count == 0 ) { if ( count == 0 ) {
@ -203,11 +237,20 @@ else if ( nonEnhancedEntityXref == null ) {
} }
} }
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 ); managedEntity.$$_hibernate_setEntityEntry( null );
return theEntityEntry; 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() { public Map.Entry<Object, EntityEntry>[] reentrantSafeEntityEntries() {
if ( dirty ) { if ( dirty ) {
reentrantSafeEntries = new EntityEntryCrossRefImpl[count]; reentrantSafeEntries = new EntityEntryCrossRefImpl[count];
@ -225,6 +268,9 @@ public Map.Entry<Object, EntityEntry>[] reentrantSafeEntityEntries() {
return reentrantSafeEntries; return reentrantSafeEntries;
} }
/**
* Clear this context of all managed entities
*/
public void clear() { public void clear() {
dirty = true; dirty = true;
@ -250,6 +296,9 @@ public void clear() {
reentrantSafeEntries = null; reentrantSafeEntries = null;
} }
/**
* Down-grade locks to NONE for all entities in this context
*/
public void downgradeLocks() { public void downgradeLocks() {
if ( head == null ) { if ( head == null ) {
return; return;
@ -263,6 +312,13 @@ public void downgradeLocks() {
} }
} }
/**
* 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 { public void serialize(ObjectOutputStream oos) throws IOException {
log.tracef( "Starting serialization of [%s] EntityEntry entries", count ); log.tracef( "Starting serialization of [%s] EntityEntry entries", count );
oos.writeInt( count ); oos.writeInt( count );
@ -281,7 +337,17 @@ public void serialize(ObjectOutputStream oos) throws IOException {
} }
} }
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(); final int count = ois.readInt();
log.tracef( "Starting deserialization of [%s] EntityEntry entries", count ); log.tracef( "Starting deserialization of [%s] EntityEntry entries", count );
@ -332,6 +398,9 @@ public int getNumberOfManagedEntities() {
return count; return count;
} }
/**
* The wrapper for entity classes which do not implement ManagedEntity
*/
private static class ManagedEntityImpl implements ManagedEntity { private static class ManagedEntityImpl implements ManagedEntity {
private final Object entityInstance; private final Object entityInstance;
private EntityEntry entityEntry; private EntityEntry entityEntry;
@ -378,6 +447,28 @@ public ManagedEntityImpl(Object entityInstance) {
} }
} }
/**
* 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 static class EntityEntryCrossRefImpl implements EntityEntryCrossRef {
private final Object entity; private final Object entity;
private EntityEntry entityEntry; private EntityEntry entityEntry;
@ -414,9 +505,4 @@ public EntityEntry setValue(EntityEntry entityEntry) {
return old; return old;
} }
} }
public static interface EntityEntryCrossRef extends Map.Entry<Object,EntityEntry> {
public Object getEntity();
public EntityEntry getEntityEntry();
}
} }

View File

@ -40,70 +40,81 @@
/** /**
* Algorithms related to foreign key constraint transparency * Algorithms related to foreign key constraint transparency
* *
* @author Gavin King * @author Gavin King
*/ */
public final class ForeignKeys { public final class ForeignKeys {
private ForeignKeys() {} /**
* Delegate for handling nullifying ("null"ing-out) non-cascaded associations
*/
public static class Nullifier { public static class Nullifier {
private final boolean isDelete; private final boolean isDelete;
private final boolean isEarlyInsert; private final boolean isEarlyInsert;
private final SessionImplementor session; private final SessionImplementor session;
private final Object self; 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) { public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SessionImplementor session) {
this.isDelete = isDelete; this.isDelete = isDelete;
this.isEarlyInsert = isEarlyInsert; this.isEarlyInsert = isEarlyInsert;
this.session = session; this.session = session;
this.self = self; this.self = self;
} }
/** /**
* Nullify all references to entities that have not yet * Nullify all references to entities that have not yet been inserted in the database, where the foreign key
* been inserted in the database, where the foreign key * points toward that entity.
* 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) public void nullifyTransientReferences(final Object[] values, final Type[] types)
throws HibernateException { throws HibernateException {
for ( int i = 0; i < types.length; i++ ) { for ( int i = 0; i < types.length; i++ ) {
values[i] = nullifyTransientReferences( values[i], types[i] ); values[i] = nullifyTransientReferences( values[i], types[i] );
} }
} }
/** /**
* Return null if the argument is an "unsaved" entity (ie. * Return null if the argument is an "unsaved" entity (ie. one with no existing database row), or the
* one with no existing database row), or the input argument * input argument otherwise. This is how Hibernate avoids foreign key constraint violations.
* 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) private Object nullifyTransientReferences(final Object value, final Type type)
throws HibernateException { throws HibernateException {
if ( value == null ) { if ( value == null ) {
return null; return null;
} }
else if ( type.isEntityType() ) { else if ( type.isEntityType() ) {
EntityType entityType = (EntityType) type; final EntityType entityType = (EntityType) type;
if ( entityType.isOneToOne() ) { if ( entityType.isOneToOne() ) {
return value; return value;
} }
else { else {
String entityName = entityType.getAssociatedEntityName(); final String entityName = entityType.getAssociatedEntityName();
return isNullifiable(entityName, value) ? null : value; return isNullifiable( entityName, value ) ? null : value;
} }
} }
else if ( type.isAnyType() ) { else if ( type.isAnyType() ) {
return isNullifiable(null, value) ? null : value; return isNullifiable( null, value ) ? null : value;
} }
else if ( type.isComponentType() ) { else if ( type.isComponentType() ) {
CompositeType actype = (CompositeType) type; final CompositeType actype = (CompositeType) type;
Object[] subvalues = actype.getPropertyValues(value, session); final Object[] subvalues = actype.getPropertyValues( value, session );
Type[] subtypes = actype.getSubtypes(); final Type[] subtypes = actype.getSubtypes();
boolean substitute = false; boolean substitute = false;
for ( int i = 0; i < subvalues.length; i++ ) { 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] ) { if ( replacement != subvalues[i] ) {
substitute = true; substitute = true;
subvalues[i] = replacement; subvalues[i] = replacement;
@ -119,20 +130,25 @@ else if ( type.isComponentType() ) {
return value; return value;
} }
} }
/** /**
* Determine if the object already exists in the database, * Determine if the object already exists in the database,
* using a "best guess" * using a "best guess"
*
* @param entityName The name of the entity
* @param object The entity instance
*/ */
private boolean isNullifiable(final String entityName, Object object) private boolean isNullifiable(final String entityName, Object object)
throws HibernateException { throws HibernateException {
if ( object == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
if (object==LazyPropertyInitializer.UNFETCHED_PROPERTY) return false; //this is kinda the best we can do... // this is the best we can do...
return false;
}
if ( object instanceof HibernateProxy ) { if ( object instanceof HibernateProxy ) {
// if its an uninitialized proxy it can't be transient // if its an uninitialized proxy it can't be transient
LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer(); final LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
if ( li.getImplementation(session) == null ) { if ( li.getImplementation( session ) == null ) {
return false; return false;
// ie. we never have to null out a reference to // ie. we never have to null out a reference to
// an uninitialized proxy // an uninitialized proxy
@ -142,100 +158,130 @@ private boolean isNullifiable(final String entityName, Object object)
object = li.getImplementation(); object = li.getImplementation();
} }
} }
// if it was a reference to self, don't need to nullify // if it was a reference to self, don't need to nullify
// unless we are using native id generation, in which // unless we are using native id generation, in which
// case we definitely need to nullify // case we definitely need to nullify
if ( object == self ) { if ( object == self ) {
return isEarlyInsert || ( return isEarlyInsert
isDelete && || ( isDelete && session.getFactory().getDialect().hasSelfReferentialForeignKeyBug() );
session.getFactory()
.getDialect()
.hasSelfReferentialForeignKeyBug()
);
} }
// See if the entity is already bound to this session, if not look at the // See if the entity is already bound to this session, if not look at the
// entity identifier and assume that the entity is persistent if the // entity identifier and assume that the entity is persistent if the
// id is not "unsaved" (that is, we rely on foreign keys to keep // id is not "unsaved" (that is, we rely on foreign keys to keep
// database integrity) // database integrity)
EntityEntry entityEntry = session.getPersistenceContext().getEntry(object); final EntityEntry entityEntry = session.getPersistenceContext().getEntry( object );
if ( entityEntry==null ) { if ( entityEntry == null ) {
return isTransient(entityName, object, null, session); return isTransient( entityName, object, null, session );
} }
else { else {
return entityEntry.isNullifiable(isEarlyInsert, session); return entityEntry.isNullifiable( isEarlyInsert, session );
} }
} }
} }
/** /**
* Is this instance persistent or detached? * Is this instance persistent or detached?
* If <tt>assumed</tt> is non-null, don't hit the database to make the * <p/>
* determination, instead assume that value; the client code must be * If <tt>assumed</tt> is non-null, don't hit the database to make the determination, instead assume that
* prepared to "recover" in the case that this assumed result is incorrect. * 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) @SuppressWarnings("SimplifiableIfStatement")
throws HibernateException { public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
if (entity instanceof HibernateProxy) return true; if ( entity instanceof HibernateProxy ) {
if ( session.getPersistenceContext().isEntryFor(entity) ) return true; return true;
return !isTransient(entityName, entity, assumed, session); }
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? * 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 * <p/>
* determination, instead assume that value; the client code must be * If <tt>assumed</tt> is non-null, don't hit the database to make the determination, instead assume that
* prepared to "recover" in the case that this assumed result is incorrect. * 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) @SuppressWarnings("UnnecessaryUnboxing")
throws HibernateException { public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
if (entity== LazyPropertyInitializer.UNFETCHED_PROPERTY) {
// an unfetched association can only point to // an unfetched association can only point to
// an entity that already exists in the db // an entity that already exists in the db
return false; return false;
} }
// let the interceptor inspect the instance to decide // let the interceptor inspect the instance to decide
Boolean isUnsaved = session.getInterceptor().isTransient(entity); Boolean isUnsaved = session.getInterceptor().isTransient( entity );
if (isUnsaved!=null) return isUnsaved.booleanValue(); if ( isUnsaved != null ) {
return isUnsaved.booleanValue();
}
// let the persister inspect the instance to decide // let the persister inspect the instance to decide
EntityPersister persister = session.getEntityPersister(entityName, entity); final EntityPersister persister = session.getEntityPersister( entityName, entity );
isUnsaved = persister.isTransient(entity, session); isUnsaved = persister.isTransient( entity, session );
if (isUnsaved!=null) return isUnsaved.booleanValue(); if ( isUnsaved != null ) {
return isUnsaved.booleanValue();
}
// we use the assumed value, if there is one, to avoid hitting // we use the assumed value, if there is one, to avoid hitting
// the database // 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 // 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.getIdentifier( entity, session ),
persister persister
); );
return snapshot==null; return snapshot == null;
} }
/** /**
* Return the identifier of the persistent or transient object, or throw * Return the identifier of the persistent or transient object, or throw
* an exception if the instance is "unsaved" * an exception if the instance is "unsaved"
* * <p/>
* Used by OneToOneType and ManyToOneType to determine what id value should * 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. * 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 * This does a "best guess" using any/all info available to use (not just the
* EntityEntry). * 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( public static Serializable getEntityIdentifierIfNotUnsaved(
final String entityName, final String entityName,
final Object object, final Object object,
final SessionImplementor session) final SessionImplementor session) throws TransientObjectException {
throws HibernateException {
if ( object == null ) { if ( object == null ) {
return null; return null;
} }
@ -245,10 +291,10 @@ public static Serializable getEntityIdentifierIfNotUnsaved(
// context-entity-identifier returns null explicitly if the entity // context-entity-identifier returns null explicitly if the entity
// is not associated with the persistence context; so make some // is not associated with the persistence context; so make some
// deeper checks... // deeper checks...
if ( isTransient(entityName, object, Boolean.FALSE, session) ) { if ( isTransient( entityName, object, Boolean.FALSE, session ) ) {
throw new TransientObjectException( throw new TransientObjectException(
"object references an unsaved transient instance - save the transient instance before flushing: " + "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 ); id = session.getEntityPersister( entityName, object ).getIdentifier( object, session );
@ -265,9 +311,9 @@ public static Serializable getEntityIdentifierIfNotUnsaved(
* @param entityName - the entity name * @param entityName - the entity name
* @param entity - the entity instance * @param entity - the entity instance
* @param values - insertable properties of the object (including backrefs), * @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 * @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 * @param session - the session
* *
* @return the transient unsaved entity dependencies that are non-nullable, * @return the transient unsaved entity dependencies that are non-nullable,
@ -278,18 +324,16 @@ public static NonNullableTransientDependencies findNonNullableTransientEntities(
Object entity, Object entity,
Object[] values, Object[] values,
boolean isEarlyInsert, boolean isEarlyInsert,
SessionImplementor session SessionImplementor session) {
) { final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
final EntityPersister persister = session.getEntityPersister( entityName, entity ); final EntityPersister persister = session.getEntityPersister( entityName, entity );
final String[] propertyNames = persister.getPropertyNames(); final String[] propertyNames = persister.getPropertyNames();
final Type[] types = persister.getPropertyTypes(); final Type[] types = persister.getPropertyTypes();
final boolean[] nullability = persister.getPropertyNullability(); final boolean[] nullability = persister.getPropertyNullability();
NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies(); final NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();
for ( int i = 0; i < types.length; i++ ) { for ( int i = 0; i < types.length; i++ ) {
collectNonNullableTransientEntities( collectNonNullableTransientEntities(
nullifier, nullifier,
i,
values[i], values[i],
propertyNames[i], propertyNames[i],
types[i], types[i],
@ -303,7 +347,6 @@ public static NonNullableTransientDependencies findNonNullableTransientEntities(
private static void collectNonNullableTransientEntities( private static void collectNonNullableTransientEntities(
Nullifier nullifier, Nullifier nullifier,
int i,
Object value, Object value,
String propertyName, String propertyName,
Type type, Type type,
@ -311,33 +354,32 @@ private static void collectNonNullableTransientEntities(
SessionImplementor session, SessionImplementor session,
NonNullableTransientDependencies nonNullableTransientEntities) { NonNullableTransientDependencies nonNullableTransientEntities) {
if ( value == null ) { if ( value == null ) {
return; // EARLY RETURN return;
} }
if ( type.isEntityType() ) {
EntityType entityType = (EntityType) type; if ( type.isEntityType() ) {
if ( ! isNullable && final EntityType entityType = (EntityType) type;
! entityType.isOneToOne() && if ( !isNullable
nullifier.isNullifiable( entityType.getAssociatedEntityName(), value ) ) { && !entityType.isOneToOne()
&& nullifier.isNullifiable( entityType.getAssociatedEntityName(), value ) ) {
nonNullableTransientEntities.add( propertyName, value ); nonNullableTransientEntities.add( propertyName, value );
} }
} }
else if ( type.isAnyType() ) { else if ( type.isAnyType() ) {
if ( ! isNullable && if ( !isNullable && nullifier.isNullifiable( null, value ) ) {
nullifier.isNullifiable( null, value ) ) {
nonNullableTransientEntities.add( propertyName, value ); nonNullableTransientEntities.add( propertyName, value );
} }
} }
else if ( type.isComponentType() ) { else if ( type.isComponentType() ) {
CompositeType actype = (CompositeType) type; final CompositeType actype = (CompositeType) type;
boolean[] subValueNullability = actype.getPropertyNullability(); final boolean[] subValueNullability = actype.getPropertyNullability();
if ( subValueNullability != null ) { if ( subValueNullability != null ) {
String[] subPropertyNames = actype.getPropertyNames(); final String[] subPropertyNames = actype.getPropertyNames();
Object[] subvalues = actype.getPropertyValues(value, session); final Object[] subvalues = actype.getPropertyValues( value, session );
Type[] subtypes = actype.getSubtypes(); final Type[] subtypes = actype.getSubtypes();
for ( int j = 0; j < subvalues.length; j++ ) { for ( int j = 0; j < subvalues.length; j++ ) {
collectNonNullableTransientEntities( collectNonNullableTransientEntities(
nullifier, nullifier,
j,
subvalues[j], subvalues[j],
subPropertyNames[j], subPropertyNames[j],
subtypes[j], subtypes[j],
@ -349,4 +391,11 @@ else if ( type.isComponentType() ) {
} }
} }
} }
/**
* Disallow instantiation
*/
private ForeignKeys() {
}
} }

View File

@ -36,45 +36,40 @@
* @author Gavin King * @author Gavin King
*/ */
public final class JoinHelper { public final class JoinHelper {
private JoinHelper() {}
/** /**
* Get the aliased columns of the owning entity which are to * Get the aliased columns of the owning entity which are to
* be used in the join * be used in the join
*/ */
public static String[] getAliasedLHSColumnNames( public static String[] getAliasedLHSColumnNames(
AssociationType type, AssociationType type,
String alias, String alias,
int property, int property,
OuterJoinLoadable lhsPersister, OuterJoinLoadable lhsPersister,
Mapping mapping Mapping mapping) {
) { return getAliasedLHSColumnNames( type, alias, property, 0, lhsPersister, mapping );
return getAliasedLHSColumnNames(type, alias, property, 0, lhsPersister, mapping);
} }
/** /**
* Get the columns of the owning entity which are to * Get the columns of the owning entity which are to
* be used in the join * be used in the join
*/ */
public static String[] getLHSColumnNames( public static String[] getLHSColumnNames(
AssociationType type, AssociationType type,
int property, int property,
OuterJoinLoadable lhsPersister, OuterJoinLoadable lhsPersister,
Mapping mapping Mapping mapping) {
) { return getLHSColumnNames( type, property, 0, lhsPersister, mapping );
return getLHSColumnNames(type, property, 0, lhsPersister, mapping);
} }
/** /**
* Get the aliased columns of the owning entity which are to * Get the aliased columns of the owning entity which are to
* be used in the join * be used in the join
*/ */
public static String[] getAliasedLHSColumnNames( public static String[] getAliasedLHSColumnNames(
AssociationType associationType, AssociationType associationType,
String columnQualifier, String columnQualifier,
int propertyIndex, int propertyIndex,
int begin, int begin,
OuterJoinLoadable lhsPersister, OuterJoinLoadable lhsPersister,
Mapping mapping) { Mapping mapping) {
if ( associationType.useLHSPrimaryKey() ) { if ( associationType.useLHSPrimaryKey() ) {
@ -90,7 +85,8 @@ public static String[] getAliasedLHSColumnNames(
); );
} }
else { else {
return ( (PropertyMapping) lhsPersister ).toColumns(columnQualifier, propertyName); //bad cast //bad cast
return ( (PropertyMapping) lhsPersister ).toColumns( columnQualifier, propertyName );
} }
} }
} }
@ -112,41 +108,57 @@ private static String[] toColumns(OuterJoinLoadable persister, String columnQual
} }
/** /**
* Get the columns of the owning entity which are to * Get the columns of the owning entity which are to be used in the join
* 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( public static String[] getLHSColumnNames(
AssociationType type, AssociationType type,
int property, int property,
int begin, int begin,
OuterJoinLoadable lhsPersister, OuterJoinLoadable lhsPersister,
Mapping mapping Mapping mapping) {
) {
if ( type.useLHSPrimaryKey() ) { if ( type.useLHSPrimaryKey() ) {
//return lhsPersister.getSubclassPropertyColumnNames(property); //return lhsPersister.getSubclassPropertyColumnNames(property);
return lhsPersister.getIdentifierColumnNames(); return lhsPersister.getIdentifierColumnNames();
} }
else { else {
String propertyName = type.getLHSPropertyName(); final String propertyName = type.getLHSPropertyName();
if (propertyName==null) { if ( propertyName == null ) {
//slice, to get the columns for this component //slice, to get the columns for this component
//property //property
return ArrayHelper.slice( return ArrayHelper.slice(
property < 0 property < 0
? lhsPersister.getIdentifierColumnNames() ? lhsPersister.getIdentifierColumnNames()
: lhsPersister.getSubclassPropertyColumnNames(property), : lhsPersister.getSubclassPropertyColumnNames( property ),
begin, begin,
type.getColumnSpan(mapping) type.getColumnSpan( mapping )
); );
} }
else { else {
//property-refs for associations defined on a //property-refs for associations defined on a
//component are not supported, so no need to slice //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( public static String getLHSTableName(
AssociationType type, AssociationType type,
int propertyIndex, int propertyIndex,
@ -155,17 +167,17 @@ public static String getLHSTableName(
return lhsPersister.getTableName(); return lhsPersister.getTableName();
} }
else { else {
String propertyName = type.getLHSPropertyName(); final String propertyName = type.getLHSPropertyName();
if (propertyName==null) { if ( propertyName == null ) {
//if there is no property-ref, assume the join //if there is no property-ref, assume the join
//is to the subclass table (ie. the table of the //is to the subclass table (ie. the table of the
//subclass that the association belongs to) //subclass that the association belongs to)
return lhsPersister.getSubclassPropertyTableName(propertyIndex); return lhsPersister.getSubclassPropertyTableName( propertyIndex );
} }
else { else {
//handle a property-ref //handle a property-ref
String propertyRefTable = lhsPersister.getPropertyTableName(propertyName); String propertyRefTable = lhsPersister.getPropertyTableName( propertyName );
if (propertyRefTable==null) { if ( propertyRefTable == null ) {
//it is possible that the tree-walking in OuterJoinLoader can get to //it is possible that the tree-walking in OuterJoinLoader can get to
//an association defined by a subclass, in which case the property-ref //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 //might refer to a property defined on a subclass of the current class
@ -173,25 +185,32 @@ public static String getLHSTableName(
//assumes that the property-ref refers to a property of the subclass //assumes that the property-ref refers to a property of the subclass
//table that the association belongs to (a reasonable guess) //table that the association belongs to (a reasonable guess)
//TODO: fix this, add: OuterJoinLoadable.getSubclassPropertyTableName(String propertyName) //TODO: fix this, add: OuterJoinLoadable.getSubclassPropertyTableName(String propertyName)
propertyRefTable = lhsPersister.getSubclassPropertyTableName(propertyIndex); propertyRefTable = lhsPersister.getSubclassPropertyTableName( propertyIndex );
} }
return propertyRefTable; return propertyRefTable;
} }
} }
} }
/** /**
* Get the columns of the associated table which are to * Get the columns of the associated table which are to be used in the join
* 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) { public static String[] getRHSColumnNames(AssociationType type, SessionFactoryImplementor factory) {
String uniqueKeyPropertyName = type.getRHSUniqueKeyPropertyName(); final String uniqueKeyPropertyName = type.getRHSUniqueKeyPropertyName();
Joinable joinable = type.getAssociatedJoinable(factory); final Joinable joinable = type.getAssociatedJoinable( factory );
if (uniqueKeyPropertyName==null) { if ( uniqueKeyPropertyName == null ) {
return joinable.getKeyColumnNames(); return joinable.getKeyColumnNames();
} }
else { else {
return ( (OuterJoinLoadable) joinable ).getPropertyColumnNames(uniqueKeyPropertyName); return ( (OuterJoinLoadable) joinable ).getPropertyColumnNames( uniqueKeyPropertyName );
} }
} }
private JoinHelper() {
}
} }

View File

@ -25,7 +25,6 @@
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -39,46 +38,368 @@
import org.hibernate.type.AssociationType; 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 Gavin King
* @author Steve Ebersole
*
* @see JoinFragment
*/ */
public class JoinSequence { public class JoinSequence {
private final SessionFactoryImplementor factory; private final SessionFactoryImplementor factory;
private final List<Join> joins = new ArrayList<Join>();
private boolean useThetaStyle = false;
private final StringBuilder conditions = new StringBuilder(); private final StringBuilder conditions = new StringBuilder();
private final List<Join> joins = new ArrayList<Join>();
private boolean useThetaStyle;
private String rootAlias; private String rootAlias;
private Joinable rootJoinable; private Joinable rootJoinable;
private Selector selector; private Selector selector;
private JoinSequence next; private JoinSequence next;
private boolean isFromPart = false; private boolean isFromPart;
@Override /**
public String toString() { * Constructs a JoinSequence
StringBuilder buf = new StringBuilder(); *
buf.append( "JoinSequence{" ); * @param factory The SessionFactory
if ( rootJoinable != null ) { */
buf.append( rootJoinable ) public JoinSequence(SessionFactoryImplementor factory) {
.append( '[' ) this.factory = factory;
.append( rootAlias )
.append( ']' );
}
for ( int i = 0; i < joins.size(); i++ ) {
buf.append( "->" ).append( joins.get( i ) );
}
return buf.append( '}' ).toString();
} }
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 AssociationType associationType;
private final Joinable joinable; private final Joinable joinable;
private final JoinType joinType; private final JoinType joinType;
private final String alias; private final String alias;
private final String[] lhsColumns; private final String[] lhsColumns;
Join(AssociationType associationType, String alias, JoinType joinType, String[] lhsColumns) Join(
throws MappingException { SessionFactoryImplementor factory,
AssociationType associationType,
String alias,
JoinType joinType,
String[] lhsColumns) throws MappingException {
this.associationType = associationType; this.associationType = associationType;
this.joinable = associationType.getAssociatedJoinable( factory ); this.joinable = associationType.getAssociatedJoinable( factory );
this.alias = alias; this.alias = alias;
@ -107,195 +428,24 @@ public String[] getLHSColumns() {
} }
@Override @Override
public String toString() { public String toString() {
return joinable.toString() + '[' + alias + ']'; return joinable.toString() + '[' + alias + ']';
} }
} }
public JoinSequence(SessionFactoryImplementor factory) { @Override
this.factory = factory; public String toString() {
} final StringBuilder buf = new StringBuilder();
buf.append( "JoinSequence{" );
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 );
if ( rootJoinable != null ) { if ( rootJoinable != null ) {
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias ); buf.append( rootJoinable )
String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters ); .append( '[' )
// JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it .append( rootAlias )
// can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track .append( ']' );
// 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 );
}
} }
for ( Join join : joins ) {
Joinable last = rootJoinable; buf.append( "->" ).append( join );
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();
} }
if ( next != null ) { return buf.append( '}' ).toString();
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);
} }
} }