HHH-9701 - Develop "immutable EntityEntry" impl

This commit is contained in:
John O'Hara 2015-04-01 18:06:30 +01:00
parent 5269bcbeeb
commit 3e5a8b6603
13 changed files with 1330 additions and 614 deletions

View File

@ -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 );
}
}

View File

@ -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;
}
}
}

View File

@ -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
*/

View File

@ -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;
}
}

View File

@ -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
);
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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;
}
/**

View File

@ -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,

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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