PART 5 A
PART 5 B
In parts 1- 7 of the article series, We had completed coding various types of associations using NHibernate. This is the last of the eight part article series. In this article we clear the question at the end of first article, "How do we manage a persistent object across sessions?". The background for the article is very important as it paves the way and introduces important topics required for understanding discussions here.
All ORM applications uses a persistence manager for functionality like database writes, reads and query. In NHibernate it is done by interfaces called Session,Transaction, Criteria etc. Each session must first open a transaction and all activities on objects are within this boundary of transaction (saying it as transaction is a highly simplified way of representing it. In orm it is generally referred to as unit of action and not necessarily a transaction but you will get an idea with the word "transaction" and saves me from going into an explanation of "what unit of action is ?" which in itself is an exhaustive & interesting topic in ORM and definitely not needed to consume orm software for application development but vital for developing ORM library like NHibernate.).
Each session is said to be associated with a PersistenceContext. PersistenceContext provides functionality like maintaining unique object identity (called identity scoping), repeatable reads, avoiding recursive load of objects in graph, automatic transactional writes, dirtychecking etc. People who have worked with ORM code will immediately know what this Persistence Context means just by reading the functionality it offers. Yes, it is the good old IdentityMap. For the rest, I explain the idea here which is essential to understand. Note that the idea expressed here is based on our previous experience in developing ORM library and not NHibernate specifically but the idea is almost the same for most ORM libraries.
As said earlier, for ORM software the first step is fixing object identity for persistent objects in a way that it is uniquely matched to a database row. The next step is ensuring that the identity is unique and only one persistent object exists representing a particular database row. This avoids ambiguity. So what is usually done to ensure this object uniqueness is, An inmemory hashtable is used with the database primarykey as the "key" field of the hashtable and the persistent object as "Value". Whenever ORM software looksup for an persistent object in the database while executing a query or gets it by id (i.e the primary key value), it first does a lookup for the persistent object in this hashtable by using the "primary key" which is key field for the hashtable. If it exists it just returns the object from hashtable. Else ORM software creates an object based on values obtained by executing the query against a database and adds it to the hashtable and then finally returns the object to the client code. Every persistent object used by the client code is stored in this hashtable. This is the first level cache for the ORM software. When you ask for an object repeatedly, only the first time it is queried against the db. Rest of the times it is got from this cache or hashtable. This is called repeatable read. The main thing is where you keep this hashtable. If you keep this hashtable at the process level and store all objects of a process there, then you need to synchronize access to the hashtable from multiple threads. So general idea is to keep this hashtable at thread level and all persistent objects modified in a session and transaction within this thread is maintained inside this hashtable per thread. This is named persistent context cache (more or less gives the rough picture of how orm library is developed). The next functionality offered by the persistent context is dirty checking. The name dirty checking is self explanatory. It means you find whether an object is modified or not within the scope of 1 transaction. ORM software uses two ways to do it, 1. By storing a snapshot of persistent object separately at the start of transaction and comparing at the end of transaction, 2. Keep an IsDirty flag and set it if an object is modified. NHibernate must use the first method of DirtyChecking obviously because we dont set any flags while modifying persistent objects and we dont interit objects from a well defined nhibernate interface which would be necessary for the second method. So what is the use of dirty checking? At the end of a transaction, NHibernate will synch the changes of all modified dirty persistent objects to the database if it is a successful transaction. You dont have to do anything. Persistent objects are managed by Nhibernate. The snapshot method is preferred by sophisticated library like NHibernate because you can finetune the sql generated for updates to database to include only fields that was actually modified instead of all the fields for the object. So now we are familiar with persistent context. This persistent context and its cache is associated with each session separately. The diagram in Figure 1 gives a rough idea about the thread level persistent context cache.
FIGURE 1 - PERSISTENT CONTEXT CACHE - Objects already in the cache are directly retrieved from the hashtable or cache in two steps. For objects not in cache 3 steps are required because first the item has to be brought into the cache and then it is returned to client. The session.getbyid(primarykey) method is for illustration purpose to explain that the client code retrieves an object by using the identifier.
Using the code
A persistent object in NHibernate exists in four states: Transient, Persistent, Removed and Detached Objects. Objects instantinated using "new" operator are called Transient objects. They do not have a db id. When this transient object is saved to a db it is associated with a db id and becomes a Persistent object. Objects loaded from db also have the db id and are called persistent objects only. These are the two main type of objects in ORM. NHibernate introduces a third object state called Detached state. IT IS VITAL TO UNDERSTAND THIS DETACHED STATE and to understand this only, the topic of persistent context cache was introduced in the BACKGROUND of this article.
When the session closes, the persistent context cache associated with the session also closes. However it is possible that the reference to a persistent object exists outliving the life of the session in which it was created. THE STATE OF THESE PERSISTENT OBJECTS OUTSIDE THE SCOPE OF PERSISTENT CONTEXT CACHE IS SAID TO BE DETACHED.
The final lesser used state is the "REMOVED" state of an object which is the intermittent state of an object which is deleted in a session from db but the transaction in which the deletion is called is not completed as yet. So until the time that transaction is completed, the object is in a state called REMOVED after which transitions to Transient state. The figure 2 below gives the rough idea of the states of the object:
FIGURE 2 - DIFFERENT STATES OF AN OBJECT
Now the problem with the fetch in the end of the first article should have been easily understandable to one and all. The problem was that we got an object using a helper method in the DBRepository class. This DBRepository instance uses a session in the getItemById method to retrieve the object from db and closes this session at the end of the method. When we later tried to consume the object we got an exception. We got an exception because by default NHibernate ses lazy=true wherein all associations for the object retrieved is not sent with the object during retrieval and only a proxy is kept instead. THIS IS THE LAZYLOADING FEATURE IN NHIBERNATE. So when this proxy is accessed with the session object closed, it throws an exception. By setting lazy=false we got the full object instead of proxy for associations. This is one of the solution but it cannot be used everywhere. But now after the background given here and our current knowledge on the different state of the objects we can comprehed the real problem. The real problem is that when the session is closed by DBRepository after returning the object we need, the persistent context cache associated with the session is also closed. So what we have in hand is a DETACHED OBJECT. This detached object cannot be directly used in anyother session because it does not exist in the persistent context cache of the other sessions. So it has to be brought into the persistence context of the other session by a process called REATTACHING. The method call for it is
This reattaches the object to the new session. Reattachment for non mdified persistent objects can be done using the method call lock() also. In scenarios where the new session is already loaded with the different instance of an object having the same db identifier as the old object that you want to load into the new session's persistent context cache, the method to use is,
new_object = newsession.merge(old_object);
This "merge" method will copy the state of the old_object inside the new_object. After this call, the client is supposed to use the new_object only. The solution to the problem we had in first article is to use these techniques of reattachment of objects. So by using Reattachment of objects, we now know the technique to use object across sessions.
Points of Interest
This concludes the eight part article series. I will write more articles on querying using NHibernate, fetching strategy in Nhibernate and full samples on object conversation using reattachment in months to come when i find time for it. Until then enjoy NHIBERNATE.
PART 5 A
PART 5 B