Sleep Soundly with Hibernate
March 3, 2004 David Morris
[The code for this article is available for download.]
Java is often considered less capable at reading and manipulating database table data than procedural programming languages, like RPG and COBOL. That’s because Java programmers often write procedural code when working with relational data, making it more difficult to take advantage of Java’s object-oriented strengths. Hibernate handles this problem by performing many of the mundane tasks associated with database work, allowing Java programmers to concentrate on business requirements instead.
Managing hand-coded SQL statements, Connections, ResultSets, and caching in your applications is error-prone and inefficient. Technologies like Enterprise JavaBeans (EJBs), Java Data Objects (JDO), and Object Data Management Group (ODMG) APIs hide the intricacies of JDBC, but adding their high level of abstraction unnecessarily complicates most applications. Hibernate sits between these two extremes and allows developers to work directly with objects, using the full power of JDBC without adding complexity.
It takes time to learn how to use any relational database to object mapping tool, and Hibernate is no exception. To become proficient, you need to read the Hibernate manual and FAQs. It also helps to understand the technologies that Hibernate relies on, like JDBC, SQL, and the Java Collections Framework. When you have questions or problems, the Hibernate Web site, issue tracking system, and user forums are helpful.
One feature of Hibernate that I find handy is its ability to run against any database. I often write and test programs that will eventually run on the iSeries, using MySql on my laptop. When it is time to test against DB2 Universal Database (DB2 UDB) for iSeries, I change a system property and continue my testing. As long as the iSeries has the best database support around, there is very little risk that this capability will lead to mass migration to other platforms.
DATA TO OBJECTS
There is more than one way to convert rows in a database to Java objects. You can open a connection using JDBC, read a row from your database, create an object, and then populate that object with values from the database. SQL–to-object mapping tools, like iBatis, centralize the storage of SQL statements and map those statements to objects. This centralizes your SQL statements and relieves you from some of the drudgery of working directly with the JDBC API. These approaches tend to fit well with procedure-oriented Java code and place a greater emphasis on SQL skills.
EJBs are on the other end of the spectrum from JDBC. One of the biggest problems developers encounter with EJBs is that they join data in an application and are much less efficient at joining tables than relational database’s, like DB2, which have been refined and tuned for decades. This leads to unnecessary round-tripping in order to retrieve data from the database and assemble the results in an application.
Object relational mapping (ORM) frameworks fit between raw JDBC and EJBs. An ORM framework goes a step further than JDBC, and uses metadata to describe a database and support the mapping of relational database rows to objects, but doesn’t hobble performance with overly granular entity relationships.
An ORM framework allows object-oriented programs to interact with data stored in relational database tables as objects. Metadata, which is information about data, describes the relationship between objects and their underlying database structure. That metadata stores information that tells the ORM framework what tables, rows, and columns to access when an object is requested. Most of the time, information is also stored that describes the relationship between objects and their corresponding database tables, along with key field and foreign key information.
One popular ORM framework is the Apache Software Foundation’s Object Relational Bridge (OJB), which is based on JDO standards. With OJB you have the security of a standard, but that advantage is offset by a committee-developed design that strives to answer every challenge.
Although Hibernate is not based on a standard, it is easily the most widely used Java ORM solution. One thing that differentiates Hibernate is that it is lightweight and stays out of the way, even though it adds support for difficult to implement features like object caching, optimistic locking, and runtime byte-code optimization. These features improve the performance of data access using Hibernate, and offset the overhead imposed by Hibernate’s metadata described model.
PREPARING TO HIBERNATE
When you are ready to try out Hibernate, go to Hibernate Web site, select the download link, and download the latest stable release, along with the latest version of Hibernate extensions. I used Hibernate 2.1.1 and Hibernate extensions 2.0.2 for this article. Hibernate 2.1.2 is due out soon and will include an important update for iSeries users that use automatically generates sequence columns for primary keys.
If you are not currently using an interactive development environment (IDE) or have not upgraded to WDSc 5.1, contact IBM or your IBM business partner and get a copy. If you are unable to get the latest version of WDSc, which is based on Eclipse 2.1, consider downloading and installing the latest version of Eclipse. The Hibernate installation instructions that follow describe how to test Hibernate with WDSc or Eclipse, but you should be able to follow along with any IDE.
Start by unzipping the Hibernate distribution files to your PC. Start WDSc and create a new project called hibernate, and create a new folder in that project named lib. Now drag and drop the following JAR files from the hibernate-2.1lib directory to the lib folder you created in the Hibernate project. The JAR files are cglib2.jar, commons-collections.jar, commons-lang.jar, commons-logging.jar, dom4j.jar, ehcache.jar, hibernate2.jar, jcs.jar, jta.jar, junit.jar, odmg.jar, and ehcache.jar. Now add the hibernate-tools.jar from the tools directory where you unzipped the Hibernate extensions ZIP file. You will also want to copy the latest version of IBM’s Java toolkit jt400.jar file into this directory.
Now create folders in the hibernate project to hold Java source (you can bypass this step if you check the Use Folder Names box and allow your ZIP file utility to create them). I used src/java and src/tests. Download and unzip the example source I created for this article to the project directory, which will be something like C:projectshibernate. When you are done, your project should have a structure that looks similar to that in Figure 1.
Figure 1: Eclipse’s Package Explorer view of the sample project |
Next, set the project classpath and source directories. Right-click the project, and select Properties. Click “Java Build Path,” then click the Libraries tab. Click the “Add Jars…” button and expand the project and lib folders. Now click the top JAR file listed, press the Shift key, click the last JAR file listed, and click the OK button. Now click the Source tab, followed by the “Add Folder…” button. Expand the project and source folders, and click the java folder. Now press the control key and click the tests folder to select both source folders. Click OK on the Source Folder Selection dialog, and click OK on the Properties dialog.
At this point, you should have no errors in the task list. Before running the example provided, you will need to edit the connection string in the db2.cfg.xml file, located in the org.iseriestoolkit.examples package. Modify the connection.url property so that it points to your iSeries system by replacing mysystem.mydomain.com with your iSeries system’s IP number or an appropriate host name.
To run the example, expand the src/tests directory in the project and click SimpleCustomerTest to select it. Next, click the Run drop-down list and click the “Run…” item. The Run dialog contains a configurations list. Right-click Java Application and select New. Your Run dialog should look like that in Figure 2. Click the (x)= Arguments tab and set the VM arguments to:
-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.defaultlog=debug
Figure 2: Set up the SimpleCustomerTest with the run dialog |
The Run dialog should now look like that in Figure 3. When you are done, click Apply and then click Run. As the test class runs, you will see messages in the console window. You can use these messages to diagnose problems and see the actual SQL statements that Hibernate is running.
Figure 3: Set the runtime arguments before running the SimpleCustomerTest program |
You may have noticed when you modified the db2.cfg.xml file that I set connection.transaction isolation to none. You can pass connection properties to your JDBC driver using connection.propertyname. In this case I set the isolation level to none because the hbm2ddl.auto setting is set to create and I had no way of setting up journaling. Whenever you use Hibernate to support production data, you should set up journaling and delete these two settings from the db2.cfg.xml file.
During my testing for this article, I found that delete statements fail when I do not use a system generated key and have transaction isolation set to off. I have never run into this in a production program and the way Hibernate handles deletes appears to be correct. At this point all I can recommend is that you use system generated keys or journal your tables if you plan on using Hibernate to delete records.
You can use the examples I wrote for this article to get familiar with Hibernate, but before you use Hibernate on a big project, set aside some time to read the Hibernate FAQs and the Hibernate manual.
TIPS FOR THE ISERIES HIBERNATOR
One area where the iSeries DB2 dialect differs from DB2 on other platforms is identity columns. Dialects tell Hibernate about the nuances of the various databases that Hibernate supports. If you are on V5R2 or later, identity columns are supported. However, unless you update the DB2400 dialect you will get an error stating something like values IDENTITY_VAL_LOCAL(), followed by an error that this is missing an INTO clause when you try to insert data into a table with a primary key that is an automatically generated identity column.
Version 2.12 of Hibernate should fix these problems. That release contains code that detects support for JDBC 3.0-compliant drivers and uses getGeneratedKeys to retrieve identity column values. Since V5R2, DB2 Universal Database for iSeries has provided this support. On those systems you should use a dialect like the DB2400V5R2Dialect I provided. On V5R1 systems there is no support for identity columns, so just specify the DB2400Dialect added with Hibernate 2.1.2 or use the version I have provided.
TOOLS OF THE HIBERNATOR
There is a three-way relationship between Java classes, database tables, and Hibernate maps. Hibernate provides tools that allow you to use any one of these three items to generate the other two. When you are starting out with Hibernate, you are most likely to generate a Hibernate mapping file and Java class from existing database tables. The DDL (data description language)-to-Hibernate mapping tool has a GUI interface and generates Hibernate mapping files and Java classes from an existing database table.
To use the DDL-to-Hibernate mapping tool, set up a run option in Eclipse like the one in Figure 4.
Figure 4: Use the run dialog to call the DDL to Hibernate mapping tool |
When you click Run, you will get a prompt like the one in Figure 5. Fill the Connection properties. Set the driver class to com.ibm.as400.access.AS400JDBCDriver, and then set your connection URL, which will be something like jdbc:as400://myhost.mydomain.com. Enter your user profile and password, and click the Tables tab. Click “tables…” to bring up a list of libraries, and select a physical file with a primary key constraint. Next, click the other tabs and fill in the requested information. I generally use Hibernate types and set my output folder to a test folder so that I don’t accidentally overlay anything. When you are done, click Generate and review the generated Hibernate mapping.
Figure 5: The DDL to Hibernate mapping tool saves time |
If you use system naming in your Java classes, you will want to delete the generated schema element. As I review the generated mapping file, I change the name attribute for tables and columns to camel case, so a column name like customername becomes customerName. I also set up relationships to other tables and objects and define their cardinality.
When I am done tweaking my generated mapping file, I use the Hibernate class generator to regenerate Java classes defined in the mapping file. Figure 6 shows the Run dialog. Before running the Hibernate class generator, you need to set the program arguments on found on the (x)= Arguments tab. The first argument is the input mapping file and the second is the output location for the generated class. They should be set to something like this:
C:/projects/hibernate/src/tests/org/iseriestoolkit/examples/customers.hbm.xml --output=C:/projects/hibernate/src/tests
Clicking Run will generate Java source that generally needs very little modification.
Figure 6: The class generator creates a Java class to support database tables |
There are dozens of tools available to support Hibernate development, including tools that are part of other projects. I have only shown you a couple, but these are representative of the types of tools you will find. Many of those tools are variations of other tools, which can be confusing at first, but it is nice to have choices. Once you are familiar with Hibernate, take some time to explore these tools and use the ones that fit your programming style.
HIBERNATING EFFECTIVELY
I hope that this brief tour of Hibernate will entice you to explore object relational mapping on your own. Hibernate is a great piece of software that makes it easier for Java developers to treat relational data as objects. One of Hibernate’s strong points is that it strikes a good balance between simplicity and flexibility. The developers of Hibernate were careful to include features that are essential to successful enterprise software development without over-complicating the Hibernate API.
To use Hibernate against older releases of OS/400, you have to make some adjustments to the DB2 SQL dialect supplied with Hibernate. Those changes are minor compared with the changes you would have to make to SQL statements to support more than one database.
Once you start using Hibernate, spend some time reading the FAQs and the manual. And make sure to participate in Hibernate’s user forums. If you do so, you will avoid most of the problems that developers encounter with Hibernate.
David Morris is a software architect at Plum Creek Timber Company, and started the iSeries-toolkit open-source project. E-mail: dmmorris@itjungle.com.