1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
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.
8 *
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.
12 *
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
16 * for more details.
17 *
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
23 *
24 */
25 package org.hibernate.event.def;
26
27 import java.io.Serializable;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.hibernate.HibernateException;
35 import org.hibernate.action.CollectionRecreateAction;
36 import org.hibernate.action.CollectionRemoveAction;
37 import org.hibernate.action.CollectionUpdateAction;
38 import org.hibernate.collection.PersistentCollection;
39 import org.hibernate.engine.ActionQueue;
40 import org.hibernate.engine.Cascade;
41 import org.hibernate.engine.CascadingAction;
42 import org.hibernate.engine.CollectionEntry;
43 import org.hibernate.engine.CollectionKey;
44 import org.hibernate.engine.Collections;
45 import org.hibernate.engine.EntityEntry;
46 import org.hibernate.engine.PersistenceContext;
47 import org.hibernate.engine.Status;
48 import org.hibernate.event.EventSource;
49 import org.hibernate.event.FlushEntityEvent;
50 import org.hibernate.event.FlushEntityEventListener;
51 import org.hibernate.event.FlushEvent;
52 import org.hibernate.engine.SessionImplementor;
53 import org.hibernate.persister.entity.EntityPersister;
54 import org.hibernate.pretty.Printer;
55 import org.hibernate.util.IdentityMap;
56 import org.hibernate.util.LazyIterator;
57
58 /**
59 * A convenience base class for listeners whose functionality results in flushing.
60 *
61 * @author Steve Eberole
62 */
63 public abstract class AbstractFlushingEventListener implements Serializable {
64
65 private static final Logger log = LoggerFactory.getLogger(AbstractFlushingEventListener.class);
66
67 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68 // Pre-flushing section
69 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70
71 /**
72 * Coordinates the processing necessary to get things ready for executions
73 * as db calls by preping the session caches and moving the appropriate
74 * entities and collections to their respective execution queues.
75 *
76 * @param event The flush event.
77 * @throws HibernateException Error flushing caches to execution queues.
78 */
79 protected void flushEverythingToExecutions(FlushEvent event) throws HibernateException {
80
81 log.trace("flushing session");
82
83 EventSource session = event.getSession();
84
85 final PersistenceContext persistenceContext = session.getPersistenceContext();
86 session.getInterceptor().preFlush( new LazyIterator( persistenceContext.getEntitiesByKey() ) );
87
88 prepareEntityFlushes(session);
89 // we could move this inside if we wanted to
90 // tolerate collection initializations during
91 // collection dirty checking:
92 prepareCollectionFlushes(session);
93 // now, any collections that are initialized
94 // inside this block do not get updated - they
95 // are ignored until the next flush
96
97 persistenceContext.setFlushing(true);
98 try {
99 flushEntities(event);
100 flushCollections(session);
101 }
102 finally {
103 persistenceContext.setFlushing(false);
104 }
105
106 //some statistics
107 if ( log.isDebugEnabled() ) {
108 log.debug( "Flushed: " +
109 session.getActionQueue().numberOfInsertions() + " insertions, " +
110 session.getActionQueue().numberOfUpdates() + " updates, " +
111 session.getActionQueue().numberOfDeletions() + " deletions to " +
112 persistenceContext.getEntityEntries().size() + " objects"
113 );
114 log.debug( "Flushed: " +
115 session.getActionQueue().numberOfCollectionCreations() + " (re)creations, " +
116 session.getActionQueue().numberOfCollectionUpdates() + " updates, " +
117 session.getActionQueue().numberOfCollectionRemovals() + " removals to " +
118 persistenceContext.getCollectionEntries().size() + " collections"
119 );
120 new Printer( session.getFactory() ).toString(
121 persistenceContext.getEntitiesByKey().values().iterator(),
122 session.getEntityMode()
123 );
124 }
125 }
126
127 /**
128 * process cascade save/update at the start of a flush to discover
129 * any newly referenced entity that must be passed to saveOrUpdate(),
130 * and also apply orphan delete
131 */
132 private void prepareEntityFlushes(EventSource session) throws HibernateException {
133
134 log.debug("processing flush-time cascades");
135
136 final Map.Entry[] list = IdentityMap.concurrentEntries( session.getPersistenceContext().getEntityEntries() );
137 //safe from concurrent modification because of how entryList() is implemented on IdentityMap
138 final int size = list.length;
139 final Object anything = getAnything();
140 for ( int i=0; i<size; i++ ) {
141 Map.Entry me = list[i];
142 EntityEntry entry = (EntityEntry) me.getValue();
143 Status status = entry.getStatus();
144 if ( status == Status.MANAGED || status == Status.SAVING ) {
145 cascadeOnFlush( session, entry.getPersister(), me.getKey(), anything );
146 }
147 }
148 }
149
150 private void cascadeOnFlush(EventSource session, EntityPersister persister, Object object, Object anything)
151 throws HibernateException {
152 session.getPersistenceContext().incrementCascadeLevel();
153 try {
154 new Cascade( getCascadingAction(), Cascade.BEFORE_FLUSH, session )
155 .cascade( persister, object, anything );
156 }
157 finally {
158 session.getPersistenceContext().decrementCascadeLevel();
159 }
160 }
161
162 protected Object getAnything() { return null; }
163
164 protected CascadingAction getCascadingAction() {
165 return CascadingAction.SAVE_UPDATE;
166 }
167
168 /**
169 * Initialize the flags of the CollectionEntry, including the
170 * dirty check.
171 */
172 private void prepareCollectionFlushes(SessionImplementor session) throws HibernateException {
173
174 // Initialize dirty flags for arrays + collections with composite elements
175 // and reset reached, doupdate, etc.
176
177 log.debug("dirty checking collections");
178
179 final List list = IdentityMap.entries( session.getPersistenceContext().getCollectionEntries() );
180 final int size = list.size();
181 for ( int i = 0; i < size; i++ ) {
182 Map.Entry e = ( Map.Entry ) list.get( i );
183 ( (CollectionEntry) e.getValue() ).preFlush( (PersistentCollection) e.getKey() );
184 }
185 }
186
187 /**
188 * 1. detect any dirty entities
189 * 2. schedule any entity updates
190 * 3. search out any reachable collections
191 */
192 private void flushEntities(FlushEvent event) throws HibernateException {
193
194 log.trace("Flushing entities and processing referenced collections");
195
196 // Among other things, updateReachables() will recursively load all
197 // collections that are moving roles. This might cause entities to
198 // be loaded.
199
200 // So this needs to be safe from concurrent modification problems.
201 // It is safe because of how IdentityMap implements entrySet()
202
203 final EventSource source = event.getSession();
204
205 final Map.Entry[] list = IdentityMap.concurrentEntries( source.getPersistenceContext().getEntityEntries() );
206 final int size = list.length;
207 for ( int i = 0; i < size; i++ ) {
208
209 // Update the status of the object and if necessary, schedule an update
210
211 Map.Entry me = list[i];
212 EntityEntry entry = (EntityEntry) me.getValue();
213 Status status = entry.getStatus();
214
215 if ( status != Status.LOADING && status != Status.GONE ) {
216 FlushEntityEvent entityEvent = new FlushEntityEvent( source, me.getKey(), entry );
217 FlushEntityEventListener[] listeners = source.getListeners().getFlushEntityEventListeners();
218 for ( int j = 0; j < listeners.length; j++ ) {
219 listeners[j].onFlushEntity(entityEvent);
220 }
221 }
222 }
223
224 source.getActionQueue().sortActions();
225 }
226
227 /**
228 * process any unreferenced collections and then inspect all known collections,
229 * scheduling creates/removes/updates
230 */
231 private void flushCollections(EventSource session) throws HibernateException {
232
233 log.trace("Processing unreferenced collections");
234
235 List list = IdentityMap.entries( session.getPersistenceContext().getCollectionEntries() );
236 int size = list.size();
237 for ( int i = 0; i < size; i++ ) {
238 Map.Entry me = ( Map.Entry ) list.get( i );
239 CollectionEntry ce = (CollectionEntry) me.getValue();
240 if ( !ce.isReached() && !ce.isIgnore() ) {
241 Collections.processUnreachableCollection( (PersistentCollection) me.getKey(), session );
242 }
243 }
244
245 // Schedule updates to collections:
246
247 log.trace( "Scheduling collection removes/(re)creates/updates" );
248
249 list = IdentityMap.entries( session.getPersistenceContext().getCollectionEntries() );
250 size = list.size();
251 ActionQueue actionQueue = session.getActionQueue();
252 for ( int i = 0; i < size; i++ ) {
253 Map.Entry me = (Map.Entry) list.get(i);
254 PersistentCollection coll = (PersistentCollection) me.getKey();
255 CollectionEntry ce = (CollectionEntry) me.getValue();
256
257 if ( ce.isDorecreate() ) {
258 session.getInterceptor().onCollectionRecreate( coll, ce.getCurrentKey() );
259 actionQueue.addAction(
260 new CollectionRecreateAction(
261 coll,
262 ce.getCurrentPersister(),
263 ce.getCurrentKey(),
264 session
265 )
266 );
267 }
268 if ( ce.isDoremove() ) {
269 session.getInterceptor().onCollectionRemove( coll, ce.getLoadedKey() );
270 actionQueue.addAction(
271 new CollectionRemoveAction(
272 coll,
273 ce.getLoadedPersister(),
274 ce.getLoadedKey(),
275 ce.isSnapshotEmpty(coll),
276 session
277 )
278 );
279 }
280 if ( ce.isDoupdate() ) {
281 session.getInterceptor().onCollectionUpdate( coll, ce.getLoadedKey() );
282 actionQueue.addAction(
283 new CollectionUpdateAction(
284 coll,
285 ce.getLoadedPersister(),
286 ce.getLoadedKey(),
287 ce.isSnapshotEmpty(coll),
288 session
289 )
290 );
291 }
292
293 }
294
295 actionQueue.sortCollectionActions();
296
297 }
298
299 /**
300 * Execute all SQL and second-level cache updates, in a
301 * special order so that foreign-key constraints cannot
302 * be violated:
303 * <ol>
304 * <li> Inserts, in the order they were performed
305 * <li> Updates
306 * <li> Deletion of collection elements
307 * <li> Insertion of collection elements
308 * <li> Deletes, in the order they were performed
309 * </ol>
310 */
311 protected void performExecutions(EventSource session) throws HibernateException {
312
313 log.trace("executing flush");
314
315 try {
316 session.getJDBCContext().getConnectionManager().flushBeginning();
317 // we need to lock the collection caches before
318 // executing entity inserts/updates in order to
319 // account for bidi associations
320 session.getActionQueue().prepareActions();
321 session.getActionQueue().executeActions();
322 }
323 catch (HibernateException he) {
324 log.error("Could not synchronize database state with session", he);
325 throw he;
326 }
327 finally {
328 session.getJDBCContext().getConnectionManager().flushEnding();
329 }
330 }
331
332
333 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334 // Post-flushing section
335 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
336
337 /**
338 * 1. Recreate the collection key -> collection map
339 * 2. rebuild the collection entries
340 * 3. call Interceptor.postFlush()
341 */
342 protected void postFlush(SessionImplementor session) throws HibernateException {
343
344 log.trace( "post flush" );
345
346 final PersistenceContext persistenceContext = session.getPersistenceContext();
347 persistenceContext.getCollectionsByKey().clear();
348 persistenceContext.getBatchFetchQueue()
349 .clearSubselects(); //the database has changed now, so the subselect results need to be invalidated
350
351 Iterator iter = persistenceContext.getCollectionEntries().entrySet().iterator();
352 while ( iter.hasNext() ) {
353 Map.Entry me = (Map.Entry) iter.next();
354 CollectionEntry collectionEntry = (CollectionEntry) me.getValue();
355 PersistentCollection persistentCollection = (PersistentCollection) me.getKey();
356 collectionEntry.postFlush(persistentCollection);
357 if ( collectionEntry.getLoadedPersister() == null ) {
358 //if the collection is dereferenced, remove from the session cache
359 //iter.remove(); //does not work, since the entrySet is not backed by the set
360 persistenceContext.getCollectionEntries()
361 .remove(persistentCollection);
362 }
363 else {
364 //otherwise recreate the mapping between the collection and its key
365 CollectionKey collectionKey = new CollectionKey(
366 collectionEntry.getLoadedPersister(),
367 collectionEntry.getLoadedKey(),
368 session.getEntityMode()
369 );
370 persistenceContext.getCollectionsByKey()
371 .put(collectionKey, persistentCollection);
372 }
373 }
374
375 session.getInterceptor().postFlush( new LazyIterator( persistenceContext.getEntitiesByKey() ) );
376
377 }
378
379 }