devxlogo

Migrate Your J2EE Apps from EJB to Hibernate

Migrate Your J2EE Apps from EJB to Hibernate

ccording to its Web site, Hibernate is a “powerful, ultra-high performance object/relational persistence and query service for Java.” And they’re not just blowing smoke. Hibernate is a key player in the movement towards more lightweight designs and is quickly becoming the Java persistence tool of choice. The decision to use Hibernate for new projects is a no-brainer, but for mature applications the choice is not as clear-cut.

If you have a large J2EE project?possibly in production?is migrating your existing homegrown or entity-bean-based solution worth it? Perhaps, it all depends on whether your existing persistence layer is deficient or unable to scale to meet your long-term goals. In some cases, the risk may be high and the costs outweigh the benefits. But if you’ve decided to go with Hibernate, you can minimize the impact of the change and make the transition?if not painless?at least well structured and predictable with a bit of upfront design.

Hibernate itself is a deceptively simple component to integrate. You can be up and running within a few hours. But once you’ve mastered the basics, you need to consider Hibernate in the context of your existing architecture. This includes:

  • Your migration strategy
  • Logical design
  • Packaging and configuration
  • Session handling strategy
  • EJB interactions: transactions, caching strategy, and primary key generation

Migration Strategy
You can use one of two basic integration approaches:

  1. Big bang ? Migrate your persistence tier wholesale all at one time.
  2. Staged ? Port it gradually, object by object or by functional or component grouping.

The advantage of a big-bang approach is that it makes a clean break with your old architecture and ensures a consistent platform going forward. Your code will be more uniform and easier to maintain and you don’t have to worry about entity/Hibernate wiring. However, doing it all at one time carries a high risk and may not fit in with your other work streams. The reality is that project teams seldom have the time, resources, or inclination to rewrite and test an entire application tier.

Adopting a staged approach is less risky, but it isn’t as clean in terms of logical design. Hibernate is container friendly. It will happily co-exist with your entity beans and participate in container-managed transactions. So you can migrate gradually, mixing entities with Hibernate. The disadvantage of this approach is the added complexity and maintenance. Developers will have to maintain at least two distinct flavors of persistence logic and understand which one to use in any given use-case.

Applicability of Hibernate
Be wary of using a one-size-fits-all approach to adopt Hibernate. A common mistake is to use Hibernate exclusively for all data-access functions. Hibernate is not applicable for all types of data access; it is suited for object-centric access. So if you’re performing reporting queries or batch processing, you may want to keep those parts of your existing infrastructure as is. Hibernate is appropriate for persisting and querying for objects, typically elements of your domain model.

Testing Is Essential
Obviously, regression testing is essential to any migration effort. Think of testing as your migration safely net. It’s only with testing that you can validate your migration strategy, whether your code works, whether it is consistent with expected behavior, and that you haven’t broken anything else. If you don’t have a regression test suite already in place then create one. If it seems like unnecessary overhead, consider that fixing bugs now is a lot cheaper than fixing them later on in the migration cycle. You can add test cases gradually as you convert each object to Hibernate. Without regression testing, you simply won’t know whether your code works until a user calls with a problem.

Logical Design of the Persistence Tier
Logical design refers to the organization, responsibilities, and collaborations of your classes within your application. Before you introduce Hibernate into the mix you need to consider the organization of your persistence-related classes and their interactions with higher tiers. If you don’t, you’ll only proliferate Hibernate specifics throughout your code in an uncontrolled fashion.

A classic distributed architecture model has distinct tiers or layers, each with well-defined responsibilities and collaborations. Typically the model contains layers devoted to presentation, business, and persistence. Classes are grouped logically into each layer. Two key elements in this design (apart from adopting Spring) can expedite your Hibernate integration:

  1. Use a rich, common domain model for representing the key domain elements in your system.
  2. Centralize and encapsulate your persistence logic behind a set of persistence interfaces. Define your persistence services in terms of your domain model.

Common Domain Model
If you don’t have one already, a common portable domain model is invaluable for modeling the key concepts in your system and for interchanging data between tiers. Moreover, if you don’t have one then mapping onto Hibernate is going to be difficult, as Hibernate is object centric. Think of the domain model as the currency of your application. All application tiers can depend on and use the domain model, including the presentation tier. As surprising as it may seem, many systems do not have a common object model or they have an object model that is non-portable.

Objects that are tied to a particular tier or functional slice must be mapped into intermediate structures in order to get persisted. This approach can lead to a replication, parallel class hierarchies, and unnecessary mapping logic. Hibernate supports rich object-relational mapping semantics so you can safely pass your domain objects down through your higher tiers to Hibernate and let it worry about the rest.

The Data Transfer Object (DTO) pattern is commonly used to aggregate and serialize data from the server to the client tier in a convenient form for the client tier to digest. It is often used in lieu of a common object model. DTOs have arisen in part because EJBs can’t be serialized to a remote tier, but also for performance reasons. Consider whether your DTO is worthwhile: does it simply mirror elements of your domain model? Without an entity to worry about, there may be no reason to hang on to your DTO. You can replace it with the real domain model class and be assured that you can pass it to Hibernate without having to write any additional mapping code. Having fewer objects that represent the same logical thing means less code to maintain.

Persistence Service Layer
In a typical J2EE environment, business-/persistence-tier collaborations are managed by the following:

  1. Wiring up entity beans or JDBC calls directly into the business tier/session beans
  2. Encapsulating data access operations inside a Data Access Object (DAO)
  3. A homegrown persistence layer, which wraps or delegates to entities, DAOs, or JDBC

The first approach is fairly messy because persistence operations are interleaved with business operations?the division of responsibility between the tiers is blurred. Entity/JDBC infrastructure is permeated throughout your code and introducing Hibernate will have a big impact on your higher tiers. Options 2 and 3 offer better models for Hibernate because the low-level data access operations are encapsulated. With a well-defined persistence layer in place, you can hide the implementation details and add Hibernate transparently from the rest of the application.

If you’re using a variation on option 1, consider updating your logical design by providing a well-defined persistence layer or DAO set as a precursor to your Hibernate migration. The purpose of this layer is to clearly decouple the persistence from the business logic and to minimize the impact of change. Although this seems like a big job, remember that you’ll have to change all your wiring anyway to use Hibernate. You may as well do it in such a way that your persistence tier is clearly defined, centralized, and encapsulated. That way, any subsequent changes are isolated.

In terms of encapsulation, consider whether you want Hibernate exceptions, queries, and sessions to be propagated outside your persistence tier. You may want to wrap the Hibernate session and transform exceptions into more generalized persistence exceptions or exceptions meaningful to the business tier. (In Hibernate 2, the exceptions are checked. In Hibernate 3, the exceptions are unchecked.) Long term, this helps makes your persistence tier agnostic to the underlying implementation. But the corresponding increase in complexity may not be worth it?especially if you don’t intend to use anything but Hibernate.

Routing Transparently from Old to New
Modeling your persistence layer in very general terms (i.e., using the base interfaces of your common domain model rather than EJB) helps to abstract your underlying implementation details. You can use interfaces to define the contract presented to and used by the rest of your application. To migrate your existing business code, replace the old entity/JDBC wiring code to have it call your persistence interfaces instead. This pattern is analogous to the way Sun uses the Provider pattern for some parts of the Java API. With JDBC, for example, Sun controls the API contract via a set of interfaces and vendors provide different implementations of the API. Your persistence service is no different. It can provide for pluggable implementations and route persistence requests appropriately. This pattern enables you to route persistence requests to your old entity/JDBC code initially and then delegate it to Hibernate later. Whether you use one or the other is transparent to the business tier.

Hibernate Packaging and Configuration
To incorporate Hibernate into your application, you have several ways, including the following:

  • Import it as a self-contained project
  • Include it and other required Jars as utility jars in the EAR

Wherever you put Hibernate, you need to ensure that your application code can access Hibernate classes and that Hibernate can class-load application server transaction manager classes. It may need them to participate in container-controlled transactions. Additional infrastructure code created to assist Hibernate should be placed in a common package that logically belongs to the infrastructure tier, is able to access Hibernate, and is in turn visible to projects that use Hibernate.

Configuration
The Hibernate Session Factory and Configuration classes are the main classes for bootstrapping Hibernate. You create a SessionFactory by first instantiating a Configuration instance, setting its properties, and then asking the Configuration instance to build a Session Factory. The SessionFactory is a relatively heavyweight object. You should ensure that you have only one factory instance per application (datasource or db-url,username) and that it is fully configured before you attempt to use it. Typically, the factory instance will be created when you first request a Hibernate session. A common pattern is to wrap up the initialization code and session management logic inside a HibernateUtil class. This class enforces that only one instance of the factory ever exists by using the Singleton (GoF) pattern or binding the factory into JNDI.

You can load and configure Hibernate object mappings in several ways, including:

  • Adding your object mappings into the main Hibernate-cfg.xml file. This has the advantage of being centralized, but it will not scale very well for a large project with many objects, projects, and developers competing for access to this one file. The file bloats very quickly.
  • Configuring object mappings in separate XML files, one per object, and adding mapping resource references to the Hibernate-cfg.xml file or registering mappings programmatically with the Hibernate Configuration class.
  • Registering mapped classes programmatically when the SessionFactory is created. In order to register your domain objects, your domain object classes must be visible to your HibernateUtil/initialization code. If your HibernateUtil class resides in an infrastructure project and your domain objects in a business project, then you’ll have to make your infrastructure project depend on your business or move your Hibernate code into the business tier.

In terms of organizing your XML mapping files, the simplest and most common approach is to name them after the classes that they map and to place them in the same package as the class definition. The downside of this is that you may not want your persistence details inter-mingled with your domain objects, especially if they are being packaged up and served to a remote client tier. Alternatively, you can place your XML in a separate project or package that mirrors the domain object packaging namespace.

Session Handling Strategy
The Hibernate Session class is the main class for interacting with Hibernate. The session keeps track of your updates, services your queries, and performs flushing of updates to the DBMS. You need to carefully manage your session lifecycle and granularity to ensure that it is synchronized with your data access strategy. It should never be held open for longer than a single application transaction or accessed by multiple threads concurrently. Hibernate will tell you if you do!

The following are the three strategies for managing Hibernate sessions in the context of an application transaction:

  1. Session-per-request. The session spans a single client/server request reply?usually a single database transaction. The session is closed when the transaction concludes. This is the typical strategy in use by EJB J2EE architectures and is probably what you’re already using.
  2. Session-per-request with detached objects. Objects are long-lived and span multiple client/server requests. Objects are detached and reattached to session instances.
  3. Long-lived session. A single session spans multiple client/server requests and is usually cached on the HTTPSession.

The current session is usually stored on a thread local variable inside the HibernateUtil class. This guarantees one session per thread and centralizes your session management logic. Rather than passing the session around as a parameter to all your methods, you can just access the HibernateUtil for the current active session whenever you need it.

The HibernateUtil manages the low-level mechanics of your session, but it doesn’t imply anything about your chosen strategy. Session management logic that uses HibernateUtil must be placed in the client/server mediation tier to support your chosen strategy. This is the logic to control Session creation, flushing, and closing. This can be tricky to isolate and encapsulate, as your code must be placed into the top most level of your use-cases, the parts of your code that manage your transactions?typically your session beans.

If you use session management strategy 1 (session-per-request) then the template code to manage your sessions looks like this:

Try {	// Initiate the current Session 	HibernateUtil.currentSession();	// Perform some CRUD operations	// and execute some business logic	// Ensure that all outstanding updates are flushed to the DBMS  	HibernateUtil.currentSession().flush().}catch (SomeCheckedException e) {	// Handle error	// Rollback Container managed transaction} finally {// Close the Session at the conclusion of the request, regardless of // whether the transaction succeeded or failed	HibernateUtil.currentSession.close();}

You must add this code, or some variant of it, to every top-most session bean method in your application where requests enter and initiate transactions (i.e., methods marked as transactional) and perform CRUD requests using Hibernate. If you’re using Session Façades (Sun BluePrints), this can become a daunting migration, as every façade method must be changed. If your façades are chained, for example, façade A invokes façade B, which invokes façade C and so on. You may then find that the Hibernate session gets flushed multiple times during an application transaction, which can lead to a lot of fine-grained DBMS I/O. One way to mitigate this is to make your HibernateUtil class smarter and perform an application-controlled flush only in the method that opened the session in the first place.

Alternative technologies such as AOP and Spring can deal with session management better than EJB, as the session management logic can be inserted transparently as a crosscutting concern. Spring provides a special interceptor to manage Hibernate sessions. However, standard J2EE has no way to intercept an EJB request and insert crosscutting code once it is inside the container. The only way to call inside a session bean is to leverage proprietary container interception APIs or build some level of indirection into the design (e.g., the EJB Command Pattern (Marinescu), J2EE COR pattern, or EJB-backed dynamic proxy). These client/server mediation patterns can make your life easier because they funnel requests via a single EJB stateless session bean. Your session management logic needs to be added in only one location.

EJB Interactions
The good news is that Hibernate will participate in your container-managed transactions if you configure it to do so. It behaves exactly as you expect it to behave. You do not need to explicitly manage transactions to get it to observe your existing semantics or isolation levels. Durable (flushed) Hibernate updates will be committed when the container commits and rolled back when the container rolls back. Hibernate will defer transaction management to the container.

Caching Strategies
Caching is a complex subject that is too broad to discuss in depth here. In a nutshell, caching is a key to improving performance. Having an appropriate cache strategy and implementation can have a big impact on Hibernate performance. You should decide on your caching strategy upfront. Hibernate is a two-level cache architecture of which the first level is the Hibernate session. Hibernate allows you to plug in cache providers for the second-level cache. If you already have a caching solution in place you may want to consider using it as your cache provider.

Working with EJB and Hibernate Concurrently
You can use entity beans and Hibernate concurrently within an application. However, they are distinct mechanisms. An update made using one may not be visible to the other, as they typically don’t share in-memory data. If you’re mixing and matching bean and Hibernate operations for the same logical entity, remember that your Hibernate updated data will not be visible to your entity unless it’s been flushed to the DBMS and vice-versa.

Your bean and Hibernate also must use the same primary key generation strategy. Otherwise, you could get duplicate keys. Hibernate provides several off-the-shelf generators. If they don’t suit you, just write one that wraps your bean key-generation mechanism.

The Object-Centric Data Access You’ve Been Waiting For
A good design, some decisions upfront, careful refactoring, and testing are key to migrating successfully. Hibernate is exceptionally easy to use and well documented. And because it’s open source, you can quickly dive into the source code to debug any problems that you encounter. Hibernate goes a long way towards fulfilling the ultimate EJB promise of scalable, portable, and efficient object-centric data access. Once you’ve made the leap towards lightweight architecture, you’ll never look back. Next step: Spring!

For more detailed information on any of the issues mentioned in this article, go to Hibernate.org or read the recently published book by Christian Bauer and Gavin King, Hibernate In Action.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist