Object Generation: A Better Approach to Hibernate Integration

Object Generation: A Better Approach to Hibernate Integration

ibernate is a practical, high-performance Java persistence framework. Using Hibernate is nearly transparent, but the one detail that even Hibernate can’t hide is the notion of object identity and equality. It’s a critical detail that typically infects all persistent objects with tedious and error-prone code. This article offers a different, proven approach for working with Hibernate that eliminates the clutter, and is both simple to incorporate and appropriate for most modern enterprise applications.

What You Need
Java 2 SDK
Hibernate 3.1
Javassist 3.1

Hibernate Persistence: The Classic Approach

Hibernate is designed to easily enable the persistence of virtually any plain old Java object (POJO). Creating a persistent object is simple, and the classic approach that the Hibernate community recommends can be summarized in just two steps:

  1. Select a business key and implement the hashCode and equals methods on the object. A business key is some combination of immutable business fields that’s not only unique, but guaranteed not to change over the lifetime of the object. It’s used to identify objects and test for equality.
  2. Write a Hibernate mapping descriptor. The descriptor maps fields in the object to columns and tables in the database. A descriptor can take various forms, but it’s commonly provided as an XML file.

Listings 1, 2, and 3 are simple examples that demonstrate this process:

  • Listing 1 is the Java code for a simple User class that’s been enabled for persistence. In this example, the user’s email address has been selected as the object’s business key. Notice how it’s used in the implementation of the hashCode and equals methods.
  • Listing 2 is the corresponding Hibernate descriptor. It simply maps fields in the object directly to columns in the database.
  • Listing 3 is the schema for the underlying database table. Notice that it’s the ID, and not the email address, that serves as the primary key. This is known as a surrogate key, a common best practice in database design.

The classic approach works, but it’s got some notable shortcomings. For the following reasons, the first step (selecting a business key and implementing the hashCode and equals methods) causes trouble for many Java developers:

  1. Many, if not most, Java objects don’t have convenient business keys. This is clear even in the sample User class. The email address is used as a business key, but it’s not a good fit since users should be able to easily change it. In fact, it’s common to have objects where nearly all fields can be changed. This often requires workarounds to make these objects fit the model. You’ll see these workarounds both in the objects themselves and in the code used to manage them.
  2. Implementing the hashCode and equals methods throughout a project’s persistent objects is tedious and error-prone. It’s just more extraneous code that you need to be write, test, and maintain.
  3. Business keys are often composed of a combination of strings. Remember, the hashCode and equals methods are used frequently behind the scenes, and although performance may not be an issue, it can be if these strings are long and similar. Besides, you’re probably always looking for easy optimizations.

Fortunately, these issues can be addressed with a new approach to Hibernate integration.

Hibernate Persistence: The Generation Approach

Object generation is a different approach to Hibernate integration. Instead of business keys, objects are identified by their surrogate key, without the need to provide custom hashCode and equals methods for each object. The concept is simple, but implementing it requires a bit of understanding and infrastructure.

A surrogate key is a unique identifier generated for each row in a given table. If you looked at the previous listings, you’ll see it as the ID column on the user table along with its companion sequence used to generate values. This means that every User object that’s persisted will have its own unique integer identifier.

A surrogate key seems like a natural choice for an object identifier, and it is but for one small problem: it doesn’t normally get its value until the object is persisted to the database. This means that an object that’s added to a collection before it’s persisted will malfunction (collections call the hashCode and equals methods behind the scenes).

The solution is to use an object generator, a modified data access object (DAO) that retrieves and assigns identifiers during object creation. Think of it as an object factory designed for Hibernate. Each object returned from the generator has a database-generated identifier that’s guaranteed to be stable and unique. The key difference between this and the classic approach is that this all happens before the object is persisted.

Making this magic happen and seamlessly integrating with Hibernate requires a bit of infrastructure. Each of the following elements is relatively straightforward, but they all need to be well orchestrated:

  1. An abstract entity base class. This class contains the implementations of the hashCode and equals methods used for all persistent objects. Strictly speaking, this class isn’t necessary, but it does eliminate extraneous code.
  2. A modified DAO. This is the object generator that retrieves and assigns identifiers upon object creation. This class makes use of the Javassist library mentioned in the requirements for this article. If you’ve never used Javassist, it’s an excellent open source library for creating or enhancing classes at runtime.
  3. A simple generated entity interface. This is an interface that all generated entities implement at runtime (via the Javassist library). It serves as a marker interface with a few simple methods to track an object’s persistent status.
  4. A custom identifier generator. This is an implementation of a Hibernate IdentifierGenerator that understands object generation and delegates to an underlying identifier generator. This is easier to demonstrate than it is to explain.
  5. A custom Hibernate interceptor. The interceptor optimizes performance by using the generated entity interface to minimize database calls.

Luckily, you’ll see none of this complexity in your final business objects, and that’s the point. The task of adding persistence is reduced to extending a base class and creating a minimal companion DAO. For the basic User class found in the previous listings, its code is reduced to little more than the necessary business logic:

public class User extends AbstractGenericEntity{   private String email;   private String password;   public String getEmail() { return email; }   public void setEmail(String e) { email = e; }   public String getPassword () { return password; }   public void setPassword(String p) { password = p; }  @Override  public final Class getEntityClass() { return User.class; }}

You’ll notice that this class doesn’t include a hashCode or equals method. Nor does it include an ID property. They’re all contained in the AbstractGenericEntity class along with the declaration of the getEntityClass method.

The AbstractGenericEntity Class

The AbstractGenericEntity class is the base class for all persistent entities. Its purpose is to contain the surrogate key (the ID property) and correctly implement the hashCode and equals methods.

Listing 4 is the full source for the AbstractGenericEntity class. You’ll notice that the implementations of the hashCode and equals methods are simple and efficient. The class has no string comparisons, and the methods are marked final to prevent business objects from overriding them:

@Overridepublic final int hashCode() throws IllegalStateException {   ...   return id.hashCode();}@Overridepublic final boolean equals(Object that)      throws IllegalStateException {   if (this == that) return true;   if (!(getEntityClass().isInstance(that))) return false;   ...   return id.equals(((AbstractGenericEntity) that).getId());}public abstract Class getEntityClass();

The abstract getEntityClass method is used by the equals method and must be defined in your concrete business object. You saw an example of this in the previous code for the User class.

With the business object defined, it’s now time to create its companion DAO. Once again, the code you’ll write is very simple, with all the heavy lifting handled in an abstract base class.

The Data Access Object

Every business object will have a corresponding data access object that it uses to manage the standard CRUD (create, read, update, and delete) operations. This is a familiar design pattern commonly used in Hibernate applications. Thanks to the use of Java’s new generics capability, the code for the User DAO is remarkably succinct:

public class UserDao extends AbstractGenericDao {   public User findByEmail(String email) {      List users = find("from User u where = ?",                              email);		return ((users.isEmpty()) ? null : users.get(0));	}   @Override   public final Class getEntityClass() { return User.class; }}

The findByEmail method is a simple query method for finding users. Without it, this class could’ve been reduced to a single line of code.

The AbstractGenericDao Class

The AbstractGenericDao class is the base for all data access objects. Its purpose is to provide CRUD capability and to correctly implement object generation. Remember, object generation is just object creation coupled with the assignment of an identifier.

Listing 5 is the full source for the AbstractGenericDao class. It’s not trivial, but it’s manageable if you take it in chunks. One inner class, EntityGenerator, encapsulates all the important behavior. New entities are generated in its generateEntity method:

public E generateEntity(SessionImpl session)      throws InstantiationException {   ...   entity = (E) generatedEntityClass.newInstance();   entityPersister.setIdentifier(entity,                                 generateIdentifier(session),                                 session.getEntityMode());   ...   return entity;}

The calls to newInstance and setIdentifier are self-explanatory, but more is going on here than meets the eye. The new instance that’s created is not an instance of the original entity class. Rather, it’s a runtime-generated subclass that’s been enhanced to add additional behavior.

The generated subclass is a simple extension that implements the GeneratedEntity interface. You won’t find this interface in the Hibernate framework. Rather, it’s a simple, custom interface that’s used to mark objects whose identifiers have been assigned during creation, otherwise known as generated entities:

public interface GeneratedEntity {   public boolean isTransient();   public void setTransient(boolean unsaved); }

The transient property is used to track an object’s persistent status. It’s an important detail that’ll show up later when discussing the Hibernate interceptor.

The enhanced subclass itself is created in the createGeneratedEntityClass method. It’s this method that makes use of the Javassist library:

final CtClass ctGeneratedClass =    classPool.makeClass(generatedEntityClassName, ctEntityClass);final CtField ctUnsavedField =    new CtField(CtClass.booleanType, "unsaved", ctGeneratedClass);...ctGeneratedClass.addInterface(ctGeneratedEntityInterface);ctUnsavedField.setModifiers(Modifier.PRIVATE);ctGeneratedClass.addField(ctUnsavedField,                           CtField.Initializer.byExpr("true"));ctGeneratedClass.addMethod(ctIsTransientMethod);ctGeneratedClass.addMethod(ctSetTransientMethod);return ctGeneratedClass.toClass(entityClass.getClassLoader());...

The result is a Java class that, if implemented in Java code, would look like the following:

public class GeneratedUser extends User     implements GeneratedEntity{  private boolean unsaved = true;  public boolean isTransient() { return unsaved; }  public void setTransient(boolean u) { unsaved = u; }}

The enhanced class is created just once for any given entity type. Subsequent requests retrieve a cached instance.

You may have noticed that the code uses a few Hibernate implementation classes that aren’t typically exposed. For example, it’s necessary to reference the SessionImpl and AbstractEntityPersister classes to access Hibernate’s underlying functionality. The solution simply won’t work without them. Purists may disagree, but this isolated, minor deviation is acceptable given the solution’s practical benefits.

The Custom Identifier Generator

Hibernate uses identifier generators to allocate identifiers for new objects. It normally retrieves a new identifier from the database or an internal cache immediately before persisting an object for the first time. You’ve already seen how object generation changes this behavior to assign an identifier upon object creation (see the generateEntity method listed earlier).

However, generating identifiers during object creation is only part of a full solution. The identifier strategy must work properly whether it’s dealing with a newly created object or an existing one that’s been retrieved from the database. No single strategy can accomplish this without introducing unnecessary overhead.

The solution is to create an identifier generator with a split personality. On the one hand, it behaves like an “assigned” generator when it detects a generated entity. On the other hand, it behaves like the entity’s natural identifier generator when a new identifier is needed or when it’s working with an existing persistent object. The result is a class that aggregates two generators and intelligently selects between the two.

Listing 6 is the source code for the GeneratedIdentifierGenerator class. The class includes only two methods (configure and generate), and understanding them both is important. The configure method creates the two identifier generators:

public void configure(Type type, Properties params, Dialect d)       throws MappingException {   String generatorName = params.getProperty(DELEGATE);   ...   delegateGenerator = IdentifierGeneratorFactory.create(                              generatorName, type, params, d);   assignedGenerator = IdentifierGeneratorFactory.create(                              "assigned", type, params, d);}

An example Hibernate descriptor helps explain how this code works. The following descriptor snippet highlights the relevant portion for the sample User class. Notice that the generator tag names the new custom class while the original generator, a sequence in this case, has been moved to its “delegate” parameter:

         sequence      user_id_seq   

The configure method of the GeneratedIdentifierGenerator class creates an identifier generator of the type named in the “delegate” parameter, passing along any additional parameters it’s received. The code also creates an instance of the “assigned” generator, a standard type supplied with Hibernate, that’s used in the generate method:

public Serializable generate(SessionImplementor session,                     Object object) throws HibernateException {   if (object instanceof GeneratedEntity) {      return assignedGenerator.generate(session, object);   } else {      // Use the real generation strategy if no object's been      // supplied or the supplied object isn't a generated entity      return delegateGenerator.generate(session, object);   }}

The generate method delegates to one of the two aggregated identifier generators. The assigned identifier generator is used for generated entities since their identifiers are assigned at creation. Otherwise, the entity’s real generator, the delegate, is invoked to retrieve a new identifier. This strategy effectively supports persistent objects under all circumstances.

The Custom Interceptor

The solution described thus far will work with no additional infrastructure. However, you’ll pay a slight performance penalty when persisting newly created objects. Most generators use a null identifier as their cue that an object is transient, but this doesn’t work with generated entities whose identifiers are assigned before they’re made persistent. It’s a subtle nuance, but with an identifier already assigned, Hibernate has no way of knowing whether the object already exists in the database. It’s forced to issue an additional query to find out.

To eliminate this overhead, you introduce a very minimal interceptor. Interceptors are used to customize Hibernate’s behavior and are ideally suited to the task at hand.

Listing 7 contains the source for the GeneratedEntityInterceptor class. Its only purpose is to manage the transient property on generated entities. Remember, this property is provided by the GeneratedEntity interface that’s implemented at runtime. The interceptor’s isTransient method simply delegates to the generated entity:

public Boolean isTransient(Object entity) {   if (entity instanceof GeneratedEntity) {      return ((GeneratedEntity)entity).isTransient() ?                          Boolean.TRUE : Boolean.FALSE;   } else {      return super.isTransient(entity);   }}

With the interceptor installed, Hibernate can now efficiently save generated entities using the appropriate INSERT or UPDATE statement.

Putting It All Together: Generating and Saving an Object

With the entire infrastructure in place, it’s time to generate and save a simple User object. Notice how the interceptor is created and included in the session factory’s configuration:

Configuration config = new Configuration();config.setInterceptor(new GeneratedEntityInterceptor());...Map users = new HashMap();User user1 = null, user2 = null;...   tx = session.beginTransaction();   UserDao userDao = new UserDao(session);   user1 = userDao.generate();   users.put(user1.getId(), user1);   // Set properties...   userDao.saveOrUpdate(user1);   user2 = userDao.generate();   users.put(user2.getId(), user2);   // Set properties...   userDao.saveOrUpdate(user2);   tx.commit();...assert(users.size() == 2);assert(users.containsValue(user1));assert(users.containsValue(user2));

Note that the generated User objects are added to the collection before they’re made persistent, but they still function properly afterwards. Generated objects are immediately usable in collections and don’t require preparation or workarounds to make them work.

Object Generation: Developer’s Cookbook

Adopting object generation in your projects is easy. In fact, it can be reduced to a series of simple steps:

  1. Create a new business object class that extends AbstractGenericEntity and implements the getEntityClass method. Remember, this class won’t contain a property for the surrogate key (the ID) or implementations of either the hashCode or equals methods.
  2. Create a new DAO class that extends AbstractGenericDao and implements the getEntityClass method. The base class already provides typesafe implementations of the basic CRUD operations, but you may need additional methods to suit your needs.
  3. Write the descriptor for your business object as you would normally, but with one small change: use the GeneratedIdentifierGenerator class as the generator and specify the object’s real generator in the “delegate” parameter. Additional parameters can be specified and will be passed to the real generator when it’s created.
  4. Use an instance of the GeneratedEntityInterceptor class when creating the Hibernate configuration for the SessionFactory used in your application. This ensures that the interceptor is incorporated and is used to minimize database traffic.
  5. Create all instances of the business object through its DAO. This is different than the approach you’ll see elsewhere, where objects are created using the Java new operator and later handed to Hibernate. Object generation requires control over the creation process.

That’s it. You’ve received the entire infrastructure for the generation approach to Hibernate persistence. However, you need to be aware of some limitations before deciding to integrate this approach into your project. Luckily, they’re not showstoppers for the majority of modern enterprise applications.

The Inevitable Limitations

Every approach to persistence has its limitations, and object generation is no exception. Its list of limitations, however, is manageable:

  1. Surrogate keys must be used for all persistent entities. These keys uniquely identify objects and are the simple foundation for the approach. Fortunately, the use of surrogate keys is a commonly recommended best practice used in many modern enterprise applications.
  2. Certain identifier generators are not supported. More specifically, any PostInsertIdentifierGenerator is not supported. The most common example is the “identity” generator used to support SQLServer identity columns. Object generation requires that an object’s identifier be allocated before the object’s persisted. This issue shouldn’t be a showstopper, though, as numerous viable workarounds exist, including the TableHiLoGenerator supplied with Hibernate.
  3. A Hibernate Session must exist when creating new objects. Remember, identifiers are allocated at creation, and this process often requires a call into the database. If you’re working on a Web application, this is commonly addressed by wrapping each request in an open session (take a look at the OpenSessionInViewFilter class packaged with the Spring framework for a good example).

Finally, in advanced use cases, some users need to detach objects from one session only to later associate them with a different session. This is commonly done to implement application or conversational transactions, and object generation easily accommodates it.

A More Streamlined Hibernate Experience

Object generation is an alternative Hibernate integration approach based on practical experience with the framework and its use in enterprise applications. The approach delivers on its promise of simplifying persistence by reducing the code you’re left to design, write, and maintain. Gone is the burden of identifying or inventing business keys and implementing customized hashCode and equals methods throughout. Instead, you use surrogate keys as identifiers and supply the supporting infrastructure, including some runtime class enhancement, to make it all work. The result is a more streamlined Hibernate experience that allows even more focus where it counts?on your business problem.


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