© Joseph B. Ottinger, Jeff Linwood and Dave Minter 2016

Joseph B. Ottinger, Jeff Linwood and Dave Minter, Beginning Hibernate, 10.1007/978-1-4842-2319-2_4

4. The Persistence Life Cycle

Joseph B. Ottinger, Jeff Linwood2 and Dave Minter3

(1)Youngsville, North Carolina, USA

(2)Austin, Texas, USA

(3)London, UK

In this chapter, we discuss the life cycle of persistent objects in Hibernate. These persistent objects can be POJOs without any special marker interfaces or inheritance related to Hibernate. Part of Hibernate’s popularity comes from its ability to work with a normal object model.

We are also going to cover some of the methods of the Session interface that are used for creating, retrieving, updating, and deleting persistent objects from Hibernate.

Introducing the Life Cycle

After adding Hibernate to your application, you do not need to change your existing Java object model to add persistence marker interfaces or any other type of hint for Hibernate. Instead, Hibernate works with normal Java objects that your application creates with the new operator or that other objects create.

For Hibernate’s purposes, these can be drawn up into two categories: objects for which Hibernate has entity mappings, and objects that are not directly recognized by Hibernate. A correctly mapped entity object will consist of fields and properties that are mapped, and that are themselves either references to correctly mapped entities, references to collections of such entities, or “value” types (primitives, primitive wrappers, strings, or arrays of these).

Given an instance of an object that is mapped to Hibernate, it can be in any one of four different states: transient, persistent, detached, or removed.

Transient objects exist in memory, as illustrated in Figure 4-1. Hibernate does not manage transient objects or persist changes to transient objects.

A321250_4_En_4_Fig1_HTML.jpg
Figure 4-1. Transient objects are independent of Hibernate

To persist the changes to a transient object, you would have to ask the session to save the transient object to the database, at which point Hibernate assigns the object an identifier and marks the object as being in persistent state.

Persistent objects exist in the database, and Hibernate manages the persistence for persistent objects. We show this relationship between the objects and the database in Figure 4-2. If fields or properties change on a persistent object, Hibernate will keep the database representation up to date when the application marks the changes as to be committed.

A321250_4_En_4_Fig2_HTML.jpg
Figure 4-2. Persistent objects are maintained by Hibernate

Detached objects have a representation in the database, but changes to the object will not be reflected in the database, and vice versa. This temporary separation of the object and the database is shown in Figure 4-3. A detached object can be created by closing the session that it was associated with, or by evicting it from the session with a call to the session’s evict() method. One reason you might consider doing this would be to read an object out of the database, modify the properties of the object in memory, and then store the results someplace other than your database. This would be an alternative to doing a deep copy of the object.

A321250_4_En_4_Fig3_HTML.jpg
Figure 4-3. Detached objects exist in the database but are not maintained by Hibernate

In order to persist changes made to a detached object, the application must reattach it to a valid Hibernate session. A detached instance can be associated with a new Hibernate session when your application calls one of the load, refresh, merge, update(), or save() methods on the new session with a reference to the detached object. After the call, the detached object would be a persistent object managed by the new Hibernate session.

Removed objects are objects that are being managed by Hibernate (persistent objects, in other words) that have been passed to the session’s remove() method. When the application marks the changes held in the session as to be committed, the entries in the database that correspond to removed objects are deleted.

Versions prior to Hibernate 3 had support for the Lifecycle and Validatable interfaces. These allowed your objects to listen for save, update, delete, load, and validate events using methods on the object. In Hibernate 3, this function moved into events and interceptors, and the old interfaces were removed. In Hibernate 4, the JPA persistence life cycle is also supported, so events can be embedded into the objects and marked with annotations.

Entities, Classes, and Names

Entities represent Java objects with mappings that permit them to be stored in the database. The mappings indicate how the fields and properties of the object should be stored in the database tables. However, it is possible that you will want objects of a particular type to be represented in two different ways in the database. For instance, you could have one Java class for users, but two different tables in the database that store users. This may not be the best database design, but similar problems are common in legacy systems. Other systems that can’t be easily modified may depend on the existing database design, and Hibernate is powerful enough to cover this scenario. In this case, how does Hibernate choose which to use?

An object representing an entity will be a normal Java class. It will also have an entity name. By default, the name of the entity will be the same as the name of the class type.1 You have the option, however, to change this via the mappings or annotations, and thus distinguish between objects of the same type that are mapped to different tables. There are, therefore, methods in the Session API that require an entity name to be provided to determine the appropriate mapping. If this is omitted, it will either be because no such distinction is needed or because, for convenience, the method assumes the most common case — that the entity name is the same as the class name – and duplicates the function of another, more specific method that permits the entity name to be specified explicitly.

Identifiers

An identifier, or identity column, maps to the concept of a primary key in relational databases. A primary key is a unique set of one or more columns that can be used to specify a particular collection of data.

There are two types of identifiers: natural and artificial.

A natural identifier is something that the application finds meaningful – a user ID, for example, or a Social Security number2 or equivalent.

An artificial identifier is one whose value is arbitrary. Our code so far uses values generated by the database (identity columns) that have no relation whatsoever with the data associated with that identifier. This tends to yield more flexibility with respect to associations and other such interactions, because the artificial identifier can be smaller than a natural identifier in many cases.

Why would artificial identifiers be better than natural identifiers? Well, there are a few possible reasons. One reason artificial identifiers might be better than natural identifiers is that an artificial identifier might be a smaller type (in memory) than a natural identifier.

Consider a user email. In most cases, user email addresses won’t change, and they tend to be unique for a given user; however, the email addresses might be at least ten bytes long (and could be much longer). An integral user ID (a long, or int) might be four or eight bytes long, and no longer.

Another reason is that artificial identifiers won’t change with the data’s natural life cycle. An email address, for example, might change over time; someone might abandon an old email address and prefer a new one. Anything that relied on that email address as a natural identifier would have to be changed synchronously to allow updates.

Yet another reason is that artificial identifiers are simple. Databases (and Hibernate) allow the use of composite identifiers – identifiers built up from more than one property in an object. However, this means that when you refer to a specific object or row in the database, you have to include all columns in that identifier, whether as an embedded object or as a set of individual columns. It’s doable, certainly; some data models require it (for legacy or other business reasons, for example). However, for efficiency’s sake, most would normally prefer artificial keys.

In Hibernate, an object attribute is marked as an identifier with the @Id annotation, as shown in Listing 4-1.

Listing 4-1. A Typical Identifier Field
@Id
public Long id;

In Listing 4-1, you see a Long – a “big integer” for H2 - that’s marked as a presumably artificial identifier. This value will need to be assigned before the object with this attribute can be persisted.

In our example code so far, though, we’ve assigned no identifiers. We’ve used another annotation, @GeneratedValue, which tells Hibernate that it is responsible for assigning and maintaining the identifier. The mechanism through which this happens depends quite a bit on the Hibernate configuration and the database in use.

There are five different generation possibilities: identity, sequence, table, auto, and none. Identity generation relies on a natural table sequencing. This is requested in the @GeneratedValue annotation by using the GenerationType.IDENTITY option, as follows:

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;

The sequence mechanism depends on the database’s ability to create table sequences (which tends to limit it to PostgreSQL, Oracle, and a few others). It corresponds to the GenerationType.SEQUENCE strategy.

The table mechanism uses a table whose purpose is to store blocks of artificial identifiers; you can let Hibernate generate this for you, or you can specify all of the table’s specifics with an additional @TableGenerator annotation. To use artificial key generation via table, use the GenerationType.TABLE strategy.

The fourth artificial key generation strategy is auto, which normally maps to the IDENTITY strategy, but depends on the database in question. (It’s supposed to default to something that’s efficient for the database in question.) To use this, use the GenerationType.AUTO strategy.

The fifth strategy isn’t actually a strategy at all: it relies on manual assignment of an identifier. If Session.persist() is called with an empty identifier, you’ll have an IdentifierGenerationException thrown.

Entities and Associations

Entities can contain references to other entities, either directly as an embedded property or field, or indirectly via a collection of some sort (arrays, sets, lists, etc.). These associations are represented using foreign key relationships in the underlying tables. These foreign keys will rely on the identifiers used by participating tables, which is another reason to prefer small (and artificial) keys.

When only one of the pair of entities contains a reference to the other, the association is unidirectional. If the association is mutual, then it is referred to as bidirectional.

Tip

A common mistake when designing entity models is to try to make all associations bidirectional. Associations that are not a natural part of the object model should not be forced into it. Hibernate Query Language often presents a more natural way to access the same information.

In associations, one (and only one) of the participating classes is referred to as “managing the relationship.” If both ends of the association manage the relationship, then we would encounter a problem when client code called the appropriate set method on both ends of the association. Should two foreign key columns be maintained – one in each direction (risking circular dependencies) – or only one?

Ideally, we would like to dictate that only changes to one end of the relationship will result in any updates to the foreign key; and indeed, Hibernate allows us to do this by marking one end of the association as being managed by the other (marked by the mappedBy attribute of the association annotation).

Caution

mappedBy is purely about how the foreign key relationships between entities are saved. It has nothing to do with saving the entities themselves. Despite this, they are often confused with the entirely orthogonal cascade functionality (described in the “Cascading Operations” section of this chapter).

While Hibernate lets us specify that changes to one association will result in changes to the database, it does not allow us to cause changes to one end of the association to be automatically reflected in the other end in the Java POJOs.

Let’s create an example, in chapter04.broken package, of a Message and an Email association, without an “owning object.” First, the Message class, as shown in Listing 4-2:

Listing 4-2. A Broken Model, Beginning with Message
package chapter04.broken;

import javax.persistence.*;

@Entity
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;


    @Column
    String content;


    @OneToOne
    Email email;


    public Message() {
    }


    public Message(String content) {
        setContent(content);
    }
    // mutators and accessors not included, for brevity
}

Listing 4-3 is the Email class, in the same package:

Listing 4-3. A Broken Model’s Message Class
package chapter04.broken;

import javax.persistence.*;

@Entity
public class Email {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;


    @Column
    String subject;


    @OneToOne
    //(mappedBy = "email")
    Message message;


    public Email() {
    }


    public Email(String subject) {
        setSubject(subject);
    }
    // mutators and accessors not included
}

With these classes, there’s no “owning relation”; the mappedBy attribute in Email is commented out. This means that we need to update both the Email and the Message in order to have our relationship properly modeled in both directions, as the Listing 4-4 test shows:

Listing 4-4. A Common Misconception About Bidirectional Associations
@Test()
public void testBrokenInversionCode() {
    Long emailId;
    Long messageId;
    Email email;
    Message message;


    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        email = new Email("Broken");
        message = new Message("Broken");


        email.setMessage(message);
        // message.setEmail(email);


        session.save(email);
        session.save(message);


        emailId = email.getId();
        messageId = message.getId();


        tx.commit();
    }


    assertNotNull(email.getMessage());
    assertNull(message.getEmail());


    try (Session session = SessionUtil.getSession()) {
        email = session.get(Email.class, emailId);
        System.out.println(email);
        message = session.get(Message.class, messageId);
        System.out.println(message);
    }


    assertNotNull(email.getMessage());
    assertNull(message.getEmail());
}

The final call to message.getEmail() will return null (assuming simple accessors and mutators are used). To get the desired effect, both entities must be updated. If the Email entity owns the association, this merely ensures the proper assignment of a foreign key column value. There is no implicit call of message.setEmail(email). This must be explicitly given, as in Listing 4-5.

Listing 4-5. The Correct Maintenance of a Bidirectional Association
@Test
public void testProperSimpleInversionCode() {
    Long emailId;
    Long messageId;
    Email email;
    Message message;


    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        email = new Email("Proper");
        message = new Message("Proper");


        email.setMessage(message);
        message.setEmail(email);


        session.save(email);
        session.save(message);


        emailId = email.getId();
        messageId = message.getId();


        tx.commit();
    }


    assertNotNull(email.getMessage());
    assertNotNull(message.getEmail());


    try (Session session = SessionUtil.getSession()) {
        email = session.get(Email.class, emailId);
        System.out.println(email);
        message = session.get(Message.class, messageId);
        System.out.println(message);
    }


    assertNotNull(email.getMessage());
    assertNotNull(message.getEmail());
}

It is common for users new to Hibernate to get confused about this point. The reason it occurs is that Hibernate is using the actual current state of the entities. In Listing 4-5, when you set the message in the email, but not the email in the message, Hibernate persists the actual relationships in the object model, instead of trying to infer a relationship, even when that relationship would be expected. The extra relationship would be an unexpected side effect, even if it might be useful in this particular case.

If we include the mapping (the mappedBy attribute), we get a different result. We’re going to modify Message (by moving it to a new package, chapter04.mapped) and Email (by moving it and including the mappedBy attribute, commented out in the prior listing).

The Message code is identical to the “broken” version, except for the package and the entity name (which means Hibernate will use “Message2” as the table name for this type), as shown in Listing 4-6:

Listing 4-6. A Corrected Message Class
package chapter04.mapped;

import javax.persistence.*;

@Entity(name = "Message2")
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;


    @Column
    String content;


    @OneToOne
    Email email;


    public Message() {
    }


    public Message(String content) {
        setContent(content);
    }
}

The Email code , in addition to changing the entity name and package, adds the mappedBy attribute. This actually adds a column to the Message’s database representation, representing the email ID. See Listing 4-7.

Listing 4-7. A Corrected Email Class
package chapter04.mapped;

import javax.persistence.*;

@Entity(name = "Email2")
public class Email {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;
    @Column
    String subject;
    @OneToOne(mappedBy = "email")
    Message message;


    public Email() {
    }


    public Email(String subject) {
        setSubject(subject);
    }
}

With the mapping contained in the Message, there are some unexpected results. Our prior test failed to reestablish some relationships, requiring them to be set in both Email and Message. Here, we have nearly the same construct, but without the same result.

First, let’s see the test code, as shown in Listing 4-8; note that this test is using the chapter04.mapped package, so it’s getting the Email and Message classes we just saw:

Listing 4-8. Misleading Behavior
@Test
public void testImpliedRelationship() {
    Long emailId;
    Long messageId;
    Email email;
    Message message;


    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        email = new Email("Inverse Email");
        message = new Message("Inverse Message");


        // email.setMessage(message);
        message.setEmail(email);


        session.save(email);
        session.save(message);


        emailId = email.getId();
        messageId = message.getId();


        tx.commit();
    }


    assertEquals(email.getSubject(), "Inverse Email");
    assertEquals(message.getContent(), "Inverse Message");
    assertNull(email.getMessage());
    assertNotNull(message.getEmail());


    try (Session session = SessionUtil.getSession()) {
        email = session.get(Email.class, emailId);
        System.out.println(email);
        message = session.get(Message.class, messageId);
        System.out.println(message);
    }


    assertNotNull(email.getMessage());
    assertNotNull(message.getEmail());
}

This test passes, even though we didn’t set the Email’s Message.

That mappingBy attribute is the cause. In the database, the Mapping2 table has a column called “email_id,” which is set to the Email’s unique identifier when we update the Message’s email property. When we close the session and reload, the relationship is set only through that column, which means the relationship is set “correctly” even though we didn’t create the relationship properly when we first created the data.

If we were to manage the relationship in the Email entity (i.e., setting the mappedBy attribute in Message.java instead of Email.java), the situation would be reversed: setting the Message’s email attribute wouldn’t be reflected in the database, but setting the Email’s message attribute would.

Here’s a summary of the points made:

  • You must explicitly manage both ends of an association.

  • Only changes to the owner of an association will be honored in the database.

  • When you load a detached entity from the database, it will reflect the foreign key relationships persisted into the database.

Table 4-1 shows how you can select the side of the relationship that should be made the owner of a bidirectional association. Remember that to make an association the owner, you must mark the other end as being mapped by the other.

Table 4-1. Marking the Owner of an Association

Type of Association

Options

One-to-one

Either end can be made the owner, but one (and only one) of them should be; if you don’t specify this, you will end up with a circular dependency.

One-to-many

The many end must be made the owner of the association.

Many-to-one

This is the same as the one-to-many relationship viewed from the opposite perspective, so the same rule applies: the many end must be made the owner of the association.

Many-to-many

Either end of the association can be made the owner.

If this all seems rather confusing, just remember that association ownership is concerned exclusively with the management of the foreign keys in the database, and things should become clearer as you use Hibernate further. Associations and mappings are discussed in detail in the next few chapters.

Saving Entities

Creating an instance of a class you mapped with a Hibernate mapping does not automatically persist the object to the database. Until you explicitly associate the object with a valid Hibernate session, the object is transient, like any other Java object. In Hibernate, we use one of the save() – or persist(), which is a synonym for save() – methods on the Session interface to store a transient object in the database, as follows:

public Serializable save(Object object) throws HibernateException

public Serializable save(String entityName,Object object) throws HibernateException

Both save() methods take a transient object reference (which must not be null) as an argument. Hibernate expects to find a mapping (either annotations or an XML mapping) for the transient object’s class; Hibernate cannot persist arbitrary unmapped objects. If you have mapped multiple entities to a Java class, you can specify which entity you are saving (Hibernate wouldn't know from just the Java class name) with the entityName argument.

The save() methods all create a new org.hibernate.event.SaveOrUpdateEvent event. We discuss events in more detail in Appendix A, although you do not have to worry about these implementation details to use Hibernate effectively.

At its simplest, we create a new object in Java, set a few of its properties, and then save it through the session. Here’s a simple object, shown in Listing 4-9:

Listing 4-9. A Simple Object for Persistence
package chapter04.model;

import javax.persistence.*;

@Entity
public class SimpleObject {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;
    @Column
    String key;
    @Column
    Long value;


    public SimpleObject() {
    }


    // mutators and accessors not included for brevity
    // equals() and hashCode() will be covered later in this chapter
}

Listing 4-10 shows how this object is saved, as shown in chapter04.general.PersistingEntitiesTest, in the testSaveLoad() method:

Listing 4-10. The testSaveLoad() Method as a Test
try (Session session = SessionUtil.getSession()) {
    Transaction tx = session.beginTransaction();


    obj = new SimpleObject();
    obj.setKey("sl");
    obj.setValue(10L);


    session.save(obj);
    assertNotNull(obj.getId());
    // we should have an id now, set by Session.save()
    id = obj.getId();


    tx.commit();
}

It is not appropriate to save an object that has already been persisted. Doing so will update the object, which will actually end up creating a duplicate with a new identifier. This can be seen in PersistingEntitiesTest’s testSavingEntitiesTwice() method, which looks like that shown in Listing 4-11:

Listing 4-11. Saving the Same Entity Twice. Don’t Do This!
@Test
public void testSavingEntitiesTwice() {
    Long id;
    SimpleObject obj;


    try(Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        obj = new SimpleObject();

        obj.setKey("osas");
        obj.setValue(10L);


        session.save(obj);
        assertNotNull(obj.getId());


        id = obj.getId();

        tx.commit();
    }


    try(Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        obj.setValue(12L);

        session.save(obj);

        tx.commit();
    }


    // note that save() creates a new row in the database!
    // this is wrong behavior. Don't do this!
    assertNotEquals(id, obj.getId());
}

When this test is run, the two identifiers would be expected to be equal but they’re not; examining the values yielded equivalent objects, except for the IDs, which were sequentially assigned as the SimpleObject @Id generation specified.

You can, however, update an object with Session.saveOrUpdate(). Listing 4-12 shows another method, testSaveOrUpdateEntity():

Listing 4-12. Updating an Object
@Test
public void testSaveOrUpdateEntity() {
    Long id;
    SimpleObject obj;


    try(Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        obj = new SimpleObject();

        obj.setKey("osas2");
        obj.setValue(14L);


        session.save(obj);
        assertNotNull(obj.getId());


        id = obj.getId();

        tx.commit();
    }


    try(Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        obj.setValue(12L);

        session.saveOrUpdate(obj);

        tx.commit();
    }


    // saveOrUpdate() will update a row in the database
    // if one matches. This is what one usually expects.
    assertEquals(id, obj.getId());
}

It wouldn’t be advisable to try to match this code construction in production code. The object goes from transient state (when it’s created) to persistent state (when it’s first saved), then back to transient state (when the session is closed). We then update the object while it’s in transient state, and move it back to persistent state when we call Session.saveOrUpdate().

Ideally, what you would do is load the object from the session in the first place (as we’ve done in most of our other examples where we show updates); this means that the updates take place on a persistent object, and we don’t actually have to call Session.save() or Session.saveOrUpdate() at all.3

Once an object is in a persistent state, Hibernate manages updates to the database itself as you change the fields and properties of the object.

Object Equality and Identity

When we discuss persistent objects in Hibernate, we also need to consider the role that object equality and identity play with Hibernate. When we have a persistent object in Hibernate, that object represents both an instance of a class in a particular Java virtual machine (JVM) and a row (or rows) in a database table (or tables).

Requesting a persistent object again from the same Hibernate session returns the same Java instance of a class, which means that you can compare the objects using the standard Java == equality syntax. If, however, you request a persistent object from more than one Hibernate session, Hibernate will provide distinct instances from each session, and the == operator will return false if you compare these object instances.

Taking this into account, if you are comparing objects in two different sessions, you will need to implement the equals() method on your Java persistence objects, which you should probably do as a regular occurrence anyway. (Just don’t forget to implement hashCode() along with it.)

Implementing equals() can be interesting. Hibernate wraps the actual object in a proxy (for various performance-enhancing reasons, like loading data on demand), so you need to factor in a class hierarchy for equivalency; it’s also typically more efficient to use accessors in your equals() and hashCode() methods, as opposed to the actual fields.

Listing 4-13 is an implementation of equals() and hashCode() for the SimpleObject entity we’ve been using, generated by IntelliJ IDEA4 and modified to use accessors:

Listing 4-13. Sample equals() and hashCode() Implementations
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof SimpleObject)) return false;


        SimpleObject that = (SimpleObject) o;

        // we prefer the method versions of accessors,
        // because of Hibernate's proxies.
        if (getId() != null ?
            !getId().equals(that.getId()) : that.getId() != null)
            return false;
        if (getKey() != null ?
            !getKey().equals(that.getKey()) : that.getKey() != null)
            return false;
        if (getValue() != null ?
            !getValue().equals(that.getValue()) : that.getValue() != null)
            return false;


        return true;
    }


    @Override
    public int hashCode() {
        int result = getId() != null ? getId().hashCode() : 0;
        result = 31 * result + (getKey() != null ? getKey().hashCode() : 0);
        result = 31 * result + (getValue() != null?getValue().hashCode() : 0);
        return result;
    }

The PersistentEntitiesTest’s testSaveLoad() method shows off the various possibilities and conditions for equality, as shown in Listing 4-14:

Listing 4-14. Testing Various Combinations of Equality
@Test
public void testSaveLoad() {
    Long id = null;
    SimpleObject obj;


    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        obj = new SimpleObject();
        obj.setKey("sl");
        obj.setValue(10L);


        session.save(obj);
        assertNotNull(obj.getId());
        // we should have an id now, set by Session.save()
        id = obj.getId();


        tx.commit();
    }


    try (Session session = SessionUtil.getSession()) {
        // we're loading the object by id
        SimpleObject o2 = session.load(SimpleObject.class, id);
        assertEquals(o2.getKey(), "sl");
        assertNotNull(o2.getValue());
        assertEquals(o2.getValue().longValue(), 10L);


        SimpleObject o3 = session.load(SimpleObject.class, id);

        // since o3 and o2 were loaded in the same session, they're not only
        // equivalent - as shown by equals() - but equal, as shown by ==.
        // since obj was NOT loaded in this session, it's equivalent but
        // not ==.
        assertEquals(o2, o3);
        assertEquals(obj, o2);


        assertTrue(o2 == o3);
        assertFalse(o2 == obj);
    }
}

Note that in this code, o2 and o3 are equal (they hold the same reference), while o2 and obj are equivalent (the references are different but hold equivalent data). Again, you shouldn’t rely on this in production code; object equivalence should always be tested with equals().

Loading Entities

Hibernate’s Session interface provides several load() methods for loading entities from your database. Each load() method requires the object’s primary key as an identifier.5

In addition to the ID, Hibernate also needs to know which class or entity name to use to find the object with that ID. If you don’t pass in the class type to load(), you’ll also need to cast the result to the correct type. The basic load() methods are as follows:

public <T> T load(Class<T> theClass, Serializable id)
public Object load(String entityName, Serializable id)
public void load(Object object, Serializable id)

The last load() method takes an object as an argument. The object should be of the same class as the object you would like loaded, and it should be empty. Hibernate will populate that object with the object you requested. While this is similar to other library calls in Java – namely, java.util.List.toArray() – this syntax can be without much of an actual benefit.

The other load() methods take a lock mode as an argument. The lock mode specifies whether Hibernate should look into the cache for the object and which database lock level Hibernate should use for the row (or rows) of data that represent this object. The Hibernate developers claim that Hibernate will usually pick the correct lock mode for you, although we have seen situations in which it is important to manually choose the correct lock. In addition, your database may choose its own locking strategy – for instance, locking down an entire table rather than multiple rows within a table. In order of least restrictive to most restrictive, the various lock modes you can use are the following:

  • NONE: Uses no row-level locking, and uses a cached object if available; this is the Hibernate default.

  • READ: Prevents other SELECT queries from reading data that is in the middle of a transaction (and thus possibly invalid) until it is committed.

  • UPGRADE: Uses the SELECT FOR UPDATE SQL syntax to lock the data until the transaction is finished.

  • UPGRADE_NOWAIT: Uses the NOWAIT keyword (for Oracle), which returns an error immediately if there is another thread using that row; otherwise this is similar to UPGRADE.

  • UPGRADE_SKIPLOCKED: Skips locks for rows already locked by other updates, but otherwise this is similar to UPGRADE.

  • OPTIMISTIC: This mode assumes that updates will not experience contention. The entity’s contents will be verified near the transaction’s end.

  • OPTIMISTIC_FORCE_INCREMENT: This is like OPTIMISTIC, except it forces the version of the object to be incremented near the transaction’s end.

  • PESSIMISTIC_READ and PESSIMISTIC_WRITE: Both of these obtain a lock immediately on row access.

  • PESSIMISTIC_FORCE_INCREMENT: This obtains the lock immediately on row access, and also immediately updates the entity version.

All of these lock modes are static fields on the org.hibernate.LockMode class. (We discuss locking and deadlocks with respect to transactions in more detail in Chapter 8.) The load() methods that use lock modes are as follows:

public <T> T load(Class<T> theClass, Serializable id, LockMode lockMode)
public Object load(String entityName, Serializable id, LockMode lockMode)

You should not use a load() method unless you are sure that the object exists. If you are not certain, then use one of the get() methods. The load() methods will throw an exception if the unique ID is not found in the database, whereas the get() methods will merely return a null reference.

Much like load(), the get() methods take an identifier and either an entity name or a class. There are also two get() methods that take a lock mode as an argument. The get() methods are as follows:

public <T> T get(Class<T> clazz, Serializable id)
public Object get(String entityName, Serializable id)
public <T> T get(Class<T> clazz, Serializable id, LockMode lockMode)
public Object get(String entityName, Serializable id, LockMode lockMode)

If you need to determine the entity name for a given object (by default, this is the same as the class name), you can call the getEntityName() method on the Session interface, as follows:

public String getEntityName(Object object)

Using the get() and load() methods is straightforward. For the following code sample, we would be getting the Supplier ID from another Java class. For instance, through a web application, someone may select a Supplier details page for the supplier with the ID 1. If we are not sure that the supplier exists, we use the get() method, with which we could check for null, as follows:

// get an id from some other Java class, for instance, through a web application
Supplier supplier = session.get(Supplier.class,id);
if (supplier == null) {
    System.out.println("Supplier not found for id " + id);
    return;
}

We can also retrieve the entity name from Hibernate and use it with either the get() or load() method. The load() method will throw an exception if an object with that ID cannot be found.

String entityName = session.getEntityName(supplier);
Supplier secondarySupplier = (Supplier) session.load(entityName,id);

It’s also worth pointing out that you can query for an entity, which allows you to look for objects with a specific identifier, as well as sets of objects that match other criteria. There’s also a Criteria API that allows you to use a declarative mechanism to build queries. These topics will be covered in later chapters.

Merging Entities

Merging is performed when you desire to have a detached entity changed to persistent state again, with the detached entity’s changes migrated to (or overriding) the database. The method signatures for the merge operations are the following:

Object merge(Object object)
Object merge(String entityName, Object object)

Merging is the inverse of refresh(), which overrides the detached entity’s values with the values from the database. Listing 4-15 is some example code, from chapter04.general.MergeRefreshTest:

Listing 4-15. Demonstrating the Use and Function of session.merge()
@Test
public void testMerge() {
    Long id;
    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        SimpleObject simpleObject = new SimpleObject();

        simpleObject.setKey("testMerge");
        simpleObject.setValue(1L);


        session.save(simpleObject);

        id = simpleObject.getId();

        tx.commit();
    }


    SimpleObject so = validateSimpleObject(id, 1L);

    so.setValue(2L);

    try (Session session = SessionUtil.getSession()) {
        // merge is potentially an update, so we need a TX
        Transaction tx = session.beginTransaction();


        session.merge(so);

        tx.commit();
    }


    validateSimpleObject(id, 2L);
}


private SimpleObject validateSimpleObject(Long id, Long value) {
    SimpleObject so = null;
    try (Session session = SessionUtil.getSession()) {
        so = session.load(SimpleObject.class, id);


        assertEquals(so.getKey(), "testMerge");
        assertEquals(so.getValue(), value);
    }


    return so;
}

This code creates an entity (a SimpleObject) and then saves it; it then verifies the object’s values (in validateSimpleObject()), which itself returns a detached entity. We update the detached object and merge() it – which should update the value in the database, which we verify.

Refreshing Entities

Hibernate provides a mechanism to refresh persistent objects from their database representation. Use one of the refresh() methods on the Session interface to refresh an instance of a persistent object, as follows:

public void refresh(Object object)
public void refresh(Object object, LockMode lockMode)

These methods will reload the properties of the object from the database, overwriting them; thus, as stated, refresh() is the inverse of merge(). Merging overrides the database with the values held by the previously transient object, and refresh() overrides the values in the transient object with the values in the database.

Hibernate usually does a very good job of taking care of this for you, so you do not have to use the refresh() method very often. There are instances where the Java object representation will be out of sync with the database representation of an object, however. For example, if you use SQL to update the database, Hibernate will not be aware that the representation changed. You do not need to use this method regularly, though. Similar to the load() method, the refresh() method can take a lock mode as an argument; see the discussion of lock modes in the previous “Loading Entities” section.

Let’s take a look at code in Listing 4-16 that uses refresh() – basically an inverse of the code we saw that demonstrated merge() – and contained in the same test class.

Listing 4-16. Demonstrating the Use and Function of session.refresh()
@Test
public void testRefresh() {
    Long id;
    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        SimpleObject simpleObject = new SimpleObject();

        simpleObject.setKey("testMerge");
        simpleObject.setValue(1L);


        session.save(simpleObject);

        id = simpleObject.getId();

        tx.commit();
    }


    SimpleObject so = validateSimpleObject(id, 1L);

    so.setValue(2L);

    try (Session session = SessionUtil.getSession()) {
        // note that refresh is a read,
        // so no TX is necessary unless an update occurs later
        session.refresh(so);
    }


    validateSimpleObject(id, 1L);
}

This code is the same as the merge() test, with two changes: the first is that it calls refresh() rather than merge() (surprise!); and the other is that it expects the object’s data to revert to the original state from the database, verifying that refresh() overrides the transient object’s data.

Updating Entities

Hibernate automatically persists changes made to persistent objects into the database.6 If a property changes on a persistent object, the associated Hibernate session will queue the change for persistence to the database using SQL. From a developer’s perspective, you do not have to do any work to store these changes, unless you would like to force Hibernate to commit all of its changes in the queue. You can also determine whether the session is dirty and changes need to be committed. When you commit a Hibernate transaction, Hibernate will take care of these details for you.

The flush() method forces Hibernate to flush the session, as follows:

public void flush() throws HibernateException

You can determine if the session is dirty with the isDirty() method, as follows:

public boolean isDirty() throws HibernateException

You can also instruct Hibernate to use a flushing mode for the session with the setHibernateFlushMode() 7 method. The getHibernateFlushMode() method returns the flush mode for the current session, as follows:

public void setHibernateFlushMode(FlushMode flushMode)
public FlushMode getHibernateFlushMode()

The possible flush modes are the following:

  • ALWAYS: Every query flushes the session before the query is executed. This is going to be very slow.

  • AUTO: Hibernate manages the query flushing to guarantee that the data returned by a query is up to date.

  • COMMIT: Hibernate flushes the session on transaction commits.

  • MANUAL: Your application needs to manage the session flushing with the flush() method. Hibernate never flushes the session itself.

By default, Hibernate uses the AUTO flush mode. Generally, you should use transaction boundaries to ensure that appropriate flushing is taking place, rather than trying to “manually” flush at the appropriate times.

Deleting Entities

In order to allow convenient removal of entities from the database, the Session interface provides a delete() method, as follows:

public void delete(Object object)

This method takes a persistent object as an argument. The argument can also be a transient object with the identifier set to the ID of the object that needs to be erased.

In the simplest form, in which you are simply deleting an object with no associations to other objects, this is straightforward; but many objects do have associations with other objects. To allow for this, Hibernate can be configured to allow deletes to cascade from one object to its associated objects.

For instance, consider the situation in which you have a parent with a collection of child objects, and you would like to delete them all. The easiest way to handle this is to use the cascade attribute on the collection’s element in the Hibernate mapping. If you set the cascade attribute to delete or all, the delete will be cascaded to all of the associated objects. Hibernate will take care of deleting these for you: deleting the parent erases the associated objects.

Hibernate also supports bulk deletes, where your application executes a DELETE HQL statement against the database. These are very useful for deleting more than one object at a time because each object does not need to be loaded into memory just to be deleted, as shown in Listing 4-17.

Listing 4-17. A Bulk Delete Using a Hibernate Query
session.createQuery("delete from User").executeUpdate();

Network traffic is greatly reduced, as are the memory requirements compared to those for individually issuing a delete() call against each entity identifier.

Caution

Bulk deletes do not cause cascade operations to be carried out. If cascade behavior is needed, you will need to carry out the appropriate deletions yourself, or use the session’s delete() method.

Cascading Operations

When you perform one of the operations described in this chapter on an entity, the operations will not be performed on the associated entities unless you explicitly tell Hibernate to perform them. When operations affect associated entities, they’re referred to as “cascading” operations, because actions flow from one object to another.

For example, the code in Listing 4-18 will fail when we try to commit the transaction, because the Message entity that is associated with the Email entity has not been persisted into the database, so the Email entity cannot be accurately represented (with its foreign key onto the appropriate message row) in its table.

Listing 4-18. A Failed save() due to Cascading
try(Session session = SessionUtil.getSession()) {
    Transaction tx=session.beginTransaction();


    Email email = new Email("Email title");
    Message message = new Message("Message content");
    email.setMessage(message);
    message.setEmail(email);


    session.save(email);

    tx.commit();
}

Ideally, we would like the save operation to be propagated from the Email entity to its associated Message object. We do this by setting the cascade operations for the properties and fields of the entity (or assigning an appropriate default value for the entity as a whole). So, the code in Listing 4-18 will perform correctly if at least the PERSIST cascade operation is set for the Email entity’s message property. The cascade types supported by the Java Persistence Architecture are as follows:

  • PERSIST

  • MERGE

  • REFRESH

  • REMOVE

  • DETACH

  • ALL

It’s worth pointing out that Hibernate has its own configuration options for cascading, which represent a superset of these; however, we’re largely following the Java Persistence Architecture specification for modeling, as this will typically be more common by far than the Hibernate-specific modeling.8

  • CascadeType.PERSIST means that save() or persist() operations cascade to related entities; for our Email and Message example, if Email’s @OneToOne includes PERSIST, saving the Email would save the Message as well.

  • CascadeType.MERGE means that related entities are merged into managed state when the owning entity is merged.

  • CascadeType.REFRESH does the same thing for the refresh() operation.

  • CascadeType.REMOVE removes all related entities association with this setting when the owning entity is deleted.

  • CascadeType.DETACH detaches all related entities if a manual detach were to occur.

  • CascadeType.ALL is shorthand for all of the cascade operations.

The cascade configuration option accepts an array of CascadeTypes; thus, to include only refreshes and merges in the cascade operation for a one-to-one relationship, you might see the following:

@OneToOne(cascade={CascadeType.REFRESH, CascadeType.MERGE})
EntityType otherSide;

There’s one more cascading operation that’s not part of the normal set, called orphan removal, which removes an owned object from the database when it’s removed from its owning relationship.

Let’s suppose we have a Library entity, which contains a list of Book entities. Listing 4-19 is our entity declarations, omitting constructors, accessors, and mutators:

Listing 4-19. A One-to-Many Relationship for an Orphan Object Demonstration
package chapter04.orphan;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;


@Entity
public class Library {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    @Column
    String name;
    @OneToMany(orphanRemoval = true, mappedBy = "library")
    List<Book> books = new ArrayList<>();
}


package chapter04.orphan;

import javax.persistence.*;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    @Column
    String title;
    @ManyToOne
    Library library;
}

Note the use of orphanRemoval in the @OneToMany annotation. Now let’s take a look at some test code, which will be fairly verbose since we need to validate our initial dataset, change it, and then revalidate; see Listing 4-20:

Listing 4-20. A Test Showing Orphan Removal
package chapter04.orphan;

import com.autumncode.hibernate.util.SessionUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
import org.testng.annotations.Test;


import java.util.List;

import static org.testng.Assert.assertEquals;

public class OrphanRemovalTest {
    @Test
    public void orphanRemovalTest() {
        Long id = createLibrary();


        try(Session session = SessionUtil.getSession()) {
            Transaction tx = session.beginTransaction();


            Library library = session.load(Library.class, id);
            assertEquals(library.getBooks().size(), 3);


            library.getBooks().remove(0);
            assertEquals(library.getBooks().size(), 2);


            tx.commit();
        }


        try(Session session = SessionUtil.getSession()) {
            Transaction tx = session.beginTransaction();


            Library l2 = session.load(Library.class, id);
            assertEquals(l2.getBooks().size(), 2);


            Query<Book> query = session.createQuery("from Book b", Book.class);
            List<Book> books = query.list();
            assertEquals(books.size(), 2);


            tx.commit();
        }
    }


    private Long createLibrary() {
        Library library=null;
        try(Session session = SessionUtil.getSession()) {
            Transaction tx = session.beginTransaction();


            library = new Library();
            library.setName("orphanLib");
            session.save(library);


            Book book = new Book();
            book.setLibrary(library);
            book.setTitle("book 1");
            session.save(book);
            library.getBooks().add(book);


            book = new Book();
            book.setLibrary(library);
            book.setTitle("book 2");
            session.save(book);
            library.getBooks().add(book);


            book = new Book();
            book.setLibrary(library);
            book.setTitle("book 3");
            session.save(book);
            library.getBooks().add(book);


            tx.commit();
        }


        return library.getId();
    }
}

What this does is not complicated: it builds a library with three books associated with it. Then it loads the library from the database, validates that it looks like it’s supposed to (“a library with three books”), and removes one from the library. It does not delete the Book entity being removed; it only removes it from the Library’s set of books, which makes it an orphan.

After committing the Library object’s new state – via tx.commit() – we reload the Library from the database and validate that it now has only two books. The book we removed is gone from the library.

That doesn’t mean it’s actually been removed, though, so we then query the database for all Book entities to see if we have two or three. We should have only two, and so it is. We removed the orphan object when updating the library.

Lazy Loading, Proxies, and Collection Wrappers

Consider the quintessential Internet web application: the online store. The store maintains a catalog of products. At the crudest level, this can be modeled as a catalog entity managing a series of product entities. In a large store, there may be tens of thousands of products grouped into various overlapping categories.

When a customer visits the store, the catalog must be loaded from the database. We probably don’t want the implementation to load every single one of the entities representing the tens of thousands of products to be loaded into memory. For a sufficiently large retailer, this might not even be possible, given the amount of physical memory available on the machine. Even if this were possible, it would probably cripple the performance of the site.

Instead, we want only the catalog to load, possibly with the categories as well. Only when the user drills down into the categories should a subset of the products in that category be loaded from the database.

To manage this problem, Hibernate provides a facility called lazy loading. When enabled (this is the default using XML mappings, but not when using annotations), an entity’s associated entities will be loaded only when they are directly requested, which can provide quite measurable performance benefits, as can be imagined. For example, the following code loads only a single entity from the database:

Email email = session.get(Email.class,new Integer(42));

However, if an association of the class is accessed, and lazy loading is in effect, the association is pulled from the database only as needed. For instance, in the following snippet, the associated Message object will be loaded since it is explicitly referenced.

// surely this email is about the meaning of life, the universe, and everything
Email email = session.get(Email.class,new Integer(42));
String text = email.getMessage().getContent();

The simplest way that Hibernate can force this behavior upon your entities is by providing a proxy implementation of them.9 Hibernate intercepts calls to the entity by substituting a proxy for it derived from the entity’s class. Where the requested information is missing, it will be loaded from the database before control is given to the parent entity’s implementation. Where the association is represented as a collection class, a wrapper (essentially a proxy for the collection, rather than for the entities that it contains) is created and substituted for the original collection.

Hibernate can only access the database via a session. If an entity is detached from the session when we try to access an association (via a proxy or collection wrapper) that has not yet been loaded, Hibernate throws a LazyInitializationException. The cure is to ensure either that the entity is made persistent again by attaching it to a session or that all of the fields that will be required are accessed before the entity is detached from the session.

If you need to determine whether a proxy, a persistence collection, or an attribute has been lazy loaded, you can call the isInitialized(Object proxy) and isPropertyInitialized(Object proxy, String propertyName) methods on the org.hibernate.Hibernate class. You can also force a proxy or collection to become fully populated by calling the initialize(Object proxy) method on the org.hibernate.Hibernate class. If you initialize a collection using this method, you will also need to initialize each object contained in the collection, as only the collection is guaranteed to be initialized.

Querying Objects

Hibernate provides several different ways to query for objects stored in the database. You can obviously use the identifier of an object to load it from the database if you know the identifier already. The Criteria Query API is a Java API for constructing a query as an object. HQL is an object-oriented query language, similar to SQL, which you may use to retrieve objects that match the query. We discuss these further in Chapters 9 and 10. Hibernate provides a way to execute SQL directly against the database to retrieve objects, should you have legacy applications that use SQL or if you need to use SQL features that are not supported through HQL and the Criteria Query API.

Summary

Hibernate provides a simple API for creating, retrieving, updating, and deleting objects from a relational database through the Session interface. Understanding the differences between transient, persistent, and detached objects in Hibernate will allow you to understand how changes to the objects update the database tables.

We have touched upon the need to create mappings to correlate the database tables with the fields and properties of the Java objects that you want to persist. The next chapter covers these in detail, and it discusses why they are required and what they can contain.

Footnotes

1 As we saw in Chapter 3, HQL uses the entity name, not the class name; but because we didn’t specify any custom entity names, the class name and the entity name were the same.

2 The U.S. Social Security Administration says that they have enough Social Security numbers to assign unique identification for “several generations.” (See http://www.ssa.gov/history/hfaq.html , Q20.) That may be good enough for a natural identifier, although privacy advocates would rightfully complain; also, note that “several generations” might not be enough. Programmers were absolutely sure that nobody would still have data with two-digit years in them … until Y2K, which took a lot of man-hours to fix.

3 We saw this in Chapter 3: chapter03.hibernate.RankingTest’s changeRanking() method does an in-place update of a persistent object.

4 IDEA is an IDE for Java; it has a free community edition and a commercial “ultimate” edition. It can be found at http://​jetbrains.​com/​idea.

5 As usual, there’s more to this than we’re discussing here. We’ll add more methods to this list as we keep going through Hibernate’s capabilities. We’re keeping the list small for simplicity’s sake.

6 We’ve mentioned this a few different times now, along with test code.

7 This method’s name has changed from earlier versions of Hibernate, to avoid the definition and meaning from the Java Persistence Architecture standard.

8 That’s the thing about standards: they’re standard.

9 This is a deeper explanation of some behavior we saw in our sample code for equals() and hashCode().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset