Skip to main content

More Persistence for Client-Side Developers

June 8, 2006

{cs.r.title}






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 " "http://today.java.net/pub/a/today/2006/05/23/ejb3-persistence-api-for-client-side-developer.html">
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:

  • update will create the tables if they are not
    already there, but leave them intact if they are. If you add new
    fields to your classes, then it will alter the existing tables,
    leaving the data intact. This is probably what you want most of the
    time.

  • create will create the tables when you first
    connect, destroying any that are already there. This is useful when
    testing or for creating installation tools.

  • create-drop will create the tables when you first
    connect to the database and then destroy them when the connection
    is closed. This is most useful for unit testing, where you want a
    clean database every time.

  • validate: I could not find much documentation on
    what validate does, but it appears to not modify the
    tables at all. I think it verifies that your objects match the
    table, but I was not able to find a case where it complained about
    a mismatch, so it may do something else.

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:

  • New (transient): The object has been created but
    has never been persisted or attached to the database. This is a
    simple object. Once it is saved to the database with the
    EntityManager.persist() method, it will become
    managed.

  • Managed (persistent): The object is currently
    attached to the database. Changes to the object will (potentially)
    create changes in the database. If necessary, after modifying an
    object you can sync the changes with the
    EntityManager.flush() method.

  • Detached: The object was previously attached to the
    database but is no longer. A detached object can be reattached
    using the EntityManager.merge() method.

  • Removed: An object that was in the database but has been
    scheduled for removal. You can do this with the
    EntityManager.remove() method.

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 "http://www.hibernate.org/hib_docs/entitymanager/reference/en/html/">
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

width="1" height="1" border="0" alt=" " />
Josh Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.
Related Topics >> Databases   |   GUI   |