HHH-9265 - Extract EntityEntry behind a factory + interface
This commit is contained in:
parent
f295d66d6e
commit
5c4dacb83e
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.hibernate.engine.internal;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
/**
|
||||
* Factory for the safe approach implementation of {@link org.hibernate.engine.spi.EntityEntry}.
|
||||
* <p>
|
||||
* Smarter implementations could store less state.
|
||||
*
|
||||
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||
*/
|
||||
public class DefaultEntityEntryFactory implements EntityEntryFactory {
|
||||
@Override
|
||||
public EntityEntry createEntityEntry(Status status, Object[] loadedState, Object rowId, Serializable id, Object version, LockMode lockMode, boolean existsInDatabase, EntityPersister persister, boolean disableVersionIncrement, boolean lazyPropertiesAreUnfetched, PersistenceContext persistenceContext) {
|
||||
|
||||
return new MutableEntityEntry(
|
||||
status,
|
||||
loadedState,
|
||||
rowId,
|
||||
id,
|
||||
version,
|
||||
lockMode,
|
||||
existsInDatabase,
|
||||
persister,
|
||||
disableVersionIncrement,
|
||||
lazyPropertiesAreUnfetched,
|
||||
persistenceContext
|
||||
);
|
||||
}
|
||||
}
|
|
@ -366,7 +366,7 @@ public class EntityEntryContext {
|
|||
for ( int i = 0; i < count; i++ ) {
|
||||
final boolean isEnhanced = ois.readBoolean();
|
||||
final Object entity = ois.readObject();
|
||||
final EntityEntry entry = EntityEntry.deserialize( ois, rtn );
|
||||
final EntityEntry entry = MutableEntityEntry.deserialize(ois, rtn);
|
||||
final ManagedEntity managedEntity;
|
||||
if ( isEnhanced ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.hibernate.engine.internal;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.service.spi.ServiceRegistryImplementor;
|
||||
import org.hibernate.service.spi.SessionFactoryServiceInitiator;
|
||||
|
||||
/**
|
||||
* Standard initiator for the {@link org.hibernate.engine.spi.EntityEntryFactory}.
|
||||
* <p>
|
||||
* Implementation note:
|
||||
* A {@link org.hibernate.service.spi.SessionFactoryServiceInitiator} is used to allow
|
||||
* overriding implementations to depend on session factory level services:
|
||||
* OGM datastore provider is an example.
|
||||
* TODO: make sure it is required to be a SessionFactoryServiceInitiator
|
||||
*
|
||||
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||
*/
|
||||
public class EntityEntryFactoryInitiator implements SessionFactoryServiceInitiator<EntityEntryFactory> {
|
||||
|
||||
public static final EntityEntryFactoryInitiator INSTANCE = new EntityEntryFactoryInitiator();
|
||||
|
||||
@Override
|
||||
public EntityEntryFactory initiateService(SessionFactoryImplementor sessionFactory, SessionFactory.SessionFactoryOptions sessionFactoryOptions, ServiceRegistryImplementor registry) {
|
||||
return new DefaultEntityEntryFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<EntityEntryFactory> getServiceInitiated() {
|
||||
return EntityEntryFactory.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,697 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2010-2015, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.engine.internal;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
|
||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityEntryExtraState;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.UniqueKeyLoadable;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* We need an entry to tell us all about the current state of an mutable object with respect to its persistent state
|
||||
*
|
||||
* Implementation Warning: Hibernate needs to instantiate a high amount of instances of this class,
|
||||
* therefore we need to take care of its impact on memory consumption.
|
||||
*
|
||||
* @author Gavin King
|
||||
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||
* @author Gunnar Morling
|
||||
* @author Sanne Grinovero <sanne@hibernate.org>
|
||||
*/
|
||||
public final class MutableEntityEntry implements Serializable, EntityEntry {
|
||||
private final Serializable id;
|
||||
private Object[] loadedState;
|
||||
private Object version;
|
||||
private final EntityPersister persister; // permanent but we only need the entityName state in a non transient way
|
||||
private transient EntityKey cachedEntityKey; // cached EntityKey (lazy-initialized)
|
||||
private final transient Object rowId;
|
||||
private final transient PersistenceContext persistenceContext;
|
||||
private EntityEntryExtraState next;
|
||||
|
||||
/**
|
||||
* Holds several boolean and enum typed attributes in a very compact manner. Enum values are stored in 4 bits
|
||||
* (where 0 represents {@code null}, and each enum value is represented by its ordinal value + 1), thus allowing
|
||||
* for up to 15 values per enum. Boolean values are stored in one bit.
|
||||
* <p>
|
||||
* The value is structured as follows:
|
||||
*
|
||||
* <pre>
|
||||
* 1 - Lock mode
|
||||
* 2 - Status
|
||||
* 3 - Previous Status
|
||||
* 4 - existsInDatabase
|
||||
* 5 - isBeingReplicated
|
||||
* 6 - loadedWithLazyPropertiesUnfetched; NOTE: this is not updated when properties are fetched lazily!
|
||||
*
|
||||
* 0000 0000 | 0000 0000 | 0654 3333 | 2222 1111
|
||||
* </pre>
|
||||
* Use {@link #setCompressedValue(MutableEntityEntry.EnumState, Enum)},
|
||||
* {@link #getCompressedValue(MutableEntityEntry.EnumState, Class)} etc
|
||||
* to access the enums and booleans stored in this value.
|
||||
* <p>
|
||||
* Representing enum values by their ordinal value is acceptable for our case as this value itself is never
|
||||
* serialized or deserialized and thus is not affected should ordinal values change.
|
||||
*/
|
||||
private transient int compressedState;
|
||||
|
||||
/**
|
||||
* @deprecated the tenantId and entityMode parameters where removed: this constructor accepts but ignores them.
|
||||
* Use the other constructor!
|
||||
*/
|
||||
@Deprecated
|
||||
public MutableEntityEntry(
|
||||
final Status status,
|
||||
final Object[] loadedState,
|
||||
final Object rowId,
|
||||
final Serializable id,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final EntityPersister persister,
|
||||
final EntityMode entityMode,
|
||||
final String tenantId,
|
||||
final boolean disableVersionIncrement,
|
||||
final boolean lazyPropertiesAreUnfetched,
|
||||
final PersistenceContext persistenceContext) {
|
||||
this( status, loadedState, rowId, id, version, lockMode, existsInDatabase,
|
||||
persister,disableVersionIncrement, lazyPropertiesAreUnfetched, persistenceContext );
|
||||
}
|
||||
|
||||
public MutableEntityEntry(
|
||||
final Status status,
|
||||
final Object[] loadedState,
|
||||
final Object rowId,
|
||||
final Serializable id,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final EntityPersister persister,
|
||||
final boolean disableVersionIncrement,
|
||||
final boolean lazyPropertiesAreUnfetched,
|
||||
final PersistenceContext persistenceContext) {
|
||||
setCompressedValue( EnumState.STATUS, status );
|
||||
// not useful strictly speaking but more explicit
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, null );
|
||||
// only retain loaded state if the status is not Status.READ_ONLY
|
||||
if ( status != Status.READ_ONLY ) {
|
||||
this.loadedState = loadedState;
|
||||
}
|
||||
this.id=id;
|
||||
this.rowId=rowId;
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
|
||||
this.version=version;
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
setCompressedValue( BooleanState.IS_BEING_REPLICATED, disableVersionIncrement );
|
||||
setCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED, lazyPropertiesAreUnfetched );
|
||||
this.persister=persister;
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* This for is used during custom deserialization handling
|
||||
*/
|
||||
@SuppressWarnings( {"JavaDoc"})
|
||||
private MutableEntityEntry(
|
||||
final SessionFactoryImplementor factory,
|
||||
final String entityName,
|
||||
final Serializable id,
|
||||
final Status status,
|
||||
final Status previousStatus,
|
||||
final Object[] loadedState,
|
||||
final Object[] deletedState,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final boolean isBeingReplicated,
|
||||
final boolean loadedWithLazyPropertiesUnfetched,
|
||||
final PersistenceContext persistenceContext) {
|
||||
this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) );
|
||||
this.id = id;
|
||||
setCompressedValue( EnumState.STATUS, status );
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, previousStatus );
|
||||
this.loadedState = loadedState;
|
||||
setDeletedState( deletedState );
|
||||
this.version = version;
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
|
||||
setCompressedValue( BooleanState.IS_BEING_REPLICATED, isBeingReplicated );
|
||||
setCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED, loadedWithLazyPropertiesUnfetched );
|
||||
this.rowId = null; // this is equivalent to the old behavior...
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockMode getLockMode() {
|
||||
return getCompressedValue( EnumState.LOCK_MODE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLockMode(LockMode lockMode) {
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return getCompressedValue( EnumState.STATUS );
|
||||
}
|
||||
|
||||
private Status getPreviousStatus() {
|
||||
return getCompressedValue( EnumState.PREVIOUS_STATUS );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(Status status) {
|
||||
if ( status == Status.READ_ONLY ) {
|
||||
//memory optimization
|
||||
loadedState = null;
|
||||
}
|
||||
|
||||
Status currentStatus = this.getStatus();
|
||||
|
||||
if ( currentStatus != status ) {
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, currentStatus );
|
||||
setCompressedValue( EnumState.STATUS, status );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getLoadedState() {
|
||||
return loadedState;
|
||||
}
|
||||
|
||||
private static final Object[] DEFAULT_DELETED_STATE = null;
|
||||
|
||||
@Override
|
||||
public Object[] getDeletedState() {
|
||||
EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
|
||||
return extra != null ? extra.getDeletedState() : DEFAULT_DELETED_STATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeletedState(Object[] deletedState) {
|
||||
EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
|
||||
if ( extra == null && deletedState == DEFAULT_DELETED_STATE ) {
|
||||
//this is the default value and we do not store the extra state
|
||||
return;
|
||||
}
|
||||
if ( extra == null ) {
|
||||
extra = new EntityEntryExtraStateHolder();
|
||||
addExtraState( extra );
|
||||
}
|
||||
extra.setDeletedState( deletedState );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExistsInDatabase() {
|
||||
return getCompressedValue( BooleanState.EXISTS_IN_DATABASE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityPersister getPersister() {
|
||||
return persister;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityKey getEntityKey() {
|
||||
if ( cachedEntityKey == null ) {
|
||||
if ( getId() == null ) {
|
||||
throw new IllegalStateException( "cannot generate an EntityKey when id is null.");
|
||||
}
|
||||
cachedEntityKey = new EntityKey( getId(), getPersister() );
|
||||
}
|
||||
return cachedEntityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityName() {
|
||||
return persister == null ? null : persister.getEntityName();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeingReplicated() {
|
||||
return getCompressedValue( BooleanState.IS_BEING_REPLICATED );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getRowId() {
|
||||
return rowId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) {
|
||||
this.loadedState = updatedState;
|
||||
setLockMode( LockMode.WRITE );
|
||||
|
||||
if ( getPersister().isVersioned() ) {
|
||||
this.version = nextVersion;
|
||||
getPersister().setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
|
||||
}
|
||||
|
||||
if ( getPersister().getInstrumentationMetadata().isInstrumented() ) {
|
||||
final FieldInterceptor interceptor = getPersister().getInstrumentationMetadata().extractInterceptor( entity );
|
||||
if ( interceptor != null ) {
|
||||
interceptor.clearDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if( entity instanceof SelfDirtinessTracker ) {
|
||||
((SelfDirtinessTracker) entity).$$_hibernate_clearDirtyAttributes();
|
||||
}
|
||||
|
||||
persistenceContext.getSession()
|
||||
.getFactory()
|
||||
.getCustomEntityDirtinessStrategy()
|
||||
.resetDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDelete() {
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() );
|
||||
setCompressedValue( EnumState.STATUS, Status.GONE );
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInsert(Object[] insertedState) {
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
||||
if ( getStatus() == Status.SAVING ) {
|
||||
return true;
|
||||
}
|
||||
else if ( earlyInsert ) {
|
||||
return !isExistsInDatabase();
|
||||
}
|
||||
else {
|
||||
return session.getPersistenceContext().getNullifiableEntityKeys().contains( getEntityKey() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getLoadedValue(String propertyName) {
|
||||
if ( loadedState == null || propertyName == null ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex( propertyName );
|
||||
return loadedState[propertyIndex];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresDirtyCheck(Object entity) {
|
||||
return isModifiableEntity()
|
||||
&& ( !isUnequivocallyNonDirty( entity ) );
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"SimplifiableIfStatement"})
|
||||
private boolean isUnequivocallyNonDirty(Object entity) {
|
||||
|
||||
if(entity instanceof SelfDirtinessTracker)
|
||||
return ((SelfDirtinessTracker) entity).$$_hibernate_hasDirtyAttributes();
|
||||
|
||||
final CustomEntityDirtinessStrategy customEntityDirtinessStrategy =
|
||||
persistenceContext.getSession().getFactory().getCustomEntityDirtinessStrategy();
|
||||
if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) persistenceContext.getSession() ) ) {
|
||||
return ! customEntityDirtinessStrategy.isDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
|
||||
}
|
||||
|
||||
if ( getPersister().hasMutableProperties() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( getPersister().getInstrumentationMetadata().isInstrumented() ) {
|
||||
// the entity must be instrumented (otherwise we cant check dirty flag) and the dirty flag is false
|
||||
return ! getPersister().getInstrumentationMetadata().extractInterceptor( entity ).isDirty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModifiableEntity() {
|
||||
Status status = getStatus();
|
||||
Status previousStatus = getPreviousStatus();
|
||||
return getPersister().isMutable()
|
||||
&& status != Status.READ_ONLY
|
||||
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceLocked(Object entity, Object nextVersion) {
|
||||
version = nextVersion;
|
||||
loadedState[ persister.getVersionProperty() ] = version;
|
||||
// TODO: use LockMode.PESSIMISTIC_FORCE_INCREMENT
|
||||
//noinspection deprecation
|
||||
setLockMode( LockMode.FORCE );
|
||||
persister.setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
Status status = getStatus();
|
||||
if (status != Status.MANAGED && status != Status.READ_ONLY) {
|
||||
throw new HibernateException("instance was not in a valid state");
|
||||
}
|
||||
return status == Status.READ_ONLY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly, Object entity) {
|
||||
if ( readOnly == isReadOnly() ) {
|
||||
// simply return since the status is not being changed
|
||||
return;
|
||||
}
|
||||
if ( readOnly ) {
|
||||
setStatus( Status.READ_ONLY );
|
||||
loadedState = null;
|
||||
}
|
||||
else {
|
||||
if ( ! persister.isMutable() ) {
|
||||
throw new IllegalStateException( "Cannot make an immutable entity modifiable." );
|
||||
}
|
||||
setStatus( Status.MANAGED );
|
||||
loadedState = getPersister().getPropertyValues( entity );
|
||||
persistenceContext.getNaturalIdHelper().manageLocalNaturalIdCrossReference(
|
||||
persister,
|
||||
id,
|
||||
loadedState,
|
||||
null,
|
||||
CachedNaturalIdValueSource.LOAD
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EntityEntry" +
|
||||
MessageHelper.infoString( getPersister().getEntityName(), id ) +
|
||||
'(' + getStatus() + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadedWithLazyPropertiesUnfetched() {
|
||||
return getCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(ObjectOutputStream oos) throws IOException {
|
||||
Status previousStatus = getPreviousStatus();
|
||||
oos.writeObject( getEntityName() );
|
||||
oos.writeObject( id );
|
||||
oos.writeObject( getStatus().name() );
|
||||
oos.writeObject( (previousStatus == null ? "" : previousStatus.name()) );
|
||||
// todo : potentially look at optimizing these two arrays
|
||||
oos.writeObject( loadedState );
|
||||
oos.writeObject( getDeletedState() );
|
||||
oos.writeObject( version );
|
||||
oos.writeObject( getLockMode().toString() );
|
||||
oos.writeBoolean( isExistsInDatabase() );
|
||||
oos.writeBoolean( isBeingReplicated() );
|
||||
oos.writeBoolean( isLoadedWithLazyPropertiesUnfetched() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserialization routine used during deserialization of a
|
||||
* Session/PersistenceContext for increased performance.
|
||||
*
|
||||
* @param ois The stream from which to read the entry.
|
||||
* @param persistenceContext The context being deserialized.
|
||||
*
|
||||
* @return The deserialized EntityEntry
|
||||
*
|
||||
* @throws java.io.IOException If a stream error occurs
|
||||
* @throws ClassNotFoundException If any of the classes declared in the stream
|
||||
* cannot be found
|
||||
*/
|
||||
public static EntityEntry deserialize(
|
||||
ObjectInputStream ois,
|
||||
PersistenceContext persistenceContext) throws IOException, ClassNotFoundException {
|
||||
String previousStatusString;
|
||||
return new MutableEntityEntry(
|
||||
persistenceContext.getSession().getFactory(),
|
||||
(String) ois.readObject(),
|
||||
(Serializable) ois.readObject(),
|
||||
Status.valueOf( (String) ois.readObject() ),
|
||||
( previousStatusString = (String) ois.readObject() ).length() == 0
|
||||
? null
|
||||
: Status.valueOf( previousStatusString ),
|
||||
(Object[]) ois.readObject(),
|
||||
(Object[]) ois.readObject(),
|
||||
ois.readObject(),
|
||||
LockMode.valueOf( (String) ois.readObject() ),
|
||||
ois.readBoolean(),
|
||||
ois.readBoolean(),
|
||||
ois.readBoolean(),
|
||||
persistenceContext
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtraState(EntityEntryExtraState extraState) {
|
||||
if ( next == null ) {
|
||||
next = extraState;
|
||||
}
|
||||
else {
|
||||
next.addExtraState( extraState );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends EntityEntryExtraState> T getExtraState(Class<T> extraStateType) {
|
||||
if ( next == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( extraStateType.isAssignableFrom( next.getClass() ) ) {
|
||||
return (T) next;
|
||||
}
|
||||
else {
|
||||
return next.getExtraState( extraStateType );
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Saves the value for the given enum property.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @param value
|
||||
* the value to store; The caller must make sure that it matches
|
||||
* the given identifier
|
||||
*/
|
||||
private <E extends Enum<E>> void setCompressedValue(EnumState<E> state, E value) {
|
||||
// reset the bits for the given property to 0
|
||||
compressedState &= state.getUnsetMask();
|
||||
// store the numeric representation of the enum value at the right offset
|
||||
compressedState |= ( state.getValue( value ) << state.getOffset() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current value of the given enum property.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @return the current value of the specified property
|
||||
*/
|
||||
private <E extends Enum<E>> E getCompressedValue(EnumState<E> state) {
|
||||
// restore the numeric value from the bits at the right offset and return the corresponding enum constant
|
||||
int index = ( ( compressedState & state.getMask() ) >> state.getOffset() ) - 1;
|
||||
return index == - 1 ? null : state.getEnumConstants()[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value for the given boolean flag.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @param value
|
||||
* the value to store
|
||||
*/
|
||||
private void setCompressedValue(BooleanState state, boolean value) {
|
||||
compressedState &= state.getUnsetMask();
|
||||
compressedState |= ( state.getValue( value ) << state.getOffset() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current value of the given boolean flag.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @return the current value of the specified flag
|
||||
*/
|
||||
private boolean getCompressedValue(BooleanState state) {
|
||||
return ( ( compressedState & state.getMask() ) >> state.getOffset() ) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an enum value stored within a number value, using four bits starting at a specified offset.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
private static class EnumState<E extends Enum<E>> {
|
||||
|
||||
private static final EnumState<LockMode> LOCK_MODE = new EnumState<LockMode>( 0, LockMode.class );
|
||||
private static final EnumState<Status> STATUS = new EnumState<Status>( 4, Status.class );
|
||||
private static final EnumState<Status> PREVIOUS_STATUS = new EnumState<Status>( 8, Status.class );
|
||||
|
||||
private final int offset;
|
||||
private final E[] enumConstants;
|
||||
private final int mask;
|
||||
private final int unsetMask;
|
||||
|
||||
private EnumState(int offset, Class<E> enumType) {
|
||||
E[] enumConstants = enumType.getEnumConstants();
|
||||
|
||||
// In case any of the enums cannot be stored in 4 bits anymore, we'd have to re-structure the compressed
|
||||
// state int
|
||||
if ( enumConstants.length > 15 ) {
|
||||
throw new AssertionFailure( "Cannot store enum type " + enumType.getName() + " in compressed state as"
|
||||
+ " it has too many values." );
|
||||
}
|
||||
|
||||
this.offset = offset;
|
||||
this.enumConstants = enumConstants;
|
||||
|
||||
// a mask for reading the four bits, starting at the right offset
|
||||
this.mask = 0xF << offset;
|
||||
|
||||
// a mask for setting the four bits at the right offset to 0
|
||||
this.unsetMask = 0xFFFF & ~mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the numeric value to be stored for the given enum value.
|
||||
*/
|
||||
private int getValue(E value) {
|
||||
return value != null ? value.ordinal() + 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset within the number value at which this enum value is stored.
|
||||
*/
|
||||
private int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for reading this enum value from the number value storing it.
|
||||
*/
|
||||
private int getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for resetting this enum value from the number value storing it.
|
||||
*/
|
||||
private int getUnsetMask() {
|
||||
return unsetMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the constants of the represented enum which is cached for performance reasons.
|
||||
*/
|
||||
private E[] getEnumConstants() {
|
||||
return enumConstants;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a boolean flag stored within a number value, using one bit at a specified offset.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
private enum BooleanState {
|
||||
|
||||
EXISTS_IN_DATABASE(13),
|
||||
IS_BEING_REPLICATED(14),
|
||||
LOADED_WITH_LAZY_PROPERTIES_UNFETCHED(15);
|
||||
|
||||
private final int offset;
|
||||
private final int mask;
|
||||
private final int unsetMask;
|
||||
|
||||
private BooleanState(int offset) {
|
||||
this.offset = offset;
|
||||
this.mask = 0x1 << offset;
|
||||
this.unsetMask = 0xFFFF & ~mask;
|
||||
}
|
||||
|
||||
private int getValue(boolean value) {
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset within the number value at which this boolean flag is stored.
|
||||
*/
|
||||
private int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for reading this flag from the number value storing it.
|
||||
*/
|
||||
private int getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for resetting this flag from the number value storing it.
|
||||
*/
|
||||
private int getUnsetMask() {
|
||||
return unsetMask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
|||
import org.hibernate.engine.spi.CollectionEntry;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
|
@ -489,7 +490,8 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
final boolean disableVersionIncrement,
|
||||
boolean lazyPropertiesAreUnfetched) {
|
||||
|
||||
final EntityEntry e = new EntityEntry(
|
||||
EntityEntryFactory entityEntryFactory = session.getFactory().getServiceRegistry().getService( EntityEntryFactory.class );
|
||||
final EntityEntry e = entityEntryFactory.createEntityEntry(
|
||||
status,
|
||||
loadedState,
|
||||
rowId,
|
||||
|
|
|
@ -28,17 +28,8 @@ import java.io.ObjectInputStream;
|
|||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
|
||||
import org.hibernate.engine.internal.EntityEntryExtraStateHolder;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.UniqueKeyLoadable;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
|
||||
/**
|
||||
* We need an entry to tell us all about the current state of an object with respect to its persistent state
|
||||
|
@ -51,225 +42,41 @@ import org.hibernate.pretty.MessageHelper;
|
|||
* @author Gunnar Morling
|
||||
* @author Sanne Grinovero <sanne@hibernate.org>
|
||||
*/
|
||||
public final class EntityEntry implements Serializable {
|
||||
private final Serializable id;
|
||||
private Object[] loadedState;
|
||||
private Object version;
|
||||
private final EntityPersister persister; // permanent but we only need the entityName state in a non transient way
|
||||
private transient EntityKey cachedEntityKey; // cached EntityKey (lazy-initialized)
|
||||
private final transient Object rowId;
|
||||
private final transient PersistenceContext persistenceContext;
|
||||
private EntityEntryExtraState next;
|
||||
public interface EntityEntry {
|
||||
LockMode getLockMode();
|
||||
|
||||
/**
|
||||
* Holds several boolean and enum typed attributes in a very compact manner. Enum values are stored in 4 bits
|
||||
* (where 0 represents {@code null}, and each enum value is represented by its ordinal value + 1), thus allowing
|
||||
* for up to 15 values per enum. Boolean values are stored in one bit.
|
||||
* <p>
|
||||
* The value is structured as follows:
|
||||
*
|
||||
* <pre>
|
||||
* 1 - Lock mode
|
||||
* 2 - Status
|
||||
* 3 - Previous Status
|
||||
* 4 - existsInDatabase
|
||||
* 5 - isBeingReplicated
|
||||
* 6 - loadedWithLazyPropertiesUnfetched; NOTE: this is not updated when properties are fetched lazily!
|
||||
*
|
||||
* 0000 0000 | 0000 0000 | 0654 3333 | 2222 1111
|
||||
* </pre>
|
||||
* Use {@link #setCompressedValue(org.hibernate.engine.spi.EntityEntry.EnumState, Enum)},
|
||||
* {@link #getCompressedValue(org.hibernate.engine.spi.EntityEntry.EnumState, Class)} etc
|
||||
* to access the enums and booleans stored in this value.
|
||||
* <p>
|
||||
* Representing enum values by their ordinal value is acceptable for our case as this value itself is never
|
||||
* serialized or deserialized and thus is not affected should ordinal values change.
|
||||
*/
|
||||
private transient int compressedState;
|
||||
void setLockMode(LockMode lockMode);
|
||||
|
||||
/**
|
||||
* @deprecated the tenantId and entityMode parameters where removed: this constructor accepts but ignores them.
|
||||
* Use the other constructor!
|
||||
*/
|
||||
@Deprecated
|
||||
public EntityEntry(
|
||||
final Status status,
|
||||
final Object[] loadedState,
|
||||
final Object rowId,
|
||||
final Serializable id,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final EntityPersister persister,
|
||||
final EntityMode entityMode,
|
||||
final String tenantId,
|
||||
final boolean disableVersionIncrement,
|
||||
final boolean lazyPropertiesAreUnfetched,
|
||||
final PersistenceContext persistenceContext) {
|
||||
this( status, loadedState, rowId, id, version, lockMode, existsInDatabase,
|
||||
persister,disableVersionIncrement, lazyPropertiesAreUnfetched, persistenceContext );
|
||||
}
|
||||
Status getStatus();
|
||||
|
||||
public EntityEntry(
|
||||
final Status status,
|
||||
final Object[] loadedState,
|
||||
final Object rowId,
|
||||
final Serializable id,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final EntityPersister persister,
|
||||
final boolean disableVersionIncrement,
|
||||
final boolean lazyPropertiesAreUnfetched,
|
||||
final PersistenceContext persistenceContext) {
|
||||
setCompressedValue( EnumState.STATUS, status );
|
||||
// not useful strictly speaking but more explicit
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, null );
|
||||
// only retain loaded state if the status is not Status.READ_ONLY
|
||||
if ( status != Status.READ_ONLY ) {
|
||||
this.loadedState = loadedState;
|
||||
}
|
||||
this.id=id;
|
||||
this.rowId=rowId;
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
|
||||
this.version=version;
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
setCompressedValue( BooleanState.IS_BEING_REPLICATED, disableVersionIncrement );
|
||||
setCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED, lazyPropertiesAreUnfetched );
|
||||
this.persister=persister;
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
void setStatus(Status status);
|
||||
|
||||
/**
|
||||
* This for is used during custom deserialization handling
|
||||
*/
|
||||
@SuppressWarnings( {"JavaDoc"})
|
||||
private EntityEntry(
|
||||
final SessionFactoryImplementor factory,
|
||||
final String entityName,
|
||||
final Serializable id,
|
||||
final Status status,
|
||||
final Status previousStatus,
|
||||
final Object[] loadedState,
|
||||
final Object[] deletedState,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final boolean isBeingReplicated,
|
||||
final boolean loadedWithLazyPropertiesUnfetched,
|
||||
final PersistenceContext persistenceContext) {
|
||||
this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) );
|
||||
this.id = id;
|
||||
setCompressedValue( EnumState.STATUS, status );
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, previousStatus );
|
||||
this.loadedState = loadedState;
|
||||
setDeletedState( deletedState );
|
||||
this.version = version;
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
|
||||
setCompressedValue( BooleanState.IS_BEING_REPLICATED, isBeingReplicated );
|
||||
setCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED, loadedWithLazyPropertiesUnfetched );
|
||||
this.rowId = null; // this is equivalent to the old behavior...
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
Serializable getId();
|
||||
|
||||
public LockMode getLockMode() {
|
||||
return getCompressedValue( EnumState.LOCK_MODE );
|
||||
}
|
||||
Object[] getLoadedState();
|
||||
|
||||
public void setLockMode(LockMode lockMode) {
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
}
|
||||
Object[] getDeletedState();
|
||||
|
||||
public Status getStatus() {
|
||||
return getCompressedValue( EnumState.STATUS );
|
||||
}
|
||||
void setDeletedState(Object[] deletedState);
|
||||
|
||||
private Status getPreviousStatus() {
|
||||
return getCompressedValue( EnumState.PREVIOUS_STATUS );
|
||||
}
|
||||
boolean isExistsInDatabase();
|
||||
|
||||
public void setStatus(Status status) {
|
||||
if ( status == Status.READ_ONLY ) {
|
||||
//memory optimization
|
||||
loadedState = null;
|
||||
}
|
||||
Object getVersion();
|
||||
|
||||
Status currentStatus = this.getStatus();
|
||||
|
||||
if ( currentStatus != status ) {
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, currentStatus );
|
||||
setCompressedValue( EnumState.STATUS, status );
|
||||
}
|
||||
}
|
||||
|
||||
public Serializable getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Object[] getLoadedState() {
|
||||
return loadedState;
|
||||
}
|
||||
|
||||
private static final Object[] DEFAULT_DELETED_STATE = null;
|
||||
|
||||
public Object[] getDeletedState() {
|
||||
EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
|
||||
return extra != null ? extra.getDeletedState() : DEFAULT_DELETED_STATE;
|
||||
}
|
||||
|
||||
public void setDeletedState(Object[] deletedState) {
|
||||
EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
|
||||
if ( extra == null && deletedState == DEFAULT_DELETED_STATE ) {
|
||||
//this is the default value and we do not store the extra state
|
||||
return;
|
||||
}
|
||||
if ( extra == null ) {
|
||||
extra = new EntityEntryExtraStateHolder();
|
||||
addExtraState( extra );
|
||||
}
|
||||
extra.setDeletedState( deletedState );
|
||||
}
|
||||
|
||||
public boolean isExistsInDatabase() {
|
||||
return getCompressedValue( BooleanState.EXISTS_IN_DATABASE );
|
||||
}
|
||||
|
||||
public Object getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public EntityPersister getPersister() {
|
||||
return persister;
|
||||
}
|
||||
EntityPersister getPersister();
|
||||
|
||||
/**
|
||||
* Get the EntityKey based on this EntityEntry.
|
||||
* @return the EntityKey
|
||||
* @throws IllegalStateException if getId() is null
|
||||
*/
|
||||
public EntityKey getEntityKey() {
|
||||
if ( cachedEntityKey == null ) {
|
||||
if ( getId() == null ) {
|
||||
throw new IllegalStateException( "cannot generate an EntityKey when id is null.");
|
||||
}
|
||||
cachedEntityKey = new EntityKey( getId(), getPersister() );
|
||||
}
|
||||
return cachedEntityKey;
|
||||
}
|
||||
EntityKey getEntityKey();
|
||||
|
||||
public String getEntityName() {
|
||||
return persister == null ? null : persister.getEntityName();
|
||||
String getEntityName();
|
||||
|
||||
}
|
||||
boolean isBeingReplicated();
|
||||
|
||||
public boolean isBeingReplicated() {
|
||||
return getCompressedValue( BooleanState.IS_BEING_REPLICATED );
|
||||
}
|
||||
|
||||
public Object getRowId() {
|
||||
return rowId;
|
||||
}
|
||||
Object getRowId();
|
||||
|
||||
/**
|
||||
* Handle updating the internal state of the entry after actually performing
|
||||
|
@ -281,71 +88,23 @@ public final class EntityEntry implements Serializable {
|
|||
* new {@link #getLoadedState() loaded state}.
|
||||
* @param nextVersion The new version.
|
||||
*/
|
||||
public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) {
|
||||
this.loadedState = updatedState;
|
||||
setLockMode( LockMode.WRITE );
|
||||
|
||||
if ( getPersister().isVersioned() ) {
|
||||
this.version = nextVersion;
|
||||
getPersister().setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
|
||||
}
|
||||
|
||||
if ( getPersister().getInstrumentationMetadata().isInstrumented() ) {
|
||||
final FieldInterceptor interceptor = getPersister().getInstrumentationMetadata().extractInterceptor( entity );
|
||||
if ( interceptor != null ) {
|
||||
interceptor.clearDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if( entity instanceof SelfDirtinessTracker) {
|
||||
((SelfDirtinessTracker) entity).$$_hibernate_clearDirtyAttributes();
|
||||
}
|
||||
|
||||
persistenceContext.getSession()
|
||||
.getFactory()
|
||||
.getCustomEntityDirtinessStrategy()
|
||||
.resetDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
|
||||
}
|
||||
void postUpdate(Object entity, Object[] updatedState, Object nextVersion);
|
||||
|
||||
/**
|
||||
* After actually deleting a row, record the fact that the instance no longer
|
||||
* exists in the database
|
||||
*/
|
||||
public void postDelete() {
|
||||
setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() );
|
||||
setCompressedValue( EnumState.STATUS, Status.GONE );
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, false );
|
||||
}
|
||||
void postDelete();
|
||||
|
||||
/**
|
||||
* After actually inserting a row, record the fact that the instance exists on the
|
||||
* database (needed for identity-column key generation)
|
||||
*/
|
||||
public void postInsert(Object[] insertedState) {
|
||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, true );
|
||||
}
|
||||
void postInsert(Object[] insertedState);
|
||||
|
||||
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
||||
if ( getStatus() == Status.SAVING ) {
|
||||
return true;
|
||||
}
|
||||
else if ( earlyInsert ) {
|
||||
return !isExistsInDatabase();
|
||||
}
|
||||
else {
|
||||
return session.getPersistenceContext().getNullifiableEntityKeys().contains( getEntityKey() );
|
||||
}
|
||||
}
|
||||
boolean isNullifiable(boolean earlyInsert, SessionImplementor session);
|
||||
|
||||
public Object getLoadedValue(String propertyName) {
|
||||
if ( loadedState == null || propertyName == null ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex( propertyName );
|
||||
return loadedState[propertyIndex];
|
||||
}
|
||||
}
|
||||
Object getLoadedValue(String propertyName);
|
||||
|
||||
/**
|
||||
* Not sure this is the best method name, but the general idea here is to return {@code true} if the entity can
|
||||
|
@ -359,34 +118,7 @@ public final class EntityEntry implements Serializable {
|
|||
* @return {@code true} indicates that the entity could possibly be dirty and that dirty check
|
||||
* should happen; {@code false} indicates there is no way the entity can be dirty
|
||||
*/
|
||||
public boolean requiresDirtyCheck(Object entity) {
|
||||
return isModifiableEntity()
|
||||
&& ( !isUnequivocallyNonDirty( entity ) );
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"SimplifiableIfStatement"})
|
||||
private boolean isUnequivocallyNonDirty(Object entity) {
|
||||
|
||||
if(entity instanceof SelfDirtinessTracker)
|
||||
return ((SelfDirtinessTracker) entity).$$_hibernate_hasDirtyAttributes();
|
||||
|
||||
final CustomEntityDirtinessStrategy customEntityDirtinessStrategy =
|
||||
persistenceContext.getSession().getFactory().getCustomEntityDirtinessStrategy();
|
||||
if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) persistenceContext.getSession() ) ) {
|
||||
return ! customEntityDirtinessStrategy.isDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
|
||||
}
|
||||
|
||||
if ( getPersister().hasMutableProperties() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( getPersister().getInstrumentationMetadata().isInstrumented() ) {
|
||||
// the entity must be instrumented (otherwise we cant check dirty flag) and the dirty flag is false
|
||||
return ! getPersister().getInstrumentationMetadata().extractInterceptor( entity ).isDirty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
boolean requiresDirtyCheck(Object entity);
|
||||
|
||||
/**
|
||||
* Can the entity be modified?
|
||||
|
@ -399,66 +131,18 @@ public final class EntityEntry implements Serializable {
|
|||
* </ul>
|
||||
* @return true, if the entity is modifiable; false, otherwise,
|
||||
*/
|
||||
public boolean isModifiableEntity() {
|
||||
Status status = getStatus();
|
||||
Status previousStatus = getPreviousStatus();
|
||||
return getPersister().isMutable()
|
||||
&& status != Status.READ_ONLY
|
||||
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
|
||||
}
|
||||
boolean isModifiableEntity();
|
||||
|
||||
public void forceLocked(Object entity, Object nextVersion) {
|
||||
version = nextVersion;
|
||||
loadedState[ persister.getVersionProperty() ] = version;
|
||||
// TODO: use LockMode.PESSIMISTIC_FORCE_INCREMENT
|
||||
//noinspection deprecation
|
||||
setLockMode( LockMode.FORCE );
|
||||
persister.setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
|
||||
}
|
||||
void forceLocked(Object entity, Object nextVersion);
|
||||
|
||||
public boolean isReadOnly() {
|
||||
Status status = getStatus();
|
||||
if (status != Status.MANAGED && status != Status.READ_ONLY) {
|
||||
throw new HibernateException("instance was not in a valid state");
|
||||
}
|
||||
return status == Status.READ_ONLY;
|
||||
}
|
||||
boolean isReadOnly();
|
||||
|
||||
public void setReadOnly(boolean readOnly, Object entity) {
|
||||
if ( readOnly == isReadOnly() ) {
|
||||
// simply return since the status is not being changed
|
||||
return;
|
||||
}
|
||||
if ( readOnly ) {
|
||||
setStatus( Status.READ_ONLY );
|
||||
loadedState = null;
|
||||
}
|
||||
else {
|
||||
if ( ! persister.isMutable() ) {
|
||||
throw new IllegalStateException( "Cannot make an immutable entity modifiable." );
|
||||
}
|
||||
setStatus( Status.MANAGED );
|
||||
loadedState = getPersister().getPropertyValues( entity );
|
||||
persistenceContext.getNaturalIdHelper().manageLocalNaturalIdCrossReference(
|
||||
persister,
|
||||
id,
|
||||
loadedState,
|
||||
null,
|
||||
CachedNaturalIdValueSource.LOAD
|
||||
);
|
||||
}
|
||||
}
|
||||
void setReadOnly(boolean readOnly, Object entity);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EntityEntry" +
|
||||
MessageHelper.infoString( getPersister().getEntityName(), id ) +
|
||||
'(' + getStatus() + ')';
|
||||
}
|
||||
String toString();
|
||||
|
||||
public boolean isLoadedWithLazyPropertiesUnfetched() {
|
||||
return getCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED );
|
||||
}
|
||||
boolean isLoadedWithLazyPropertiesUnfetched();
|
||||
|
||||
/**
|
||||
* Custom serialization routine used during serialization of a
|
||||
|
@ -468,250 +152,12 @@ public final class EntityEntry implements Serializable {
|
|||
*
|
||||
* @throws java.io.IOException If a stream error occurs
|
||||
*/
|
||||
public void serialize(ObjectOutputStream oos) throws IOException {
|
||||
Status previousStatus = getPreviousStatus();
|
||||
oos.writeObject( getEntityName() );
|
||||
oos.writeObject( id );
|
||||
oos.writeObject( getStatus().name() );
|
||||
oos.writeObject( (previousStatus == null ? "" : previousStatus.name()) );
|
||||
// todo : potentially look at optimizing these two arrays
|
||||
oos.writeObject( loadedState );
|
||||
oos.writeObject( getDeletedState() );
|
||||
oos.writeObject( version );
|
||||
oos.writeObject( getLockMode().toString() );
|
||||
oos.writeBoolean( isExistsInDatabase() );
|
||||
oos.writeBoolean( isBeingReplicated() );
|
||||
oos.writeBoolean( isLoadedWithLazyPropertiesUnfetched() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserialization routine used during deserialization of a
|
||||
* Session/PersistenceContext for increased performance.
|
||||
*
|
||||
* @param ois The stream from which to read the entry.
|
||||
* @param persistenceContext The context being deserialized.
|
||||
*
|
||||
* @return The deserialized EntityEntry
|
||||
*
|
||||
* @throws IOException If a stream error occurs
|
||||
* @throws ClassNotFoundException If any of the classes declared in the stream
|
||||
* cannot be found
|
||||
*/
|
||||
public static EntityEntry deserialize(
|
||||
ObjectInputStream ois,
|
||||
PersistenceContext persistenceContext) throws IOException, ClassNotFoundException {
|
||||
String previousStatusString;
|
||||
return new EntityEntry(
|
||||
persistenceContext.getSession().getFactory(),
|
||||
(String) ois.readObject(),
|
||||
(Serializable) ois.readObject(),
|
||||
Status.valueOf( (String) ois.readObject() ),
|
||||
( previousStatusString = (String) ois.readObject() ).length() == 0
|
||||
? null
|
||||
: Status.valueOf( previousStatusString ),
|
||||
(Object[]) ois.readObject(),
|
||||
(Object[]) ois.readObject(),
|
||||
ois.readObject(),
|
||||
LockMode.valueOf( (String) ois.readObject() ),
|
||||
ois.readBoolean(),
|
||||
ois.readBoolean(),
|
||||
ois.readBoolean(),
|
||||
persistenceContext
|
||||
);
|
||||
}
|
||||
void serialize(ObjectOutputStream oos) throws IOException;
|
||||
|
||||
//the following methods are handling extraState contracts.
|
||||
//they are not shared by a common superclass to avoid alignment padding
|
||||
//we are trading off duplication for padding efficiency
|
||||
public void addExtraState(EntityEntryExtraState extraState) {
|
||||
if ( next == null ) {
|
||||
next = extraState;
|
||||
}
|
||||
else {
|
||||
next.addExtraState( extraState );
|
||||
}
|
||||
}
|
||||
void addExtraState(EntityEntryExtraState extraState);
|
||||
|
||||
public <T extends EntityEntryExtraState> T getExtraState(Class<T> extraStateType) {
|
||||
if ( next == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( extraStateType.isAssignableFrom( next.getClass() ) ) {
|
||||
return (T) next;
|
||||
}
|
||||
else {
|
||||
return next.getExtraState( extraStateType );
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Saves the value for the given enum property.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @param value
|
||||
* the value to store; The caller must make sure that it matches
|
||||
* the given identifier
|
||||
*/
|
||||
private <E extends Enum<E>> void setCompressedValue(EnumState<E> state, E value) {
|
||||
// reset the bits for the given property to 0
|
||||
compressedState &= state.getUnsetMask();
|
||||
// store the numeric representation of the enum value at the right offset
|
||||
compressedState |= ( state.getValue( value ) << state.getOffset() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current value of the given enum property.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @return the current value of the specified property
|
||||
*/
|
||||
private <E extends Enum<E>> E getCompressedValue(EnumState<E> state) {
|
||||
// restore the numeric value from the bits at the right offset and return the corresponding enum constant
|
||||
int index = ( ( compressedState & state.getMask() ) >> state.getOffset() ) - 1;
|
||||
return index == - 1 ? null : state.getEnumConstants()[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value for the given boolean flag.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @param value
|
||||
* the value to store
|
||||
*/
|
||||
private void setCompressedValue(BooleanState state, boolean value) {
|
||||
compressedState &= state.getUnsetMask();
|
||||
compressedState |= ( state.getValue( value ) << state.getOffset() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current value of the given boolean flag.
|
||||
*
|
||||
* @param state
|
||||
* identifies the value to store
|
||||
* @return the current value of the specified flag
|
||||
*/
|
||||
private boolean getCompressedValue(BooleanState state) {
|
||||
return ( ( compressedState & state.getMask() ) >> state.getOffset() ) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an enum value stored within a number value, using four bits starting at a specified offset.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
private static class EnumState<E extends Enum<E>> {
|
||||
|
||||
private static final EnumState<LockMode> LOCK_MODE = new EnumState<LockMode>( 0, LockMode.class );
|
||||
private static final EnumState<Status> STATUS = new EnumState<Status>( 4, Status.class );
|
||||
private static final EnumState<Status> PREVIOUS_STATUS = new EnumState<Status>( 8, Status.class );
|
||||
|
||||
private final int offset;
|
||||
private final E[] enumConstants;
|
||||
private final int mask;
|
||||
private final int unsetMask;
|
||||
|
||||
private EnumState(int offset, Class<E> enumType) {
|
||||
E[] enumConstants = enumType.getEnumConstants();
|
||||
|
||||
// In case any of the enums cannot be stored in 4 bits anymore, we'd have to re-structure the compressed
|
||||
// state int
|
||||
if ( enumConstants.length > 15 ) {
|
||||
throw new AssertionFailure( "Cannot store enum type " + enumType.getName() + " in compressed state as"
|
||||
+ " it has too many values." );
|
||||
}
|
||||
|
||||
this.offset = offset;
|
||||
this.enumConstants = enumConstants;
|
||||
|
||||
// a mask for reading the four bits, starting at the right offset
|
||||
this.mask = 0xF << offset;
|
||||
|
||||
// a mask for setting the four bits at the right offset to 0
|
||||
this.unsetMask = 0xFFFF & ~mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the numeric value to be stored for the given enum value.
|
||||
*/
|
||||
private int getValue(E value) {
|
||||
return value != null ? value.ordinal() + 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset within the number value at which this enum value is stored.
|
||||
*/
|
||||
private int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for reading this enum value from the number value storing it.
|
||||
*/
|
||||
private int getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for resetting this enum value from the number value storing it.
|
||||
*/
|
||||
private int getUnsetMask() {
|
||||
return unsetMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the constants of the represented enum which is cached for performance reasons.
|
||||
*/
|
||||
private E[] getEnumConstants() {
|
||||
return enumConstants;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a boolean flag stored within a number value, using one bit at a specified offset.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
private enum BooleanState {
|
||||
|
||||
EXISTS_IN_DATABASE(13),
|
||||
IS_BEING_REPLICATED(14),
|
||||
LOADED_WITH_LAZY_PROPERTIES_UNFETCHED(15);
|
||||
|
||||
private final int offset;
|
||||
private final int mask;
|
||||
private final int unsetMask;
|
||||
|
||||
private BooleanState(int offset) {
|
||||
this.offset = offset;
|
||||
this.mask = 0x1 << offset;
|
||||
this.unsetMask = 0xFFFF & ~mask;
|
||||
}
|
||||
|
||||
private int getValue(boolean value) {
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset within the number value at which this boolean flag is stored.
|
||||
*/
|
||||
private int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for reading this flag from the number value storing it.
|
||||
*/
|
||||
private int getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit mask for resetting this flag from the number value storing it.
|
||||
*/
|
||||
private int getUnsetMask() {
|
||||
return unsetMask;
|
||||
}
|
||||
}
|
||||
<T extends EntityEntryExtraState> T getExtraState(Class<T> extraStateType);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.hibernate.engine.spi;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.service.Service;
|
||||
|
||||
/**
|
||||
* Contract to build {@link org.hibernate.engine.spi.EntityEntry}
|
||||
*
|
||||
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||
*/
|
||||
public interface EntityEntryFactory extends Service {
|
||||
|
||||
/**
|
||||
* Creates {@link org.hibernate.engine.spi.EntityEntry}.
|
||||
*/
|
||||
EntityEntry createEntityEntry(
|
||||
final Status status,
|
||||
final Object[] loadedState,
|
||||
final Object rowId,
|
||||
final Serializable id,
|
||||
final Object version,
|
||||
final LockMode lockMode,
|
||||
final boolean existsInDatabase,
|
||||
final EntityPersister persister,
|
||||
final boolean disableVersionIncrement,
|
||||
final boolean lazyPropertiesAreUnfetched,
|
||||
final PersistenceContext persistenceContext);
|
||||
}
|
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.engine.internal.EntityEntryFactoryInitiator;
|
||||
import org.hibernate.engine.query.spi.NativeQueryInterpreterInitiator;
|
||||
import org.hibernate.engine.spi.CacheInitiator;
|
||||
import org.hibernate.event.service.internal.EventListenerServiceInitiator;
|
||||
|
@ -48,6 +49,7 @@ public class StandardSessionFactoryServiceInitiators {
|
|||
serviceInitiators.add( EventListenerServiceInitiator.INSTANCE );
|
||||
serviceInitiators.add( StatisticsInitiator.INSTANCE );
|
||||
serviceInitiators.add( CacheInitiator.INSTANCE );
|
||||
serviceInitiators.add( EntityEntryFactoryInitiator.INSTANCE );
|
||||
|
||||
serviceInitiators.add( NativeQueryInterpreterInitiator.INSTANCE );
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.io.ObjectInputStream;
|
|||
import java.io.ObjectOutputStream;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.internal.MutableEntityEntry;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
|
@ -118,7 +119,7 @@ public class EntityEntryTest {
|
|||
oos.flush();
|
||||
|
||||
InputStream is = new ByteArrayInputStream( baos.toByteArray() );
|
||||
EntityEntry deserializedEntry = EntityEntry.deserialize(new ObjectInputStream( is ), getPersistenceContextMock() );
|
||||
EntityEntry deserializedEntry = MutableEntityEntry.deserialize(new ObjectInputStream( is ), getPersistenceContextMock() );
|
||||
|
||||
assertEquals( LockMode.OPTIMISTIC, deserializedEntry.getLockMode() );
|
||||
assertEquals( Status.MANAGED, deserializedEntry.getStatus() );
|
||||
|
@ -129,7 +130,7 @@ public class EntityEntryTest {
|
|||
|
||||
private EntityEntry createEntityEntry() {
|
||||
|
||||
return new EntityEntry(
|
||||
return new MutableEntityEntry(
|
||||
Status.MANAGED, // status
|
||||
new Object[]{}, // loadedState
|
||||
1L, // rowId
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
|||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
|
@ -222,7 +223,7 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
|||
}
|
||||
|
||||
static EntityEntry makeEntityEntry() {
|
||||
return new EntityEntry(
|
||||
return new DefaultEntityEntryFactory().createEntityEntry(
|
||||
Status.MANAGED,
|
||||
null,
|
||||
null,
|
||||
|
|
Loading…
Reference in New Issue