In Hibernate + Middlegen Roundtrip Development applied it was shown how to generate Hibernate POJOs + mapping files from a legacy (SQL server) database. As soon as business objects start using these POJOs, you almost immediately need to deal with the problem that one query may cause the whole database to be loaded into your application. Cause: the relation between the tables in the database. Remedy: enable lazy loading in the Hibernate mapping files. Complication: lazy loading only works within a Hibernate session, which implies that a Hibernate session has to be kept open over the full request/response cycle, instead of e.g. merely in the DAO, where the actual synchronization with the database is taken care of (via the ORM framework, of course).
More specifically, the Hibernate session has to remain open over the complete request/response cycle for lazy loading to work properly. Of course, the first thing which comes to mind when dealing with the request/response cycle is the use of filters, which will be presented here. Read on to learn more about the generic design which we have applied to our application.
Architecture/design considerations
Access to the database is preferrably abstracted by data access objects (DAOs).
Each of the tables in the legacy (SQL server) database is represented by a plain old java object (POJO). A POJO consists of member variables representing the fields in the table accompanied with associated getters and setters. The associated Hibernate mapping files do not only map the POJOs to the tables in the database, but also contain the definition of the relations among the POJOs, as dictated by the database ER structure.
These POJOs are also know as data transfer objects (DTO) or value transfer objects (VTO).
If necessary, a POJO has an associated DAO. A DAO provides basic CRUD (create, read, update, delete) and List operations. Because of the aforementioned relations between the tables and hence between the POJOs, it is important that lazy loading is enabled. This prevents one request from causing the whole database to be loaded into memory!
For a proper understanding, let us shortly recapitulate the meachnism behind lazy loading. At the time a servlet (or a class invoked from the servlet) asks for data from the database, this request is postponed till the JSP page/servlet actually needs these data to fill the page, or more specifically, to generate the HTML output/response. Only those data are actually being retrieved from the database by the O/R framework, in our case Hibernate.
In Hibernate, lazy loading is transparently made available to the developer, since Hibernate “pulls it off” to replace the Set (or Collection) containing the data at Java byte code level by its own set, thereby being able to ‘listen’ for events that really request the data in the set. Lazy loading is available within one Hibernate session. The developer only has to set lazy loading to true in the Hibernate mapping files. By the way, note that the definition of a Hibernate session is unrelated to a HTTP session.
From Karl Baum’s Weblog we quote:
The obvious solution is to employ the lazy loading mechanism provided by hibernate. This initialization strategy only loads an object’s one-to-many and many-to-many relationships when these fields are accessed. The scenario is practically transparent to the developer and a minimum amount of database requests are made, resulting in major performance gains. One drawback to this technique is that lazy loading requires the Hibernate session to remain open while the data object is in use. This causes a major problem when trying to abstract the persistence layer via the Data Access Object pattern. In order to fully abstract the persistence mechanism, all database logic, including opening and closing sessions, must not be performed in the application layer. Most often, this logic is concealed behind the DAO implementation classes which implement interface stubs.
Summarizing, for lazy loading to work properly, we have to come up with a mechanism that takes care of a proper web application lifecycle management and at the same time allows the “view code” (from MVC) to remain unaware of initializing anything, i.e. keeps persistency (Hibernate) and the presentation layer separated.
To this extent, we defined a class (WebappLifecycle) that offers four hooks (i.e. methods) to the web application, two which are related to the startup and shutdown events using the web application life cycle events, and two which are related to the filter mechanism.
Using a filter, we activate the beginRequest() and endRequest() methods of the application model (from MVC) via some intermediairy classes. The model contains the business logic, hence the Struts-based presentation layer remains “thin”. The implementation of the client model is split into two parts:
- The O/R specific part, including the sessions and transaction management related operations specific to the O/R framework used.
- The application model specific part, which contains the business logic.
Ad 1:
When a request is started, a marker for the request being in process is set to true, assuming that a model has already been created. If not, the getModel() method of the model broker asks the server model to create a model instance. This instance is associated with the client request in an appropriate way, e.g. the request or thread handling the current request.
Ad 2:
When the view asks for data (to be stored, retrieved, etc), it communicates with this part of the model.
For each action, the model asks the DAO factory to get the appropriate DAO for this action and to handle the data transfer. Each DAO extends from a BaseDAO, and uses the implementation of this superclass to handle the persistence management.
Implementation specifics:
The contextInitialized() and contextDestroyed() methods of the ApplicationListener class are invoked at application startup and shutdown events respectively. Using these methods we invoke the two corresponding methods in the WebappLifecycle class, that take care of the application wide startup and shutdown. These methods initialize the ServerModel, which is responsible for the creation of the “per client request/response cycle” model (AmisHoursModel).
The following collaboration diagram depicts the interaction of the various component during a typical list operation:
The remaining two methods startRequest() and stopRequest() in the WebappLifecycle class are invoked from the ClientRequestFilter class, which in turn is activated before and after a request (just as a filter should behave). These methods of the WebappLifecycle class call the startRequest() and stopRequest() methods of the (AmisHours) client model.
The AmisHoursModel is obtained by the ModelBroker. This broker makes sure that the client model is created by the ServerModel if one is not present yet, otherwise returns the instance available. Moreover, the abstraction of a ModelBroker allows one to switch easily the mechanisme which “stores” the instance. Here, the the client model instance is “attached” to the current thread, using the ThreadLocal class, but if we would want to change that to associate it e.g. to the current request, we would only need to change the code in the ModelBroker.
Concluding remark
We did not closely look at the Spring framework yet, but it seems that all these issues are very elegantly addressed by it, see e.g. Introducing the Spring Framework by Rod Johnson. It is definitely our intention to have a closer look at Spring, but to anyone who wants to make remarks based on the architecture outlined above and the Spring framework should feel free to leave a comment.
No Responses