ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Hibernate for Java SE

by Jason Lee
11/16/2005

By now it's easy to see that Hibernate is quickly becoming a popular (if not the most popular) O/R mapper/data integration framework for J2EE. It offers a developer clear, concise, and powerful tools for working with relational databases in the enterprise. But what if you need outside access to those entities that are wrapped up in your J2EE web application? Do you develop separate, yet identical, entities to access your data? Or do you write additional web components to manage the internal access to your data?

At some point, these questions are bound to come up, and for me, it was when my company needed to upload records into our database from multiple vendors in multiple file formats. I thought about the approach I had always taken before, which was to write shell and SQL scripts (and even stored procedures) to upload the data. But because our data model is quite complex, I decided to leverage what existing entities, Spring DAOs, and services outside of our web app I could and develop a custom J2SE command-line data loader.

The Big Question: Should You?

Much of the documentation and many of the examples that exist for Hibernate today all revolve around using it in a container. Whether it's for a web app or in-house "fat app," there's usually a container involved. There's good reason for this. Containers are designed to include support for all sorts of features, such as transactions, threading, and security. Nowadays, these are required tools for developing mid-size and enterprise applications. But what should you do when you need access to your entities objects outside your container? Should you use your existing infrastructure and code, or come at the problem from a different angle, possibly with a different language altogether? Of course, there is no right answer, and for the rest of this article I will explain my approach, which was to reuse my existing entities/POJOs outside of my Spring container.

Related Reading

Hibernate: A Developer's Notebook
By James Elliott

Scripting languages such as Perl, Python, Ruby, or even Tcl (yes, I've done this before) seem to have some advantages at first. A lot of the time, they're quick to hack up, easy to get initial results, and can bypass a lot of the underlying complexity of Hibernate. It's possible in just a few lines to make a connection to a database, select some results, and print them out to the screen or some log file. However, depending on your data model, things can (and usually do) get quite complicated. Say you have a person table that has a foreign key to an address table, and while inserting data, the address doesn't insert properly, which causes the person to not insert as well: a typical transactional issue. One could argue that it's not too hard to account for this in your scripting language du jour, just like in your main application. But the question remains: why should you? If this logic already exists in your application, why code it again? And since this probably won't be the only case, you will be duplicating work and logic and thusly introducing the possibility of errors, as well.

Some people, however, will see this as OK and use whatever tool they feel is best for that job. Maybe you already have some sort of separate infrastructure for reasons outside of programming. Maybe you upload and test your data on a separate database and after it passes various tests, it is then migrated to a production database. Or maybe your database maintenance is outsourced and you just send the files to your partner company and they take of the issues. In the end, there could be many other reasons not to use your existing Hibernate data layer--none of them right or wrong. But if you can and will use your existing code base outside of your app, then read on. I might be able to uncover a few tips and save you a few headaches in the process.

Configuration

Once you have decided that it is OK to use your existing Hibernate objects outside of your container, then the first thing you'll have to do is to manage all of the configuration yourself. The approach I will take for the rest of the article is via a command line, standalone Java app. Since you probably already have a Hibernate XML configuration file set up, you already know what parameters are required, such as JNDI DataSource name, entity mapping files, and miscellaneous properties for logging SQL. If you decide to use a command-line application, you will have to figure out how to parse the XML file and add it to your new configuration. While parsing an XML document is not impossible, it can sometimes be a little side project in itself. Thus, I recommend using a regular properties file. A properties file is pretty straightforward and fairly easy to load up and get values from. The following is an example of a minimal set of properties needed to configure Hibernate (sans any entity mappings):

hibernate.dialect=net.sf.hibernate.dialect.PostgreSQLDialect
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.connection.url=jdbc:postgresql://devserver/devdb
hibernate.connection.username=dbuser
hibernate.connection.password=dbpassword
hibernate.query.substitutions yes 'Y'

As you can see, the above properties specify the database dialect, the JDBC driver class, database server name, user name and password, and whether or not to use query substitutions. Once you have defined these and saved them in a hibernate.properties file (which should reside in your classpath, by the way), they are fairly easy to load up and hand off to the Hibernate Configuration object.


Properties props = new Properties();
try {
    props.load(props.getClass()
    .getResourceAsStream("hibernate.properties"));
}catch(Exception e){
    System.out.println("Error loading hibernate "+
     "properties.");
    e.printStackTrace();
    System.exit(0);
}

String driver = 
    props.getProperty("hibernate.connection." +
     "driver_class");
String connUrl =
    props.getProperty("hibernate.connection.url");
String username =
    props.getProperty("hibernate.connection." +
     "username");
String password =
    props.getProperty("hibernate.connection.password");
            
// In my examples, I use Postgres, but Hibernate 
// supports virtually every popular dbms out there.
Class.forName("org.postgresql.Driver");
Connection conn =
    DriverManager.getConnection(connUrl, username, 
     password);

Configuration cfg = new Configuration(); 
cfg.setProperties( props );
SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession(conn);

This will give you a Hibernate Session object that is ready to use. However, we still need to figure out how to use the existing entity mappings. The book Hibernate in Action, section 2.3.1, shows how you can load in entity XML mapping files, like so:

Configuration cfg = new Configuration(); 
cfg.addResource("hello/Message.hbm.xml"); 
cfg.setProperties( System.getProperties() ); 
SessionFactory sessions = cfg.buildSessionFactory();

This code clearly shows the loading of the Message entity definition from the hello package. This is fine for a few examples, but extremely tedious and error-prone for more than just a few entities. Plus, not only are the mappings hard-coded, they're maintained manually and you now have to update the loader code every time a new entity is added! Yuck. There is an easier way to discover and load these mappings so that they will always be as current as the latest .jar is.

First off, as in your web or enterprise app, your mapping files will need to be in the classpath in order for Hibernate to work correctly. This is a good thing, since all you will have to do is use that same .jar and find those mapping file names. Since you also may have multiple .jar files in your classpath, you'll need to specify which one contains your mappings. The following is one way to find those mappings.

String cp = System.getProperty("java.class.path");
String jarFile = null;
List   hbmList = null;

String[] cparr = cp.split("\\:");
for(int j=0;j<cparr.length;j++){
    // The following assumes our entities 
    // are wrapped up in a jar file 
    // called 'dbobjs.jar'
    if(cparr[j].indexOf("dbobjs.jar") != -1)
      jarFile=(cparr[j]);
}

if(jarFile != null){
    JarFile jar = new JarFile(new File(jarFile));
    Enumeration e = jar.entries();
    if(e.hasMoreElements()){
        hbmList = new ArrayList();
            while(e.hasMoreElements()){
                // Object comes back
                // as JarFile$JarFileEntry
                JarEntry entry = 
                  (JarEntry)e.nextElement();
                if(entry.getName()
                   .indexOf(".hbm.xml") != -1){ 
                    hbmList.add(entry.getName());
                }
            }
    }else {
        System.out.println("Error: The entity "+
        "jar dbobjs.jar was not found in " +
        "classpath: " + cp);
    }
}

The above code essentially grabs the classpath system property that the VM was initialized with, looks for the .jar file that contains the entity mapping files, parses the names of those files, and then adds them to an ArrayList. When we have the ArrayList with the full names of each of the entity mappings, they can then be passed to the Hibernate Configuration object like so:

Configuration cfg = new Configuration(); 

Iterator iterator = hbmFileNames.iterator();
while(iterator.hasNext()){
    cfg.addResource((String)iterator.next()); 
}

Once we have our Hibernate Session object all set up with the proper mappings, we're ready to go to the next step: putting the entities to use.

Using the Session

At this point, you could pick up various articles or how-tos for Hibernate and persistence or querying various objects as well as numerous examples on how to use transactions, so I will not go into any detail on those points. Instead, think about what you need to do with the entities and how that affects the Hibernate Session object. Is it possible to use existing business objects or even data access objects? When I set up my data layer, I used Spring and some of the classes it provides to manage connections, transactions, and Sessions. These objects are all defined in XML configuration files with various rules and relationships tightly integrated in Spring. First, my DAO objects are injected into my services via Spring's dependency injection (see Bruce Tate's "Five Things I Love About Spring" for more on dependency injection). Then the services are configured to catch specific DAO exceptions (in an XML config file), which Spring will transact appropriately. While I decided it was too much work to try to integrate Spring into my data loader app, I did make some minor adjustments to my DAO objects so that they could be used outside of my web app.

Let's say I have a method in a PersonDAO that saves a person object. Since my container sets the Hibernate Session up for me, I wouldn't be able to reuse that DAO method outside of the container because it expects a Session object to already exist and be fully configured. Here's what a typical PersonDAO might look like with Session support provided by the Spring container:

import org.springframework.orm.hibernate.HibernateTemplate;
import test.pojos.Person;

public class PersonDAO extends HibernateTemplate {

    public PersonDAO(){}

    public Person save(Person aPerson) {
        if(aPerson != null) super.save(person);
        return person;
    }
}

The above class extends the Spring HibernateTemplate class, which provides all sorts of nice base methods for working with Hibernate. And since HibernateTemplate manages most of the mundane operations, you only have to focus on your specific persistence needs. There should also be proper exception handling, but for this demonstration, the above code will suffice.

Now, to add support for Sessions outside of the container, we only have to make a few minor modifications:

import org.springframework.orm.hibernate.HibernateTemplate;
import net.sf.hibernate.Session;
import test.pojos.Person;

public class PersonDAO extends HibernateTemplate {

    public PersonDAO(){}

    public void setExternalSessionFactory(Session aSession){
        setSessionFactory(session.getSessionFactory());
    }

    public Person save(Person aPerson) {
        if(aPerson != null) super.save(person);
        return person;
    }
}

Since HibernateTemplate extends HibernateAccessor, I'm allowed to set a SessionFactory from any Session object I choose. This is a highly flexible design on the part of the Spring team and makes reuse of existing code much easier.

Now you might not be using Spring and therefore have a completely different approach. Say you don't have fancy Spring dependency injection and you look up your Session object from JNDI like so:

import net.sf.hibernate.Session;

public class PersonDAO {

// This example assumes that there is a Hibernate
// Session object at the following JNDI location
// on a Tomcat 5.5 server:
// java:/comp/env/obj/hibernateSession

    private Session session;

    public PersonDAO(){
        try {
            Context initCtx = new InitialContext();
            Context envCtx = (Context) 
             initCtx.lookup("java:comp/env");
            session = (Session)envCtx.
             lookup("obj/hibernateSession");
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    public Person save(Person aPerson) {
        if(aPerson != null) session.save(person);
        return person;
    }
}

The above example relies on the application container to make available a Hibernate Session object for its use. The simplest way for use to use this outside of our container would be to just add another constructor that accepts a Session object:

import net.sf.hibernate.Session;

public class PersonDAO {

// This example assumes that there is a Hibernate
// Session object at the following JNDI location
// on a Tomcat 5.5 server:
// java:/comp/env/obj/hibernateSession

    private Session session;

    public PersonDAO(){
        try {
            Context initCtx = new InitialContext();
            Context envCtx = (Context) 
             initCtx.lookup("java:comp/env");
            session = (Session)envCtx.
             lookup("obj/hibernateSession");
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    public PersonDAO(Session aSession){
        session = aSession;
    }

    public Person save(Person aPerson) {
        if(aPerson != null) session.save(person);
        return person;
    }
}

Obviously we're not handling many exceptions, transaction issues, or the fact that we're even sharing a Session object across many methods, which could cause some concurrency issues depending on how your container or framework handles object instances. However, I think it is clear that the above examples demonstrate that you can reuse a lot of existing data layer code. It just requires a little creative thought. Just make sure that if you do try to use your entities and DAOs outside of your app server, test, test, test!

Conclusion

As you can see, there are some tricks to using your Hibernate entities and DAOs outside of a web container, but it can definitely be done. The biggest challenges are finding the entity mappings and figuring out how to reconfigure, or augment, your existing data access objects (DAOs). When working with the latter, take care to manage your transactions, as you might not have any of your existing business services to rely upon. But in the end, you can have access to all of your entities and the objects used to persist them, which can save a lot of redevelopment time. Good luck!

Jason Lee has been developing web apps for the past ten years and currently is VP of technology at a small startup.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.