More Persistence for Client-Side Developers More Persistence for Client-Side Developers

by Joshua Marinacci
06/08/2006

Java Persistence is a powerful technology designed to store all of your data objects in a full relational database without having to write a single line of SQL. Though originally created for the server side, I will show you how useful it can be in client applications. In " An Introduction to Java Persistence for Client-Side Developers" I introduced the Java Persistence API, the libraries to support it, and showed you how to set up a trivial but complete application. In part two we will take a look at advanced features like automatic table creation, property control, and inter-object relationships. Then we will build a complete address book application using everything we have learned.

Under the Hood

Before I get into the advanced persistence features, I'd like to discuss what is really going on here. When you use the Persistence API, it isn't magically saving your objects into the ether. It's actually creating complicated SQL statements under the hood, but you are isolated from this when you use the API. This does bring up the question of how the tables are managed. What happens if the tables aren't already there? What happens if you change your domain objects? Will the tables be out of sync? For the most part, the persistence engine will take care of these details for you, but you have to give it a few hints.

One of the things I glossed over in part one is the hibernate.hbmddl.auto property in the persistence.xml file. This property controls how Hibernate will create and manage your tables. It is specific to Hibernate, but most implementations will have an equivalent property.

The hibernate.hbmddl.auto property has four possible values: validate, update, create, and create-drop:

The Object Lifecycle

Another subtlety that I glossed over in the introduction is the object lifecycle. Though it is true that all persistence operations (saving, loading, searching) should happen in a transaction, the object lifecycle is a bit more complicated than that. Any object which can be persisted (an "entity," in Java EE terminology) can be in one of four states that indicate how it is connected (or not connected) to the database. The states are:

These definitions are actually more complicated than I have described here, especially when you are using container-managed persistence. However, for the purposes of most client applications, we can ignore these extra details. For more information, take a look at the Hibernate Entity Manager reference.

Property Mapping

Java Persistence provides many ways to customize your properties. It has very good defaults, so you can often stick with those, but sometimes you may want more control over your properties. By default, every property has an implicit @Basic annotation, which just means that the field will be mapped using default conversions (such as String to VARCHAR, int to INT4, etc). Thus:

String name;

is the same as:

@Basic 
String name;

The first thing to consider when you design your persistable data object is whether you want to use getters and setters or leave your fields naked. Java Persistence can directly access your object member variables if you want it to. It can even access private fields using reflection hacks, though this wouldn't work in a non-secure context like an applet. If you want to use property accessors instead of direct field access, you only need to move the annotations from the field declaration to the getter method. For example, if I wanted use accessors for the id field, I could change the code from:

@Id @GeneratedValue
public Long id;

to

private Long id;
@Id @GeneratedValue

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

The defaults are great, but sometimes you want more control. For example, you might have a field that you don't want to persist because it is an intermediate value. You can tell the Persistence engine to ignore it with the @Transient annotation.

@Transient
public String getDisplayName() {
    return getFirst() + " " + getLast();
}

You can also give the persistence engine hints about how to save your data. Suppose the Person object has a photo property stored as a BufferedImage. The image should be stored as a binary large object (BLOB), rather than a VARCHAR as a String would be. You can tell the engine to use BLOBs or character large objects (CLOBs) with the @Lob annotation. The engine will then do the right thing (CLOBs for chars or anything that can be serialized to chars, and BLOBs for bytes or anything that can be serialized to bytes).

@Lob
public BufferedImage getPhoto() {
    return photo;
}

The Persistence API also lets you decide when to do fetching. Suppose you search for a bunch of Person objects but you only want to load the photos of the ones that will appear on screen. Photos are potentially large objects, so you only want to load the ones that are needed. In this case you don't want the engine to load the photo property of the Person objects until you directly call the getPhoto() method. The Persistence API lets you control this with the fetch parameter. To lazily load the Photo property of a Person, you would do the following:


@Basic(fetch = FetchType.LAZY)
@Lob
public BufferedImage getPhoto() {
    return photo;
}

LAZY is the default so that loading is optimized for speed. You could also have the engine eagerly load your data with the FetchType.EAGER value. Tweaking the fetch type is a great way to improve the performance of your application.

Java Persistence has many other ways to customize your properties like controlling the column names used, changing the way enums are stored, or adding constraints like nullable and field lengths. This are useful if you want to conform to an existing database, but in general I prefer to use the defaults and only tweak the settings when I want to optimize the persistence.

One-to-One Mapping

So far, I have only discussed storing a single class. This is great for demos, but real applications have more than one class; usually, a bunch of objects connected in a variety of ways. The links between these objects are called associations or relationships, and the Persistence API provides a couple of ways to define these.

The simplest kind of relationship is a one-to-one mapping. This means that each instance of object A has one and only one instance of object B attached to it. To continue our address book example, let's add a Ringtone object that could be used on a cellphone. Here is the Ringtone class, with the boilerplate getters and setters omitted:


@Entity
public class Ringtone implements Serializable {
    
    private Long id;
    private String ringtoneDataPath;
    private long length;
    
    public void play() {
        // perform some long operation
    }

    // accessors for the rest of the properties omitted
}

Each Person in the address book will have a single Ringtone object attached to it using the @OneToOne annotation.

@Entity
public class Person implements Serializable {
    ...
    @OneToOne
    public Ringtone getRingtone() {
        return ringtone;
    }

Before you compile, be sure to add the new Ringtone class to the persistence.xml file so that the persistence engine knows about it. Other than that, you don't need to do anything extra. If you want the Ringtone to have a reference back to its Person, then you can add a Person property and the engine will take care of the rest. This is called a bidirectional association. If you omit the back reference, then it would be called a unidirectional association.

If you use the class as described above, you may have some exceptions complaining about not being inside of a transaction. The problem is that your first call to get/setRingtone() happens after the transaction has been committed. This means you have lost the connection to the database by the time you interact with the Ringtone object. Since Person is the center of your object model, it would be nice to just have the Ringtone automatically loaded with the Person. You can do this with the cascade parameter:

@OneToOne(cascade=CascadeType.ALL)
public Ringtone getRingtone() {
    return ringtone;
}

The cascade parameter also has the values PERSIST, MERGE, REMOVE, and REFESH. I recommend using the CascadeType.ALL type since it completely ties the subordinate object to the main one, which is almost always what you want.

Collection Mapping

Ringtone uses a one-to-one mapping very nicely, but most domain models consist of more than just these simple links. To really take advantage of persistence, you should use collections. Person should have a List of addresses, for example. The Persistence API lets you do this with one-to-many associations.

Below is an Address class, again with the extra getters and setters removed.

@Entity
public class Address {

    private String street;
    private String city;
    private String state;
    private int zipcode;
    @Id @GeneratedValue
    private long id;    
    
    // getters and setters omitted
}

To allow a Person to have more than one Address, we can map it with the @OneToMany annotation.

private List<Address> addresses;

@OneToMany(cascade=CascadeType.ALL)
public List<Address> getAddresses() {
    return addresses;
}

public void setAddresses(List<Address> addresses) {
    this.addresses = addresses;
}

public void addAddress(Address addr1) {
    this.addresses.add(addr1);
}

Notice that there is a getter and a setter for the List as a whole, as well as an addAddress method. The persistence engine will only use the get/set methods. The add method is just for developer usage. Again I have used the CascadeType.ALL option to ensure that the Address list is loaded along with the Person.

In addition to Lists, the Persistence API also supports Sets, Maps, and general Collection instances. Although I won't go into it here, you can also use another association type called @ManyToMany, though I haven't found it to be as useful.

The great thing about using collections is that they can be typesafe. You can keep all of your objects in a single object graph and access them without casting. In the code above, I'm using generics to define a list of addresses instead of just a list of generic objects. Compared to direct SQL calls, Persistence frees up your developers so they can focus on more important things; like building great GUIs.

Inheritance

One thing I haven't addressed yet is inheritance. Most non-trivial applications will use some inheritance in the domain model. This is one of the principal features of object-oriented programming, after all. For example, the address book program could store Contact objects that have concrete implementations like Email and InstantMessaging. Again, if you don't want to do anything special, the persistence engine can handle this using the defaults.

Below are three new classes: the abstract class Contact, and its two concrete subclasses Email and InstantMessaging. Contact defines one property, Category, which is an enum describing whether this is a work or personal contact. The subclasses define properties specific to their particular mediums.

@Entity
@Inheritance
public abstract class Contact {
    @Id @GeneratedValue
    private long id;
    
    public enum Category { PERSONAL, WORK, OTHER };
    
    private Category category;
    
    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }
    
}

@Entity
public class Email extends Contact {
    private String address;
    
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
}

@Entity
public class InstantMessaging extends Contact {
    public enum Type { AIM, YAHOO, MSN, JABBER };
    private String accountName;
    private Type type;

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }
    
}

Notice that the @Inheritance annotation is added to the class at the top of the hierarchy, Contact, but not the others. @Inheritance takes a strategy parameter to define how the classes should be mapped to tables. If you want all of your classes to share a single table instead of giving them each their own table, you would use the annotation like this: @Inheritance(strategy=InheritanceType.SINGLE_TABLE). Unless you are dealing with an existing database, however, you can just use the default strategy. In fact, you often don't need to use the @Inheritance annotation at all. If you leave it off, the Persistence engine will do the right thing. Also note that you can only use abstract and concrete classes with Java Persistence. Annotating interfaces is not currently supported.

A Complete Application

To illustrate all of the techniques in this article, I created a sample application called AddressBook2 that simply creates and stores addresses. It binds the data model created earlier in this article to a Swing GUI. Using the excellent GUI builder Matisse in NetBeans 5, I was able to construct a fully featured application in about four hours. Patterned after the Mac OS X equivalent, this application lets the user create, edit, save, and delete address-book entries. To keep the GUI implementation simple, each Person entry is only allowed to have one Address, though obviously the data model supports more. You can find the source code to AddressBook2 is in the .zip file in the Resources section. Figure 1 shows what it looks like:

Figure 1
Figure 1. Address Book GUI (Click image for full-size screen shot)

The bulk of the code for AddressBook is in the AddressBook.java class. Most of the code is dedicated to setting up the GUI and copying data into and out of the data objects. There is a lot of boilerplate code that looks like this:

private void setSelectedPerson(Person person) {
    this.selectedPerson = person;
    clearFields();
    first.setText(person.getFirst());
    middle.setText(person.getMiddle());
    last.setText(person.getLast());
    
    contacts.removeAll();
    for(Contact c : person.getContacts()) {
        ContactView view = new ContactView(this);
        view.setEnabled(false);
        view.setContact(c);
        contacts.add(view);
    }
    // ...
}

Once the new data binding JSR is approved, most of the boilerplate will go away, but in the meantime we can be happy knowing it's at least easy and relatively foolproof; just a lot of copying values from data objects to text fields and back.

My general design pattern for building this GUI was to create a view panel for each data object. Thus AddressBook has a setSelectedPerson() method that will set the values of each visual component from the specified Person. AddressBook has a subpanel for storing contacts (emails, IMs, etc.). This subpanel contains one ContactView for each contact. Using this pattern makes the GUI scalable, meaning I can easily change how a particular data object is viewed, or add support for new data objects (say, a new kind of Contact called SMSTextMessage or a Google map link) without modifying the rest of the GUI.

Once the data objects are loaded up with data, the actual persistence part is very simple. I have a Main object that does the actual loading, saving, and deleting of operations. The implementations are quite minimal, each performing the basic operation wrapped in a transaction:

Main.java

//....
public void save(Person person) {
    EntityTransaction tx = manager.getTransaction();
    tx.begin();
    try {
        manager.persist(person);
        tx.commit();
    } catch (Exception ex) {
        System.out.println(ex.getMessage());
        ex.printStackTrace();
        tx.rollback();
        throw new DBError("There was a problem saving",ex);
    }
    
}
public List<Person> search() {
    EntityTransaction tx = manager.getTransaction();
    tx.begin();
    try {
        Query query = manager.createQuery("select p from Person p");
        List<Person> results = (List<Person>)query.getResultList();
        tx.commit();
        return results;
    } catch (Exception ex) {
        System.out.println(ex.getMessage());
        ex.printStackTrace();
        tx.rollback();
        // report an error to the user
        throw new DBError("There was a problem searching",ex);
    }
}

public void remove(Person person) {
    EntityTransaction tx = manager.getTransaction();
    tx.begin();
    try {
        manager.remove(person);
        tx.commit();
    } catch (Exception ex) {
        System.out.println(ex.getMessage());
        ex.printStackTrace();
        tx.rollback();
        throw new DBError("There was a problem searching",ex);
    }
}

Conclusion

Persistence allows desktop applications to access remote databases easily, simplifying the typical CRUD app development process. Persistence also allows client developers to put database-like features (e.g., iTunes search) directly inside of their applications through embedded databases. Java Persistence is one of many great server-side APIs that can really make life easy for the client developer.

Resources

Joshua Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.


 Feed java.net RSS Feeds