big code cleanup to DefaultFlushEntityEventListener
This commit is contained in:
parent
1d12490dab
commit
e930e7c68d
|
@ -11,16 +11,13 @@ import java.util.Arrays;
|
|||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.StaleObjectStateException;
|
||||
import org.hibernate.action.internal.DelayedPostInsertIdentifier;
|
||||
import org.hibernate.action.internal.EntityUpdateAction;
|
||||
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||
import org.hibernate.engine.internal.Nullability;
|
||||
import org.hibernate.engine.internal.Versioning;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
|
@ -42,6 +39,8 @@ import org.hibernate.pretty.MessageHelper;
|
|||
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
|
||||
|
||||
/**
|
||||
* An event that occurs for each entity instance at flush time
|
||||
*
|
||||
|
@ -73,14 +72,13 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
final Object oid = persister.getIdentifier( object, session );
|
||||
|
||||
if ( id == null ) {
|
||||
throw new AssertionFailure( "null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)" );
|
||||
throw new AssertionFailure( "null id in " + persister.getEntityName()
|
||||
+ " entry (don't flush the Session after an exception occurs)" );
|
||||
}
|
||||
|
||||
if ( !persister.getIdentifierType().isEqual( id, oid, session.getFactory() ) ) {
|
||||
throw new HibernateException(
|
||||
"identifier of an instance of " + persister.getEntityName() + " was altered from "
|
||||
+ oid + " to " + id
|
||||
);
|
||||
throw new HibernateException( "identifier of an instance of " + persister.getEntityName()
|
||||
+ " was altered from " + oid + " to " + id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,19 +90,23 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
Object[] current,
|
||||
Object[] loaded,
|
||||
SessionImplementor session) {
|
||||
if ( entity instanceof PersistentAttributeInterceptable ) {
|
||||
final PersistentAttributeInterceptor interceptor =
|
||||
( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor();
|
||||
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
|
||||
// EARLY EXIT!!!
|
||||
// nothing to check - the entity is an un-initialized enhancement-as-proxy reference
|
||||
return;
|
||||
if ( !isUninitializedEnhanced( entity ) ) {
|
||||
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
|
||||
if ( naturalIdMapping != null && entry.getStatus() != Status.READ_ONLY ) {
|
||||
naturalIdMapping.verifyFlushState( entry.getId(), current, loaded, session );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
|
||||
if ( naturalIdMapping != null && entry.getStatus() != Status.READ_ONLY ) {
|
||||
naturalIdMapping.verifyFlushState( entry.getId(), current, loaded, session );
|
||||
private static boolean isUninitializedEnhanced(Object entity) {
|
||||
if ( entity instanceof PersistentAttributeInterceptable ) {
|
||||
final PersistentAttributeInterceptor interceptor =
|
||||
( (PersistentAttributeInterceptable) entity).$$_hibernate_getInterceptor();
|
||||
// the entity is an un-initialized enhancement-as-proxy reference
|
||||
return interceptor instanceof EnhancementAsProxyLazinessInterceptor;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,36 +117,33 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
|
||||
final Object entity = event.getEntity();
|
||||
final EntityEntry entry = event.getEntityEntry();
|
||||
final EventSource session = event.getSession();
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
final Status status = entry.getStatus();
|
||||
final Type[] types = persister.getPropertyTypes();
|
||||
|
||||
final boolean mightBeDirty = entry.requiresDirtyCheck( entity );
|
||||
|
||||
final Object[] values = getValues( entity, entry, mightBeDirty, session );
|
||||
final Object[] values = getValues( entity, entry, mightBeDirty, event.getSession() );
|
||||
|
||||
event.setPropertyValues( values );
|
||||
|
||||
//TODO: avoid this for non-new instances where mightBeDirty==false
|
||||
|
||||
boolean substitute = wrapCollections( session, persister, entity, entry.getId(), types, values );
|
||||
boolean substitute = wrapCollections( event, values );
|
||||
|
||||
if ( isUpdateNecessary( event, mightBeDirty ) ) {
|
||||
substitute = scheduleUpdate( event ) || substitute;
|
||||
}
|
||||
|
||||
if ( status != Status.DELETED ) {
|
||||
if ( entry.getStatus() != Status.DELETED ) {
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
// now update the object
|
||||
// has to be outside the main if block above (because of collections)
|
||||
if ( substitute ) {
|
||||
persister.setPropertyValues( entity, values );
|
||||
}
|
||||
|
||||
// Search for collections by reachability, updating their role.
|
||||
// We don't want to touch collections reachable from a deleted object
|
||||
if ( persister.hasCollections() ) {
|
||||
new FlushVisitor( session, entity ).processEntityPropertyValues( values, types );
|
||||
new FlushVisitor(event.getSession(), entity )
|
||||
.processEntityPropertyValues( values, persister.getPropertyTypes() );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,39 +161,31 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
}
|
||||
else {
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
|
||||
checkId( entity, persister, entry.getId(), session );
|
||||
|
||||
// grab its current state
|
||||
Object[] values = persister.getValues( entity );
|
||||
|
||||
checkNaturalId( persister, entity, entry, values, loadedState, session );
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap up any new collections directly referenced by the object
|
||||
* or its components.
|
||||
*/
|
||||
private boolean wrapCollections(
|
||||
EventSource session,
|
||||
EntityPersister persister,
|
||||
Object entity,
|
||||
Object id,
|
||||
Type[] types,
|
||||
Object[] values
|
||||
) {
|
||||
FlushEntityEvent event,
|
||||
Object[] values) {
|
||||
final EntityEntry entry = event.getEntityEntry();
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
if ( persister.hasCollections() ) {
|
||||
|
||||
// wrap up any new collections directly referenced by the object
|
||||
// or its components
|
||||
|
||||
// NOTE: we need to do the wrap here even if its not "dirty",
|
||||
// NOTE: we need to do the wrap here even if it's not "dirty",
|
||||
// because collections need wrapping but changes to _them_
|
||||
// don't dirty the container. Also, for versioned data, we
|
||||
// need to wrap before calling searchForDirtyCollections
|
||||
|
||||
WrapVisitor visitor = new WrapVisitor( entity, id ,session );
|
||||
// substitutes into values by side-effect
|
||||
visitor.processEntityPropertyValues( values, types );
|
||||
WrapVisitor visitor = new WrapVisitor( event.getEntity(), entry.getId() , event.getSession() );
|
||||
// substitutes into values by side effect
|
||||
visitor.processEntityPropertyValues( values, persister.getPropertyTypes() );
|
||||
return visitor.isSubstitutionRequired();
|
||||
}
|
||||
else {
|
||||
|
@ -241,14 +232,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
// increment the version number (if necessary)
|
||||
final Object nextVersion = getNextVersion( event );
|
||||
|
||||
// if it was dirtied by a collection only
|
||||
int[] dirtyProperties = event.getDirtyProperties();
|
||||
if ( event.isDirtyCheckPossible() && dirtyProperties == null ) {
|
||||
if ( !intercepted && !event.hasDirtyCollection() ) {
|
||||
throw new AssertionFailure( "dirty, but no dirty properties" );
|
||||
}
|
||||
dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY;
|
||||
}
|
||||
int[] dirtyProperties = getDirtyProperties( event, intercepted );
|
||||
|
||||
// check nullability but do not doAfterTransactionCompletion command execute
|
||||
// we'll use scheduled updates for that.
|
||||
|
@ -277,6 +261,22 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
return intercepted;
|
||||
}
|
||||
|
||||
private static int[] getDirtyProperties(FlushEntityEvent event, boolean intercepted) {
|
||||
int[] dirtyProperties = event.getDirtyProperties();
|
||||
if ( event.isDirtyCheckPossible() && dirtyProperties == null ) {
|
||||
if ( !intercepted && !event.hasDirtyCollection() ) {
|
||||
throw new AssertionFailure( "dirty, but no dirty properties" );
|
||||
}
|
||||
else {
|
||||
// it was dirtied by a collection only
|
||||
return ArrayHelper.EMPTY_INT_ARRAY;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return dirtyProperties;
|
||||
}
|
||||
}
|
||||
|
||||
private static void logScheduleUpdate(EntityEntry entry, EventSource session, Status status, EntityPersister persister) {
|
||||
if ( LOG.isTraceEnabled() ) {
|
||||
if ( status == Status.DELETED ) {
|
||||
|
@ -309,63 +309,53 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
}
|
||||
|
||||
protected boolean handleInterception(FlushEntityEvent event) {
|
||||
|
||||
//give the Interceptor a chance to modify property values
|
||||
final boolean intercepted = invokeInterceptor(
|
||||
event.getSession(),
|
||||
event.getEntity(),
|
||||
event.getEntityEntry(),
|
||||
event.getPropertyValues(),
|
||||
event.getEntityEntry().getPersister()
|
||||
);
|
||||
|
||||
final boolean intercepted = invokeInterceptor( event );
|
||||
//now we might need to recalculate the dirtyProperties array
|
||||
if ( intercepted && event.isDirtyCheckPossible() ) {
|
||||
dirtyCheck( event );
|
||||
}
|
||||
|
||||
return intercepted;
|
||||
}
|
||||
|
||||
protected boolean invokeInterceptor(
|
||||
SessionImplementor session,
|
||||
Object entity,
|
||||
EntityEntry entry,
|
||||
final Object[] values,
|
||||
EntityPersister persister) {
|
||||
protected boolean invokeInterceptor(FlushEntityEvent event) {
|
||||
final EntityEntry entry = event.getEntityEntry();
|
||||
final Object entity = event.getEntity();
|
||||
final Object id = entry.getId();
|
||||
final Object[] values = event.getPropertyValues();
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
final EventSource session = event.getSession();
|
||||
|
||||
boolean isDirty = false;
|
||||
|
||||
if ( entry.getStatus() != Status.DELETED ) {
|
||||
if ( callbackRegistry.preUpdate( entity ) ) {
|
||||
isDirty = copyState( entity, persister.getPropertyTypes(), values, session.getFactory() );
|
||||
}
|
||||
}
|
||||
|
||||
final boolean answerFromInterceptor = session.getInterceptor().onFlushDirty(
|
||||
final boolean stateModified = session.getInterceptor().onFlushDirty(
|
||||
entity,
|
||||
entry.getId(),
|
||||
id,
|
||||
values,
|
||||
entry.getLoadedState(),
|
||||
persister.getPropertyNames(),
|
||||
persister.getPropertyTypes()
|
||||
);
|
||||
|
||||
return answerFromInterceptor || isDirty;
|
||||
return stateModified || isDirty;
|
||||
}
|
||||
|
||||
private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactory sf) {
|
||||
private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactoryImplementor factory) {
|
||||
// copy the entity state into the state array and return true if the state has changed
|
||||
final Object[] newState = sf.unwrap( SessionFactoryImplementor.class )
|
||||
.getRuntimeMetamodels()
|
||||
final Object[] newState = factory.getRuntimeMetamodels()
|
||||
.getEntityMappingType( entity.getClass() )
|
||||
.getEntityPersister()
|
||||
.getValues( entity );
|
||||
|
||||
boolean isDirty = false;
|
||||
for ( int index = 0, size = newState.length; index < size; index++ ) {
|
||||
if ( ( state[index] == LazyPropertyInitializer.UNFETCHED_PROPERTY &&
|
||||
newState[index] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) ||
|
||||
( state[index] != newState[index] && !types[index].isEqual( state[index], newState[index] ) ) ) {
|
||||
if ( isDirty( types[index], state[index], newState[index] ) ) {
|
||||
isDirty = true;
|
||||
state[index] = newState[index];
|
||||
}
|
||||
|
@ -373,35 +363,27 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
return isDirty;
|
||||
}
|
||||
|
||||
private static boolean isDirty(Type types, Object state, Object newState) {
|
||||
return state == UNFETCHED_PROPERTY && newState != UNFETCHED_PROPERTY
|
||||
|| state != newState && !types.isEqual( state, newState );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to retrieve an entities next version value
|
||||
*/
|
||||
private Object getNextVersion(FlushEntityEvent event) throws HibernateException {
|
||||
|
||||
EntityEntry entry = event.getEntityEntry();
|
||||
EntityPersister persister = entry.getPersister();
|
||||
if ( persister.isVersioned() ) {
|
||||
|
||||
Object[] values = event.getPropertyValues();
|
||||
|
||||
if ( entry.isBeingReplicated() ) {
|
||||
return Versioning.getVersion( values, persister );
|
||||
}
|
||||
else {
|
||||
|
||||
final boolean isVersionIncrementRequired = isVersionIncrementRequired(
|
||||
event,
|
||||
entry,
|
||||
persister,
|
||||
event.getDirtyProperties()
|
||||
);
|
||||
|
||||
final Object nextVersion = isVersionIncrementRequired
|
||||
final Object nextVersion = isVersionIncrementRequired( event, entry )
|
||||
? Versioning.increment( entry.getVersion(), persister.getVersionMapping(), event.getSession() )
|
||||
: entry.getVersion(); //use the current version
|
||||
|
||||
Versioning.setVersion( values, nextVersion, persister );
|
||||
|
||||
return nextVersion;
|
||||
}
|
||||
}
|
||||
|
@ -411,41 +393,30 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
|
||||
}
|
||||
|
||||
private boolean isVersionIncrementRequired(
|
||||
FlushEntityEvent event,
|
||||
EntityEntry entry,
|
||||
EntityPersister persister,
|
||||
int[] dirtyProperties
|
||||
) {
|
||||
return entry.getStatus() != Status.DELETED && (
|
||||
dirtyProperties == null ||
|
||||
Versioning.isVersionIncrementRequired(
|
||||
dirtyProperties,
|
||||
event.hasDirtyCollection(),
|
||||
persister.getPropertyVersionability()
|
||||
)
|
||||
);
|
||||
private boolean isVersionIncrementRequired(FlushEntityEvent event, EntityEntry entry) {
|
||||
if ( entry.getStatus() == Status.DELETED ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
int[] dirtyProperties = event.getDirtyProperties();
|
||||
return dirtyProperties == null
|
||||
|| Versioning.isVersionIncrementRequired(
|
||||
dirtyProperties,
|
||||
event.hasDirtyCollection(),
|
||||
event.getEntityEntry().getPersister().getPropertyVersionability()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all necessary checking to determine if an entity needs an SQL update
|
||||
* to synchronize its state to the database. Modifies the event by side-effect!
|
||||
* to synchronize its state to the database. Modifies the event by side effect!
|
||||
* Note: this method is quite slow, avoid calling if possible!
|
||||
*/
|
||||
protected final boolean isUpdateNecessary(FlushEntityEvent event) throws HibernateException {
|
||||
if ( !event.isDirtyCheckPossible() ) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
int[] dirtyProperties = event.getDirtyProperties();
|
||||
if ( dirtyProperties != null && dirtyProperties.length != 0 ) {
|
||||
return true; //TODO: suck into event class
|
||||
}
|
||||
else {
|
||||
return hasDirtyCollections( event );
|
||||
}
|
||||
|
||||
}
|
||||
return !event.isDirtyCheckPossible()
|
||||
|| event.hasDirtyProperties()
|
||||
|| hasDirtyCollections(event);
|
||||
}
|
||||
|
||||
private boolean hasDirtyCollections(FlushEntityEvent event) {
|
||||
|
@ -476,130 +447,147 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
* Perform a dirty check, and attach the results to the event
|
||||
*/
|
||||
protected void dirtyCheck(final FlushEntityEvent event) throws HibernateException {
|
||||
int[] dirtyProperties = getDirtyProperties( event );
|
||||
event.setDatabaseSnapshot( null );
|
||||
if ( dirtyProperties == null ) {
|
||||
// do the dirty check the hard way
|
||||
dirtyProperties = performDirtyCheck( event );
|
||||
}
|
||||
else {
|
||||
// the Interceptor, SelfDirtinessTracker, or CustomEntityDirtinessStrategy
|
||||
// already handled the dirty check for us
|
||||
event.setDirtyProperties( dirtyProperties );
|
||||
event.setDirtyCheckHandledByInterceptor( true );
|
||||
event.setDirtyCheckPossible( true );
|
||||
}
|
||||
logDirtyProperties( event.getEntityEntry(), dirtyProperties );
|
||||
}
|
||||
|
||||
final Object entity = event.getEntity();
|
||||
final Object[] values = event.getPropertyValues();
|
||||
private static int[] performDirtyCheck(FlushEntityEvent event) {
|
||||
final SessionImplementor session = event.getSession();
|
||||
boolean dirtyCheckPossible;
|
||||
int[] dirtyProperties = null;
|
||||
try {
|
||||
session.getEventListenerManager().dirtyCalculationStart();
|
||||
// object loaded by update()
|
||||
final EntityEntry entry = event.getEntityEntry();
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
final Object[] values = event.getPropertyValues();
|
||||
final Object[] loadedState = entry.getLoadedState();
|
||||
final Object entity = event.getEntity();
|
||||
if ( loadedState != null ) {
|
||||
// dirty check against the usual snapshot of the entity
|
||||
dirtyProperties = persister.findDirty( values, loadedState, entity, session );
|
||||
dirtyCheckPossible = true;
|
||||
}
|
||||
else if ( entry.getStatus() == Status.DELETED && !entry.isModifiableEntity() ) {
|
||||
// A non-modifiable (e.g., read-only or immutable) entity needs to be have
|
||||
// references to transient entities set to null before being deleted. No other
|
||||
// fields should be updated.
|
||||
if ( values != entry.getDeletedState() ) {
|
||||
throw new IllegalStateException(
|
||||
"Entity has status Status.DELETED but values != entry.getDeletedState"
|
||||
);
|
||||
}
|
||||
// Even if loadedState == null, we can dirty-check by comparing currentState and
|
||||
// entry.getDeletedState() because the only fields to be updated are those that
|
||||
// refer to transient entities that are being set to null.
|
||||
// - currentState contains the entity's current property values.
|
||||
// - entry.getDeletedState() contains the entity's current property values with
|
||||
// references to transient entities set to null.
|
||||
// - dirtyProperties will only contain properties that refer to transient entities
|
||||
final Object[] currentState = persister.getValues( event.getEntity() );
|
||||
dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session );
|
||||
dirtyCheckPossible = true;
|
||||
}
|
||||
else {
|
||||
// dirty check against the database snapshot, if possible/necessary
|
||||
final Object[] databaseSnapshot = getDatabaseSnapshot( persister, entry.getId(), session );
|
||||
if ( databaseSnapshot != null ) {
|
||||
dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session );
|
||||
dirtyCheckPossible = true;
|
||||
event.setDatabaseSnapshot( databaseSnapshot );
|
||||
}
|
||||
else {
|
||||
dirtyCheckPossible = false;
|
||||
}
|
||||
}
|
||||
event.setDirtyProperties( dirtyProperties );
|
||||
event.setDirtyCheckHandledByInterceptor( false );
|
||||
event.setDirtyCheckPossible( dirtyCheckPossible );
|
||||
}
|
||||
finally {
|
||||
session.getEventListenerManager().dirtyCalculationEnd( dirtyProperties != null );
|
||||
}
|
||||
return dirtyProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the dirty properties from either the Interceptor,
|
||||
* the bytecode enhancement, or a custom dirtiness strategy.
|
||||
*/
|
||||
private static int[] getDirtyProperties(FlushEntityEvent event) {
|
||||
int[] dirtyProperties = getDirtyPropertiesFromInterceptor( event );
|
||||
if ( dirtyProperties != null ) {
|
||||
return dirtyProperties;
|
||||
}
|
||||
else {
|
||||
final Object entity = event.getEntity();
|
||||
return entity instanceof SelfDirtinessTracker
|
||||
? getDirtyPropertiesFromSelfDirtinessTracker( (SelfDirtinessTracker) entity, event )
|
||||
: getDirtyPropertiesFromCustomEntityDirtinessStrategy( event );
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] getDirtyPropertiesFromInterceptor(FlushEntityEvent event) {
|
||||
final EntityEntry entry = event.getEntityEntry();
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
final Object id = entry.getId();
|
||||
final Object[] loadedState = entry.getLoadedState();
|
||||
|
||||
int[] dirtyProperties = session.getInterceptor().findDirty(
|
||||
entity,
|
||||
id,
|
||||
values,
|
||||
loadedState,
|
||||
return event.getSession().getInterceptor().findDirty(
|
||||
event.getEntity(),
|
||||
entry.getId(),
|
||||
event.getPropertyValues(),
|
||||
entry.getLoadedState(),
|
||||
persister.getPropertyNames(),
|
||||
persister.getPropertyTypes()
|
||||
);
|
||||
|
||||
if ( dirtyProperties == null ) {
|
||||
if ( entity instanceof SelfDirtinessTracker ) {
|
||||
boolean hasDirtyAttributes = ((SelfDirtinessTracker) entity).$$_hibernate_hasDirtyAttributes();
|
||||
if ( hasDirtyAttributes || persister.hasMutableProperties() ) {
|
||||
String[] dirtyAttributes = ((SelfDirtinessTracker) entity).$$_hibernate_getDirtyAttributes();
|
||||
dirtyProperties = persister.resolveDirtyAttributeIndexes(
|
||||
values,
|
||||
loadedState,
|
||||
dirtyAttributes,
|
||||
session
|
||||
);
|
||||
}
|
||||
else {
|
||||
dirtyProperties = ArrayHelper.EMPTY_INT_ARRAY;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// see if the custom dirtiness strategy can tell us...
|
||||
class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext {
|
||||
private int[] found;
|
||||
|
||||
@Override
|
||||
public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) {
|
||||
found = new DirtyCheckAttributeInfoImpl( event ).visitAttributes( attributeChecker );
|
||||
if ( found.length == 0 ) {
|
||||
found = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
DirtyCheckContextImpl context = new DirtyCheckContextImpl();
|
||||
session.getFactory().getCustomEntityDirtinessStrategy().findDirty(
|
||||
entity,
|
||||
persister,
|
||||
session,
|
||||
context
|
||||
);
|
||||
dirtyProperties = context.found;
|
||||
}
|
||||
}
|
||||
|
||||
event.setDatabaseSnapshot( null );
|
||||
|
||||
final boolean interceptorHandledDirtyCheck;
|
||||
//The dirty check is considered possible unless proven otherwise (see below)
|
||||
boolean dirtyCheckPossible = true;
|
||||
|
||||
if ( dirtyProperties == null ) {
|
||||
// Interceptor returned null, so do the dirty check ourself, if possible
|
||||
try {
|
||||
session.getEventListenerManager().dirtyCalculationStart();
|
||||
|
||||
interceptorHandledDirtyCheck = false;
|
||||
// object loaded by update()
|
||||
dirtyCheckPossible = loadedState != null;
|
||||
if ( dirtyCheckPossible ) {
|
||||
// dirty check against the usual snapshot of the entity
|
||||
dirtyProperties = persister.findDirty( values, loadedState, entity, session );
|
||||
}
|
||||
else if ( entry.getStatus() == Status.DELETED && !entry.isModifiableEntity() ) {
|
||||
// A non-modifiable (e.g., read-only or immutable) entity needs to be have
|
||||
// references to transient entities set to null before being deleted. No other
|
||||
// fields should be updated.
|
||||
if ( values != entry.getDeletedState() ) {
|
||||
throw new IllegalStateException(
|
||||
"Entity has status Status.DELETED but values != entry.getDeletedState"
|
||||
);
|
||||
}
|
||||
// Even if loadedState == null, we can dirty-check by comparing currentState and
|
||||
// entry.getDeletedState() because the only fields to be updated are those that
|
||||
// refer to transient entities that are being set to null.
|
||||
// - currentState contains the entity's current property values.
|
||||
// - entry.getDeletedState() contains the entity's current property values with
|
||||
// references to transient entities set to null.
|
||||
// - dirtyProperties will only contain properties that refer to transient entities
|
||||
final Object[] currentState = persister.getValues( event.getEntity() );
|
||||
dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session );
|
||||
dirtyCheckPossible = true;
|
||||
}
|
||||
else {
|
||||
// dirty check against the database snapshot, if possible/necessary
|
||||
final Object[] databaseSnapshot = getDatabaseSnapshot( persister, id, session );
|
||||
if ( databaseSnapshot != null ) {
|
||||
dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session );
|
||||
dirtyCheckPossible = true;
|
||||
event.setDatabaseSnapshot( databaseSnapshot );
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
session.getEventListenerManager().dirtyCalculationEnd( dirtyProperties != null );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// either the Interceptor, the bytecode enhancement or a custom dirtiness strategy handled the dirty checking
|
||||
interceptorHandledDirtyCheck = true;
|
||||
}
|
||||
|
||||
logDirtyProperties( id, dirtyProperties, persister );
|
||||
|
||||
event.setDirtyProperties( dirtyProperties );
|
||||
event.setDirtyCheckHandledByInterceptor( interceptorHandledDirtyCheck );
|
||||
event.setDirtyCheckPossible( dirtyCheckPossible );
|
||||
|
||||
}
|
||||
|
||||
private class DirtyCheckAttributeInfoImpl implements CustomEntityDirtinessStrategy.AttributeInformation {
|
||||
private static int[] getDirtyPropertiesFromCustomEntityDirtinessStrategy(FlushEntityEvent event) {
|
||||
// see if the custom dirtiness strategy can tell us...
|
||||
class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext {
|
||||
private int[] found;
|
||||
@Override
|
||||
public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) {
|
||||
found = new DirtyCheckAttributeInfoImpl( event ).visitAttributes( attributeChecker );
|
||||
if ( found.length == 0 ) {
|
||||
found = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
EventSource session = event.getSession();
|
||||
DirtyCheckContextImpl context = new DirtyCheckContextImpl();
|
||||
session.getFactory().getCustomEntityDirtinessStrategy()
|
||||
.findDirty( event.getEntity(), event.getEntityEntry().getPersister(), session, context );
|
||||
return context.found;
|
||||
}
|
||||
|
||||
private static int[] getDirtyPropertiesFromSelfDirtinessTracker(SelfDirtinessTracker tracker, FlushEntityEvent event) {
|
||||
final EntityEntry entry = event.getEntityEntry();
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
if ( tracker.$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) {
|
||||
return persister.resolveDirtyAttributeIndexes(
|
||||
event.getPropertyValues(),
|
||||
entry.getLoadedState(),
|
||||
tracker.$$_hibernate_getDirtyAttributes(),
|
||||
event.getSession()
|
||||
);
|
||||
}
|
||||
else {
|
||||
return ArrayHelper.EMPTY_INT_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DirtyCheckAttributeInfoImpl implements CustomEntityDirtinessStrategy.AttributeInformation {
|
||||
private final FlushEntityEvent event;
|
||||
private final EntityPersister persister;
|
||||
private final int numberOfAttributes;
|
||||
|
@ -648,11 +636,9 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
|
||||
public int[] visitAttributes(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) {
|
||||
databaseSnapshot = null;
|
||||
index = 0;
|
||||
|
||||
final int[] indexes = new int[numberOfAttributes];
|
||||
int count = 0;
|
||||
for (; index < numberOfAttributes; index++ ) {
|
||||
for ( index = 0; index < numberOfAttributes; index++ ) {
|
||||
if ( attributeChecker.isDirty( this ) ) {
|
||||
indexes[count++] = index;
|
||||
}
|
||||
|
@ -661,8 +647,9 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
}
|
||||
}
|
||||
|
||||
private void logDirtyProperties(Object id, int[] dirtyProperties, EntityPersister persister) {
|
||||
private void logDirtyProperties(EntityEntry entry, int[] dirtyProperties) {
|
||||
if ( dirtyProperties != null && dirtyProperties.length > 0 && LOG.isTraceEnabled() ) {
|
||||
final EntityPersister persister = entry.getPersister();
|
||||
final String[] allPropertyNames = persister.getPropertyNames();
|
||||
final String[] dirtyPropertyNames = new String[dirtyProperties.length];
|
||||
for ( int i = 0; i < dirtyProperties.length; i++ ) {
|
||||
|
@ -670,13 +657,13 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
}
|
||||
LOG.tracev(
|
||||
"Found dirty properties [{0}] : {1}",
|
||||
MessageHelper.infoString( persister.getEntityName(), id ),
|
||||
MessageHelper.infoString( persister.getEntityName(), entry.getId() ),
|
||||
Arrays.toString( dirtyPropertyNames )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] getDatabaseSnapshot(
|
||||
private static Object[] getDatabaseSnapshot(
|
||||
EntityPersister persister,
|
||||
Object id,
|
||||
SessionImplementor session) {
|
||||
|
@ -694,7 +681,6 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
return snapshot;
|
||||
}
|
||||
// TODO: optimize away this lookup for entities w/o unsaved-value="undefined"
|
||||
final EntityKey entityKey = session.generateEntityKey( id, persister );
|
||||
return persistenceContext.getCachedDatabaseSnapshot( entityKey );
|
||||
return persistenceContext.getCachedDatabaseSnapshot( session.generateEntityKey( id, persister ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,9 @@ public class FlushEntityEvent extends AbstractEvent {
|
|||
public void setDirtyProperties(int[] dirtyProperties) {
|
||||
this.dirtyProperties = dirtyProperties;
|
||||
}
|
||||
public boolean hasDirtyProperties() {
|
||||
return dirtyProperties != null && dirtyProperties.length != 0;
|
||||
}
|
||||
public boolean hasDirtyCollection() {
|
||||
return hasDirtyCollection;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue