Hibernate is not itself a database. It is a lightweight object-relational mapping tool. Transaction management is delegated to the underlying database connection. If the connection is enlisted with JTA, operations performed by the Session are atomically part of the wider JTA transaction. Hibernate can be seen as a thin adapter to JDBC, adding object oriented semantics.
A SessionFactory is an expensive-to-create, threadsafe object intended to be shared by all application threads. A Session is an inexpensive, nonthreadsafe object that should be used once, for a single business process, and then discarded. For example, when using Hibernate in a servlet-based application, servlets could obtain a SessionFactory using
SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");
Each call to a service method could create a new Session, flush() it, commit() its connection, close() it and finally discard it.
In a stateless session bean, a similar approach could be used. The bean would obtain a SessionFactory in setSessionContext(). Then each business method would create a Session, flush() it and close() it. Of course, the application should not commit() the connection. (Leave that to JTA.)
Ensure you understand the semantics of flush(). Flushing synchronizes the persistent store with in-memory changes but not vice-versa. So when you flush() and then commit() the connection, the session will continue to contain potentially stale data. The only way you may continue to use a session after a flush() and commit() is by using versioned data.
The next few sections will discuss alternative approaches that utilize versioning to ensure transaction atomicity. These are considered "advanced" approaches to be used with care.
You should observe the following practices when creating Hibernate Sessions:
Never create more than one concurrent Session or Transaction instance per database connection
Be extremely careful when creating more than one Session per datastore per transaction. The Session itself keeps track of updates made to loaded objects, so a different Session might see stale data.
The Session is not threadsafe. We can't see why you would need to share a session between two concurrent threads but if you must, make sure your threads carefully synchronize on the Session object before accessing it.
Many business processes require a whole series of interactions with the user interleaved with database accesses. In web and enterprise applications it is not acceptable for a database transaction to span a user interaction. Maintaining isolation of business processes becomes the partial responsibility of the application tier. The only approach that is consistent with high concurrency and high scalability is optimistic locking with versioning. Hibernate provides for three possible approaches to writing application code that uses optimistic locking.
A single Session instance and its persistent instances are used for the whole business process. The Session uses optimistic locking with versioning to ensure that many database transactions appear to the application as a single logical transaction. The Session is disconnected when waiting for user interaction. This approach is the most efficient in terms of database access. The application need not concern itself with version checking or with reassociating transient instances.
// foo is an instance loaded earlier by the Session session.reconnect(); foo.setProperty("bar"); session.flush(); session.connection().commit(); session.disconnect();
Each interaction with the persistent store occurs in a new Session. However, the same persistent instances are reused for each interaction with the database. The application manipulates the state of transient instances originally loaded in another Session and then "reassociates" them using Session.update() or Session.saveOrUpdate().
// foo is an instance loaded by a previous Session foo.setProperty("bar"); session = factory.openSession(); session.saveOrUpdate(foo); session.flush(); session.connection().commit(); session.close();
Each interaction with the persistent store occurs in a new Session that reloads all persistent instances from the datastore before manipulating them. This approach forces the application to carry out its own version checking to ensure business process isolation. (Of course, Hibernate will still update version numbers for you.) This approach is the least efficient in terms of database access. It is the approach most similar to entity EJBs.
// foo is an instance loaded by a previous Session session = factory.openSession(); int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException(); foo.setProperty("bar"); session.flush(); session.connection().commit(); session.close();
Of course, if you are operating in a low-data-concurrency environment and don't require version checking, you may use this approach and just skip the version check.
The first approach described above is to maintain a single Session for a whole business process thats spans user think time. (For example, a servlet might keep a Session in the user's HttpSession.) For performance reasons you should
commit the Transaction (or JDBC connection) and then
disconnect the Session from the JDBC connection
before waiting for user activity. The method Session.disconnect() will disconnect the session from the JDBC connection and return the connection to the pool (unless you provided the connection).
Session.reconnect() obtains a new connection (or you may supply one) and restarts the session. After reconnection, to force a version check on data you aren't updating, you may call Session.lock() on any objects that might have been updated by another transaction. You don't need to lock any data that you are updating.
Heres an example:
SessionFactory sessions; List fooList; Bar bar; .... Session s = sessions.openSession(); Transaction tx = null; try { tx = s.beginTransaction(); fooList = s.find( "select foo from eg.Foo foo where foo.Date = current date" // uses db2 date function ); bar = (Bar) s.create(Bar.class); tx.commit(); } catch (Exception e) { if (tx!=null) tx.rollback(); s.close(); throw e; } s.disconnect();
Later on:
s.reconnect(); try { tx = s.beginTransaction(); bar.setFooTable( new HashMap() ); Iterator iter = fooList.iterator(); while ( iter.hasNext() ) { Foo foo = (Foo) iter.next(); s.lock(foo, LockMode.READ); //check that foo isn't stale bar.getFooTable().put( foo.getName(), foo ); } tx.commit(); } catch (Exception e) { if (tx!=null) tx.rollback(); throw e; } finally { s.close(); }
You can see from this how the relationship between Transactions and Sessions is many-to-one, A Session represents a conversation between the application and the persistent store. The Transaction breaks that conversation up into atomic units of work.
It is not intended that users spend much time worring about locking strategies. Its usually enough to specify an isolation level and then simply let the database do all the work. However, advanced users may sometimes wish to obtain pessimistic locks, or re-obtain locks at the start of a new transaction.
The LockMode class defines the different lock levels that may be acquired by Hibernate. A lock is obtained by the following mechanisms:
LockMode.WRITE is acquired automatically when Hibernate updates or inserts a row.
LockMode.UPGRADE may be acquired upon explicit user request using SELECT ... FOR UPDATE on databases which support that syntax.
LockMode.UPGRADE_NOWAIT may be acquired upon explicit user request using a SELECT ... FOR UPDATE NOWAIT under Oracle.
LockMode.READ is acquired automatically when Hibernate reads data under Repeatable Read or Serializable isolation level. May be re-acquired by explicit user request.
LockMode.NONE represents the absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to update() or saveOrUpdate() also start out in this lock mode.
The "explicit user request" is expressed in one of the following ways:
A call to Session.load(), specifying a LockMode.
A call to Session.lock().
A call to Query.setLockMode().
If Session.load() is called with UPGRADE or UPGRADE_NOWAIT, and the requested object was not yet loaded by the session, the object is loaded using SELECT ... FOR UPDATE. If load() is called for an object that is already loaded with a less restrictive lock than the one requested, Hibernate calls lock() for that object.
Session.lock() performs a version number check if the specified lock mode is READ, UPGRADE or UPGRADE_NOWAIT. (In the case of UPGRADE or UPGRADE_NOWAIT, SELECT ... FOR UPDATE is used.)
If the database does not support the requested lock mode, Hibernate will use an appropriate alternate mode (instead of throwing an exception). This ensures that applications will be portable.