Confluence Docs 3.0 : Hibernate Sessions and Transaction Management Guidelines This page last changed on Dec 17, 2007 by jnolen.
| These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
### Transaction Management Transaction demarcation is provided by Spring, with a few wrinkles. We wrap managers in transaction interceptors, but not DAOs. We use whatever the default isolation level is for whatever database we're connecting to We commit the transaction manually between performing an action, and displaying the view. The last point is necessary because in some cases, we were sending redirect responses to the browser then committing the transaction. A quick browser would request the redirect page before their transaction was committed, and view stale data as a result. By committing the transaction before we render the view, we make sure that everything we expect to be in the database is in the database before the browser has a chance to re-request it. ### Hibernate Sessions Sessions are a Hibernate construct used to mediate connections with the database. The session opens a single database connection when it is created, and holds onto it until the session is closed. Every object that is loaded by Hibernate from the database is associated with the session, allowing Hibernate to automatically persist objects that are modified, and allowing Hibernate to implement functionality such as lazy-loading. #### Disconnected Objects If an object is evicted from its session (for example via a clear, see below), or the session is closed while the object is still kept alive, the object is "disconnected" from the session. A disconnected object will continue to work so long as you don't perform any operation that it needs to go back to the database for, such as accessing a lazily-loaded collection. If you see a LazyInitializationException, it means that a Hibernate-managed object has lived longer than its session. Managed objects are not portable between sessions. Trying to load an object in one session then save it into another session will also result in an error. (You can use `Session.load()` or `Session.get()` to re-introduce an object to a new session, but you're much better off fixing whatever problem is causing you to try to move objects between sessions in the first place. #### Caching Storing hibernate objects in caches is a bad idea. By definition, a hibernate-managed object placed in a cache will outlive its session. Even if caching such an object is safe now, it's quite likely that in the future we might switch some of its properties to be lazily-loaded, or change code-paths so that properties that were previously being loaded before the object was cached aren't being loaded any more. The `LazyInitializationException` errors that result rarely show up in tests, and are hard to diagnose and fix. Hibernate maintains its own second-level cache (shared between Confluence nodes via Tangosol Coherence) that does not suffer from this problem. Use it in preference to manually caching Hibernate data. If you need to cache information from Hibernate, don't cache the Hibernate objects themselves. A useful alternative is to cache the object's ID and class, and then retrieve the object in the context of the current session using `Session.get(class, id)`. ID lookups go straight through Hibernate's own second-level cache, so are (hopefully) efficient. The `getHandle()` and `findByHandle()` methods of the `AnyTypeObjectDao` provide a helpful API for doing just this. #### Flushing and Clearing When the session persists its changes to the database, this is called "flushing". During a flush, each object associated with the session is checked to see if it has changed state. Any object with changed state will be persisted to the database, regardless of whether the changed objects are explicitly saved or not. You can configure Hibernate's flush behaviour, but the default (`FlushMode.AUTO`) will flush the session: When you manually call `flush()` on the session Before Hibernate performs a query, if Hibernate believes flushing is necessary for the query to get accurate results When a transaction is committed When the session is closed. How long a flush takes is a function of the number of objects associated with the session. Thus, the more objects you load during the lifetime of a session, the less efficient each query will be (as a flush will generally be done prior to each query). If you have some long-running operation that gets slower and slower and slower as it runs, it's possible that the Hibernate session is the cause. Operations that cycle through large numbers of objects may want to periodically `clear()` their session. Clearing will dissociate all objects from the session, so they won't build up making flushes slower and slower. Clearing a session does not flush it first, so you'll want to flush manually or you will lose any pending, unsaved changes. #### Multi-threading Hibernate sessions are not thread-safe. Not only does this mean you shouldn't pass a Hibernate session into a new thread, it also means that because objects you load from a session can be called from (and call back to) their owning session, you must not share Hibernate-managed objects between threads. Once again, try to only pass object IDs, and load the object freshly from the new thread's own session. Spring's transaction management places the Hibernate session in a ThreadLocal variable, accessed via the `sessionFactory`. All Confluence DAOs use that ThreadLocal. This means that when you create a new thread you no longer have access to the Hibernate session for that thread (a good thing, as above), and you are no longer part of your current transaction. #### The Session In View Filter Confluence uses the "Session in view" pattern for managing Hibernate sessions. The `SessionInViewFilter` opens a Hibernate session which is then available for the entire web request. The advantages of this is that you avoid session-related errors: The session lifecycle is uniform for every request Hibernate objects remain "alive" for the whole request, thus you can still retrieve lazily-loaded data in Velocity templates The disadvantages are: Each request monopolises a database connection from the moment a request comes in, to the last byte sent to the client Each session will end up associated with every object that is loaded for the duration of the request Developers are often caught out by the way sessions behave when threads haven't come in through the web tier (i.e. Quartz jobs) #### Non-Web Requests Non-web requests do not automatically have a Hibernate session to work with, because they don't come in through the Session In View Filter. This includes start-up events, quartz jobs, and any long-running task that spawns a new thread. As a result, a new session will be opened when you make a call to a transaction-managed Spring object, and closed when that call returns. A very common programming error in this context is to retrieve a collection of objects from a manager, then do something to each object. The moment the call to the manager returns, all objects will be detached from their containing session. If you try to do anything to them after that, you won't get the result you expected. I'm not sure if this sequence diagram helps, but here goes... Consider moving such operations into the manager itself, so the whole operation will be wrapped in the one transaction. Alternatively, if making everything run in separate transactions is what you want, have the job retrieve a collection of IDs, and pass those back to the manager one by one. Attachments: txn-sequence.png (image/png)

Document generated by Confluence on Nov 05, 2009 23:34