diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EssentialEntityPersisterDetails.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EssentialEntityPersisterDetails.java new file mode 100644 index 0000000000..3c81ef4245 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EssentialEntityPersisterDetails.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 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.engine.internal; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.entity.EntityEssentials; +import org.hibernate.type.Type; + + +public class EssentialEntityPersisterDetails implements EntityEssentials { + + private final Type identifierType; + private final boolean isBatchLoadable; + private final String entityName; + private final String rootEntityName; + + public EssentialEntityPersisterDetails(Type identifierType, boolean isBatchLoadable, String entityName, String rootEntityName) { + this.identifierType = identifierType; + this.isBatchLoadable = isBatchLoadable; + this.entityName = entityName; + this.rootEntityName = rootEntityName; + } + + @Override + public Type getIdentifierType() { + return identifierType; + } + + @Override + public boolean isBatchLoadable() { + return isBatchLoadable; + } + + @Override + public String getEntityName() { + return entityName; + } + + @Override + public String getRootEntityName() { + return rootEntityName; + } + + @Override + public SessionFactoryImplementor getFactory() { + return null; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index a1530e2164..d6e11d8c84 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -1440,6 +1440,7 @@ public class StatefulPersistenceContext implements PersistenceContext { LOG.trace( "Serializing persistent-context" ); } final StatefulPersistenceContext rtn = new StatefulPersistenceContext( session ); + SessionFactoryImplementor sfi = session==null ? null : session.getFactory(); // during deserialization, we need to reconnect all proxies and // collections to this session, as well as the EntityEntry and @@ -1457,7 +1458,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } rtn.entitiesByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); for ( int i = 0; i < count; i++ ) { - rtn.entitiesByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() ); + rtn.entitiesByKey.put( EntityKey.deserialize( ois, sfi ), ois.readObject() ); } count = ois.readInt(); @@ -1483,7 +1484,7 @@ public class StatefulPersistenceContext implements PersistenceContext { null ); for ( int i = 0; i < count; i++ ) { - final EntityKey ek = EntityKey.deserialize( ois, session ); + final EntityKey ek = EntityKey.deserialize( ois, sfi ); final Object proxy = ois.readObject(); if ( proxy instanceof HibernateProxy ) { ( (HibernateProxy) proxy ).getHibernateLazyInitializer().setSession( session ); @@ -1503,7 +1504,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } rtn.entitySnapshotsByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); for ( int i = 0; i < count; i++ ) { - rtn.entitySnapshotsByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() ); + rtn.entitySnapshotsByKey.put( EntityKey.deserialize( ois, sfi ), ois.readObject() ); } rtn.entityEntryContext = EntityEntryContext.deserialize( ois, rtn ); @@ -1544,7 +1545,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } rtn.nullifiableEntityKeys = new HashSet(); for ( int i = 0; i < count; i++ ) { - rtn.nullifiableEntityKeys.add( EntityKey.deserialize( ois, session ) ); + rtn.nullifiableEntityKeys.add( EntityKey.deserialize( ois, sfi ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java index f9f07d57a6..0b00ac1b00 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java @@ -194,7 +194,7 @@ public final class EntityEntry implements Serializable { if ( getId() == null ) { throw new IllegalStateException( "cannot generate an EntityKey when id is null."); } - cachedEntityKey = new EntityKey( getId(), getPersister(), tenantId ); + cachedEntityKey = new EntityKey( getId(), getPersister() ); } return cachedEntityKey; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java index b78bebf323..e49131107d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as + * 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. @@ -29,29 +29,31 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import org.hibernate.AssertionFailure; +import org.hibernate.engine.internal.EssentialEntityPersisterDetails; import org.hibernate.internal.util.compare.EqualsHelper; +import org.hibernate.persister.entity.EntityEssentials; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.Type; /** - * Uniquely identifies of an entity instance in a particular session by identifier. + * Uniquely identifies of an entity instance in a particular Session by identifier. + * Note that it's only safe to be used within the scope of a Session: it doesn't consider for example the tenantId + * as part of the equality definition. *

* Information used to determine uniqueness consists of the entity-name and the identifier value (see {@link #equals}). + *

+ * Performance considerations: lots of instances of this type are created at runtime. Make sure each one is as small as possible + * by storing just the essential needed. * * @author Gavin King + * @author Sanne Grinovero */ public final class EntityKey implements Serializable { + private final Serializable identifier; - private final String entityName; - private final String rootEntityName; - private final String tenantId; - private final int hashCode; - - private final Type identifierType; - private final boolean isBatchLoadable; - private final SessionFactoryImplementor factory; + private final EntityEssentials persister; /** * Construct a unique identifier for an entity class instance. @@ -62,61 +64,42 @@ public final class EntityKey implements Serializable { * * @param id The entity id * @param persister The entity persister - * @param tenantId The tenant identifier of the session to which this key belongs */ - public EntityKey(Serializable id, EntityPersister persister, String tenantId) { + public EntityKey(Serializable id, EntityPersister persister) { + this.persister = persister; if ( id == null ) { throw new AssertionFailure( "null identifier" ); } this.identifier = id; - this.rootEntityName = persister.getRootEntityName(); - this.entityName = persister.getEntityName(); - this.tenantId = tenantId; - - this.identifierType = persister.getIdentifierType(); - this.isBatchLoadable = persister.isBatchLoadable(); - this.factory = persister.getFactory(); this.hashCode = generateHashCode(); } /** - * Used to reconstruct an EntityKey during deserialization. + * Used to reconstruct an EntityKey during deserialization. Note that this constructor + * is used only in very specific situations: the SessionFactory isn't actually available + * and so both equals and hashcode implementations can't be implemented correctly. * * @param identifier The identifier value - * @param rootEntityName The root entity name - * @param entityName The specific entity name - * @param identifierType The type of the identifier value - * @param batchLoadable Whether represented entity is eligible for batch loading - * @param factory The session factory - * @param tenantId The entity's tenant id (from the session that loaded it). + * @param fakePersister Is a placeholder for the EntityPersister, only providing essential methods needed for this purpose. + * @param hashCode The hashCode needs to be provided as it can't be calculated correctly without the SessionFactory. */ - private EntityKey( - Serializable identifier, - String rootEntityName, - String entityName, - Type identifierType, - boolean batchLoadable, - SessionFactoryImplementor factory, - String tenantId) { + private EntityKey(Serializable identifier, EntityEssentials fakePersister, int hashCode) { + this.persister = fakePersister; + if ( identifier == null ) { + throw new AssertionFailure( "null identifier" ); + } this.identifier = identifier; - this.rootEntityName = rootEntityName; - this.entityName = entityName; - this.identifierType = identifierType; - this.isBatchLoadable = batchLoadable; - this.factory = factory; - this.tenantId = tenantId; - this.hashCode = generateHashCode(); + this.hashCode = hashCode; } private int generateHashCode() { int result = 17; - result = 37 * result + rootEntityName.hashCode(); - result = 37 * result + identifierType.getHashCode( identifier, factory ); + result = 37 * result + persister.getIdentifierType().getHashCode( identifier, persister.getFactory() ); return result; } public boolean isBatchLoadable() { - return isBatchLoadable; + return persister.isBatchLoadable(); } public Serializable getIdentifier() { @@ -124,7 +107,7 @@ public final class EntityKey implements Serializable { } public String getEntityName() { - return entityName; + return persister.getEntityName(); } @Override @@ -132,14 +115,27 @@ public final class EntityKey implements Serializable { if ( this == other ) { return true; } - if ( other == null || getClass() != other.getClass() ) { + if ( other == null || EntityKey.class != other.getClass() ) { return false; } final EntityKey otherKey = (EntityKey) other; - return otherKey.rootEntityName.equals( this.rootEntityName ) - && identifierType.isEqual( otherKey.identifier, this.identifier, factory ) - && EqualsHelper.equals( tenantId, otherKey.tenantId ); + return samePersistentType( otherKey ) + && sameIdentifier( otherKey ); + + } + + private boolean sameIdentifier(final EntityKey otherKey) { + return persister.getIdentifierType().isEqual( otherKey.identifier, this.identifier, persister.getFactory() ); + } + + private boolean samePersistentType(final EntityKey otherKey) { + if ( otherKey.persister == persister ) { + return true; + } + else { + return EqualsHelper.equals( otherKey.persister.getRootEntityName(), persister.getRootEntityName() ); + } } @Override @@ -150,7 +146,7 @@ public final class EntityKey implements Serializable { @Override public String toString() { return "EntityKey" + - MessageHelper.infoString( factory.getEntityPersister( entityName ), identifier, factory ); + MessageHelper.infoString( this.persister, identifier, persister.getFactory() ); } /** @@ -162,12 +158,12 @@ public final class EntityKey implements Serializable { * @throws IOException Thrown by Java I/O */ public void serialize(ObjectOutputStream oos) throws IOException { + oos.writeObject( persister.getIdentifierType() ); + oos.writeBoolean( isBatchLoadable() ); oos.writeObject( identifier ); - oos.writeObject( rootEntityName ); - oos.writeObject( entityName ); - oos.writeObject( identifierType ); - oos.writeBoolean( isBatchLoadable ); - oos.writeObject( tenantId ); + oos.writeObject( persister.getEntityName() ); + oos.writeObject( persister.getRootEntityName() ); + oos.writeInt( hashCode ); } /** @@ -175,24 +171,28 @@ public final class EntityKey implements Serializable { * Session/PersistenceContext for increased performance. * * @param ois The stream from which to read the entry. - * @param session The session being deserialized. + * @param sessionFactory The SessionFactory owning the Session being deserialized. * * @return The deserialized EntityEntry * * @throws IOException Thrown by Java I/O * @throws ClassNotFoundException Thrown by Java I/O */ - public static EntityKey deserialize( - ObjectInputStream ois, - SessionImplementor session) throws IOException, ClassNotFoundException { - return new EntityKey( - (Serializable) ois.readObject(), - (String) ois.readObject(), - (String) ois.readObject(), - (Type) ois.readObject(), - ois.readBoolean(), - (session == null ? null : session.getFactory()), - (String) ois.readObject() - ); + public static EntityKey deserialize(ObjectInputStream ois, SessionFactoryImplementor sessionFactory) throws IOException, ClassNotFoundException { + final Type identifierType = (Type) ois.readObject(); + final boolean isBatchLoadable = ois.readBoolean(); + final Serializable id = (Serializable) ois.readObject(); + final String entityName = (String) ois.readObject(); + final String rootEntityName = (String) ois.readObject(); + final int hashCode = ois.readInt(); + if ( sessionFactory != null) { + final EntityPersister entityPersister = sessionFactory.getEntityPersister( entityName ); + return new EntityKey(id, entityPersister); + } + else { + //This version will produce an EntityKey which is technically unable to satisfy the equals contract! + final EntityEssentials fakePersister = new EssentialEntityPersisterDetails(identifierType, isBatchLoadable, entityName, rootEntityName); + return new EntityKey(id, fakePersister, hashCode); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java index 807744a73a..816ff6f16f 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java @@ -324,7 +324,7 @@ public abstract class AbstractSessionImpl @Override public EntityKey generateEntityKey(Serializable id, EntityPersister persister) { - return new EntityKey( id, persister, getTenantIdentifier() ); + return new EntityKey( id, persister ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityEssentials.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityEssentials.java new file mode 100644 index 0000000000..1a379e0453 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityEssentials.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 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.persister.entity; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.type.Type; + +/** + * This the internal contract exposed by EntityPersister to EntityKey instances. + * The purpose is to keep the number of fields for EntityKey to a minimum amount + * and still be able to set it the properties listed here without having to create + * a complete EntityPersister implementation. + * + * @see org.hibernate.persister.entity.EntityPersister + * @see org.hibernate.engine.spi.EntityKey + * + * @author Sanne Grinovero + */ +public interface EntityEssentials { + + Type getIdentifierType(); + + boolean isBatchLoadable(); + + String getEntityName(); + + String getRootEntityName(); + + SessionFactoryImplementor getFactory(); + +} 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 a45a1fb98f..839c175ab2 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 @@ -63,7 +63,7 @@ import org.hibernate.type.VersionType; * @see org.hibernate.persister.spi.PersisterFactory * @see org.hibernate.persister.spi.PersisterClassResolver */ -public interface EntityPersister extends OptimisticCacheSource, EntityDefinition { +public interface EntityPersister extends OptimisticCacheSource, EntityDefinition, EntityEssentials { /** * The property name of the "special" identifier property in HQL diff --git a/hibernate-core/src/main/java/org/hibernate/pretty/MessageHelper.java b/hibernate-core/src/main/java/org/hibernate/pretty/MessageHelper.java index f519ecdc41..85e1400247 100644 --- a/hibernate-core/src/main/java/org/hibernate/pretty/MessageHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/pretty/MessageHelper.java @@ -29,6 +29,7 @@ import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.entity.EntityEssentials; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.Type; @@ -82,11 +83,11 @@ public final class MessageHelper { * * @param persister The persister for the entity * @param id The entity id value - * @param factory The session factory + * @param factory The session factory - Could be null! * @return An info string, in the form [FooBar#1] */ public static String infoString( - EntityPersister persister, + EntityEssentials persister, Object id, SessionFactoryImplementor factory) { StringBuilder s = new StringBuilder(); @@ -110,7 +111,12 @@ public final class MessageHelper { s.append( id ); } else { - s.append( idType.toLoggableString( id, factory ) ); + if ( factory != null ) { + s.append( idType.toLoggableString( id, factory ) ); + } + else { + s.append( "" ); + } } } s.append( ']' );