2
* Hibernate, Relational Persistence for Idiomatic Java
4
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5
* indicated by the @author tags or express copyright attribution
6
* statements applied by the authors. All third-party contributions are
7
* distributed under license by Red Hat Middleware LLC.
9
* This copyrighted material is made available to anyone wishing to use, modify,
10
* copy, or redistribute it subject to the terms and conditions of the GNU
11
* Lesser General Public License, as published by the Free Software Foundation.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
18
* You should have received a copy of the GNU Lesser General Public License
19
* along with this distribution; if not, write to:
20
* Free Software Foundation, Inc.
21
* 51 Franklin Street, Fifth Floor
22
* Boston, MA 02110-1301 USA
25
package org.hibernate.engine;
27
import java.io.IOException;
28
import java.io.InvalidObjectException;
29
import java.io.ObjectInputStream;
30
import java.io.ObjectOutputStream;
31
import java.io.Serializable;
32
import java.util.ArrayList;
33
import java.util.HashMap;
34
import java.util.HashSet;
35
import java.util.Iterator;
36
import java.util.List;
39
import org.apache.commons.collections.map.ReferenceMap;
40
import org.slf4j.Logger;
41
import org.slf4j.LoggerFactory;
42
import org.hibernate.AssertionFailure;
43
import org.hibernate.Hibernate;
44
import org.hibernate.HibernateException;
45
import org.hibernate.LockMode;
46
import org.hibernate.MappingException;
47
import org.hibernate.NonUniqueObjectException;
48
import org.hibernate.PersistentObjectException;
49
import org.hibernate.TransientObjectException;
50
import org.hibernate.engine.loading.LoadContexts;
51
import org.hibernate.pretty.MessageHelper;
52
import org.hibernate.collection.PersistentCollection;
53
import org.hibernate.persister.collection.CollectionPersister;
54
import org.hibernate.persister.entity.EntityPersister;
55
import org.hibernate.proxy.HibernateProxy;
56
import org.hibernate.proxy.LazyInitializer;
57
import org.hibernate.tuple.ElementWrapper;
58
import org.hibernate.util.IdentityMap;
59
import org.hibernate.util.MarkerObject;
62
* A <tt>PersistenceContext</tt> represents the state of persistent "stuff" which
63
* Hibernate is tracking. This includes persistent entities, collections,
64
* as well as proxies generated.
66
* There is meant to be a one-to-one correspondence between a SessionImpl and
67
* a PersistentContext. The SessionImpl uses the PersistentContext to track
68
* the current state of its context. Event-listeners then use the
69
* PersistentContext to drive their processing.
71
* @author Steve Ebersole
73
public class StatefulPersistenceContext implements PersistenceContext {
75
public static final Object NO_ROW = new MarkerObject( "NO_ROW" );
77
private static final Logger log = LoggerFactory.getLogger( StatefulPersistenceContext.class );
78
private static final Logger PROXY_WARN_LOG = LoggerFactory.getLogger( StatefulPersistenceContext.class.getName() + ".ProxyWarnLog" );
79
private static final int INIT_COLL_SIZE = 8;
81
private SessionImplementor session;
83
// Loaded entity instances, by EntityKey
84
private Map entitiesByKey;
86
// Loaded entity instances, by EntityUniqueKey
87
private Map entitiesByUniqueKey;
89
// Identity map of EntityEntry instances, by the entity instance
90
private Map entityEntries;
92
// Entity proxies, by EntityKey
93
private Map proxiesByKey;
95
// Snapshots of current database state for entities
96
// that have *not* been loaded
97
private Map entitySnapshotsByKey;
99
// Identity map of array holder ArrayHolder instances, by the array instance
100
private Map arrayHolders;
102
// Identity map of CollectionEntry instances, by the collection wrapper
103
private Map collectionEntries;
105
// Collection wrappers, by the CollectionKey
106
private Map collectionsByKey; //key=CollectionKey, value=PersistentCollection
108
// Set of EntityKeys of deleted objects
109
private HashSet nullifiableEntityKeys;
111
// properties that we have tried to load, and not found in the database
112
private HashSet nullAssociations;
114
// A list of collection wrappers that were instantiating during result set
115
// processing, that we will need to initialize at the end of the query
116
private List nonlazyCollections;
118
// A container for collections we load up when the owning entity is not
119
// yet loaded ... for now, this is purely transient!
120
private Map unownedCollections;
122
private int cascading = 0;
123
private int loadCounter = 0;
124
private boolean flushing = false;
126
private boolean hasNonReadOnlyEntities = false;
128
private LoadContexts loadContexts;
129
private BatchFetchQueue batchFetchQueue;
134
* Constructs a PersistentContext, bound to the given session.
136
* @param session The session "owning" this context.
138
public StatefulPersistenceContext(SessionImplementor session) {
139
this.session = session;
141
entitiesByKey = new HashMap( INIT_COLL_SIZE );
142
entitiesByUniqueKey = new HashMap( INIT_COLL_SIZE );
143
proxiesByKey = new ReferenceMap( ReferenceMap.HARD, ReferenceMap.WEAK );
144
entitySnapshotsByKey = new HashMap( INIT_COLL_SIZE );
146
entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
147
collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
148
collectionsByKey = new HashMap( INIT_COLL_SIZE );
149
arrayHolders = IdentityMap.instantiate( INIT_COLL_SIZE );
151
nullifiableEntityKeys = new HashSet();
153
initTransientState();
156
private void initTransientState() {
157
nullAssociations = new HashSet( INIT_COLL_SIZE );
158
nonlazyCollections = new ArrayList( INIT_COLL_SIZE );
161
public boolean isStateless() {
165
public SessionImplementor getSession() {
169
public LoadContexts getLoadContexts() {
170
if ( loadContexts == null ) {
171
loadContexts = new LoadContexts( this );
176
public void addUnownedCollection(CollectionKey key, PersistentCollection collection) {
177
if (unownedCollections==null) {
178
unownedCollections = new HashMap(8);
180
unownedCollections.put(key, collection);
183
public PersistentCollection useUnownedCollection(CollectionKey key) {
184
if (unownedCollections==null) {
188
return (PersistentCollection) unownedCollections.remove(key);
193
* Get the <tt>BatchFetchQueue</tt>, instantiating one if
196
public BatchFetchQueue getBatchFetchQueue() {
197
if (batchFetchQueue==null) {
198
batchFetchQueue = new BatchFetchQueue(this);
200
return batchFetchQueue;
203
public void clear() {
204
Iterator itr = proxiesByKey.values().iterator();
205
while ( itr.hasNext() ) {
206
final LazyInitializer li = ( ( HibernateProxy ) itr.next() ).getHibernateLazyInitializer();
209
Map.Entry[] collectionEntryArray = IdentityMap.concurrentEntries( collectionEntries );
210
for ( int i = 0; i < collectionEntryArray.length; i++ ) {
211
( ( PersistentCollection ) collectionEntryArray[i].getKey() ).unsetSession( getSession() );
213
arrayHolders.clear();
214
entitiesByKey.clear();
215
entitiesByUniqueKey.clear();
216
entityEntries.clear();
217
entitySnapshotsByKey.clear();
218
collectionsByKey.clear();
219
collectionEntries.clear();
220
if ( unownedCollections != null ) {
221
unownedCollections.clear();
223
proxiesByKey.clear();
224
nullifiableEntityKeys.clear();
225
if ( batchFetchQueue != null ) {
226
batchFetchQueue.clear();
228
hasNonReadOnlyEntities = false;
229
if ( loadContexts != null ) {
230
loadContexts.cleanup();
234
public boolean hasNonReadOnlyEntities() {
235
return hasNonReadOnlyEntities;
238
public void setEntryStatus(EntityEntry entry, Status status) {
239
entry.setStatus(status);
240
setHasNonReadOnlyEnties(status);
243
private void setHasNonReadOnlyEnties(Status status) {
244
if ( status==Status.DELETED || status==Status.MANAGED || status==Status.SAVING ) {
245
hasNonReadOnlyEntities = true;
249
public void afterTransactionCompletion() {
251
Iterator iter = entityEntries.values().iterator();
252
while ( iter.hasNext() ) {
253
( (EntityEntry) iter.next() ).setLockMode(LockMode.NONE);
258
* Get the current state of the entity as known to the underlying
259
* database, or null if there is no corresponding row
261
public Object[] getDatabaseSnapshot(Serializable id, EntityPersister persister)
262
throws HibernateException {
263
EntityKey key = new EntityKey( id, persister, session.getEntityMode() );
264
Object cached = entitySnapshotsByKey.get(key);
266
return cached==NO_ROW ? null : (Object[]) cached;
269
Object[] snapshot = persister.getDatabaseSnapshot( id, session );
270
entitySnapshotsByKey.put( key, snapshot==null ? NO_ROW : snapshot );
275
public Object[] getNaturalIdSnapshot(Serializable id, EntityPersister persister)
276
throws HibernateException {
277
if ( !persister.hasNaturalIdentifier() ) {
281
// if the natural-id is marked as non-mutable, it is not retrieved during a
282
// normal database-snapshot operation...
283
int[] props = persister.getNaturalIdentifierProperties();
284
boolean[] updateable = persister.getPropertyUpdateability();
285
boolean allNatualIdPropsAreUpdateable = true;
286
for ( int i = 0; i < props.length; i++ ) {
287
if ( !updateable[ props[i] ] ) {
288
allNatualIdPropsAreUpdateable = false;
293
if ( allNatualIdPropsAreUpdateable ) {
294
// do this when all the properties are updateable since there is
295
// a certain likelihood that the information will already be
297
Object[] entitySnapshot = getDatabaseSnapshot( id, persister );
298
if ( entitySnapshot == NO_ROW ) {
301
Object[] naturalIdSnapshot = new Object[ props.length ];
302
for ( int i = 0; i < props.length; i++ ) {
303
naturalIdSnapshot[i] = entitySnapshot[ props[i] ];
305
return naturalIdSnapshot;
308
return persister.getNaturalIdentifierSnapshot( id, session );
313
* Retrieve the cached database snapshot for the requested entity key.
315
* This differs from {@link #getDatabaseSnapshot} is two important respects:<ol>
316
* <li>no snapshot is obtained from the database if not already cached</li>
317
* <li>an entry of {@link #NO_ROW} here is interpretet as an exception</li>
319
* @param key The entity key for which to retrieve the cached snapshot
320
* @return The cached snapshot
321
* @throws IllegalStateException if the cached snapshot was == {@link #NO_ROW}.
323
public Object[] getCachedDatabaseSnapshot(EntityKey key) {
324
Object snapshot = entitySnapshotsByKey.get( key );
325
if ( snapshot == NO_ROW ) {
326
throw new IllegalStateException( "persistence context reported no row snapshot for " + MessageHelper.infoString( key.getEntityName(), key.getIdentifier() ) );
328
return ( Object[] ) snapshot;
331
/*public void removeDatabaseSnapshot(EntityKey key) {
332
entitySnapshotsByKey.remove(key);
335
public void addEntity(EntityKey key, Object entity) {
336
entitiesByKey.put(key, entity);
337
getBatchFetchQueue().removeBatchLoadableEntityKey(key);
341
* Get the entity instance associated with the given
344
public Object getEntity(EntityKey key) {
345
return entitiesByKey.get(key);
348
public boolean containsEntity(EntityKey key) {
349
return entitiesByKey.containsKey(key);
353
* Remove an entity from the session cache, also clear
354
* up other state associated with the entity, all except
355
* for the <tt>EntityEntry</tt>
357
public Object removeEntity(EntityKey key) {
358
Object entity = entitiesByKey.remove(key);
359
Iterator iter = entitiesByUniqueKey.values().iterator();
360
while ( iter.hasNext() ) {
361
if ( iter.next()==entity ) iter.remove();
363
entitySnapshotsByKey.remove(key);
364
nullifiableEntityKeys.remove(key);
365
getBatchFetchQueue().removeBatchLoadableEntityKey(key);
366
getBatchFetchQueue().removeSubselect(key);
371
* Get an entity cached by unique key
373
public Object getEntity(EntityUniqueKey euk) {
374
return entitiesByUniqueKey.get(euk);
378
* Add an entity to the cache by unique key
380
public void addEntity(EntityUniqueKey euk, Object entity) {
381
entitiesByUniqueKey.put(euk, entity);
385
* Retreive the EntityEntry representation of the given entity.
387
* @param entity The entity for which to locate the EntityEntry.
388
* @return The EntityEntry for the given entity.
390
public EntityEntry getEntry(Object entity) {
391
return (EntityEntry) entityEntries.get(entity);
395
* Remove an entity entry from the session cache
397
public EntityEntry removeEntry(Object entity) {
398
return (EntityEntry) entityEntries.remove(entity);
402
* Is there an EntityEntry for this instance?
404
public boolean isEntryFor(Object entity) {
405
return entityEntries.containsKey(entity);
409
* Get the collection entry for a persistent collection
411
public CollectionEntry getCollectionEntry(PersistentCollection coll) {
412
return (CollectionEntry) collectionEntries.get(coll);
416
* Adds an entity to the internal caches.
418
public EntityEntry addEntity(
421
final Object[] loadedState,
422
final EntityKey entityKey,
423
final Object version,
424
final LockMode lockMode,
425
final boolean existsInDatabase,
426
final EntityPersister persister,
427
final boolean disableVersionIncrement,
428
boolean lazyPropertiesAreUnfetched
431
addEntity( entityKey, entity );
438
entityKey.getIdentifier(),
443
disableVersionIncrement,
444
lazyPropertiesAreUnfetched
450
* Generates an appropriate EntityEntry instance and adds it
451
* to the event source's internal caches.
453
public EntityEntry addEntry(
456
final Object[] loadedState,
458
final Serializable id,
459
final Object version,
460
final LockMode lockMode,
461
final boolean existsInDatabase,
462
final EntityPersister persister,
463
final boolean disableVersionIncrement,
464
boolean lazyPropertiesAreUnfetched) {
466
EntityEntry e = new EntityEntry(
475
session.getEntityMode(),
476
disableVersionIncrement,
477
lazyPropertiesAreUnfetched
479
entityEntries.put(entity, e);
481
setHasNonReadOnlyEnties(status);
485
public boolean containsCollection(PersistentCollection collection) {
486
return collectionEntries.containsKey(collection);
489
public boolean containsProxy(Object entity) {
490
return proxiesByKey.containsValue( entity );
494
* Takes the given object and, if it represents a proxy, reassociates it with this event source.
496
* @param value The possible proxy to be reassociated.
497
* @return Whether the passed value represented an actual proxy which got initialized.
498
* @throws MappingException
500
public boolean reassociateIfUninitializedProxy(Object value) throws MappingException {
501
if ( value instanceof ElementWrapper ) {
502
value = ( (ElementWrapper) value ).getElement();
505
if ( !Hibernate.isInitialized(value) ) {
506
HibernateProxy proxy = (HibernateProxy) value;
507
LazyInitializer li = proxy.getHibernateLazyInitializer();
508
reassociateProxy(li, proxy);
517
* If a deleted entity instance is re-saved, and it has a proxy, we need to
518
* reset the identifier of the proxy
520
public void reassociateProxy(Object value, Serializable id) throws MappingException {
521
if ( value instanceof ElementWrapper ) {
522
value = ( (ElementWrapper) value ).getElement();
525
if ( value instanceof HibernateProxy ) {
526
if ( log.isDebugEnabled() ) log.debug("setting proxy identifier: " + id);
527
HibernateProxy proxy = (HibernateProxy) value;
528
LazyInitializer li = proxy.getHibernateLazyInitializer();
529
li.setIdentifier(id);
530
reassociateProxy(li, proxy);
535
* Associate a proxy that was instantiated by another session with this session
537
* @param li The proxy initializer.
538
* @param proxy The proxy to reassociate.
540
private void reassociateProxy(LazyInitializer li, HibernateProxy proxy) {
541
if ( li.getSession() != this.getSession() ) {
542
EntityPersister persister = session.getFactory().getEntityPersister( li.getEntityName() );
543
EntityKey key = new EntityKey( li.getIdentifier(), persister, session.getEntityMode() );
544
// any earlier proxy takes precedence
545
if ( !proxiesByKey.containsKey( key ) ) {
546
proxiesByKey.put( key, proxy );
548
proxy.getHibernateLazyInitializer().setSession( session );
553
* Get the entity instance underlying the given proxy, throwing
554
* an exception if the proxy is uninitialized. If the given object
555
* is not a proxy, simply return the argument.
557
public Object unproxy(Object maybeProxy) throws HibernateException {
558
if ( maybeProxy instanceof ElementWrapper ) {
559
maybeProxy = ( (ElementWrapper) maybeProxy ).getElement();
562
if ( maybeProxy instanceof HibernateProxy ) {
563
HibernateProxy proxy = (HibernateProxy) maybeProxy;
564
LazyInitializer li = proxy.getHibernateLazyInitializer();
565
if ( li.isUninitialized() ) {
566
throw new PersistentObjectException(
567
"object was an uninitialized proxy for " +
571
return li.getImplementation(); //unwrap the object
579
* Possibly unproxy the given reference and reassociate it with the current session.
581
* @param maybeProxy The reference to be unproxied if it currently represents a proxy.
582
* @return The unproxied instance.
583
* @throws HibernateException
585
public Object unproxyAndReassociate(Object maybeProxy) throws HibernateException {
586
if ( maybeProxy instanceof ElementWrapper ) {
587
maybeProxy = ( (ElementWrapper) maybeProxy ).getElement();
590
if ( maybeProxy instanceof HibernateProxy ) {
591
HibernateProxy proxy = (HibernateProxy) maybeProxy;
592
LazyInitializer li = proxy.getHibernateLazyInitializer();
593
reassociateProxy(li, proxy);
594
return li.getImplementation(); //initialize + unwrap the object
602
* Attempts to check whether the given key represents an entity already loaded within the
604
* @param object The entity reference against which to perform the uniqueness check.
605
* @throws HibernateException
607
public void checkUniqueness(EntityKey key, Object object) throws HibernateException {
608
Object entity = getEntity(key);
609
if ( entity == object ) {
610
throw new AssertionFailure( "object already associated, but no entry was found" );
612
if ( entity != null ) {
613
throw new NonUniqueObjectException( key.getIdentifier(), key.getEntityName() );
618
* If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy
619
* and overwrite the registration of the old one. This breaks == and occurs only for
620
* "class" proxies rather than "interface" proxies. Also init the proxy to point to
621
* the given target implementation if necessary.
623
* @param proxy The proxy instance to be narrowed.
624
* @param persister The persister for the proxied entity.
625
* @param key The internal cache key for the proxied entity.
626
* @param object (optional) the actual proxied entity instance.
627
* @return An appropriately narrowed instance.
628
* @throws HibernateException
630
public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object)
631
throws HibernateException {
633
boolean alreadyNarrow = persister.getConcreteProxyClass( session.getEntityMode() )
634
.isAssignableFrom( proxy.getClass() );
636
if ( !alreadyNarrow ) {
637
if ( PROXY_WARN_LOG.isWarnEnabled() ) {
639
"Narrowing proxy to " +
640
persister.getConcreteProxyClass( session.getEntityMode() ) +
641
" - this operation breaks =="
645
if ( object != null ) {
646
proxiesByKey.remove(key);
647
return object; //return the proxied object
650
proxy = persister.createProxy( key.getIdentifier(), session );
651
proxiesByKey.put(key, proxy); //overwrite old proxy
658
if ( object != null ) {
659
LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
660
li.setImplementation(object);
670
* Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
671
* third argument (the entity associated with the key) if no proxy exists. Init
672
* the proxy to the target implementation, if necessary.
674
public Object proxyFor(EntityPersister persister, EntityKey key, Object impl)
675
throws HibernateException {
676
if ( !persister.hasProxy() ) return impl;
677
Object proxy = proxiesByKey.get(key);
678
if ( proxy != null ) {
679
return narrowProxy(proxy, persister, key, impl);
687
* Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
688
* argument (the entity associated with the key) if no proxy exists.
689
* (slower than the form above)
691
public Object proxyFor(Object impl) throws HibernateException {
692
EntityEntry e = getEntry(impl);
693
EntityPersister p = e.getPersister();
694
return proxyFor( p, new EntityKey( e.getId(), p, session.getEntityMode() ), impl );
698
* Get the entity that owns this persistent collection
700
public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) throws MappingException {
701
return getEntity( new EntityKey( key, collectionPersister.getOwnerEntityPersister(), session.getEntityMode() ) );
705
* Get the entity that owned this persistent collection when it was loaded
707
* @param collection The persistent collection
708
* @return the owner, if its entity ID is available from the collection's loaded key
709
* and the owner entity is in the persistence context; otherwise, returns null
711
public Object getLoadedCollectionOwnerOrNull(PersistentCollection collection) {
712
CollectionEntry ce = getCollectionEntry( collection );
713
if ( ce.getLoadedPersister() == null ) {
714
return null; // early exit...
716
Object loadedOwner = null;
717
// TODO: an alternative is to check if the owner has changed; if it hasn't then
718
// return collection.getOwner()
719
Serializable entityId = getLoadedCollectionOwnerIdOrNull( ce );
720
if ( entityId != null ) {
721
loadedOwner = getCollectionOwner( entityId, ce.getLoadedPersister() );
727
* Get the ID for the entity that owned this persistent collection when it was loaded
729
* @param collection The persistent collection
730
* @return the owner ID if available from the collection's loaded key; otherwise, returns null
732
public Serializable getLoadedCollectionOwnerIdOrNull(PersistentCollection collection) {
733
return getLoadedCollectionOwnerIdOrNull( getCollectionEntry( collection ) );
737
* Get the ID for the entity that owned this persistent collection when it was loaded
739
* @param ce The collection entry
740
* @return the owner ID if available from the collection's loaded key; otherwise, returns null
742
private Serializable getLoadedCollectionOwnerIdOrNull(CollectionEntry ce) {
743
if ( ce == null || ce.getLoadedKey() == null || ce.getLoadedPersister() == null ) {
746
// TODO: an alternative is to check if the owner has changed; if it hasn't then
747
// get the ID from collection.getOwner()
748
return ce.getLoadedPersister().getCollectionType().getIdOfOwnerOrNull( ce.getLoadedKey(), session );
752
* add a collection we just loaded up (still needs initializing)
754
public void addUninitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id) {
755
CollectionEntry ce = new CollectionEntry(collection, persister, id, flushing);
756
addCollection(collection, ce, id);
760
* add a detached uninitialized collection
762
public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection collection) {
763
CollectionEntry ce = new CollectionEntry( persister, collection.getKey() );
764
addCollection( collection, ce, collection.getKey() );
768
* Add a new collection (ie. a newly created one, just instantiated by the
769
* application, with no database state or snapshot)
770
* @param collection The collection to be associated with the persistence context
772
public void addNewCollection(CollectionPersister persister, PersistentCollection collection)
773
throws HibernateException {
774
addCollection(collection, persister);
778
* Add an collection to the cache, with a given collection entry.
780
* @param coll The collection for which we are adding an entry.
781
* @param entry The entry representing the collection.
782
* @param key The key of the collection's entry.
784
private void addCollection(PersistentCollection coll, CollectionEntry entry, Serializable key) {
785
collectionEntries.put( coll, entry );
786
CollectionKey collectionKey = new CollectionKey( entry.getLoadedPersister(), key, session.getEntityMode() );
787
PersistentCollection old = ( PersistentCollection ) collectionsByKey.put( collectionKey, coll );
790
throw new AssertionFailure("bug adding collection twice");
792
// or should it actually throw an exception?
793
old.unsetSession( session );
794
collectionEntries.remove( old );
795
// watch out for a case where old is still referenced
796
// somewhere in the object graph! (which is a user error)
801
* Add a collection to the cache, creating a new collection entry for it
803
* @param collection The collection for which we are adding an entry.
804
* @param persister The collection persister
806
private void addCollection(PersistentCollection collection, CollectionPersister persister) {
807
CollectionEntry ce = new CollectionEntry( persister, collection );
808
collectionEntries.put( collection, ce );
812
* add an (initialized) collection that was created by another session and passed
813
* into update() (ie. one with a snapshot and existing state on the database)
815
public void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection collection)
816
throws HibernateException {
817
if ( collection.isUnreferenced() ) {
818
//treat it just like a new collection
819
addCollection( collection, collectionPersister );
822
CollectionEntry ce = new CollectionEntry( collection, session.getFactory() );
823
addCollection( collection, ce, collection.getKey() );
828
* add a collection we just pulled out of the cache (does not need initializing)
830
public CollectionEntry addInitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id)
831
throws HibernateException {
832
CollectionEntry ce = new CollectionEntry(collection, persister, id, flushing);
833
ce.postInitialize(collection);
834
addCollection(collection, ce, id);
839
* Get the collection instance associated with the <tt>CollectionKey</tt>
841
public PersistentCollection getCollection(CollectionKey collectionKey) {
842
return (PersistentCollection) collectionsByKey.get(collectionKey);
846
* Register a collection for non-lazy loading at the end of the
849
public void addNonLazyCollection(PersistentCollection collection) {
850
nonlazyCollections.add(collection);
854
* Force initialization of all non-lazy collections encountered during
855
* the current two-phase load (actually, this is a no-op, unless this
856
* is the "outermost" load)
858
public void initializeNonLazyCollections() throws HibernateException {
859
if ( loadCounter == 0 ) {
860
log.debug( "initializing non-lazy collections" );
861
//do this work only at the very highest level of the load
862
loadCounter++; //don't let this method be called recursively
865
while ( ( size = nonlazyCollections.size() ) > 0 ) {
866
//note that each iteration of the loop may add new elements
867
( (PersistentCollection) nonlazyCollections.remove( size - 1 ) ).forceInitialization();
872
clearNullProperties();
879
* Get the <tt>PersistentCollection</tt> object for an array
881
public PersistentCollection getCollectionHolder(Object array) {
882
return (PersistentCollection) arrayHolders.get(array);
886
* Register a <tt>PersistentCollection</tt> object for an array.
887
* Associates a holder with an array - MUST be called after loading
888
* array, since the array instance is not created until endLoad().
890
public void addCollectionHolder(PersistentCollection holder) {
891
//TODO:refactor + make this method private
892
arrayHolders.put( holder.getValue(), holder );
895
public PersistentCollection removeCollectionHolder(Object array) {
896
return (PersistentCollection) arrayHolders.remove(array);
900
* Get the snapshot of the pre-flush collection state
902
public Serializable getSnapshot(PersistentCollection coll) {
903
return getCollectionEntry(coll).getSnapshot();
907
* Get the collection entry for a collection passed to filter,
908
* which might be a collection wrapper, an array, or an unwrapped
909
* collection. Return null if there is no entry.
911
public CollectionEntry getCollectionEntryOrNull(Object collection) {
912
PersistentCollection coll;
913
if ( collection instanceof PersistentCollection ) {
914
coll = (PersistentCollection) collection;
915
//if (collection==null) throw new TransientObjectException("Collection was not yet persistent");
918
coll = getCollectionHolder(collection);
919
if ( coll == null ) {
920
//it might be an unwrapped collection reference!
921
//try to find a wrapper (slowish)
922
Iterator wrappers = IdentityMap.keyIterator(collectionEntries);
923
while ( wrappers.hasNext() ) {
924
PersistentCollection pc = (PersistentCollection) wrappers.next();
925
if ( pc.isWrapper(collection) ) {
933
return (coll == null) ? null : getCollectionEntry(coll);
937
* Get an existing proxy by key
939
public Object getProxy(EntityKey key) {
940
return proxiesByKey.get(key);
944
* Add a proxy to the session cache
946
public void addProxy(EntityKey key, Object proxy) {
947
proxiesByKey.put(key, proxy);
951
* Remove a proxy from the session cache.
953
* Additionally, ensure that any load optimization references
954
* such as batch or subselect loading get cleaned up as well.
956
* @param key The key of the entity proxy to be removed
957
* @return The proxy reference.
959
public Object removeProxy(EntityKey key) {
960
if ( batchFetchQueue != null ) {
961
batchFetchQueue.removeBatchLoadableEntityKey( key );
962
batchFetchQueue.removeSubselect( key );
964
return proxiesByKey.remove( key );
968
* Record the fact that an entity does not exist in the database
970
* @param key the primary key of the entity
972
/*public void addNonExistantEntityKey(EntityKey key) {
973
nonExistantEntityKeys.add(key);
977
* Record the fact that an entity does not exist in the database
979
* @param key a unique key of the entity
981
/*public void addNonExistantEntityUniqueKey(EntityUniqueKey key) {
982
nonExistentEntityUniqueKeys.add(key);
985
/*public void removeNonExist(EntityKey key) {
986
nonExistantEntityKeys.remove(key);
990
* Retrieve the set of EntityKeys representing nullifiable references
992
public HashSet getNullifiableEntityKeys() {
993
return nullifiableEntityKeys;
996
public Map getEntitiesByKey() {
997
return entitiesByKey;
1000
public Map getEntityEntries() {
1001
return entityEntries;
1004
public Map getCollectionEntries() {
1005
return collectionEntries;
1008
public Map getCollectionsByKey() {
1009
return collectionsByKey;
1013
* Do we already know that the entity does not exist in the
1016
/*public boolean isNonExistant(EntityKey key) {
1017
return nonExistantEntityKeys.contains(key);
1021
* Do we already know that the entity does not exist in the
1024
/*public boolean isNonExistant(EntityUniqueKey key) {
1025
return nonExistentEntityUniqueKeys.contains(key);
1028
public int getCascadeLevel() {
1032
public int incrementCascadeLevel() {
1036
public int decrementCascadeLevel() {
1040
public boolean isFlushing() {
1044
public void setFlushing(boolean flushing) {
1045
this.flushing = flushing;
1049
* Call this before begining a two-phase load
1051
public void beforeLoad() {
1056
* Call this after finishing a two-phase load
1058
public void afterLoad() {
1063
* Returns a string representation of the object.
1065
* @return a string representation of the object.
1067
public String toString() {
1068
return new StringBuffer()
1069
.append("PersistenceContext[entityKeys=")
1070
.append(entitiesByKey.keySet())
1071
.append(",collectionKeys=")
1072
.append(collectionsByKey.keySet())
1078
* Search <tt>this</tt> persistence context for an associated entity instance which is considered the "owner" of
1079
* the given <tt>childEntity</tt>, and return that owner's id value. This is performed in the scenario of a
1080
* uni-directional, non-inverse one-to-many collection (which means that the collection elements do not maintain
1081
* a direct reference to the owner).
1083
* As such, the processing here is basically to loop over every entity currently associated with this persistence
1084
* context and for those of the correct entity (sub) type to extract its collection role property value and see
1085
* if the child is contained within that collection. If so, we have found the owner; if not, we go on.
1087
* Also need to account for <tt>mergeMap</tt> which acts as a local copy cache managed for the duration of a merge
1088
* operation. It represents a map of the detached entity instances pointing to the corresponding managed instance.
1090
* @param entityName The entity name for the entity type which would own the child
1091
* @param propertyName The name of the property on the owning entity type which would name this child association.
1092
* @param childEntity The child entity instance for which to locate the owner instance id.
1093
* @param mergeMap A map of non-persistent instances from an on-going merge operation (possibly null).
1095
* @return The id of the entityName instance which is said to own the child; null if an appropriate owner not
1098
public Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) {
1099
final String collectionRole = entityName + '.' + propertyName;
1100
final EntityPersister persister = session.getFactory().getEntityPersister( entityName );
1101
final CollectionPersister collectionPersister = session.getFactory().getCollectionPersister( collectionRole );
1103
// iterate all the entities currently associated with the persistence context.
1104
Iterator entities = entityEntries.entrySet().iterator();
1105
while ( entities.hasNext() ) {
1106
final Map.Entry me = ( Map.Entry ) entities.next();
1107
final EntityEntry entityEntry = ( EntityEntry ) me.getValue();
1108
// does this entity entry pertain to the entity persister in which we are interested (owner)?
1109
if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) {
1110
final Object entityEntryInstance = me.getKey();
1112
//check if the managed object is the parent
1113
boolean found = isFoundInParent(
1117
collectionPersister,
1121
if ( !found && mergeMap != null ) {
1122
//check if the detached object being merged is the parent
1123
Object unmergedInstance = mergeMap.get( entityEntryInstance );
1124
Object unmergedChild = mergeMap.get( childEntity );
1125
if ( unmergedInstance != null && unmergedChild != null ) {
1126
found = isFoundInParent(
1130
collectionPersister,
1137
return entityEntry.getId();
1143
// if we get here, it is possible that we have a proxy 'in the way' of the merge map resolution...
1144
// NOTE: decided to put this here rather than in the above loop as I was nervous about the performance
1145
// of the loop-in-loop especially considering this is far more likely the 'edge case'
1146
if ( mergeMap != null ) {
1147
Iterator mergeMapItr = mergeMap.entrySet().iterator();
1148
while ( mergeMapItr.hasNext() ) {
1149
final Map.Entry mergeMapEntry = ( Map.Entry ) mergeMapItr.next();
1150
if ( mergeMapEntry.getKey() instanceof HibernateProxy ) {
1151
final HibernateProxy proxy = ( HibernateProxy ) mergeMapEntry.getKey();
1152
if ( persister.isSubclassEntityName( proxy.getHibernateLazyInitializer().getEntityName() ) ) {
1153
boolean found = isFoundInParent(
1157
collectionPersister,
1158
mergeMap.get( proxy )
1161
found = isFoundInParent(
1163
mergeMap.get( childEntity ),
1165
collectionPersister,
1166
mergeMap.get( proxy )
1170
return proxy.getHibernateLazyInitializer().getIdentifier();
1180
private boolean isFoundInParent(
1183
EntityPersister persister,
1184
CollectionPersister collectionPersister,
1185
Object potentialParent) {
1186
Object collection = persister.getPropertyValue(
1189
session.getEntityMode()
1191
return collection != null
1192
&& Hibernate.isInitialized( collection )
1193
&& collectionPersister.getCollectionType().contains( collection, childEntity, session );
1197
* Search the persistence context for an index of the child object,
1198
* given a collection role
1200
public Object getIndexInOwner(String entity, String property, Object childEntity, Map mergeMap) {
1202
EntityPersister persister = session.getFactory()
1203
.getEntityPersister(entity);
1204
CollectionPersister cp = session.getFactory()
1205
.getCollectionPersister(entity + '.' + property);
1206
Iterator entities = entityEntries.entrySet().iterator();
1207
while ( entities.hasNext() ) {
1208
Map.Entry me = (Map.Entry) entities.next();
1209
EntityEntry ee = (EntityEntry) me.getValue();
1210
if ( persister.isSubclassEntityName( ee.getEntityName() ) ) {
1211
Object instance = me.getKey();
1213
Object index = getIndexInParent(property, childEntity, persister, cp, instance);
1215
if (index==null && mergeMap!=null) {
1216
Object unmergedInstance = mergeMap.get(instance);
1217
Object unmergedChild = mergeMap.get(childEntity);
1218
if ( unmergedInstance!=null && unmergedChild!=null ) {
1219
index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance);
1223
if (index!=null) return index;
1229
private Object getIndexInParent(
1232
EntityPersister persister,
1233
CollectionPersister collectionPersister,
1234
Object potentialParent
1236
Object collection = persister.getPropertyValue( potentialParent, property, session.getEntityMode() );
1237
if ( collection!=null && Hibernate.isInitialized(collection) ) {
1238
return collectionPersister.getCollectionType().indexOf(collection, childEntity);
1246
* Record the fact that the association belonging to the keyed
1249
public void addNullProperty(EntityKey ownerKey, String propertyName) {
1250
nullAssociations.add( new AssociationKey(ownerKey, propertyName) );
1254
* Is the association property belonging to the keyed entity null?
1256
public boolean isPropertyNull(EntityKey ownerKey, String propertyName) {
1257
return nullAssociations.contains( new AssociationKey(ownerKey, propertyName) );
1260
private void clearNullProperties() {
1261
nullAssociations.clear();
1264
public void setReadOnly(Object entity, boolean readOnly) {
1265
EntityEntry entry = getEntry(entity);
1267
throw new TransientObjectException("Instance was not associated with the session");
1269
entry.setReadOnly(readOnly, entity);
1270
hasNonReadOnlyEntities = hasNonReadOnlyEntities || !readOnly;
1273
public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId) {
1274
Object entity = entitiesByKey.remove( oldKey );
1275
EntityEntry oldEntry = ( EntityEntry ) entityEntries.remove( entity );
1277
EntityKey newKey = new EntityKey( generatedId, oldEntry.getPersister(), getSession().getEntityMode() );
1278
addEntity( newKey, entity );
1281
oldEntry.getStatus(),
1282
oldEntry.getLoadedState(),
1283
oldEntry.getRowId(),
1285
oldEntry.getVersion(),
1286
oldEntry.getLockMode(),
1287
oldEntry.isExistsInDatabase(),
1288
oldEntry.getPersister(),
1289
oldEntry.isBeingReplicated(),
1290
oldEntry.isLoadedWithLazyPropertiesUnfetched()
1295
* Used by the owning session to explicitly control serialization of the
1296
* persistence context.
1298
* @param oos The stream to which the persistence context should get written
1299
* @throws IOException serialization errors.
1301
public void serialize(ObjectOutputStream oos) throws IOException {
1302
log.trace( "serializing persistent-context" );
1304
oos.writeBoolean( hasNonReadOnlyEntities );
1306
oos.writeInt( entitiesByKey.size() );
1307
log.trace( "starting serialization of [" + entitiesByKey.size() + "] entitiesByKey entries" );
1308
Iterator itr = entitiesByKey.entrySet().iterator();
1309
while ( itr.hasNext() ) {
1310
Map.Entry entry = ( Map.Entry ) itr.next();
1311
( ( EntityKey ) entry.getKey() ).serialize( oos );
1312
oos.writeObject( entry.getValue() );
1315
oos.writeInt( entitiesByUniqueKey.size() );
1316
log.trace( "starting serialization of [" + entitiesByUniqueKey.size() + "] entitiesByUniqueKey entries" );
1317
itr = entitiesByUniqueKey.entrySet().iterator();
1318
while ( itr.hasNext() ) {
1319
Map.Entry entry = ( Map.Entry ) itr.next();
1320
( ( EntityUniqueKey ) entry.getKey() ).serialize( oos );
1321
oos.writeObject( entry.getValue() );
1324
oos.writeInt( proxiesByKey.size() );
1325
log.trace( "starting serialization of [" + proxiesByKey.size() + "] proxiesByKey entries" );
1326
itr = proxiesByKey.entrySet().iterator();
1327
while ( itr.hasNext() ) {
1328
Map.Entry entry = ( Map.Entry ) itr.next();
1329
( ( EntityKey ) entry.getKey() ).serialize( oos );
1330
oos.writeObject( entry.getValue() );
1333
oos.writeInt( entitySnapshotsByKey.size() );
1334
log.trace( "starting serialization of [" + entitySnapshotsByKey.size() + "] entitySnapshotsByKey entries" );
1335
itr = entitySnapshotsByKey.entrySet().iterator();
1336
while ( itr.hasNext() ) {
1337
Map.Entry entry = ( Map.Entry ) itr.next();
1338
( ( EntityKey ) entry.getKey() ).serialize( oos );
1339
oos.writeObject( entry.getValue() );
1342
oos.writeInt( entityEntries.size() );
1343
log.trace( "starting serialization of [" + entityEntries.size() + "] entityEntries entries" );
1344
itr = entityEntries.entrySet().iterator();
1345
while ( itr.hasNext() ) {
1346
Map.Entry entry = ( Map.Entry ) itr.next();
1347
oos.writeObject( entry.getKey() );
1348
( ( EntityEntry ) entry.getValue() ).serialize( oos );
1351
oos.writeInt( collectionsByKey.size() );
1352
log.trace( "starting serialization of [" + collectionsByKey.size() + "] collectionsByKey entries" );
1353
itr = collectionsByKey.entrySet().iterator();
1354
while ( itr.hasNext() ) {
1355
Map.Entry entry = ( Map.Entry ) itr.next();
1356
( ( CollectionKey ) entry.getKey() ).serialize( oos );
1357
oos.writeObject( entry.getValue() );
1360
oos.writeInt( collectionEntries.size() );
1361
log.trace( "starting serialization of [" + collectionEntries.size() + "] collectionEntries entries" );
1362
itr = collectionEntries.entrySet().iterator();
1363
while ( itr.hasNext() ) {
1364
Map.Entry entry = ( Map.Entry ) itr.next();
1365
oos.writeObject( entry.getKey() );
1366
( ( CollectionEntry ) entry.getValue() ).serialize( oos );
1369
oos.writeInt( arrayHolders.size() );
1370
log.trace( "starting serialization of [" + arrayHolders.size() + "] arrayHolders entries" );
1371
itr = arrayHolders.entrySet().iterator();
1372
while ( itr.hasNext() ) {
1373
Map.Entry entry = ( Map.Entry ) itr.next();
1374
oos.writeObject( entry.getKey() );
1375
oos.writeObject( entry.getValue() );
1378
oos.writeInt( nullifiableEntityKeys.size() );
1379
log.trace( "starting serialization of [" + nullifiableEntityKeys.size() + "] nullifiableEntityKeys entries" );
1380
itr = nullifiableEntityKeys.iterator();
1381
while ( itr.hasNext() ) {
1382
EntityKey entry = ( EntityKey ) itr.next();
1383
entry.serialize( oos );
1387
public static StatefulPersistenceContext deserialize(
1388
ObjectInputStream ois,
1389
SessionImplementor session) throws IOException, ClassNotFoundException {
1390
log.trace( "deserializing persistent-context" );
1391
StatefulPersistenceContext rtn = new StatefulPersistenceContext( session );
1393
// during deserialization, we need to reconnect all proxies and
1394
// collections to this session, as well as the EntityEntry and
1395
// CollectionEntry instances; these associations are transient
1396
// because serialization is used for different things.
1399
// todo : we can actually just determine this from the incoming EntityEntry-s
1400
rtn.hasNonReadOnlyEntities = ois.readBoolean();
1402
int count = ois.readInt();
1403
log.trace( "staring deserialization of [" + count + "] entitiesByKey entries" );
1404
rtn.entitiesByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1405
for ( int i = 0; i < count; i++ ) {
1406
rtn.entitiesByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() );
1409
count = ois.readInt();
1410
log.trace( "staring deserialization of [" + count + "] entitiesByUniqueKey entries" );
1411
rtn.entitiesByUniqueKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1412
for ( int i = 0; i < count; i++ ) {
1413
rtn.entitiesByUniqueKey.put( EntityUniqueKey.deserialize( ois, session ), ois.readObject() );
1416
count = ois.readInt();
1417
log.trace( "staring deserialization of [" + count + "] proxiesByKey entries" );
1418
rtn.proxiesByKey = new ReferenceMap( ReferenceMap.HARD, ReferenceMap.WEAK, count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count, .75f );
1419
for ( int i = 0; i < count; i++ ) {
1420
EntityKey ek = EntityKey.deserialize( ois, session );
1421
Object proxy = ois.readObject();
1422
if ( proxy instanceof HibernateProxy ) {
1423
( ( HibernateProxy ) proxy ).getHibernateLazyInitializer().setSession( session );
1424
rtn.proxiesByKey.put( ek, proxy );
1427
log.trace( "encountered prunded proxy" );
1429
// otherwise, the proxy was pruned during the serialization process
1432
count = ois.readInt();
1433
log.trace( "staring deserialization of [" + count + "] entitySnapshotsByKey entries" );
1434
rtn.entitySnapshotsByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1435
for ( int i = 0; i < count; i++ ) {
1436
rtn.entitySnapshotsByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() );
1439
count = ois.readInt();
1440
log.trace( "staring deserialization of [" + count + "] entityEntries entries" );
1441
rtn.entityEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1442
for ( int i = 0; i < count; i++ ) {
1443
Object entity = ois.readObject();
1444
EntityEntry entry = EntityEntry.deserialize( ois, session );
1445
rtn.entityEntries.put( entity, entry );
1448
count = ois.readInt();
1449
log.trace( "staring deserialization of [" + count + "] collectionsByKey entries" );
1450
rtn.collectionsByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1451
for ( int i = 0; i < count; i++ ) {
1452
rtn.collectionsByKey.put( CollectionKey.deserialize( ois, session ), ois.readObject() );
1455
count = ois.readInt();
1456
log.trace( "staring deserialization of [" + count + "] collectionEntries entries" );
1457
rtn.collectionEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1458
for ( int i = 0; i < count; i++ ) {
1459
final PersistentCollection pc = ( PersistentCollection ) ois.readObject();
1460
final CollectionEntry ce = CollectionEntry.deserialize( ois, session );
1461
pc.setCurrentSession( session );
1462
rtn.collectionEntries.put( pc, ce );
1465
count = ois.readInt();
1466
log.trace( "staring deserialization of [" + count + "] arrayHolders entries" );
1467
rtn.arrayHolders = IdentityMap.instantiate( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1468
for ( int i = 0; i < count; i++ ) {
1469
rtn.arrayHolders.put( ois.readObject(), ois.readObject() );
1472
count = ois.readInt();
1473
log.trace( "staring deserialization of [" + count + "] nullifiableEntityKeys entries" );
1474
rtn.nullifiableEntityKeys = new HashSet();
1475
for ( int i = 0; i < count; i++ ) {
1476
rtn.nullifiableEntityKeys.add( EntityKey.deserialize( ois, session ) );
1480
catch ( HibernateException he ) {
1481
throw new InvalidObjectException( he.getMessage() );