DekGenius.com
Previous Section  < Day Day Up >  Next Section

7.3 Using Your Persistent Model

Within a Hibernate application, there are at least two types of code. You've already seen the model, which is completely independent of Hibernate. You also have the client that uses that model. This client can take a number of forms. It can be a J2EE application that uses EJB session beans, a lightweight container like Spring, a complex, non-EJB J2EE application that uses JTA to manage transactions, or a simple Java application.

In the previous section, I showed how to build a persistent model and map it to a relational database schema. That object model is completely transparent with respect to persistence. In this section, I show how to use that model. Of course, the code that accesses the persistent model must use Hibernate classes to load and save data, and possibly manage transactions.

Some simple persistence frameworks make each access to the database independent. If you're using such a framework, you can't manage transactions or cache in the persistence layer, and it places a heavier burden on the application, especially in a web environment. Like most of the more robust persistence frameworks, Hibernate uses sessions. Think of a session as a running conversation between your application and the persistent model, and through it, the database. A session factory provides a convenient attachment point for configuration options, and also caches and metadata related to a single Hibernate configuration.

7.3.1 Configuring Hibernate

You're going to need another type of configuration. If you were using EJB, you'd use a deployment descriptor to describe the configuration of your system. Hibernate has some of the same requirements. You're going to want to configure your JDBC drivers, connection pools, transaction strategy, security, and the like. Break configuration tasks into your configuration file and a few lines of code in order to load your configuration through the Hibernate API.

Recall that you configure the mapping between each persistent class and the database schema. The inventers of Hibernate had to decide how to organize the rest of the configuration. The main question is this: exactly what will the users be configuring: an application, a Hibernate instance, or all Hibernate instances? Some of those solutions—such as configuring an instance or all Hibernate instances—are simple, but they don't allow enough flexibility, for instance, for accessing two databases (with separate configurations) from the same application. One of the options (configuring every session) is too expensive. Instead, Hibernate lets you configure a session factory. Then, all of the sessions that you get from a factory are configured the same way.

Here's the configuration file for the discussion application, called hibernate.properties:

hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/disc
hibernate.connection.username = batate
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect
hibernate.show_sql=true

In this simple configuration, I've configured only the JDBC driver and one option that tells Hibernate to log all SQL statements that Hibernate generates. You can see the JDBC configuration parameters you'd expect, such as the URL, the driver's class name, and the connection credentials. The configuration file also has a dialect, a pluggable API that tells Hibernate the version of SQL to use. Very few SQL implementations actually meet the ANSII standard. In addition, you often want your framework to take advantage of extensions that aid performance or flexibility.

After you've built your model, mapped your model, and built your configuration file, load your configuration with a couple of lines of code at the beginning of each Hibernate client. You'll need a separate session factory for each configuration. Our discussion application only needs one session factory. Here's the code that loads our configuration:

class BoardManager {
  
  SessionFactory factory;
  Configuration cfg;

  BoardManager( ) {
    try {
      cfg = new Configuration( )
        .addClass(discussion.User.class)
        .addClass(discussion.Topic.class)
        .addClass(discussion.Post.class);
        factory = cfg.buildSessionFactory( );
    } catch (Exception e) {
      System.out.println("Hibernate configuration failed:" + e);
    }
  }

Notice that this code performs three discrete steps. First, it loads the configuration specified in the hibernate.properties file. Second, it adds the mapping for each class in your persistent model. If you've ever coded Smalltalk, you might have seen this coding style. All of the addClass( ) methods are chained together. If you prefer, and your coding standards permit, you can break out each addClass( ) method into a separate statement, like this:

cfg.addClass(discussion.User.class);
cfg.addClass(discussion.Topic.class);
cfg.addClass(discussion.Post.class);

Finally, build the factory. Keep in mind that the configuration file is very small; in a production environment, it's likely to be much larger. You're probably going to want to specify your own connection pool. You may want to configure a different transaction strategy (such as JTA), a cache manager, logging and debugging options, or even EJB sessions. A complete description of the entire configuration is beyond the scope of this book, but you can find out about each parameter in the excellent Hibernate documentation.

7.3.2 Using Your Model

Now you've got a model that's mapped onto a relational database. You also have a fully configured persistence framework in Hibernate to manage it. In keeping with our five principles for better Java, the model is completely transparent with respect to persistence. The concepts encapsulated by the business model are fully separated from all other aspects of the application. All that remains is to tell Hibernate how to move data to and from your model.

I like to have a class named xManager that manages persistence for a model. In this example, I've named the class that manages persistence for the discussion application BoardManager.

7.3.2.1 Loading data

The first task is to get Hibernate to populate your business objects. Like most persistence frameworks, you can ask Hibernate to populate your model in two ways. First, you can specify the ID of the root-level object that you wish to load. Second, you can issue a query in Hibernate Query Language, which is similar to SQL. Look at this code, which checks the password for a user:

public boolean checkPassword(String id, String password)
          throws Exception {

  User user = loadUser(id);
  if (user == null) {
    return false;
  }
  if (user.getPassword( ).equals(password)) {
    return true;
  } 
  return false;
}

In this example, the method loads the user, given the user's identifier. Then, the method can access the user object directly, checking the value of the password property. In this method, exceptions bubble back up to the calling method. If you know the identifiers of the objects that you need, this is a convenient way to use the model.

Alternatively, you might need to use the SQL-like query language. For example, you might need to load all posts for a user. This code fragment does the trick:

List posts = session.find(
  "from posts as p where p.userID = ?",
  userID,
  Hibernate.string
);

The query language is nearly identical to SQL. You can specify parameters with the "?" character. The parameter list for the method specifies the values and types of the parameters. You don't see a SELECT statement, because usually you don't need one. You're usually going to return a list of objects of the type identified in the from clause.

Hibernate also supports some advanced SQL notions, such as aggregates. For example, to count the users in our table, issue the query:

SELECT count(*)
FROM users

Most query language for object-oriented persistence frameworks work only with true objects. SQL aggregate functions return a scalar type, not an object. Hibernate opts to keep a richer, more flexible query language by staying as close as possible to SQL. Other query languages, such as those for EJB and JDO, have had more limited success.

7.3.2.2 Updating the database

Now let's look at how you tell Hibernate to update the database. The discussion application must save new users to the database:

public void saveUser(User user) throws Exception {
  Session session = null;
  try {
    session = factory.openSession( );
    session.save(user);

    session.flush( );
    session.connection( ).commit( );
          
    return;
    
  }
  finally {
        if(session.isOpen( ))
     session.close( );
  }
}

For all the work that it does, the meat of the method is remarkably simple. I get a new session from the session factory and then call the save method on the session, passing in a new user. The rest of the method processes exceptions and cleans up the connection. The flush makes sure the cache is flushed, so the data will be in the database. The commit method commits the transaction, and we're done. In this case, User is a very simple object, but it might have been a complex object, and if I'd configured it to do so in the mapping, Hibernate would save each related object without any additional intervention from me.

Similarly, here's the code to delete a user from the database:

public User removeUser(String id) throws Exception {
  Session session = null;
  try {
    session = factory.openSession( );
    User user = new User( );    // object must first be loaded to be deleted
    session.load(user, id);
    session.delete(user);

    session.flush( );
    session.connection( ).commit( );
    return user;
  } catch (Exception e) {
    return null;              // not found condition should not force error
  } 
    finally {
    if(session.isOpen( )) {
           session.close( );
   }
         
}

This code is similar to the saveUser method, with one difference: you must first load the User into the session in order to delete it. Once again, if you need to remove an object with relationships, such as a topic that contains other posts, you can choose to cascade the delete to all related objects, if you configure the mapping to do so. You can see that the persistence framework takes care of the tedious details of integrating a relational database and a persistent model. You didn't have to work with the JDBC connection and you didn't have to worry about coding the correct SQL. You simply tell Hibernate to save, remove, or update the object, and you're off to the races.

    Previous Section  < Day Day Up >  Next Section