diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java index ba4a43e284..e97d659b2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java @@ -108,16 +108,12 @@ public void execute() throws HibernateException { final SessionFactoryImplementor factory = getSession().getFactory(); if ( isCachePutEnabled( persister, session ) ) { - - CacheEntry ce = new CacheEntry( + CacheEntry ce = persister.buildCacheEntry( + instance, getState(), - persister, - persister.hasUninitializedLazyProperties( instance ), version, - session, - instance - ); - + session + ); cacheEntry = persister.getCacheEntryStructure().structure(ce); final CacheKey ck = session.generateCacheKey( id, persister.getIdentifierType(), persister.getRootEntityName() ); boolean put = persister.getCacheAccessStrategy().insert( ck, cacheEntry, version ); diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java index db48dd1d38..ec4ad74f46 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java @@ -185,14 +185,7 @@ public void execute() throws HibernateException { } else { //TODO: inefficient if that cache is just going to ignore the updated state! - CacheEntry ce = new CacheEntry( - state, - persister, - persister.hasUninitializedLazyProperties( instance ), - nextVersion, - getSession(), - instance - ); + CacheEntry ce = persister.buildCacheEntry( instance,state, nextVersion, getSession() ); cacheEntry = persister.getCacheEntryStructure().structure( ce ); boolean put = persister.getCacheAccessStrategy().update( ck, cacheEntry, nextVersion, previousVersion ); if ( put && factory.getStatistics().isStatisticsEnabled() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/SharedCacheDelegate.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/SharedCacheDelegate.java new file mode 100644 index 0000000000..4ef4792a3f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/SharedCacheDelegate.java @@ -0,0 +1,43 @@ +/* + * 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.cache.spi; + +import java.io.Serializable; + +import org.hibernate.persister.entity.EntityPersister; + +/** + * Provides central access to putting and getting data into and out of the shared cache. + *

+ * Scope-wise, this delegate is per-session + * + * @author Steve Ebersole + */ +public interface SharedCacheDelegate { + + public void storeEntity(Object entity, EntityPersister persister, Serializable id); + public Object retrieveEntity(EntityPersister persister, Serializable id, Object optionalEntityInstance); + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/CacheEntry.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/CacheEntry.java index 69e4dc6c19..63d847563a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/CacheEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/CacheEntry.java @@ -25,137 +25,41 @@ import java.io.Serializable; -import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; import org.hibernate.Interceptor; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.event.service.spi.EventListenerGroup; -import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.EventType; -import org.hibernate.event.spi.PreLoadEvent; -import org.hibernate.event.spi.PreLoadEventListener; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.TypeHelper; /** * A cached instance of a persistent class * * @author Gavin King + * @author Steve Ebersole */ -public final class CacheEntry implements Serializable { +public interface CacheEntry extends Serializable { + public boolean isReferenceEntry(); - private final Serializable[] disassembledState; - private final String subclass; - private final boolean lazyPropertiesAreUnfetched; - private final Object version; - - public String getSubclass() { - return subclass; - } - - public boolean areLazyPropertiesUnfetched() { - return lazyPropertiesAreUnfetched; - } - - public CacheEntry( - final Object[] state, - final EntityPersister persister, - final boolean unfetched, - final Object version, - final SessionImplementor session, - final Object owner) - throws HibernateException { - //disassembled state gets put in a new array (we write to cache by value!) - this.disassembledState = TypeHelper.disassemble( - state, - persister.getPropertyTypes(), - persister.isLazyPropertiesCacheable() ? - null : persister.getPropertyLaziness(), - session, - owner - ); - subclass = persister.getEntityName(); - lazyPropertiesAreUnfetched = unfetched || !persister.isLazyPropertiesCacheable(); - this.version = version; - } - - public Object getVersion() { - return version; - } + /** + * Hibernate stores all entries pertaining to a given entity hierarchy in a single region. This attribute + * tells us the specific entity type represented by the cached data. + * + * @return The entry's exact entity type. + */ + public String getSubclass(); - CacheEntry(Serializable[] state, String subclass, boolean unfetched, Object version) { - this.disassembledState = state; - this.subclass = subclass; - this.lazyPropertiesAreUnfetched = unfetched; - this.version = version; - } + /** + * Retrieves the version (optimistic locking) associated with this cache entry. + * + * @return The version of the entity represented by this entry + */ + public Object getVersion(); - public Object[] assemble( - final Object instance, - final Serializable id, - final EntityPersister persister, - final Interceptor interceptor, - final EventSource session) - throws HibernateException { + public boolean areLazyPropertiesUnfetched(); - if ( !persister.getEntityName().equals(subclass) ) { - throw new AssertionFailure("Tried to assemble a different subclass instance"); - } - return assemble(disassembledState, instance, id, persister, interceptor, session); - - } - - private static Object[] assemble( - final Serializable[] values, - final Object result, - final Serializable id, - final EntityPersister persister, - final Interceptor interceptor, - final EventSource session) throws HibernateException { - - //assembled state gets put in a new array (we read from cache by value!) - Object[] assembledProps = TypeHelper.assemble( - values, - persister.getPropertyTypes(), - session, result - ); - - //persister.setIdentifier(result, id); //before calling interceptor, for consistency with normal load - - //TODO: reuse the PreLoadEvent - final PreLoadEvent preLoadEvent = new PreLoadEvent( session ) - .setEntity( result ) - .setState( assembledProps ) - .setId( id ) - .setPersister( persister ); - - final EventListenerGroup listenerGroup = session - .getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class ) - .getEventListenerGroup( EventType.PRE_LOAD ); - for ( PreLoadEventListener listener : listenerGroup.listeners() ) { - listener.onPreLoad( preLoadEvent ); - } - - persister.setPropertyValues( result, assembledProps ); - - return assembledProps; - } - - public Serializable[] getDisassembledState() { - // todo: this was added to support initializing an entity's EntityEntry snapshot during reattach; - // this should be refactored to instead expose a method to assemble a EntityEntry based on this - // state for return. - return disassembledState; - } - - public String toString() { - return "CacheEntry(" + subclass + ')' + ArrayHelper.toString(disassembledState); - } + // todo: this was added to support initializing an entity's EntityEntry snapshot during reattach; + // this should be refactored to instead expose a method to assemble a EntityEntry based on this + // state for return. + public Serializable[] getDisassembledState(); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/ReferenceCacheEntryImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/ReferenceCacheEntryImpl.java new file mode 100644 index 0000000000..95d00ed8db --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/ReferenceCacheEntryImpl.java @@ -0,0 +1,75 @@ +/* + * 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.cache.spi.entry; + +import java.io.Serializable; + +import org.hibernate.Interceptor; +import org.hibernate.event.spi.EventSource; +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Steve Ebersole + */ +public class ReferenceCacheEntryImpl implements CacheEntry { + private final Object reference; + private final String subclass; + + public ReferenceCacheEntryImpl(Object reference, String subclass) { + this.reference = reference; + this.subclass = subclass; + } + + @Override + public boolean isReferenceEntry() { + return true; + } + + @Override + public String getSubclass() { + return subclass; + } + + @Override + public Object getVersion() { + // reference data cannot be versioned + return null; + } + + @Override + public boolean areLazyPropertiesUnfetched() { + // reference data cannot define lazy attributes + return false; + } + + @Override + public Serializable[] getDisassembledState() { + // reference data is not disassembled into the cache + return null; + } + + public Object getReference() { + return reference; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java new file mode 100644 index 0000000000..51f9fceacd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java @@ -0,0 +1,170 @@ +/* + * 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.cache.spi.entry; + +import java.io.Serializable; + +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; +import org.hibernate.Interceptor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.event.service.spi.EventListenerGroup; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.PreLoadEvent; +import org.hibernate.event.spi.PreLoadEventListener; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.TypeHelper; + +/** + * @author Steve Ebersole + */ +public class StandardCacheEntryImpl implements CacheEntry { + private final Serializable[] disassembledState; + private final String subclass; + private final boolean lazyPropertiesAreUnfetched; + private final Object version; + + @Override + public boolean isReferenceEntry() { + return false; + } + + @Override + public Serializable[] getDisassembledState() { + // todo: this was added to support initializing an entity's EntityEntry snapshot during reattach; + // this should be refactored to instead expose a method to assemble a EntityEntry based on this + // state for return. + return disassembledState; + } + + @Override + public String getSubclass() { + return subclass; + } + + @Override + public boolean areLazyPropertiesUnfetched() { + return lazyPropertiesAreUnfetched; + } + + @Override + public Object getVersion() { + return version; + } + + public StandardCacheEntryImpl( + final Object[] state, + final EntityPersister persister, + final boolean unfetched, + final Object version, + final SessionImplementor session, + final Object owner) + throws HibernateException { + //disassembled state gets put in a new array (we write to cache by value!) + this.disassembledState = TypeHelper.disassemble( + state, + persister.getPropertyTypes(), + persister.isLazyPropertiesCacheable() ? + null : persister.getPropertyLaziness(), + session, + owner + ); + subclass = persister.getEntityName(); + lazyPropertiesAreUnfetched = unfetched || !persister.isLazyPropertiesCacheable(); + this.version = version; + } + + StandardCacheEntryImpl(Serializable[] state, String subclass, boolean unfetched, Object version) { + this.disassembledState = state; + this.subclass = subclass; + this.lazyPropertiesAreUnfetched = unfetched; + this.version = version; + } + + public boolean isDeepCopyNeeded() { + // for now always return true. + // todo : See discussion on HHH-7872 + return true; + } + + public Object[] assemble( + final Object instance, + final Serializable id, + final EntityPersister persister, + final Interceptor interceptor, + final EventSource session) + throws HibernateException { + + if ( !persister.getEntityName().equals(subclass) ) { + throw new AssertionFailure("Tried to assemble a different subclass instance"); + } + + return assemble(disassembledState, instance, id, persister, interceptor, session); + } + + private static Object[] assemble( + final Serializable[] values, + final Object result, + final Serializable id, + final EntityPersister persister, + final Interceptor interceptor, + final EventSource session) throws HibernateException { + + //assembled state gets put in a new array (we read from cache by value!) + Object[] assembledProps = TypeHelper.assemble( + values, + persister.getPropertyTypes(), + session, result + ); + + //persister.setIdentifier(result, id); //before calling interceptor, for consistency with normal load + + //TODO: reuse the PreLoadEvent + final PreLoadEvent preLoadEvent = new PreLoadEvent( session ) + .setEntity( result ) + .setState( assembledProps ) + .setId( id ) + .setPersister( persister ); + + final EventListenerGroup listenerGroup = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ); + for ( PreLoadEventListener listener : listenerGroup.listeners() ) { + listener.onPreLoad( preLoadEvent ); + } + + persister.setPropertyValues( result, assembledProps ); + + return assembledProps; + } + + public String toString() { + return "CacheEntry(" + subclass + ')' + ArrayHelper.toString( disassembledState ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java index 095f91606c..90e1f86952 100755 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java @@ -31,7 +31,11 @@ import org.hibernate.persister.entity.EntityPersister; /** + * Structured CacheEntry format for entities. Used to store the entry into the second-level cache + * as a Map so that users can more easily see the cached state. + * * @author Gavin King + * @author Steve Ebersole */ public class StructuredCacheEntry implements CacheEntryStructure { @@ -52,7 +56,7 @@ public Object destructure(Object item, SessionFactoryImplementor factory) { for ( int i=0; i classMeta = new HashMap(); @@ -380,7 +382,7 @@ public void sessionFactoryClosed(SessionFactory factory) { } } - EntityPersister cp = serviceRegistry.getService( PersisterFactory.class ).createEntityPersister( + EntityPersister cp = persisterFactory.createEntityPersister( model, accessStrategy, naturalIdAccessStrategy, @@ -409,7 +411,7 @@ public void sessionFactoryClosed(SessionFactory factory) { entityAccessStrategies.put( cacheRegionName, accessStrategy ); cacheAccess.addCacheRegion( cacheRegionName, collectionRegion ); } - CollectionPersister persister = serviceRegistry.getService( PersisterFactory.class ).createCollectionPersister( + CollectionPersister persister = persisterFactory.createCollectionPersister( cfg, model, accessStrategy, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index dbf1f76376..c56a4e924e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -56,6 +56,8 @@ import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; import org.hibernate.cache.spi.entry.CacheEntry; import org.hibernate.cache.spi.entry.CacheEntryStructure; +import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl; +import org.hibernate.cache.spi.entry.StandardCacheEntryImpl; import org.hibernate.cache.spi.entry.StructuredCacheEntry; import org.hibernate.cache.spi.entry.UnstructuredCacheEntry; import org.hibernate.dialect.lock.LockingStrategy; @@ -149,7 +151,7 @@ public abstract class AbstractEntityPersister private final EntityRegionAccessStrategy cacheAccessStrategy; private final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy; private final boolean isLazyPropertiesCacheable; - private final CacheEntryStructure cacheEntryStructure; + private final CacheEntryHelper cacheEntryHelper; private final EntityMetamodel entityMetamodel; private final EntityTuplizer entityTuplizer; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -501,9 +503,6 @@ public AbstractEntityPersister( this.cacheAccessStrategy = cacheAccessStrategy; this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy; isLazyPropertiesCacheable = persistentClass.isLazyPropertiesCacheable(); - this.cacheEntryStructure = factory.getSettings().isStructuredCacheEntriesEnabled() ? - (CacheEntryStructure) new StructuredCacheEntry(this) : - (CacheEntryStructure) new UnstructuredCacheEntry(); this.entityMetamodel = new EntityMetamodel( persistentClass, factory ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); @@ -778,6 +777,48 @@ public AbstractEntityPersister( temporaryIdTableName = persistentClass.getTemporaryIdTableName(); temporaryIdTableDDL = persistentClass.getTemporaryIdTableDDL(); + + this.cacheEntryHelper = buildCacheEntryHelper(); + } + + protected CacheEntryHelper buildCacheEntryHelper() { + if ( cacheAccessStrategy == null ) { + // the entity defined no caching... + return NoopCacheEntryHelper.INSTANCE; + } + + if ( canUseReferenceCacheEntries() ) { + entityMetamodel.setLazy( false ); + // todo : do we also need to unset proxy factory? + return new ReferenceCacheEntryHelper( this ); + } + + return factory.getSettings().isStructuredCacheEntriesEnabled() + ? new StructuredCacheEntryHelper( this ) + : new StandardCacheEntryHelper( this ); + } + + protected 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; } @@ -793,10 +834,6 @@ public AbstractEntityPersister( entityBinding.getHierarchyDetails().getCaching() == null ? false : entityBinding.getHierarchyDetails().getCaching().isCacheLazyProperties(); - this.cacheEntryStructure = - factory.getSettings().isStructuredCacheEntriesEnabled() ? - new StructuredCacheEntry(this) : - new UnstructuredCacheEntry(); this.entityMetamodel = new EntityMetamodel( entityBinding, factory ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); int batch = entityBinding.getBatchSize(); @@ -1104,6 +1141,8 @@ public AbstractEntityPersister( temporaryIdTableName = null; temporaryIdTableDDL = null; + + this.cacheEntryHelper = buildCacheEntryHelper(); } protected static String getTemplateFromString(String string, SessionFactoryImplementor factory) { @@ -4066,10 +4105,16 @@ public EntityRegionAccessStrategy getCacheAccessStrategy() { return cacheAccessStrategy; } + @Override public CacheEntryStructure getCacheEntryStructure() { - return cacheEntryStructure; + return cacheEntryHelper.getCacheEntryStructure(); } - + + @Override + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session) { + return cacheEntryHelper.buildCacheEntry( entity, state, version, session ); + } + public boolean hasNaturalIdCache() { return naturalIdRegionAccessStrategy != null; } @@ -4925,5 +4970,97 @@ public String getTableAliasForColumn(String columnName, String rootAlias) { public int determineTableNumberForColumn(String columnName) { return 0; } - + + /** + * Consolidated these onto a single helper because the 2 pieces work in tandem. + */ + public static interface CacheEntryHelper { + public CacheEntryStructure getCacheEntryStructure(); + + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session); + } + + private static class StandardCacheEntryHelper implements CacheEntryHelper { + private final EntityPersister persister; + + private StandardCacheEntryHelper(EntityPersister persister) { + this.persister = persister; + } + + @Override + public CacheEntryStructure getCacheEntryStructure() { + return UnstructuredCacheEntry.INSTANCE; + } + + @Override + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session) { + return new StandardCacheEntryImpl( + state, + persister, + persister.hasUninitializedLazyProperties( entity ), + version, + session, + entity + ); + } + } + + private static class ReferenceCacheEntryHelper implements CacheEntryHelper { + private final EntityPersister persister; + + private ReferenceCacheEntryHelper(EntityPersister persister) { + this.persister = persister; + } + + @Override + public CacheEntryStructure getCacheEntryStructure() { + return UnstructuredCacheEntry.INSTANCE; + } + + @Override + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session) { + return new ReferenceCacheEntryImpl( entity, persister.getEntityName() ); + } + } + + private static class StructuredCacheEntryHelper implements CacheEntryHelper { + private final EntityPersister persister; + private final StructuredCacheEntry structure; + + private StructuredCacheEntryHelper(EntityPersister persister) { + this.persister = persister; + this.structure = new StructuredCacheEntry( persister ); + } + + @Override + public CacheEntryStructure getCacheEntryStructure() { + return structure; + } + + @Override + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session) { + return new StandardCacheEntryImpl( + state, + persister, + persister.hasUninitializedLazyProperties( entity ), + version, + session, + entity + ); + } + } + + private static class NoopCacheEntryHelper implements CacheEntryHelper { + public static final NoopCacheEntryHelper INSTANCE = new NoopCacheEntryHelper(); + + @Override + public CacheEntryStructure getCacheEntryStructure() { + return UnstructuredCacheEntry.INSTANCE; + } + + @Override + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session) { + throw new HibernateException( "Illegal attempt to build cache entry for non-cached entity" ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 29b4faa7ac..f95ea71970 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -35,6 +35,7 @@ import org.hibernate.cache.spi.OptimisticCacheSource; 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.spi.CascadeStyle; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -475,7 +476,9 @@ public void update( * Get the cache structure */ public CacheEntryStructure getCacheEntryStructure(); - + + public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SessionImplementor session); + /** * Does this class have a natural id cache */ diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/ReferenceCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/ReferenceCacheTest.java new file mode 100644 index 0000000000..0d65243ea2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/ReferenceCacheTest.java @@ -0,0 +1,140 @@ +/* + * 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 javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Immutable; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.persister.entity.EntityPersister; + +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +public class ReferenceCacheTest extends BaseCoreFunctionalTestCase { + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.setProperty( AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, "true" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MyReferenceData.class }; + } + + @Test + public void testUseOfDirectReferencesInCache() throws Exception { + EntityPersister persister = (EntityPersister) sessionFactory().getClassMetadata( MyReferenceData.class ); + assertFalse( persister.isMutable() ); + assertTrue( persister.buildCacheEntry( null, null, null, null ).isReferenceEntry() ); + assertFalse( persister.hasProxy() ); + + final MyReferenceData myReferenceData = new MyReferenceData( 1, "first item", "abc" ); + + // save a reference in one session + Session s = openSession(); + s.beginTransaction(); + s.save( myReferenceData ); + s.getTransaction().commit(); + s.close(); + + // now load it in another + s = openSession(); + s.beginTransaction(); +// MyReferenceData loaded = (MyReferenceData) s.get( MyReferenceData.class, 1 ); + MyReferenceData loaded = (MyReferenceData) s.load( MyReferenceData.class, 1 ); + s.getTransaction().commit(); + s.close(); + + // the 2 instances should be the same (==) + assertTrue( "The two instances were different references", myReferenceData == loaded ); + + // cleanup + s = openSession(); + s.beginTransaction(); + s.delete( myReferenceData ); + s.getTransaction().commit(); + s.close(); + } + + @Entity( name="MyReferenceData" ) + @Immutable + @Cacheable + @Cache( usage = CacheConcurrencyStrategy.READ_ONLY ) +// @Proxy( lazy = false ) + @SuppressWarnings("UnusedDeclaration") + public static class MyReferenceData { + @Id + private Integer id; + private String name; + private String theValue; + + public MyReferenceData(Integer id, String name, String theValue) { + this.id = id; + this.name = name; + this.theValue = theValue; + } + + protected MyReferenceData() { + } + + 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; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java index ee64cfb2d7..3e32129981 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java @@ -35,6 +35,7 @@ import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; 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.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CascadeStyle; @@ -400,6 +401,12 @@ public CacheEntryStructure getCacheEntryStructure() { return null; } + @Override + public CacheEntry buildCacheEntry( + Object entity, Object[] state, Object version, SessionImplementor session) { + return null; + } + @Override public ClassMetadata getClassMetadata() { return null; diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java index 3289c7a75d..62258c377b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java @@ -13,7 +13,9 @@ import org.hibernate.bytecode.spi.EntityInstrumentationMetadata; 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.cache.spi.entry.StandardCacheEntryImpl; import org.hibernate.cache.spi.entry.UnstructuredCacheEntry; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.spi.CascadeStyle; @@ -566,6 +568,19 @@ public CacheEntryStructure getCacheEntryStructure() { return new UnstructuredCacheEntry(); } + @Override + public CacheEntry buildCacheEntry( + Object entity, Object[] state, Object version, SessionImplementor session) { + return new StandardCacheEntryImpl( + state, + this, + this.hasUninitializedLazyProperties( entity ), + version, + session, + entity + ); + } + @Override public boolean hasSubselectLoadableCollections() { return false;