RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Sizing Up Open Source Java Persistence : Page 5

Confused and puzzled by the plethora of persistence options in Java? You are not alone. Examine how some popular open source persistence frameworks stack up against one another and JDBC.

Entity Relationships and Cascade Operations
Handling persistence across object relationships is one of the more difficult issues to deal with when writing your own persistence code. Does your application have to keep track of each object and whether modifications have been made to it? If not, how do you determine whether to call on code to save the associated objects (addresses for example) when a simple property change has been made to the root object (in this case an employee or customer)? Imagine a condition where addresses are both added and removed from a person object. How does JDBC code handle this type of operation when the call to persist a person and its associated object graph is received?

Persistence frameworks can really take the burden of these tasks off the developer. In frameworks like JPOX JDO and Hibernate, you simply request to save the root object and, depending on configuration, this persists (or removes) all associations and associated objects in the database accordingly. This is known as cascading operations. A request to save a customer or employee results in a cascading operation to all affected associated objects—automatically! This saves an immense amount of Java code, but also removes many of the headaches that go with managing association details.

Of course, this feature also comes with a fair amount of configuration. You need to spell out the the structure and nature of each relationship in XML. The XML that defines these relationships can be less than straightforward. Take, as an example, the configuration of the one-to-one relationship between person/employee and user objects in Hibernate:

  • In the Person.hbm.xml file, relating an Employee to a User in a 1-1 relationship:
    <many-to-one name="user" class="User" column="user_id" cascade="all" unique="true" lazy="false"/>
  • In the User.hbm.xml file, relating a User to an Employee in a 1-1 relationship:
    <one-to-one name="employee" class="Employee" property-ref="user" lazy="false"/>
In Hibernate, from the perspective of a person object, a one-to-one relationship is a "unique" instance of a one-to-many relationship. Huh?? These types of configuration take a lot of time to learn and can be frustrating to figure out. Each framework has its own way of specifying these relationships and how the system should react when objects in a graph are created, updated, removed, or retrieved. To get a flavor for the differences, take a look at how the relationship from Person to Address is configured in both JDO and Hibernate, as shown respectively in the configuration segments below:
  • In the package.jdo file for JPOX JDO, relating Person to Address:
    <field name="addresses" table="person_address> 
      <collection element-type="Address"/>
        <join><column name="person_id"/></join>
        <element><column name="address_id"/></element>
  • In the Person.hbm.xml for Hibernate, relating Person to Address:
    	<set name="addresses" table="Person_Address">
    	  <key column="person_id"/>
    	  <many-to-many class="Address" column="address_id"/>
Remember, this is configuration code, so a compiler is not going to help you get this right. You have to code, configure, compile, deploy, and execute the code before you know if you got it right.

In my opinion, the mapping of associations in JDO and Castor are a little easier to learn; perhaps a bit more intuitive. However, you will generally find more help in the community when it comes to Hibernate.

While the configuration in Castor seems straightforward, I had many issues in getting associated objects to persist when creating or updating an object graph. According to the documentation, Castor is supposed to offer cascading features. However, I was unable to get these to work properly. I had no problems, on the other hand, setting up the configuration and reading all of the objects into an object graph.

In fact, Castor's own Web site documentation (which is a bit dated and has not been updated to reflect some recent releases) suggests Castor has, at least in the past, taken a different view of object relationships than do some of the other frameworks. It suggests that "related objects via one-to-one relation are not created/removed automatically." Further, again according to the documentation, "Castor currently only supports bi-directional relationships." Uni-directional support has been added, but it is unclear to what level it is supported or works.

As mentioned earlier, there is no automatic cascading in JDBC or iBatis. You have to provide the cascading features in the JDBC or iBatis code—resulting in a lot more code and requiring a lot of forethought about how objects will be assembled, changed, updated, and removed in the application.

Lazy Loading
In a concept somewhat related to cascading operations, lazy loading effects how many objects are created and loaded into memory whenever a root object is requested from a persistence framework. Consider the operation findAllEmployees(). An employee can be associated to any number of addresses, say, an organization and a user. The addresses can each have an association to other employees. The organization could be related to other employees. In other words, a single object, when created and loaded with data from the database is potentially part of a much larger object graph. What part of the graph should be loaded?

In the case of JDBC code, or when using the iBatis framework, you must answer this question and write the code necessary to load the objects for a graph that you need.

When using a persistence framework like Castor, JPOX JDO, or Hibernate, the framework can automatically load as much of the graph as you need, based on configuration. Properties and associated objects that are instantiated and loaded at the time of the initial request of the root object are said to be "eagerly" loaded. Objects/data that are not instantiated and loaded immediately might still be accessed by the application. This data is loaded "lazily." That is, it gets retrieved and put into objects only when requested by the application.

Lazy loading, and how it is managed by the framework, has an impact on performance based on how many trips to the database are made, the amount of code needed to deal with data not yet loaded, and how much memory is used by unneeded/unused objects.

As an example of how much of an impact lazy versus eager loading can have on performance, Table 7 (below) provides performance statistics for Hibernate and JDO loading of employees with both strategies. In the case of JDO, the initial retrieval can be four times as fast. In both cases, there is considerable savings when lazy loading. Of course, if associated data is needed, additional requests of the database will be required.

Hibernate with eager fetch

JDO with deep “fetch groups”

Hibernate with lazy load

JDO with shallow “fetch groups”

Run 0 took:  12500

Run 0 took:  6203

Run 0 took:  11453

Run 0 took:  3422

Run 1 took:  7687

Run 1 took:  1563

Run 1 took:  5922

Run 1 took:  969

Run 2 took:  7531

Run 2 took:  11609

Run 2 took:  5828

Run 2 took:  562

Run 3 took:  7688

Run 3 took:  1594

Run 3 took:  5984

Run 3 took:  1188

Run 4 took:  7500

Run 4 took:  10484

Run 4 took:  5610

Run 4 took:  468

Run 5 took:  7437

Run 5 took:  1469

Run 5 took:  5812

Run 5 took:  938

Run 6 took:  7422

Run 6 took:  10437

Run 6 took:  6485

Run 6 took:  687

Run 7 took:  7656

Run 7 took:  1141

Run 7 took:  5765

Run 7 took:  1516

Run 8 took:  7532

Run 8 took:  10625

Run 8 took:  5938

Run 8 took:  484

Run 9 took:  7937

Run 9 took:  1250

Run 9 took:  5593

Run 9 took:  1547

Average:  8089

Average:  5637

Average:  6439

Average:  1178

Table 7. Lazy vs. Eager Loading: The statistics above are times in milliseconds to load five thousand employee objects using eager fetching and lazy loading.

An important fact to consider when looking at lazy loading is what happens when the connection and/or transaction to the database is closed? What if the application needs access to an associated object or property and it was not loaded as a result of lazy loading? The answer to this question is handled in the next section.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date