In my efforts to address the problems encountered using the 'open session->load data->close session' model in a thick-client environment, I've written a 'mini-spec' that proposes a 'synchronized singleton' pattern approach.
I thought that before I released it to development for implementation, I'd post it here for (valued) critical review. If this is all wrong, I'd much rather hear about it now than discover it during/after development.
Its long, and may not be relevant to many, but my hope is that someone will have time to peruse it, and if there are glaring errors and/or ommissions, provide feedback.
Enjoy.
-Mitch
Quote:
OVERVIEW
The use of the Hibernate Object/Relational mapping tool within [our product] necessitates special consideration of our database session management strategy. Our current 'open session->load objects->close session' model has lead to significant data redundancy issues that warrant a new Hibernate session management strategy.
'SESSION' DEFINITION
Within the context of Hibernate, a database 'session' defines both the physical (i.e. JDBC) database connection and a local cache of database objects that Hibernate manages with respect to the database. Hibernate uses this session cache to optimize interaction with the database. When a request is made to load objects from the database, the Session object being used for the request will first consult it's local cache prior to generating a database query. If the desired object exists within the session cache, no database interaction is required, and the object is retrieved from the cache. If the object does not exist in the session cache, the associated data is retrieved from the database, local data objects are created from that data and added to the session cache, and finally returned to the requestor.
This session cache facilitates 'lazy loading' of database objects. If the Session object is closed, the session cache is destroyed. In this case, Hibernate must honor the relationships specified by the Hibernate mapping files, and retrieve and populate the entire object graph on behalf of the requestor. Obviously, this precludes loading objects on demand.
PROBLEM DEFINITION
Without an existing session object, and it's associated session cache, significant duplication of data exists in the client. For example, if a client component requests a given object from the database, and subsequently closes the Session with which it accessed the object's data, Hibernate has no choice but to populate an entire object graph representing that object(i.e. loads all reachable objects as defined by the object mappings). This situation results in a couple of problems. The client component may not need, and may never use much of the data contained in the object graph associated with a particular object. This results in data retrieved from the database that is never accessed. Furthermore, assume that the user requests an object via a different client component. A new Session object will be allocated to service the request, with an empty session cache. This results in pulling the entire object graph for that object, regardless of the fact that this data already resides in the client as a result of the previous client component's query. Not only does this result in needless overhead as redundant data is retrieved and redundant local objects populated, it creates a dangerous situation in that if one of the two copies of a given object graph is modified, and updated to the database, the 'other' copy of the object graph now contains stale data.
PROPOSED SOLUTION
A simple solution is to migrate from our current 'open session->load data->close session' semantics to an implementation of the singleton pattern for a single, system-wide database session object within the client. This single database session object will be used for *all* database interaction, resulting in a valid cache of all database objects retrieved from the database. This means that once a particular object graph has been loaded from the database, all subsequent requests for objects within that object graph will come from the local session cache, and not require redundant database access. This also ensures that all references to database objects within the client remain fresh (with regard to the session cache). In other words, changes to objects within one portion of the client, are immediately visible to any other client components holding references to the modified data objects.
IMPLEMENTATION
In accordance with the singleton pattern specification, there will exist a HibernateSession object that implements a 'public static synchronized Session getCurrent()' method which returns the singleton hibernate session object. Technically speaking, the singleton pattern specifies a 'getInstance()' method. However, getCurrent() is more syntactically correct for this implementation.
THREAD MANAGEMENT
Since Hibernate session objects are inherently *not* thread safe, HibernateSession.getCurrent() enforces a single threaded access model. If a thread makes a call to HibernateSession.getCurrent(), and the singleton Session object is already in use by another thread, the subsequent calling thread will block (via Object.wait()) until the Session object becomes available. This approach requires that the HibernateSession class implement a 'public static HibernateSession.releaseSession(Session s)' method. This method *must* be called when a thread is done with the Session object. HibernateSession.releaseSession() is responsible for notifying waiting threads and re-allocating the Session object.
For consistency sake, there is also a 'public static synchronized Session HibernateSession.getCurrent(long timeout)' method which returns after the specified number of milliseconds if the singleton Session object does not become available. In this case, HibernateSession.getCurrent() throws an InterruptedException.
DATABASE CONNECTION MANAGEMENT (POOLING)
While it is reasonable to maintain a long-lived session object from a data consistency perspective, this approach presents problems from a physical database connection (i.e. JDBC) perspective. Physical database connections are subject to failure. These failures can result from session timeouts, network interruptions, etc. Fortunately, Hibernate supports the ability to dissociate the physical database connection from the session object. Since the management of physical database connections is better left to existing JDBC connection pooling technologies, interaction with the database connection pooling mechanism will be incorporated into the singleton session pattern implementation. HibernateSession.getCurrent() will allocate a JDBC connection from the connection pool, and associate that JDBC connection with the Session object prior to returning the Session to the requestor. HibernateSession.releaseSession() will detach the JDBC connection from the Session object and return the connection to the pool implementation. This allows for validation and/or error recovery of connection related problems by the pool implementation.
USAGE PATTERN
In order to access persistent objects within the client, the singleton Session object is accessed via the HibernateSession.getCurrent() method. The returned Session object is then used for interaction with the persistence layer. When the requestor is done with the Session object, HibernateSession.releaseSession() is called. It is imperative that HibernateSession.releaseSession() be called to release the singleton Session object for use by other requestors.
A typical implementation is as follows:
try{
Session s = HibernateSession.getCurrent(5000);
{...interact with the database...}
} catch (HibernateException he) {
{...error handling code...}
} catch (InterruptedException ie) {
{...notify the user...}
} finally {
// guard against InterruptedExceptions
if(s != null)
HibernateSession.releaseSession(s);
}