Chapter 4. Persistent Classes

4.1. Simple Example

Most Java applications require a persistent class representing felines.

package eg;
import java.util.Set;
import java.util.Date;

public class Cat {
    private Long id; // identifier
    private Date birthdate;
    private Cat mate;
    private Set kittens
    private Color color;
    private char sex;
    private float weight;

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

    void setMate(Cat mate) {
        this.mate = mate;
    }
    public Cat getMate() {
        return mate;
    }

    void setBirthdate(Date date) {
        birthdate = date;
    }
    public Date getBirthdate() {
        return birthdate;
    }
    void setWeight(float weight) {
        this.weight = weight;
    }
    public float getWeight() {
        return weight;
    }

    public Color getColor() {
        return color;
    }
    void setColor(Color color) {
        this.color = color;
    }
    void setKittens(Set kittens) {
        this.kittens = kittens;
    }
    public Set getKittens() {
        return kittens;
    }
    // addKitten not needed by Hibernate
    public void addKitten(Cat kitten) {
        kittens.add(kitten);
    }
    void setSex(char sex) {
        this.sex=sex;
    }
    public char getSex() {
        return sex;
    }
}

There are three main rules to follow here:

4.1.1. Declare accessors and mutators for persistent fields

Cat declares accessor methods for all its persistent fields. Many other ORM tools directly persist instance variables. We believe it is far better to decouple this implementation detail from the persistence mechanism. Hibernate persists JavaBeans style properties, and recognizes method names of the form getFoo, isFoo and setFoo.

Properties need not be declared public - Hibernate can persist a property with a default, protected or private get / set pair.

4.1.2. Implement a default constructor

Cat has an implicit default (no-argument) constructor. All persistent classes must have a default constructor (which may be non-public) so Hibernate can instantiate them using Constructor.newInstance().

4.1.3. Provide an identifier property (optional)

Cat has a property called id. This property holds the primary key column of a database table. The property might have been called anything, and its type might have been any primitive type, any primitive "wrapper" type, java.lang.String or java.util.Date. (If your legacy database table has composite keys, you can even use a user-defined class with properties of these types - see the section on composite identifiers below.)

The identifier property is optional. You can leave it off and let Hibernate keep track of object identifiers internally. However, for many applications it is still a good (and very popular) design decision.

What's more, some functionality is available only to classes which declare an identifier property:

  • Cascaded updates (see "Lifecycle Objects")

  • Session.saveOrUpdate()

We recommend you declare consistently-named identifier properties on persistent classes. We further recommend that you use a nullable (ie. non-primitive) type.

4.1.4. Prefer non-final classes (optional)

A central feature of Hibernate, proxies, depends upon the persistent class being either non-final, or the implementation of an interface that declares all public methods.

You can persist final classes that do not implement an interface with Hibernate, but you won't be able to use proxies - which will limit your options for performance tuning somewhat.

4.2. Inheritance

A subclass must also observe the first and second rules. It inherits its identifier property from Cat.

package eg;

public class DomesticCat extends Cat {
        private String name;

        public String getName() {
                return name;
        }
        protected void setName(String name) {
                this.name=name;
        }
}

4.3. Persistent Lifecycle Callbacks

Optionally, a persistent class might implement the interface Lifecycle which provides some callbacks that allow the persistent object to perform necessary initialization/cleanup after save or load and before deletion or update.

public interface Lifecycle {
        public boolean onSave(Session s) throws CallbackException;   (1)
        public boolean onUpdate(Session s) throws CallbackException; (2)
        public boolean onDelete(Session s) throws CallbackException; (3)
        public void onLoad(Session s, Serializable id);              (4)
}
1

onSave - called just before the object is saved or inserted

2

onUpdate - called just before an object is updated (when the object is passed to Session.update())

3

onDelete - called just before an object is deleted

4

onLoad - called just after an object is loaded

onSave(), onDelete() and onUpdate() may be used to cascade saves and deletions of dependent objects. This is an alternative to declaring cascaded operations in the mapping file. onLoad() may be used to initialize transient properties of the object from its persistent state. It may not be used to load dependent objects since the Session interface may not be invoked from inside this method. A further intended usage of onLoad(), onSave() and onUpdate() is to store a reference to the current Session for later use.

Note that onUpdate() is not called every time the object's persistent state is updated. It is called only when a transient object is passed to Session.update().

If onSave(), onUpdate() or onDelete() return true, the operation is silently vetoed. If a CallbackException is thrown, the operation is vetoed and the exception is passed back to the application.

Note that onSave() is called after an identifier is assigned to the object, except when native key generation is used.

4.4. Validatable

If the persistent class needs to check invariants before its state is persisted, it may implement the following interface:

public interface Validatable {
        public void validate() throws ValidationFailure;
}

The object should throw a ValidationFailure if an invariant was violated. An instance of Validatable should not change its state from inside validate().

Unlike the callback methods of the Lifecycle interface, validate() might be called at unpredictable times. The application should not rely upon calls to validate() for business functionality.

4.5. XDoclet Example

In the next section we will show how Hibernate mappings may be expressed using a simple, readable XML format. Many Hibernate users prefer to embed mapping information directly in sourcecode using XDoclet @hibernate.tags. We will not cover this approach in this document, since strictly it is considered part of XDoclet. However, we include the following example of the Cat class with XDoclet mappings.

package eg;
import java.util.Set;
import java.util.Date;

/**
 * @hibernate.class
 *  table="CATS"
 */
public class Cat {
    private Long id; // identifier
    private Date birthdate;
    private Cat mate;
    private Set kittens
    private Color color;
    private char sex;
    private float weight;

    /**
     * @hibernate.id
     *  generator-class="native"
     *  column="CAT_ID"
     */
    public Long getId() {
        return id;
    }
    private void setId(Long id) {
        this.id=id;
    }

    /**
     * @hibernate.many-to-one
     *  column="MATE_ID"
     */
    public Cat getMate() {
        return mate;
    }
    void setMate(Cat mate) {
        this.mate = mate;
    }

    /**
     * @hibernate.property
     *  column="BIRTH_DATE"
     */
    public Date getBirthdate() {
        return birthdate;
    }
    void setBirthdate(Date date) {
        birthdate = date;
    }
    /**
     * @hibernate.property
     *  column="WEIGHT"
     */
    public float getWeight() {
        return weight;
    }
    void setWeight(float weight) {
        this.weight = weight;
    }

    /**
     * @hibernate.property
     *  column="COLOR"
     *  not-null="true"
     */
    public Color getColor() {
        return color;
    }
    void setColor(Color color) {
        this.color = color;
    }
    /**
     * @hibernate.set
     *  lazy="true"
     *  order-by="BIRTH_DATE"
     * @hibernate.collection-key
     *  column="PARENT_ID"
     * @hibernate.collection-one-to-many
     */
    public Set getKittens() {
        return kittens;
    }
    void setKittens(Set kittens) {
        this.kittens = kittens;
    }
    // addKitten not needed by Hibernate
    public void addKitten(Cat kitten) {
        kittens.add(kitten);
    }

    /**
     * @hibernate.property
     *  column="SEX"
     *  not-null="true"
     *  update="false"
     */
    public char getSex() {
        return sex;
    }
    void setSex(char sex) {
        this.sex=sex;
    }
}