HHH-9701 - Develop "immutable EntityEntry" impl
This commit is contained in:
parent
5269bcbeeb
commit
3e5a8b6603
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2008, 2013, 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;
|
||||
|
||||
/**
|
||||
* This exception is thrown when an invalid LockMode is selected for an entity. This occurs if the
|
||||
* user tries to set an inappropriate LockMode for an entity.
|
||||
*
|
||||
* @author John O'Hara
|
||||
*/
|
||||
public class UnsupportedLockAttemptException extends HibernateException {
|
||||
public UnsupportedLockAttemptException(String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public UnsupportedLockAttemptException(Throwable cause) {
|
||||
super( cause );
|
||||
}
|
||||
|
||||
public UnsupportedLockAttemptException(String message, Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,668 @@
|
|||
/*
|
||||
* 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.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 abstract class AbstractEntityEntry implements Serializable, EntityEntry {
|
||||
protected final Serializable id;
|
||||
protected Object[] loadedState;
|
||||
protected Object version;
|
||||
protected final EntityPersister persister; // permanent but we only need the entityName state in a non transient way
|
||||
protected transient EntityKey cachedEntityKey; // cached EntityKey (lazy-initialized)
|
||||
protected final transient Object rowId;
|
||||
protected final transient PersistenceContext persistenceContext;
|
||||
protected 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.internal.AbstractEntityEntry.EnumState, Enum)},
|
||||
* {@link #getCompressedValue(org.hibernate.engine.internal.AbstractEntityEntry.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 AbstractEntityEntry(
|
||||
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 AbstractEntityEntry(
|
||||
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"})
|
||||
protected AbstractEntityEntry(
|
||||
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;
|
||||
}
|
||||
|
||||
final 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() {
|
||||
final 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 {
|
||||
final 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() {
|
||||
final Status status = getStatus();
|
||||
final 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() {
|
||||
final 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 {
|
||||
final 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() );
|
||||
}
|
||||
|
||||
|
||||
@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 );
|
||||
}
|
||||
}
|
||||
|
||||
public PersistenceContext getPersistenceContext(){
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected <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
|
||||
*/
|
||||
protected <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
|
||||
final 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
|
||||
*/
|
||||
protected 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
|
||||
*/
|
||||
protected 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
|
||||
*/
|
||||
protected static class EnumState<E extends Enum<E>> {
|
||||
|
||||
protected static final EnumState<LockMode> LOCK_MODE = new EnumState<LockMode>( 0, LockMode.class );
|
||||
protected static final EnumState<Status> STATUS = new EnumState<Status>( 4, Status.class );
|
||||
protected static final EnumState<Status> PREVIOUS_STATUS = new EnumState<Status>( 8, Status.class );
|
||||
|
||||
protected final int offset;
|
||||
protected final E[] enumConstants;
|
||||
protected final int mask;
|
||||
protected final int unsetMask;
|
||||
|
||||
private EnumState(int offset, Class<E> enumType) {
|
||||
final 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
|
||||
*/
|
||||
protected 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,18 +23,21 @@
|
|||
*/
|
||||
package org.hibernate.engine.internal;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Defines a context for maintaining the relation between an entity associated with the Session ultimately owning this
|
||||
* EntityEntryContext instance and that entity's corresponding EntityEntry. 2 approaches are supported:<ul>
|
||||
|
@ -239,7 +242,10 @@ public class EntityEntryContext {
|
|||
|
||||
// finally clean out the ManagedEntity and return the associated EntityEntry
|
||||
final EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
|
||||
managedEntity.$$_hibernate_setEntityEntry( null );
|
||||
// need to think about implications for memory leaks here if we don't removed reference to EntityEntry
|
||||
if( canClearEntityEntryReference(managedEntity) ){
|
||||
managedEntity.$$_hibernate_setEntityEntry( null );
|
||||
}
|
||||
return theEntityEntry;
|
||||
}
|
||||
|
||||
|
@ -278,7 +284,10 @@ public class EntityEntryContext {
|
|||
while ( node != null ) {
|
||||
final ManagedEntity nextNode = node.$$_hibernate_getNextManagedEntity();
|
||||
|
||||
node.$$_hibernate_setEntityEntry( null );
|
||||
if( canClearEntityEntryReference(node) ){
|
||||
node.$$_hibernate_setEntityEntry( null );
|
||||
}
|
||||
|
||||
node.$$_hibernate_setPreviousManagedEntity( null );
|
||||
node.$$_hibernate_setNextManagedEntity( null );
|
||||
|
||||
|
@ -331,6 +340,9 @@ public class EntityEntryContext {
|
|||
// so we know whether or not to build a ManagedEntityImpl on deserialize
|
||||
oos.writeBoolean( managedEntity == managedEntity.$$_hibernate_getEntityInstance() );
|
||||
oos.writeObject( managedEntity.$$_hibernate_getEntityInstance() );
|
||||
// we need to know which implementation of EntityEntry is being serialized
|
||||
oos.writeInt( managedEntity.$$_hibernate_getEntityEntry().getClass().getName().length() );
|
||||
oos.writeChars( managedEntity.$$_hibernate_getEntityEntry().getClass().getName() );
|
||||
managedEntity.$$_hibernate_getEntityEntry().serialize( oos );
|
||||
|
||||
managedEntity = managedEntity.$$_hibernate_getNextManagedEntity();
|
||||
|
@ -366,7 +378,16 @@ public class EntityEntryContext {
|
|||
for ( int i = 0; i < count; i++ ) {
|
||||
final boolean isEnhanced = ois.readBoolean();
|
||||
final Object entity = ois.readObject();
|
||||
final EntityEntry entry = MutableEntityEntry.deserialize(ois, rtn);
|
||||
|
||||
//Call deserialize method dynamically via reflection
|
||||
final int numChars = ois.readInt();
|
||||
final char[] entityEntryClassNameArr = new char[numChars];
|
||||
for ( int j = 0; j < numChars; j++ ) {
|
||||
entityEntryClassNameArr[j] = ois.readChar();
|
||||
}
|
||||
|
||||
final EntityEntry entry = deserializeEntityEntry( entityEntryClassNameArr, ois, rtn );
|
||||
|
||||
final ManagedEntity managedEntity;
|
||||
if ( isEnhanced ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
|
@ -396,10 +417,53 @@ public class EntityEntryContext {
|
|||
return context;
|
||||
}
|
||||
|
||||
private static EntityEntry deserializeEntityEntry(char[] entityEntryClassNameArr, ObjectInputStream ois, StatefulPersistenceContext rtn){
|
||||
EntityEntry entry = null;
|
||||
|
||||
final String entityEntryClassName = new String( entityEntryClassNameArr );
|
||||
final Class entityEntryClass = rtn.getSession().getFactory().getServiceRegistry().getService( ClassLoaderService.class ).classForName( entityEntryClassName );
|
||||
|
||||
try {
|
||||
final Method deserializeMethod = entityEntryClass.getDeclaredMethod( "deserialize", ObjectInputStream.class, PersistenceContext.class );
|
||||
entry = (EntityEntry) deserializeMethod.invoke( null, ois, rtn );
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
log.errorf( "Enable to deserialize [%s]", entityEntryClassName );
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
log.errorf( "Enable to deserialize [%s]", entityEntryClassName );
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
log.errorf( "Enable to deserialize [%s]", entityEntryClassName );
|
||||
}
|
||||
|
||||
return entry;
|
||||
|
||||
}
|
||||
|
||||
public int getNumberOfManagedEntities() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
Check instance type of EntityEntry and if type is ImmutableEntityEntry, check to see if entity is referenced cached in the second level cache
|
||||
*/
|
||||
private boolean canClearEntityEntryReference(ManagedEntity managedEntity){
|
||||
|
||||
if( managedEntity.$$_hibernate_getEntityEntry() == null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if( !(managedEntity.$$_hibernate_getEntityEntry() instanceof ImmutableEntityEntry) ) {
|
||||
return true;
|
||||
}
|
||||
else if( managedEntity.$$_hibernate_getEntityEntry().getPersister().canUseReferenceCacheEntries() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
/**
|
||||
* The wrapper for entity classes which do not implement ManagedEntity
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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.EntityMode;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.UnsupportedLockAttemptException;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
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 ImmutableEntityEntry extends AbstractEntityEntry {
|
||||
|
||||
/**
|
||||
* 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.internal.ImmutableEntityEntry.EnumState, Enum)},
|
||||
* {@link #getCompressedValue(org.hibernate.engine.internal.ImmutableEntityEntry.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 ImmutableEntityEntry(
|
||||
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 ImmutableEntityEntry(
|
||||
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) {
|
||||
|
||||
super( status, loadedState, rowId, id, version, lockMode, existsInDatabase, persister,
|
||||
disableVersionIncrement, lazyPropertiesAreUnfetched, persistenceContext );
|
||||
}
|
||||
|
||||
/**
|
||||
* This for is used during custom deserialization handling
|
||||
*/
|
||||
@SuppressWarnings( {"JavaDoc"})
|
||||
private ImmutableEntityEntry(
|
||||
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) {
|
||||
|
||||
super( factory, entityName, id, status, previousStatus, loadedState, deletedState,
|
||||
version, lockMode, existsInDatabase, isBeingReplicated, loadedWithLazyPropertiesUnfetched,
|
||||
persistenceContext );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLockMode(LockMode lockMode) {
|
||||
|
||||
switch(lockMode) {
|
||||
case NONE : case READ:
|
||||
setCompressedValue( EnumState.LOCK_MODE, lockMode );
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedLockAttemptException("Lock mode not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ImmutableEntityEntry(
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
public PersistenceContext getPersistenceContext(){
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.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;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Factory for the safe approach implementation of {@link org.hibernate.engine.spi.EntityEntry}.
|
||||
* <p/>
|
||||
* Smarter implementations could store less state.
|
||||
*
|
||||
* @author Emmanuel Bernard
|
||||
*/
|
||||
public class ImmutableEntityEntryFactory implements EntityEntryFactory {
|
||||
/**
|
||||
* Singleton access
|
||||
*/
|
||||
public static final ImmutableEntityEntryFactory INSTANCE = new ImmutableEntityEntryFactory();
|
||||
|
||||
private ImmutableEntityEntryFactory() {
|
||||
}
|
||||
|
||||
@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 ImmutableEntityEntry(
|
||||
status,
|
||||
loadedState,
|
||||
rowId,
|
||||
id,
|
||||
version,
|
||||
lockMode,
|
||||
existsInDatabase,
|
||||
persister,
|
||||
disableVersionIncrement,
|
||||
lazyPropertiesAreUnfetched,
|
||||
persistenceContext
|
||||
);
|
||||
}
|
||||
}
|
|
@ -23,29 +23,16 @@
|
|||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -59,41 +46,7 @@ import java.io.Serializable;
|
|||
* @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;
|
||||
public final class MutableEntityEntry extends AbstractEntityEntry {
|
||||
|
||||
/**
|
||||
* @deprecated the tenantId and entityMode parameters where removed: this constructor accepts but ignores them.
|
||||
|
@ -130,22 +83,9 @@ public final class MutableEntityEntry implements Serializable, EntityEntry {
|
|||
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;
|
||||
|
||||
super( status, loadedState, rowId, id, version, lockMode, existsInDatabase, persister,
|
||||
disableVersionIncrement, lazyPropertiesAreUnfetched, persistenceContext );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,304 +106,12 @@ public final class MutableEntityEntry implements Serializable, EntityEntry {
|
|||
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;
|
||||
|
||||
super( factory, entityName, id, status, previousStatus, loadedState, deletedState,
|
||||
version, lockMode, existsInDatabase, isBeingReplicated, loadedWithLazyPropertiesUnfetched,
|
||||
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
|
||||
|
@ -500,198 +148,4 @@ public final class MutableEntityEntry implements Serializable, EntityEntry {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,13 +40,13 @@ import org.hibernate.persister.entity.EntityPersister;
|
|||
*
|
||||
* @author Emmanuel Bernard
|
||||
*/
|
||||
public class DefaultEntityEntryFactory implements EntityEntryFactory {
|
||||
public class MutableEntityEntryFactory implements EntityEntryFactory {
|
||||
/**
|
||||
* Singleton access
|
||||
*/
|
||||
public static final DefaultEntityEntryFactory INSTANCE = new DefaultEntityEntryFactory();
|
||||
public static final MutableEntityEntryFactory INSTANCE = new MutableEntityEntryFactory();
|
||||
|
||||
private DefaultEntityEntryFactory() {
|
||||
private MutableEntityEntryFactory() {
|
||||
}
|
||||
|
||||
@Override
|
|
@ -23,22 +23,6 @@
|
|||
*/
|
||||
package org.hibernate.persister.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.FetchMode;
|
||||
|
@ -65,7 +49,8 @@ import org.hibernate.cache.spi.entry.UnstructuredCacheEntry;
|
|||
import org.hibernate.dialect.lock.LockingStrategy;
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.engine.internal.CacheHelper;
|
||||
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.MutableEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.ImmutableEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.StatefulPersistenceContext;
|
||||
import org.hibernate.engine.internal.Versioning;
|
||||
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
|
||||
|
@ -133,9 +118,24 @@ import org.hibernate.type.EntityType;
|
|||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.TypeHelper;
|
||||
import org.hibernate.type.VersionType;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Basic functionality for persisting an entity via JDBC
|
||||
* through either generated or custom SQL
|
||||
|
@ -158,6 +158,7 @@ public abstract class AbstractEntityPersister
|
|||
private final CacheEntryHelper cacheEntryHelper;
|
||||
private final EntityMetamodel entityMetamodel;
|
||||
private final EntityTuplizer entityTuplizer;
|
||||
private final EntityEntryFactory entityEntryFactory;
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
private final String[] rootTableKeyColumnNames;
|
||||
|
@ -279,6 +280,8 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
protected final BasicEntityPropertyMapping propertyMapping;
|
||||
|
||||
private final boolean useReferenceCacheEntries;
|
||||
|
||||
protected void addDiscriminatorToInsert(Insert insert) {}
|
||||
|
||||
protected void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {}
|
||||
|
@ -512,6 +515,13 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory );
|
||||
this.entityTuplizer = this.entityMetamodel.getTuplizer();
|
||||
|
||||
if( entityMetamodel.isMutable() ) {
|
||||
this.entityEntryFactory = MutableEntityEntryFactory.INSTANCE;
|
||||
}
|
||||
else {
|
||||
this.entityEntryFactory = ImmutableEntityEntryFactory.INSTANCE;
|
||||
}
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
int batch = persistentClass.getBatchSize();
|
||||
|
@ -784,7 +794,31 @@ public abstract class AbstractEntityPersister
|
|||
temporaryIdTableName = persistentClass.getTemporaryIdTableName();
|
||||
temporaryIdTableDDL = persistentClass.getTemporaryIdTableDDL();
|
||||
|
||||
|
||||
// Check if we can use Reference Cached entities in 2lc
|
||||
// todo : should really validate that the cache access type is read-only
|
||||
boolean refCacheEntries = true;
|
||||
if ( ! factory.getSettings().isDirectReferenceCacheEntriesEnabled() ) {
|
||||
refCacheEntries = false;
|
||||
}
|
||||
|
||||
// for now, limit this to just entities that:
|
||||
// 1) are immutable
|
||||
if ( entityMetamodel.isMutable() ) {
|
||||
refCacheEntries = false;
|
||||
}
|
||||
|
||||
// 2) have no associations. Eventually we want to be a little more lenient with associations.
|
||||
for ( Type type : getSubclassPropertyTypeClosure() ) {
|
||||
if ( type.isAssociationType() ) {
|
||||
refCacheEntries = false;
|
||||
}
|
||||
}
|
||||
|
||||
useReferenceCacheEntries = refCacheEntries;
|
||||
|
||||
this.cacheEntryHelper = buildCacheEntryHelper();
|
||||
|
||||
}
|
||||
|
||||
protected CacheEntryHelper buildCacheEntryHelper() {
|
||||
|
@ -805,26 +839,7 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
public boolean canUseReferenceCacheEntries() {
|
||||
// todo : should really validate that the cache access type is read-only
|
||||
|
||||
if ( ! factory.getSettings().isDirectReferenceCacheEntriesEnabled() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// for now, limit this to just entities that:
|
||||
// 1) are immutable
|
||||
if ( entityMetamodel.isMutable() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2) have no associations. Eventually we want to be a little more lenient with associations.
|
||||
for ( Type type : getSubclassPropertyTypeClosure() ) {
|
||||
if ( type.isAssociationType() ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return useReferenceCacheEntries;
|
||||
}
|
||||
|
||||
protected static String getTemplateFromString(String string, SessionFactoryImplementor factory) {
|
||||
|
@ -4815,8 +4830,7 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
@Override
|
||||
public EntityEntryFactory getEntityEntryFactory() {
|
||||
// todo : in ORM terms this should check #isMutable() and return an appropriate one.
|
||||
return DefaultEntityEntryFactory.INSTANCE;
|
||||
return this.entityEntryFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -46,7 +46,7 @@ import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
|||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.MutableEntityEntryFactory;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
|
@ -226,7 +226,7 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
|||
}
|
||||
|
||||
static EntityEntry makeEntityEntry() {
|
||||
return DefaultEntityEntryFactory.INSTANCE.createEntityEntry(
|
||||
return MutableEntityEntryFactory.INSTANCE.createEntityEntry(
|
||||
Status.MANAGED,
|
||||
null,
|
||||
null,
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, 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.test.cache;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
import org.hibernate.annotations.Immutable;
|
||||
import org.hibernate.annotations.Proxy;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.persistence.Cacheable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author John O'Hara
|
||||
*/
|
||||
public class ByteCodeEnhancedImmutableReferenceCacheTest extends BaseCoreFunctionalTestCase {
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
super.configure( configuration );
|
||||
configuration.setProperty( AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, "true" );
|
||||
configuration.setProperty( AvailableSettings.USE_QUERY_CACHE, "true" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[]{MyEnhancedReferenceData.class};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseOfDirectReferencesInCache() throws Exception {
|
||||
EntityPersister persister = (EntityPersister) sessionFactory().getClassMetadata( MyEnhancedReferenceData.class );
|
||||
assertFalse( persister.isMutable() );
|
||||
assertTrue( persister.buildCacheEntry( null, null, null, null ).isReferenceEntry() );
|
||||
assertFalse( persister.hasProxy() );
|
||||
|
||||
final MyEnhancedReferenceData myReferenceData = new MyEnhancedReferenceData( 1, "first item", "abc" );
|
||||
|
||||
// save a reference in one session
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
s.save( myReferenceData );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
assertNotNull( myReferenceData.$$_hibernate_getEntityEntry() );
|
||||
|
||||
// now load it in another
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
// MyEnhancedReferenceData loaded = (MyEnhancedReferenceData) s.get( MyEnhancedReferenceData.class, 1 );
|
||||
MyEnhancedReferenceData loaded = (MyEnhancedReferenceData) s.load( MyEnhancedReferenceData.class, 1 );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
// the 2 instances should be the same (==)
|
||||
assertTrue( "The two instances were different references", myReferenceData == loaded );
|
||||
|
||||
// now try query caching
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
MyEnhancedReferenceData queried = (MyEnhancedReferenceData) s.createQuery( "from MyEnhancedReferenceData" ).setCacheable( true ).list().get( 0 );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
// the 2 instances should be the same (==)
|
||||
assertTrue( "The two instances were different references", myReferenceData == queried );
|
||||
|
||||
// cleanup
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
s.delete( myReferenceData );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
|
||||
@Entity(name = "MyEnhancedReferenceData")
|
||||
@Immutable
|
||||
@Cacheable
|
||||
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
|
||||
@Proxy(lazy = false)
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public static class MyEnhancedReferenceData implements ManagedEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
private String theValue;
|
||||
|
||||
@Transient
|
||||
private transient EntityEntry entityEntry;
|
||||
@Transient
|
||||
private transient ManagedEntity previous;
|
||||
@Transient
|
||||
private transient ManagedEntity next;
|
||||
|
||||
public MyEnhancedReferenceData(Integer id, String name, String theValue) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.theValue = theValue;
|
||||
}
|
||||
|
||||
protected MyEnhancedReferenceData() {
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getTheValue() {
|
||||
return theValue;
|
||||
}
|
||||
|
||||
public void setTheValue(String theValue) {
|
||||
this.theValue = theValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object $$_hibernate_getEntityInstance() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityEntry $$_hibernate_getEntityEntry() {
|
||||
return entityEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
|
||||
this.entityEntry = entityEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedEntity $$_hibernate_getNextManagedEntity() {
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_setNextManagedEntity(ManagedEntity next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) {
|
||||
this.previous = previous;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ import org.hibernate.cache.spi.entry.CacheEntry;
|
|||
import org.hibernate.cache.spi.entry.CacheEntryStructure;
|
||||
import org.hibernate.cfg.NotYetImplementedException;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.MutableEntityEntryFactory;
|
||||
import org.hibernate.engine.spi.CascadeStyle;
|
||||
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
@ -122,7 +122,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
|
|||
|
||||
@Override
|
||||
public EntityEntryFactory getEntityEntryFactory() {
|
||||
return DefaultEntityEntryFactory.INSTANCE;
|
||||
return MutableEntityEntryFactory.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.hibernate.cache.spi.entry.CacheEntryStructure;
|
|||
import org.hibernate.cache.spi.entry.StandardCacheEntryImpl;
|
||||
import org.hibernate.cache.spi.entry.UnstructuredCacheEntry;
|
||||
import org.hibernate.cfg.NotYetImplementedException;
|
||||
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.MutableEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.TwoPhaseLoad;
|
||||
import org.hibernate.engine.spi.CascadeStyle;
|
||||
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||
|
@ -77,7 +77,7 @@ public class CustomPersister implements EntityPersister {
|
|||
|
||||
@Override
|
||||
public EntityEntryFactory getEntityEntryFactory() {
|
||||
return DefaultEntityEntryFactory.INSTANCE;
|
||||
return MutableEntityEntryFactory.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
|
|||
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
||||
import org.hibernate.cache.spi.entry.CacheEntry;
|
||||
import org.hibernate.cache.spi.entry.CacheEntryStructure;
|
||||
import org.hibernate.engine.internal.DefaultEntityEntryFactory;
|
||||
import org.hibernate.engine.internal.MutableEntityEntryFactory;
|
||||
import org.hibernate.engine.spi.CascadeStyle;
|
||||
import org.hibernate.engine.spi.EntityEntryFactory;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
@ -149,7 +149,7 @@ public class PersisterClassProviderTest {
|
|||
|
||||
@Override
|
||||
public EntityEntryFactory getEntityEntryFactory() {
|
||||
return DefaultEntityEntryFactory.INSTANCE;
|
||||
return MutableEntityEntryFactory.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue