55package org .hibernate .engine .internal ;
66
77import java .io .IOException ;
8+ import java .io .ObjectInputStream ;
89import java .io .ObjectOutputStream ;
910import java .io .Serializable ;
1011
1112import org .hibernate .AssertionFailure ;
1213import org .hibernate .CustomEntityDirtinessStrategy ;
1314import org .hibernate .HibernateException ;
1415import org .hibernate .LockMode ;
16+ import org .hibernate .UnsupportedLockAttemptException ;
1517import org .hibernate .bytecode .enhance .spi .interceptor .EnhancementAsProxyLazinessInterceptor ;
1618import org .hibernate .collection .spi .PersistentCollection ;
1719import org .hibernate .engine .spi .EntityEntry ;
3335import org .checkerframework .checker .nullness .qual .Nullable ;
3436
3537import static org .hibernate .LockMode .PESSIMISTIC_FORCE_INCREMENT ;
36- import static org .hibernate .engine .internal .AbstractEntityEntry .BooleanState .EXISTS_IN_DATABASE ;
37- import static org .hibernate .engine .internal .AbstractEntityEntry .BooleanState .IS_BEING_REPLICATED ;
38- import static org .hibernate .engine .internal .AbstractEntityEntry .EnumState .LOCK_MODE ;
39- import static org .hibernate .engine .internal .AbstractEntityEntry .EnumState .PREVIOUS_STATUS ;
40- import static org .hibernate .engine .internal .AbstractEntityEntry .EnumState .STATUS ;
38+ import static org .hibernate .engine .internal .EntityEntryImpl .BooleanState .EXISTS_IN_DATABASE ;
39+ import static org .hibernate .engine .internal .EntityEntryImpl .BooleanState .IS_BEING_REPLICATED ;
40+ import static org .hibernate .engine .internal .EntityEntryImpl .EnumState .LOCK_MODE ;
41+ import static org .hibernate .engine .internal .EntityEntryImpl .EnumState .PREVIOUS_STATUS ;
42+ import static org .hibernate .engine .internal .EntityEntryImpl .EnumState .STATUS ;
4143import static org .hibernate .engine .internal .ManagedTypeHelper .asManagedEntity ;
4244import static org .hibernate .engine .internal .ManagedTypeHelper .asPersistentAttributeInterceptable ;
4345import static org .hibernate .engine .internal .ManagedTypeHelper .asSelfDirtinessTracker ;
5254import static org .hibernate .engine .spi .Status .MANAGED ;
5355import static org .hibernate .engine .spi .Status .READ_ONLY ;
5456import static org .hibernate .engine .spi .Status .SAVING ;
57+ import static org .hibernate .internal .util .StringHelper .nullIfEmpty ;
5558import static org .hibernate .pretty .MessageHelper .infoString ;
5659import static org .hibernate .proxy .HibernateProxy .extractLazyInitializer ;
5760
6366 * @author Gunnar Morling
6467 * @author Sanne Grinovero
6568 */
66- public abstract class AbstractEntityEntry implements Serializable , EntityEntry {
67-
68- protected final Object id ;
69- protected Object [] loadedState ;
70- protected Object version ;
71- protected final EntityPersister persister ; // permanent but we only need the entityName state in a non transient way
72- protected transient EntityKey cachedEntityKey ; // cached EntityKey (lazy-initialized)
73- protected final transient Object rowId ;
74- protected final transient PersistenceContext persistenceContext ;
75- protected transient @ Nullable ImmutableBitSet maybeLazySet ;
76- protected EntityEntryExtraState next ;
69+ public final class EntityEntryImpl implements Serializable , EntityEntry {
70+
71+ private final Object id ;
72+ private Object [] loadedState ;
73+ private Object version ;
74+ private final EntityPersister persister ; // permanent but we only need the entityName state in a non transient way
75+ private transient EntityKey cachedEntityKey ; // cached EntityKey (lazy-initialized)
76+ private final transient Object rowId ;
77+ private final transient PersistenceContext persistenceContext ;
78+ private transient @ Nullable ImmutableBitSet maybeLazySet ;
79+ private EntityEntryExtraState next ;
7780
7881 /**
7982 * Holds several boolean and enum typed attributes in a very compact manner. Enum values are stored in 4 bits
@@ -102,7 +105,7 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
102105 */
103106 private transient int compressedState ;
104107
105- public AbstractEntityEntry (
108+ public EntityEntryImpl (
106109 final Status status ,
107110 final Object [] loadedState ,
108111 final Object rowId ,
@@ -127,14 +130,17 @@ public AbstractEntityEntry(
127130 setCompressedValue ( LOCK_MODE , lockMode );
128131 setCompressedValue ( IS_BEING_REPLICATED , disableVersionIncrement );
129132 this .persister = persister ;
130- this .persistenceContext = persistenceContext ;
133+ // don't store PersistenceContext for immutable entity, see HHH-10251
134+ this .persistenceContext =
135+ persister == null || persister .isMutable ()
136+ ? persistenceContext
137+ : null ;
131138 }
132139
133140 /**
134- * This for is used during custom deserialization handling
141+ * Overloaded form used during custom deserialization
135142 */
136- protected AbstractEntityEntry (
137- final SessionFactoryImplementor factory ,
143+ private EntityEntryImpl (
138144 final String entityName ,
139145 final Object id ,
140146 final Status status ,
@@ -145,10 +151,14 @@ protected AbstractEntityEntry(
145151 final LockMode lockMode ,
146152 final boolean existsInDatabase ,
147153 final boolean isBeingReplicated ,
154+ final boolean mutable ,
148155 final PersistenceContext persistenceContext ) {
149- this .persister = factory == null
150- ? null
151- : factory .getMappingMetamodel ().getEntityDescriptor ( entityName );
156+ final SessionFactoryImplementor factory =
157+ persistenceContext .getSession ().getFactory ();
158+ this .persister =
159+ factory == null
160+ ? null
161+ : factory .getMappingMetamodel ().getEntityDescriptor ( entityName );
152162 this .id = id ;
153163 setCompressedValue ( STATUS , status );
154164 setCompressedValue ( PREVIOUS_STATUS , previousStatus );
@@ -159,7 +169,8 @@ protected AbstractEntityEntry(
159169 setCompressedValue ( EXISTS_IN_DATABASE , existsInDatabase );
160170 setCompressedValue ( IS_BEING_REPLICATED , isBeingReplicated );
161171 this .rowId = null ; // this is equivalent to the old behavior...
162- this .persistenceContext = persistenceContext ;
172+ // don't store PersistenceContext for immutable entity, see HHH-10251
173+ this .persistenceContext = mutable ? persistenceContext : null ;
163174 }
164175
165176 @ Override
@@ -169,10 +180,14 @@ public LockMode getLockMode() {
169180
170181 @ Override
171182 public void setLockMode (LockMode lockMode ) {
183+ if ( lockMode .greaterThan ( LockMode .READ )
184+ && persister !=null && !persister .isMutable () ) {
185+ throw new UnsupportedLockAttemptException ( "Lock mode " + lockMode
186+ + " not supported for read-only entity" );
187+ }
172188 setCompressedValue ( LOCK_MODE , lockMode );
173189 }
174190
175-
176191 @ Override
177192 public Status getStatus () {
178193 return getCompressedValue ( STATUS );
@@ -197,12 +212,12 @@ public void setStatus(Status status) {
197212 }
198213
199214 @ Override
200- public final Object getId () {
215+ public Object getId () {
201216 return id ;
202217 }
203218
204219 @ Override
205- public final Object [] getLoadedState () {
220+ public Object [] getLoadedState () {
206221 return loadedState ;
207222 }
208223
@@ -232,7 +247,7 @@ public boolean isExistsInDatabase() {
232247 }
233248
234249 @ Override
235- public final Object getVersion () {
250+ public Object getVersion () {
236251 return version ;
237252 }
238253
@@ -242,7 +257,7 @@ public void postInsert(Object version) {
242257 }
243258
244259 @ Override
245- public final EntityPersister getPersister () {
260+ public EntityPersister getPersister () {
246261 return persister ;
247262 }
248263
@@ -283,8 +298,8 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion)
283298 persister .setValue ( entity , persister .getVersionProperty (), nextVersion );
284299 }
285300
286- processIfSelfDirtinessTracker ( entity , AbstractEntityEntry ::clearDirtyAttributes );
287- processIfManagedEntity ( entity , AbstractEntityEntry ::useTracker );
301+ processIfSelfDirtinessTracker ( entity , EntityEntryImpl ::clearDirtyAttributes );
302+ processIfManagedEntity ( entity , EntityEntryImpl ::useTracker );
288303
289304 final SharedSessionContractImplementor session = getPersistenceContext ().getSession ();
290305 session .getFactory ().getCustomEntityDirtinessStrategy ()
@@ -437,11 +452,11 @@ public void setReadOnly(boolean readOnly, Object entity) {
437452 setStatus ( READ_ONLY );
438453 loadedState = null ;
439454 }
455+ else if ( !persister .isMutable () ) {
456+ throw new IllegalStateException ( "Cannot make an entity of immutable type '"
457+ + persister .getEntityName () + "' modifiable" );
458+ }
440459 else {
441- if ( ! persister .isMutable () ) {
442- throw new IllegalStateException ( "Cannot make an entity of immutable type '"
443- + persister .getEntityName () + "' modifiable" );
444- }
445460 setStatus ( MANAGED );
446461 loadedState = persister .getValues ( entity );
447462 TypeHelper .deepCopy (
@@ -483,19 +498,51 @@ public String toString() {
483498 @ Override
484499 public void serialize (ObjectOutputStream oos ) throws IOException {
485500 final Status previousStatus = getPreviousStatus ();
486- oos .writeObject ( getEntityName () );
501+ final String entityName = getEntityName ();
502+ oos .writeUTF ( entityName == null ? "" : entityName );
487503 oos .writeObject ( id );
488- oos .writeObject ( getStatus ().name () );
489- oos .writeObject ( ( previousStatus == null ? "" : previousStatus .name () ) );
504+ oos .writeInt ( getStatus ().ordinal () );
505+ oos .writeInt ( previousStatus == null ? - 1 : previousStatus .ordinal ( ) );
490506 // todo : potentially look at optimizing these two arrays
491507 oos .writeObject ( loadedState );
492508 oos .writeObject ( getDeletedState () );
493509 oos .writeObject ( version );
494- oos .writeObject ( getLockMode ().toString () );
510+ oos .writeInt ( getLockMode ().ordinal () );
495511 oos .writeBoolean ( isExistsInDatabase () );
496512 oos .writeBoolean ( isBeingReplicated () );
513+ oos .writeBoolean ( persister == null || persister .isMutable () );
497514 }
498515
516+ /**
517+ * Custom deserialization routine used during deserialization
518+ * of a {@link PersistenceContext} for increased performance.
519+ *
520+ * @param ois The stream from which to read the entry
521+ * @param persistenceContext The context being deserialized
522+ *
523+ * @return The deserialized {@code EntityEntry}
524+ *
525+ * @throws IOException If a stream error occurs
526+ * @throws ClassNotFoundException If any of the classes declared
527+ * in the stream cannot be found
528+ */
529+ public static EntityEntry deserialize (ObjectInputStream ois , PersistenceContext persistenceContext )
530+ throws IOException , ClassNotFoundException {
531+ return new EntityEntryImpl (
532+ nullIfEmpty ( ois .readUTF () ),
533+ ois .readObject (),
534+ Status .fromOrdinal ( ois .readInt () ),
535+ Status .fromOrdinal ( ois .readInt () ),
536+ (Object []) ois .readObject (),
537+ (Object []) ois .readObject (),
538+ ois .readObject (),
539+ LockMode .values ()[ ois .readInt () ],
540+ ois .readBoolean (),
541+ ois .readBoolean (),
542+ ois .readBoolean (),
543+ persistenceContext
544+ );
545+ }
499546
500547 @ Override
501548 public void addExtraState (EntityEntryExtraState extraState ) {
@@ -520,7 +567,10 @@ public <T extends EntityEntryExtraState> T getExtraState(Class<T> extraStateType
520567 }
521568 }
522569
523- public PersistenceContext getPersistenceContext (){
570+ public PersistenceContext getPersistenceContext () {
571+ if ( persistenceContext == null ) {
572+ throw new UnsupportedOperationException ( "PersistenceContext not available for immutable entity" );
573+ }
524574 return persistenceContext ;
525575 }
526576
@@ -533,7 +583,7 @@ public PersistenceContext getPersistenceContext(){
533583 * the value to store; The caller must make sure that it matches
534584 * the given identifier
535585 */
536- protected <E extends Enum <E >> void setCompressedValue (EnumState <E > state , E value ) {
586+ <E extends Enum <E >> void setCompressedValue (EnumState <E > state , E value ) {
537587 // reset the bits for the given property to 0
538588 compressedState &= state .getUnsetMask ();
539589 // store the numeric representation of the enum value at the right offset
@@ -547,7 +597,7 @@ protected <E extends Enum<E>> void setCompressedValue(EnumState<E> state, E valu
547597 * identifies the value to store
548598 * @return the current value of the specified property
549599 */
550- protected <E extends Enum <E >> E getCompressedValue (EnumState <E > state ) {
600+ <E extends Enum <E >> E getCompressedValue (EnumState <E > state ) {
551601 // restore the numeric value from the bits at the right offset and return the corresponding enum constant
552602 final int index = ( ( compressedState & state .getMask () ) >> state .getOffset () ) - 1 ;
553603 return index == - 1 ? null : state .getEnumConstants ()[index ];
@@ -561,7 +611,7 @@ protected <E extends Enum<E>> E getCompressedValue(EnumState<E> state) {
561611 * @param value
562612 * the value to store
563613 */
564- protected void setCompressedValue (BooleanState state , boolean value ) {
614+ void setCompressedValue (BooleanState state , boolean value ) {
565615 compressedState &= state .getUnsetMask ();
566616 compressedState |= ( state .getValue ( value ) << state .getOffset () );
567617 }
@@ -573,7 +623,7 @@ protected void setCompressedValue(BooleanState state, boolean value) {
573623 * identifies the value to store
574624 * @return the current value of the specified flag
575625 */
576- protected boolean getCompressedValue (BooleanState state ) {
626+ boolean getCompressedValue (BooleanState state ) {
577627 return ( ( compressedState & state .getMask () ) >> state .getOffset () ) == 1 ;
578628 }
579629
@@ -582,7 +632,7 @@ protected boolean getCompressedValue(BooleanState state) {
582632 *
583633 * @author Gunnar Morling
584634 */
585- protected static class EnumState <E extends Enum <E >> {
635+ static class EnumState <E extends Enum <E >> {
586636
587637 protected static final EnumState <LockMode > LOCK_MODE = new EnumState <>( 0 , LockMode .class );
588638 protected static final EnumState <Status > STATUS = new EnumState <>( 4 , Status .class );
@@ -593,11 +643,11 @@ protected static class EnumState<E extends Enum<E>> {
593643 protected final int mask ;
594644 protected final int unsetMask ;
595645
596- private EnumState (int offset , Class <E > enumType ) {
646+ EnumState (int offset , Class <E > enumType ) {
597647 final E [] enumConstants = enumType .getEnumConstants ();
598648
599649 // In case any of the enums cannot be stored in 4 bits anymore,
600- // we'd have to re-structure the compressed state int
650+ // we'd have to restructure the compressed state int
601651 if ( enumConstants .length > 15 ) {
602652 throw new AssertionFailure ( "Cannot store enum type " + enumType .getName ()
603653 + " in compressed state as it has too many values." );
@@ -654,7 +704,7 @@ private E[] getEnumConstants() {
654704 *
655705 * @author Gunnar Morling
656706 */
657- protected enum BooleanState {
707+ enum BooleanState {
658708
659709 EXISTS_IN_DATABASE (13 ),
660710 IS_BEING_REPLICATED (14 );
0 commit comments