mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-27 14:30:16 +00:00
HHH-8683 Class org.hibernate.engine.spi.EntityEntry consumes lots of memory
This commit is contained in:
parent
d84c6b35aa
commit
dedd24afc2
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014, 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.engine.spi.EntityEntryExtraState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains optional state from {@link org.hibernate.engine.spi.EntityEntry}.
|
||||||
|
*
|
||||||
|
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||||
|
*/
|
||||||
|
public class EntityEntryExtraStateHolder implements EntityEntryExtraState {
|
||||||
|
private EntityEntryExtraState next;
|
||||||
|
private Object[] deletedState;
|
||||||
|
|
||||||
|
public Object[] getDeletedState() {
|
||||||
|
return deletedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeletedState(Object[] deletedState) {
|
||||||
|
this.deletedState = deletedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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
|
||||||
|
@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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Hibernate, Relational Persistence for Idiomatic Java
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
*
|
*
|
||||||
* Copyright (c) 2010, Red Hat Inc. or third-party contributors as
|
* Copyright (c) 2010-2014, Red Hat Inc. or third-party contributors as
|
||||||
* indicated by the @author tags or express copyright attribution
|
* indicated by the @author tags or express copyright attribution
|
||||||
* statements applied by the authors. All third-party contributions are
|
* statements applied by the authors. All third-party contributions are
|
||||||
* distributed under license by Red Hat Inc.
|
* distributed under license by Red Hat Inc.
|
||||||
@ -28,12 +28,14 @@
|
|||||||
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.CustomEntityDirtinessStrategy;
|
||||||
import org.hibernate.EntityMode;
|
import org.hibernate.EntityMode;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
|
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.persister.entity.UniqueKeyLoadable;
|
||||||
import org.hibernate.pretty.MessageHelper;
|
import org.hibernate.pretty.MessageHelper;
|
||||||
@ -41,26 +43,49 @@
|
|||||||
/**
|
/**
|
||||||
* 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
|
||||||
*
|
*
|
||||||
|
* 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 Gavin King
|
||||||
|
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||||
|
* @author Gunnar Morling
|
||||||
|
* @author Sanne Grinovero <sanne@hibernate.org>
|
||||||
*/
|
*/
|
||||||
public final class EntityEntry implements Serializable {
|
public final class EntityEntry implements Serializable {
|
||||||
private LockMode lockMode;
|
|
||||||
private Status status;
|
|
||||||
private Status previousStatus;
|
|
||||||
private final Serializable id;
|
private final Serializable id;
|
||||||
private Object[] loadedState;
|
private Object[] loadedState;
|
||||||
private Object[] deletedState;
|
|
||||||
private boolean existsInDatabase;
|
|
||||||
private Object version;
|
private Object version;
|
||||||
private transient EntityPersister persister;
|
private final EntityPersister persister; // permanent but we only need the entityName state in a non transient way
|
||||||
private final String entityName;
|
private transient EntityKey cachedEntityKey; // cached EntityKey (lazy-initialized)
|
||||||
// cached EntityKey (lazy-initialized)
|
|
||||||
private transient EntityKey cachedEntityKey;
|
|
||||||
private boolean isBeingReplicated;
|
|
||||||
//NOTE: this is not updated when properties are fetched lazily!
|
|
||||||
private boolean loadedWithLazyPropertiesUnfetched;
|
|
||||||
private final transient Object rowId;
|
private final transient Object rowId;
|
||||||
private final transient PersistenceContext persistenceContext;
|
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(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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated the tenantId and entityMode parameters where removed: this constructor accepts but ignores them.
|
* @deprecated the tenantId and entityMode parameters where removed: this constructor accepts but ignores them.
|
||||||
@ -97,21 +122,21 @@ public EntityEntry(
|
|||||||
final boolean disableVersionIncrement,
|
final boolean disableVersionIncrement,
|
||||||
final boolean lazyPropertiesAreUnfetched,
|
final boolean lazyPropertiesAreUnfetched,
|
||||||
final PersistenceContext persistenceContext) {
|
final PersistenceContext persistenceContext) {
|
||||||
this.status = status;
|
setCompressedValue( EnumState.STATUS, status );
|
||||||
this.previousStatus = null;
|
// not useful strictly speaking but more explicit
|
||||||
|
setCompressedValue( EnumState.PREVIOUS_STATUS, null );
|
||||||
// only retain loaded state if the status is not Status.READ_ONLY
|
// only retain loaded state if the status is not Status.READ_ONLY
|
||||||
if ( status != Status.READ_ONLY ) {
|
if ( status != Status.READ_ONLY ) {
|
||||||
this.loadedState = loadedState;
|
this.loadedState = loadedState;
|
||||||
}
|
}
|
||||||
this.id=id;
|
this.id=id;
|
||||||
this.rowId=rowId;
|
this.rowId=rowId;
|
||||||
this.existsInDatabase=existsInDatabase;
|
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
|
||||||
this.version=version;
|
this.version=version;
|
||||||
this.lockMode=lockMode;
|
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||||
this.isBeingReplicated=disableVersionIncrement;
|
setCompressedValue( BooleanState.IS_BEING_REPLICATED, disableVersionIncrement );
|
||||||
this.loadedWithLazyPropertiesUnfetched = lazyPropertiesAreUnfetched;
|
setCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED, lazyPropertiesAreUnfetched );
|
||||||
this.persister=persister;
|
this.persister=persister;
|
||||||
this.entityName = persister == null ? null : persister.getEntityName();
|
|
||||||
this.persistenceContext = persistenceContext;
|
this.persistenceContext = persistenceContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,33 +158,35 @@ private EntityEntry(
|
|||||||
final boolean isBeingReplicated,
|
final boolean isBeingReplicated,
|
||||||
final boolean loadedWithLazyPropertiesUnfetched,
|
final boolean loadedWithLazyPropertiesUnfetched,
|
||||||
final PersistenceContext persistenceContext) {
|
final PersistenceContext persistenceContext) {
|
||||||
this.entityName = entityName;
|
|
||||||
this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) );
|
this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) );
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.status = status;
|
setCompressedValue( EnumState.STATUS, status );
|
||||||
this.previousStatus = previousStatus;
|
setCompressedValue( EnumState.PREVIOUS_STATUS, previousStatus );
|
||||||
this.loadedState = loadedState;
|
this.loadedState = loadedState;
|
||||||
this.deletedState = deletedState;
|
setDeletedState( deletedState );
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.lockMode = lockMode;
|
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||||
this.existsInDatabase = existsInDatabase;
|
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
|
||||||
this.isBeingReplicated = isBeingReplicated;
|
setCompressedValue( BooleanState.IS_BEING_REPLICATED, isBeingReplicated );
|
||||||
this.loadedWithLazyPropertiesUnfetched = loadedWithLazyPropertiesUnfetched;
|
setCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED, loadedWithLazyPropertiesUnfetched );
|
||||||
// this is equivalent to the old behavior...
|
this.rowId = null; // this is equivalent to the old behavior...
|
||||||
this.rowId = null;
|
|
||||||
this.persistenceContext = persistenceContext;
|
this.persistenceContext = persistenceContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LockMode getLockMode() {
|
public LockMode getLockMode() {
|
||||||
return lockMode;
|
return getCompressedValue( EnumState.LOCK_MODE, LockMode.class );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLockMode(LockMode lockMode) {
|
public void setLockMode(LockMode lockMode) {
|
||||||
this.lockMode = lockMode;
|
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getStatus() {
|
public Status getStatus() {
|
||||||
return status;
|
return getCompressedValue( EnumState.STATUS, Status.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
private Status getPreviousStatus() {
|
||||||
|
return getCompressedValue( EnumState.PREVIOUS_STATUS, Status.class );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStatus(Status status) {
|
public void setStatus(Status status) {
|
||||||
@ -167,9 +194,12 @@ public void setStatus(Status status) {
|
|||||||
//memory optimization
|
//memory optimization
|
||||||
loadedState = null;
|
loadedState = null;
|
||||||
}
|
}
|
||||||
if ( this.status != status ) {
|
|
||||||
this.previousStatus = this.status;
|
Status currentStatus = this.getStatus();
|
||||||
this.status = status;
|
|
||||||
|
if ( currentStatus != status ) {
|
||||||
|
setCompressedValue( EnumState.PREVIOUS_STATUS, currentStatus );
|
||||||
|
setCompressedValue( EnumState.STATUS, status );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,16 +211,28 @@ public Object[] getLoadedState() {
|
|||||||
return loadedState;
|
return loadedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Object[] DEFAULT_DELETED_STATE = null;
|
||||||
|
|
||||||
public Object[] getDeletedState() {
|
public Object[] getDeletedState() {
|
||||||
return deletedState;
|
EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
|
||||||
|
return extra != null ? extra.getDeletedState() : DEFAULT_DELETED_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDeletedState(Object[] deletedState) {
|
public void setDeletedState(Object[] deletedState) {
|
||||||
this.deletedState = 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() {
|
public boolean isExistsInDatabase() {
|
||||||
return existsInDatabase;
|
return getCompressedValue( BooleanState.EXISTS_IN_DATABASE );
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getVersion() {
|
public Object getVersion() {
|
||||||
@ -217,11 +259,12 @@ public EntityKey getEntityKey() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getEntityName() {
|
public String getEntityName() {
|
||||||
return entityName;
|
return persister == null ? null : persister.getEntityName();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBeingReplicated() {
|
public boolean isBeingReplicated() {
|
||||||
return isBeingReplicated;
|
return getCompressedValue( BooleanState.IS_BEING_REPLICATED );
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getRowId() {
|
public Object getRowId() {
|
||||||
@ -269,9 +312,9 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion)
|
|||||||
* exists in the database
|
* exists in the database
|
||||||
*/
|
*/
|
||||||
public void postDelete() {
|
public void postDelete() {
|
||||||
previousStatus = status;
|
setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() );
|
||||||
status = Status.GONE;
|
setCompressedValue( EnumState.STATUS, Status.GONE );
|
||||||
existsInDatabase = false;
|
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -279,7 +322,7 @@ public void postDelete() {
|
|||||||
* database (needed for identity-column key generation)
|
* database (needed for identity-column key generation)
|
||||||
*/
|
*/
|
||||||
public void postInsert(Object[] insertedState) {
|
public void postInsert(Object[] insertedState) {
|
||||||
existsInDatabase = true;
|
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
||||||
@ -357,6 +400,8 @@ private boolean isUnequivocallyNonDirty(Object entity) {
|
|||||||
* @return true, if the entity is modifiable; false, otherwise,
|
* @return true, if the entity is modifiable; false, otherwise,
|
||||||
*/
|
*/
|
||||||
public boolean isModifiableEntity() {
|
public boolean isModifiableEntity() {
|
||||||
|
Status status = getStatus();
|
||||||
|
Status previousStatus = getPreviousStatus();
|
||||||
return getPersister().isMutable()
|
return getPersister().isMutable()
|
||||||
&& status != Status.READ_ONLY
|
&& status != Status.READ_ONLY
|
||||||
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
|
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
|
||||||
@ -372,8 +417,9 @@ public void forceLocked(Object entity, Object nextVersion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReadOnly() {
|
public boolean isReadOnly() {
|
||||||
if ( status != Status.MANAGED && status != Status.READ_ONLY ) {
|
Status status = getStatus();
|
||||||
throw new HibernateException( "instance was not in a valid state" );
|
if (status != Status.MANAGED && status != Status.READ_ONLY) {
|
||||||
|
throw new HibernateException("instance was not in a valid state");
|
||||||
}
|
}
|
||||||
return status == Status.READ_ONLY;
|
return status == Status.READ_ONLY;
|
||||||
}
|
}
|
||||||
@ -405,11 +451,13 @@ public void setReadOnly(boolean readOnly, Object entity) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "EntityEntry" + MessageHelper.infoString( entityName, id ) + '(' + status + ')';
|
return "EntityEntry" +
|
||||||
|
MessageHelper.infoString( getPersister().getEntityName(), id ) +
|
||||||
|
'(' + getStatus() + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoadedWithLazyPropertiesUnfetched() {
|
public boolean isLoadedWithLazyPropertiesUnfetched() {
|
||||||
return loadedWithLazyPropertiesUnfetched;
|
return getCompressedValue( BooleanState.LOADED_WITH_LAZY_PROPERTIES_UNFETCHED );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -418,21 +466,22 @@ public boolean isLoadedWithLazyPropertiesUnfetched() {
|
|||||||
*
|
*
|
||||||
* @param oos The stream to which we should write the serial data.
|
* @param oos The stream to which we should write the serial data.
|
||||||
*
|
*
|
||||||
* @throws IOException If a stream error occurs
|
* @throws java.io.IOException If a stream error occurs
|
||||||
*/
|
*/
|
||||||
public void serialize(ObjectOutputStream oos) throws IOException {
|
public void serialize(ObjectOutputStream oos) throws IOException {
|
||||||
oos.writeObject( entityName );
|
Status previousStatus = getPreviousStatus();
|
||||||
|
oos.writeObject( getEntityName() );
|
||||||
oos.writeObject( id );
|
oos.writeObject( id );
|
||||||
oos.writeObject( status.name() );
|
oos.writeObject( getStatus().name() );
|
||||||
oos.writeObject( (previousStatus == null ? "" : previousStatus.name()) );
|
oos.writeObject( (previousStatus == null ? "" : previousStatus.name()) );
|
||||||
// todo : potentially look at optimizing these two arrays
|
// todo : potentially look at optimizing these two arrays
|
||||||
oos.writeObject( loadedState );
|
oos.writeObject( loadedState );
|
||||||
oos.writeObject( deletedState );
|
oos.writeObject( getDeletedState() );
|
||||||
oos.writeObject( version );
|
oos.writeObject( version );
|
||||||
oos.writeObject( lockMode.toString() );
|
oos.writeObject( getLockMode().toString() );
|
||||||
oos.writeBoolean( existsInDatabase );
|
oos.writeBoolean( isExistsInDatabase() );
|
||||||
oos.writeBoolean( isBeingReplicated );
|
oos.writeBoolean( isBeingReplicated() );
|
||||||
oos.writeBoolean( loadedWithLazyPropertiesUnfetched );
|
oos.writeBoolean( isLoadedWithLazyPropertiesUnfetched() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -470,4 +519,192 @@ public static EntityEntry deserialize(
|
|||||||
persistenceContext
|
persistenceContext
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void setCompressedValue(EnumState state, Enum<?> 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
|
||||||
|
* @param type
|
||||||
|
* the actual enum type of the given property; The caller must
|
||||||
|
* make sure that it matches the given identifier
|
||||||
|
* @return the current value of the specified property
|
||||||
|
*/
|
||||||
|
private <E extends Enum<?>> E getCompressedValue(EnumState state, Class<E> type) {
|
||||||
|
E[] enumConstants = type.getEnumConstants();
|
||||||
|
// 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 : enumConstants[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 enum EnumState {
|
||||||
|
|
||||||
|
LOCK_MODE(0, LockMode.class),
|
||||||
|
STATUS(4, Status.class),
|
||||||
|
PREVIOUS_STATUS(8, Status.class);
|
||||||
|
|
||||||
|
private final int offset;
|
||||||
|
private final int mask;
|
||||||
|
private final int unsetMask;
|
||||||
|
|
||||||
|
private <E extends Enum<?>> EnumState(int offset, Class<E> enumType) {
|
||||||
|
// In case any of the enums cannot be stored in 4 bits anymore, we'd have to re-structure the compressed
|
||||||
|
// state int
|
||||||
|
if ( enumType.getEnumConstants().length > 15 ) {
|
||||||
|
throw new AssertionFailure( "Cannot store enum type " + enumType.getName() + " in compressed state as"
|
||||||
|
+ " it has too many values." );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.offset = offset;
|
||||||
|
|
||||||
|
// 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(Enum<?> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,49 @@
|
|||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014, 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation methods for extra state objects attached to {@link org.hibernate.engine.spi.EntityEntry}.
|
||||||
|
*
|
||||||
|
* @author Emmanuel Bernard <emmanuel@hibernate.org>
|
||||||
|
*/
|
||||||
|
public interface EntityEntryExtraState {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach additional state to the core state of {@link org.hibernate.engine.spi.EntityEntry}
|
||||||
|
* <p>
|
||||||
|
* Implementations must delegate to the next state or add it as next state if last in line.
|
||||||
|
*/
|
||||||
|
void addExtraState(EntityEntryExtraState extraState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve additional state by class type or null if no extra state of that type is present.
|
||||||
|
* <p>
|
||||||
|
* Implementations must return self if they match or delegate discovery to the next state in line.
|
||||||
|
*/
|
||||||
|
<T extends EntityEntryExtraState> T getExtraState(Class<T> extraStateType);
|
||||||
|
|
||||||
|
//a remove method is ugly to define and has not real use case that we found: left out
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user