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++ ) {
|
for ( int i = 0; i < count; i++ ) {
|
||||||
final boolean isEnhanced = ois.readBoolean();
|
final boolean isEnhanced = ois.readBoolean();
|
||||||
final Object entity = ois.readObject();
|
final Object entity = ois.readObject();
|
||||||
final EntityEntry entry = EntityEntry.deserialize( ois, rtn );
|
final EntityEntry entry = MutableEntityEntry.deserialize(ois, rtn);
|
||||||
final ManagedEntity managedEntity;
|
final ManagedEntity managedEntity;
|
||||||
if ( isEnhanced ) {
|
if ( isEnhanced ) {
|
||||||
managedEntity = (ManagedEntity) entity;
|
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.CollectionEntry;
|
||||||
import org.hibernate.engine.spi.CollectionKey;
|
import org.hibernate.engine.spi.CollectionKey;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
|
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
@ -489,7 +490,8 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
final boolean disableVersionIncrement,
|
final boolean disableVersionIncrement,
|
||||||
boolean lazyPropertiesAreUnfetched) {
|
boolean lazyPropertiesAreUnfetched) {
|
||||||
|
|
||||||
final EntityEntry e = new EntityEntry(
|
EntityEntryFactory entityEntryFactory = session.getFactory().getServiceRegistry().getService( EntityEntryFactory.class );
|
||||||
|
final EntityEntry e = entityEntryFactory.createEntityEntry(
|
||||||
status,
|
status,
|
||||||
loadedState,
|
loadedState,
|
||||||
rowId,
|
rowId,
|
||||||
|
|
|
@ -28,17 +28,8 @@ import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
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.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.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
|
* 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 Gunnar Morling
|
||||||
* @author Sanne Grinovero <sanne@hibernate.org>
|
* @author Sanne Grinovero <sanne@hibernate.org>
|
||||||
*/
|
*/
|
||||||
public final class EntityEntry implements Serializable {
|
public interface EntityEntry {
|
||||||
private final Serializable id;
|
LockMode getLockMode();
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
void setLockMode(LockMode lockMode);
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
Status getStatus();
|
||||||
* @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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityEntry(
|
void setStatus(Status status);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Serializable getId();
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LockMode getLockMode() {
|
Object[] getLoadedState();
|
||||||
return getCompressedValue( EnumState.LOCK_MODE );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLockMode(LockMode lockMode) {
|
Object[] getDeletedState();
|
||||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status getStatus() {
|
void setDeletedState(Object[] deletedState);
|
||||||
return getCompressedValue( EnumState.STATUS );
|
|
||||||
}
|
|
||||||
|
|
||||||
private Status getPreviousStatus() {
|
boolean isExistsInDatabase();
|
||||||
return getCompressedValue( EnumState.PREVIOUS_STATUS );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(Status status) {
|
Object getVersion();
|
||||||
if ( status == Status.READ_ONLY ) {
|
|
||||||
//memory optimization
|
|
||||||
loadedState = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status currentStatus = this.getStatus();
|
EntityPersister getPersister();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the EntityKey based on this EntityEntry.
|
* Get the EntityKey based on this EntityEntry.
|
||||||
* @return the EntityKey
|
* @return the EntityKey
|
||||||
* @throws IllegalStateException if getId() is null
|
* @throws IllegalStateException if getId() is null
|
||||||
*/
|
*/
|
||||||
public EntityKey getEntityKey() {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEntityName() {
|
String getEntityName();
|
||||||
return persister == null ? null : persister.getEntityName();
|
|
||||||
|
|
||||||
}
|
boolean isBeingReplicated();
|
||||||
|
|
||||||
public boolean isBeingReplicated() {
|
Object getRowId();
|
||||||
return getCompressedValue( BooleanState.IS_BEING_REPLICATED );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getRowId() {
|
|
||||||
return rowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle updating the internal state of the entry after actually performing
|
* 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}.
|
* new {@link #getLoadedState() loaded state}.
|
||||||
* @param nextVersion The new version.
|
* @param nextVersion The new version.
|
||||||
*/
|
*/
|
||||||
public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) {
|
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() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After actually deleting a row, record the fact that the instance no longer
|
* After actually deleting a row, record the fact that the instance no longer
|
||||||
* exists in the database
|
* exists in the database
|
||||||
*/
|
*/
|
||||||
public void postDelete() {
|
void postDelete();
|
||||||
setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() );
|
|
||||||
setCompressedValue( EnumState.STATUS, Status.GONE );
|
|
||||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After actually inserting a row, record the fact that the instance exists on the
|
* After actually inserting a row, record the fact that the instance exists on the
|
||||||
* database (needed for identity-column key generation)
|
* database (needed for identity-column key generation)
|
||||||
*/
|
*/
|
||||||
public void postInsert(Object[] insertedState) {
|
void postInsert(Object[] insertedState);
|
||||||
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
boolean isNullifiable(boolean earlyInsert, SessionImplementor session);
|
||||||
if ( getStatus() == Status.SAVING ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if ( earlyInsert ) {
|
|
||||||
return !isExistsInDatabase();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return session.getPersistenceContext().getNullifiableEntityKeys().contains( getEntityKey() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getLoadedValue(String propertyName) {
|
Object getLoadedValue(String propertyName);
|
||||||
if ( loadedState == null || propertyName == null ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex( propertyName );
|
|
||||||
return loadedState[propertyIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not sure this is the best method name, but the general idea here is to return {@code true} if the entity can
|
* 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
|
* @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
|
* should happen; {@code false} indicates there is no way the entity can be dirty
|
||||||
*/
|
*/
|
||||||
public boolean requiresDirtyCheck(Object entity) {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can the entity be modified?
|
* Can the entity be modified?
|
||||||
|
@ -399,66 +131,18 @@ public final class EntityEntry implements Serializable {
|
||||||
* </ul>
|
* </ul>
|
||||||
* @return true, if the entity is modifiable; false, otherwise,
|
* @return true, if the entity is modifiable; false, otherwise,
|
||||||
*/
|
*/
|
||||||
public boolean isModifiableEntity() {
|
boolean isModifiableEntity();
|
||||||
Status status = getStatus();
|
|
||||||
Status previousStatus = getPreviousStatus();
|
|
||||||
return getPersister().isMutable()
|
|
||||||
&& status != Status.READ_ONLY
|
|
||||||
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forceLocked(Object entity, Object nextVersion) {
|
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isReadOnly() {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReadOnly(boolean readOnly, Object entity) {
|
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
|
@Override
|
||||||
public String toString() {
|
String toString();
|
||||||
return "EntityEntry" +
|
|
||||||
MessageHelper.infoString( getPersister().getEntityName(), id ) +
|
|
||||||
'(' + getStatus() + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLoadedWithLazyPropertiesUnfetched() {
|
boolean isLoadedWithLazyPropertiesUnfetched();
|
||||||
return getCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom serialization routine used during serialization of a
|
* 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
|
* @throws java.io.IOException If a stream error occurs
|
||||||
*/
|
*/
|
||||||
public void serialize(ObjectOutputStream oos) throws IOException {
|
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//the following methods are handling extraState contracts.
|
//the following methods are handling extraState contracts.
|
||||||
//they are not shared by a common superclass to avoid alignment padding
|
//they are not shared by a common superclass to avoid alignment padding
|
||||||
//we are trading off duplication for padding efficiency
|
//we are trading off duplication for padding efficiency
|
||||||
public void addExtraState(EntityEntryExtraState extraState) {
|
void addExtraState(EntityEntryExtraState extraState);
|
||||||
if ( next == null ) {
|
|
||||||
next = extraState;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next.addExtraState( extraState );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends EntityEntryExtraState> T getExtraState(Class<T> extraStateType) {
|
<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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.engine.internal.EntityEntryFactoryInitiator;
|
||||||
import org.hibernate.engine.query.spi.NativeQueryInterpreterInitiator;
|
import org.hibernate.engine.query.spi.NativeQueryInterpreterInitiator;
|
||||||
import org.hibernate.engine.spi.CacheInitiator;
|
import org.hibernate.engine.spi.CacheInitiator;
|
||||||
import org.hibernate.event.service.internal.EventListenerServiceInitiator;
|
import org.hibernate.event.service.internal.EventListenerServiceInitiator;
|
||||||
|
@ -48,6 +49,7 @@ public class StandardSessionFactoryServiceInitiators {
|
||||||
serviceInitiators.add( EventListenerServiceInitiator.INSTANCE );
|
serviceInitiators.add( EventListenerServiceInitiator.INSTANCE );
|
||||||
serviceInitiators.add( StatisticsInitiator.INSTANCE );
|
serviceInitiators.add( StatisticsInitiator.INSTANCE );
|
||||||
serviceInitiators.add( CacheInitiator.INSTANCE );
|
serviceInitiators.add( CacheInitiator.INSTANCE );
|
||||||
|
serviceInitiators.add( EntityEntryFactoryInitiator.INSTANCE );
|
||||||
|
|
||||||
serviceInitiators.add( NativeQueryInterpreterInitiator.INSTANCE );
|
serviceInitiators.add( NativeQueryInterpreterInitiator.INSTANCE );
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
|
import org.hibernate.engine.internal.MutableEntityEntry;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,7 +119,7 @@ public class EntityEntryTest {
|
||||||
oos.flush();
|
oos.flush();
|
||||||
|
|
||||||
InputStream is = new ByteArrayInputStream( baos.toByteArray() );
|
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( LockMode.OPTIMISTIC, deserializedEntry.getLockMode() );
|
||||||
assertEquals( Status.MANAGED, deserializedEntry.getStatus() );
|
assertEquals( Status.MANAGED, deserializedEntry.getStatus() );
|
||||||
|
@ -129,7 +130,7 @@ public class EntityEntryTest {
|
||||||
|
|
||||||
private EntityEntry createEntityEntry() {
|
private EntityEntry createEntityEntry() {
|
||||||
|
|
||||||
return new EntityEntry(
|
return new MutableEntityEntry(
|
||||||
Status.MANAGED, // status
|
Status.MANAGED, // status
|
||||||
new Object[]{}, // loadedState
|
new Object[]{}, // loadedState
|
||||||
1L, // rowId
|
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.EnhancementContext;
|
||||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||||
|
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||||
import org.hibernate.engine.spi.CompositeOwner;
|
import org.hibernate.engine.spi.CompositeOwner;
|
||||||
import org.hibernate.engine.spi.CompositeTracker;
|
import org.hibernate.engine.spi.CompositeTracker;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
|
@ -222,7 +223,7 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
static EntityEntry makeEntityEntry() {
|
static EntityEntry makeEntityEntry() {
|
||||||
return new EntityEntry(
|
return new DefaultEntityEntryFactory().createEntityEntry(
|
||||||
Status.MANAGED,
|
Status.MANAGED,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
Loading…
Reference in New Issue