© 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_6

6. Mapping with Annotations

Joseph B. Ottinger, Jeff Linwood2 and Dave Minter3

(1)Youngsville, North Carolina, USA

(2)Austin, Texas, USA

(3)London, UK

In Chapter 5, we discussed the need to create mappings between the database model and the object model. Mappings can be created in two different ways: via inline annotations (as we’ve done through the book so far), or as separate XML files in one of two primary formats.

The XML-based mapping is rarely used outside of situations where mapping an object model to a preexisting schema is required; even then, adept use of the annotations can match the XML configuration’s features.

Creating Hibernate Mappings with Annotations

Prior to the inline annotations, the only way to create mappings was through XML files – although tools from Hibernate and third-party projects1 allowed part or all of these to be generated from Java source code. Although using annotations is the newest way to define mappings, it is not automatically the best way to do so. We will briefly discuss the drawbacks and benefits of annotations before discussing when and how to apply them.

The Cons of Annotations

If you are upgrading from an earlier Hibernate environment, you may already have XML-based mapping files to support your code base. All else being equal, you will not want to reexpress these mappings using annotations just for the sake of it.

If you are migrating from a legacy environment, you may not want to alter the preexisting POJO source code, lest you contaminate known good code with possible bugs.2 Annotations are compiled into the class files, after all, and therefore might be considered to be changes to source or delivered artifacts.

If you do not have the source code to your POJOs (because it was generated by an automated tool or something similar), you may prefer the use of external XML-based mappings to the decompilation of class files to obtain Java source code for alteration.

Maintaining the mapping information as external XML files allows that mapping information to be modified to reflect business changes or schema alterations without forcing you to rebuild the application as a whole. However, building an application when you have a build system in place (Maven or Gradle, along with any continuous integration tools you might have) is usually pretty easy, so this isn’t much of a convincing argument either way.

The Pros of Annotations

Having considered the drawbacks, there are some powerful benefits to using annotations.

First, and perhaps most persuasively, we find annotations-based mappings to be far more intuitive than their XML-based alternatives, as they are immediately in the source code along with the properties with which they are associated. Most coders tend to prefer the annotations because fewer files have to be kept synchronized with each other.

Partly as a result of this, annotations are less verbose than their XML equivalents, as evidenced by the contrast between Listings 6-1 and 6-2.

Listing 6-1. A Minimal Class Mapped Using Annotations
import javax.persistence.* ;

@Entity
public class Sample {
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer id;
    public String name;
}
Listing 6-2. A Minimal Class Mapped Using XML
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE
   hibernate-mapping
   PUBLIC
   "-//Hibernate/Hibernate Mapping DTD//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">


<hibernate-mapping default-access="field">
   <class name="Sample">
      <id type="int" column="id">
         <generator class="native"/>
      </id>
      <property name="name" type="string"/>
   </class>
</hibernate-mapping>

Some of the latter’s verbosity is in the nature of XML itself (the tag names and the boilerplate document-type declaration), and some of it is due to the closer integration of annotations with the source code. Here, for example, the XML file must explicitly declare that field access is used in place of property access (i.e., the fields are accessed directly rather than through their get/set methods); but the annotation infers this from the fact that it has been applied to the id field rather than the getId() method.

Hibernate uses and supports the JPA 2 persistence annotations. If you elect not to use Hibernate-specific features in your code and annotations, you will have the freedom to deploy your entities to environments using other ORM tools that support JPA 2.

Finally – and perhaps a minor point – because the annotations are compiled directly into the appropriate class files, there is less risk that a missing or stale mapping file will cause problems at deployment (this point will perhaps prove most persuasive to those who already have some experience with this hazard of the XML technique).

Choosing Which to Use

In general, prefer annotations; the annotations themselves are portable across JPA implementations, and they’re well known. Tools can create the annotated source code directly from a database, so synchronization is less of an issue than it could be, even with a preexisting schema.

The XML mapping can be done either in Hibernate’s proprietary format or JPA’s standard XML configuration, which is similar but not identical; if you somehow find XML to be a preferable configuration format, you’re probably better off using the XML format from the industry-standard JPA configuration.3

JPA 2 Persistence Annotations

When you develop using annotations, you start with a Java class and then annotate the source code listing with metadata notations. Hibernate uses reflection at runtime to read the annotations and apply the mapping information. If you want to use the Hibernate tools to generate your database schema, you must first compile your entity classes containing their annotations. In this section, we are going to introduce the significant core of the JPA 2 annotations alongside a simple set of classes to illustrate how they are applied.

The most common annotations, just for reference’s sake, are @Entity, @Id, and @Column; other common ones we’ll encounter often are @GenerationStrategy (associated with @Id), and the association-related annotations like @ManyToOne, @OneToMany, and @ManyToMany.

The set of example classes represents a publisher’s catalog of books. You start with a single class, Book, which has no annotations or mapping information. For purposes of this example, you do not have an existing database schema to work with, so you need to define your relational database schema as you go.

At the beginning of the example, the Book class is very simple. It has two fields, title and pages; and an identifier, id, which is an integer. The title is a String object, and pages is an integer. As we go through this example, we will add annotations, fields, and methods to the Book class. The complete source code listing for the Book and Author classes is given at the end of this chapter; the source files for the rest are available in the source code download for this chapter on the Apress website ( www.apress.com ).

Listing 6-3 gives the source code of the Book class, in its unannotated form, as a starting point for the example.

Listing 6-3. The Book Class, Unannotated
ppackage chapter06.primarykey.before;

public class Book {
    String title;
    int pages;
    int id;


    public String getTitle() {
        return title;
    }


    public void setTitle(String title) {
        this.title = title;
    }


    public int getPages() {
        return pages;
    }


    public void setPages(int pages) {
        this.pages = pages;
    }


    public int getId() {
        return id;
    }


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

As you can see, this is a POJO. We are going to annotate this class as we go along, explaining the concepts behind annotation. In the end, we’ll move it to a different package so that we’ll have a good before-and-after picture of what the entity should be.

Entity Beans with @Entity

The first step is to annotate the Book class as a JPA 2 entity bean. We add the @Entity annotation to the Book class, as follows:

package chapter06.primarykey.after;

import javax.persistence.*;

@Entity
public class Book
    public Book() {
    }

The JPA 2 standard annotations are contained in the javax.persistence package, so we import the appropriate annotations. Some IDEs will use specific imports, as opposed to “star imports,” in situations where few classes in a given package are imported; typically, an entity will use quite a few annotations from this package so chances are it’ll end up with star imports in any event.

The @Entity annotation marks this class as an entity bean, so it must have a no-argument constructor that is visible with at least protected scope.4 Hibernate supports package scope as the minimum, but you lose portability to other JPA implementations if you take advantage of this. Other JPA 2 rules for an entity bean class are (a) that the class must not be final, and (b) that the entity bean class must be concrete. Many of the rules for JPA 2 entity bean classes and Hibernate’s persistent objects are the same – partly because the Hibernate team had much input into the JPA 2 design process, and partly because there are only so many ways to design a relatively unobtrusive object-relational persistence solution.

So far, we’ve added the Entity annotation, a constructor, and an import statement. The rest of the POJO has been left alone.

Primary Keys with @Id and @GeneratedValue

Each entity bean has to have a primary key, which you annotate on the class with the @Id annotation . Typically, the primary key will be a single field, though it can also be a composite of multiple fields.

The placement of the @Id annotation determines the default access strategy that Hibernate will use for the mapping. If the annotation is applied to a field as shown in Listing 6-4, then field access will be used.

Listing 6-4. A Class with Field Access
import javax.persistence.*;

@Entity
public class Sample {
   @Id
   int id;


   public int getId() {
      return this.id;
   }


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

If, instead, the annotation is applied to the accessor for the field, as shown in Listing 6-5, then property access will be used. Property accessmeans that Hibernate will call the mutator instead of actually setting the field directly; this also means the mutator can alter the value as it’s being set, or change other states available in the object. Which one you choose depends on your preference and need; usually field access is enough.5

Listing 6-5. The Same Class with Property Access
import javax.persistence.*;

@Entity
public class Sample {
   int id;


   @Id
   public int getId() {
      return this.id;
   }


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

Here you can see one of the strengths of the annotations approach. Because the annotations are placed inline with the source code, information can be extracted from the context of the mapping in the code, allowing many mapping decisions to be inferred rather than stated explicitly – which helps to further reduce the verbosity of the annotations.

By default, the @Id annotation will not create a primary key generation strategy,6 which means that you, as the code’s author, need to determine what valid primary keys are. You can have Hibernate determine primary keys for you through the use of the @GeneratedValue annotation . This takes a pair of attributes: strategy and generator.

The strategy attribute must be a value from the javax.persistence.GenerationType enumeration. If you do not specify a generator type, the default is AUTO. There are four different types of primary key generators on GenerationType, as follows:

  • AUTO: Hibernate decides which generator type to use, based on the database’s support for primary key generation.

  • IDENTITY: The database is responsible for determining and assigning the next primary key.

  • SEQUENCE: Some databases support a SEQUENCE column type. See the “Generating Primary Key Values with @SequenceGenerator” section later in the chapter.

  • TABLE: This type keeps a separate table with the primary key values. See the “Generating Primary Key Values with @TableGenerator” section later in the chapter.

The generator attribute allows the use of a custom generation mechanism. Hibernate provides named generators for each of the four strategies in addition to others, such as “hilo,” “uuid,” and “guid.” If you need to use Hibernate-specific primary key generators, you risk forfeiting portability of your application to other JPA 2 environments; that said, the Hibernate generators provide more flexibility and control.

For the Book class, we are going to use the AUTO key generation strategy. Letting Hibernate determine which generator type to use makes your code portable between different databases.

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
int id;

Generating Primary Key Values with @SequenceGenerator

As noted in the section on the @Id tag, we can declare the primary key property as being generated by a database sequence. A sequence is a database object that can be used as a source of primary key values. It is similar to the use of an identity column type, except that a sequence is independent of any particular table and can therefore be used by multiple tables.

To declare the specific sequence object to use and its properties, you must include the @SequenceGenerator annotation on the annotated field. Here’s an example:

@Id
@SequenceGenerator(name="seq1",sequenceName="HIB_SEQ")
@GeneratedValue(strategy=SEQUENCE,generator="seq1")
int id;

Here, a sequence-generation annotation named seq1has been declared. This refers to the database sequence object called HIB_SEQ. The name seq1 is then referenced as the generator attribute of the @GeneratedValue annotation.

Only the sequence generator name is mandatory; the other attributes will take sensible default values, but you should provide an explicit value for the sequenceName attribute as a matter of good practice anyway. If not specified, the sequenceName value to be used is selected by the persistence provider (in this case, Hibernate). The other (optional) attributes are initialValue (the generator starts with this number) and allocationSize (the number of ids in the sequence reserved at a time); these default to values of 1 and 50, respectively.

Generating Primary Key Values with @TableGenerator

The @TableGenerator annotation is used in a very similar way to the @SequenceGenerator annotation, but because @TableGenerator manipulates a standard database table to obtain its primary key values, instead of using a vendor-specific sequence object, it is guaranteed to be portable between database platforms.

Note

For optimal portability and optimal performance, you should not specify the use of a table generator, but instead use the @GeneratorValue(strategy=GeneratorType.AUTO) configuration, which allows the persistence provider to select the most appropriate strategy for the database in use.

As with the sequence generator, the name attributes of @TableGenerator are mandatory and the other attributes are optional, with the table details being selected by the persistence provider:

@Id
@TableGenerator(name="tablegen",
                table="ID_TABLE",
                pkColumnName="ID",
                valueColumnName="NEXT_ID")
@GeneratedValue(strategy=TABLE,generator="tablegen")
int id;

The optional attributes are as follows:

  • allocationSize: Allows the number of primary keys set aside at one time to be tuned for performance.

  • catalog: Allows the catalog that the table resides within to be specified.

  • initialValue: Allows the starting primary key value to be specified.

  • pkColumnName: Allows the primary key column of the table to be identified. The table can contain the details necessary for generating primary key values for multiple entities.

  • pkColumnValue: Allows the primary key for the row containing the primary key generation information to be identified.

  • schema: Allows the schema that the table resides within to be specified.

  • table: The name of the table containing the primary key values.

  • uniqueConstraints: Allows additional constraints to be applied to the table for schema generation.

  • valueColumnName: Allows the column containing the primary key generation information for the current entity to be identified.

Because the table can be used to contain the primary key values for a variety of entries, it is likely to have a single row for each of the entities using it. It therefore needs its own primary key (pkColumnName), as well as a column containing the next primary key value to be used (pkColumnValue) for any of the entities obtaining their primary keys from it.

Compound Primary Keys with @Id, @IdClass, or @EmbeddedId

While the use of single-column surrogate keys is advantageous for various reasons, you may sometimes be forced to work with business keys. When these are contained in a single column, you can use @Id without specifying a generation strategy (forcing the user to assign a primary key value before the entity can be persisted). However, when the primary key consists of multiple columns, you need to take a different strategy to group these together in a way that allows the persistence engine to manipulate the key values as a single object.

You must create a class to represent this primary key. It will not require a primary key of its own, of course, but it must be a public class, must have a default constructor, must be serializable, and must implement hashCode() and equals() methods to allow the Hibernate code to test for primary key collisions (i.e., they must be implemented with the appropriate database semantics for the primary key values).

Your three strategies for using this primary key class once it has been created are as follows:

  1. Mark it as @Embeddable and add to your entity class a normal property for it, marked with @Id.

  2. Add to your entity class a normal property for it, marked with @EmbeddableId.

  3. Add properties to your entity class for all of its fields, mark them with @Id, and mark your entity class with @IdClass, supplying the class of your primary key class.

All these techniques require the use of an id class because Hibernate must be supplied with a primary key object when various parts of its persistence API are invoked. For example, you can retrieve an instance of an entity by invoking the Session object’s get() method, which takes as its parameter a single serializable object representing the entity’s primary key.

The use of @Id with a class marked as @Embeddable, as shown in Listing 6-6, is the most natural approach. The @Embeddable tag can be used for nonprimary key embeddable values anyway (@Embeddable is discussed in more detail later in the chapter). It allows you to treat the compound primary key as a single property, and it permits the reuse of the @Embeddable class in other tables.

One thing worth pointing out: the embedded primary key classes must be serializable (i.e., they must implement java.io.Serializable, although it’s possible you could use java.io.Externalizable as well7).

Listing 6-6. Using the @Idand @Embeddable Annotations to Map a Compound Primary Key: The CPKBook
package chapter06.compoundpk;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;


@Entity
public class CPKBook {
    @Id
    ISBN id;
    @Column
    String name;


    public CPKBook() {
    }


    public ISBN getId() {
        return id;
    }


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


    public String getName() {
        return name;
    }


    public void setName(String title) {
        this.name = title;
    }
}

The next most natural approach is the use of the @EmbeddedId tag. Here, the primary key class cannot be used in other tables since it is not an @Embeddable entity, but it does allow us to treat the key as a single attribute of the Account class (in Listings 6-7 and 6-8, the implementation of AccountPk is identical to that in Listing 6-6, and is thus omitted for brevity). Note that in Listings 6-7 and 6-8, the AccountPk class is not marked as @Embeddable.

Listing 6-7. Using the @Id and @Embeddable Annotations to Map a Compound Primary Key: The ISBN
package chapter06.compoundpk;

import javax.persistence.Embeddable;
import java.io.Serializable;


@Embeddable
public class ISBN implements Serializable {
    @Column(name=”group_number”) // because “group” is an invalid column name for SQL
    int group;
    int publisher;
    int title;
    int checkdigit;


    public ISBN() {
    }


    public int getGroup() {
        return group;
    }


    public void setGroup(int group) {
        this.group = group;
    }


    public int getPublisher() {
        return publisher;
    }


    public void setPublisher(int publisher) {
        this.publisher = publisher;
    }


    public int getTitle() {
        return title;
    }


    public void setTitle(int title) {
        this.title = title;
    }


    public int getCheckdigit() {
        return checkdigit;
    }


    public void setCheckdigit(int checkdigit) {
        this.checkdigit = checkdigit;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ISBN)) return false;


        ISBN isbn = (ISBN) o;

        if (checkdigit != isbn.checkdigit) return false;
        if (group != isbn.group) return false;
        if (publisher != isbn.publisher) return false;
        if (title != isbn.title) return false;


        return true;
    }


    @Override
    public int hashCode() {
        int result = group;
        result = 31 * result + publisher;
        result = 31 * result + title;
        result = 31 * result + checkdigit;
        return result;
    }
}
Note

The use of @Column(name="group_number") in the ISBN class will be discussed further, in “Mapping Properties and Fields with @Column” later in this chapter; this is required here because “group” – the natural column name for this field – is an invalid column name for SQL. We’re going to be seeing more embedded primary keys with the same format, and this annotation will be used in them, as well.

Listing 6-8. Using the @EmbeddedId Annotation to Map a Compound Primary Key
package chapter06.compoundpk;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import java.io.Serializable;


@Entity
public class EmbeddedPKBook {
    @EmbeddedId
    EmbeddedISBN id;
    @Column
    String name;


    static class EmbeddedISBN implements Serializable {
    // looks fundamentally the same as the ISBN class from Listing 6-7
    }
}

Finally, the use of the @IdClass and @Id annotations allows us to map the compound primary key class using properties of the entity itself corresponding to the names of the properties in the primary key class. The names must correspond (there is no mechanism for overriding this), and the primary key class must honor the same obligations as with the other two techniques. The only advantage to this approach is its ability to “hide” the use of the primary key class from the interface of the enclosing entity.

The @IdClass annotation takes a value parameter of Class type, which must be the class to be used as the compound primary key. The fields that correspond to the properties of the primary key class to be used must all be annotated with @Id—note in Listing 6-9 that the getCode() and getNumber() methods of the Account class are so annotated, and the AccountPk class is not mapped as @Embeddable, but it is supplied as the value of the @IdClass annotation.

Listing 6-9. Using the @IdClassand @Id Annotations to Map a Compound Primary Key
package chapter06.compoundpk;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import java.io.Serializable;


@Entity
@IdClass(IdClassBook.EmbeddedISBN.class)
public class IdClassBook {
    @Id
    int group;
    @Id
    int publisher;
    @Id
    int title;
    @Id
    int checkdigit;
    String name;


    public IdClassBook() {
    }


    public int getGroup() {
        return group;
    }


    public void setGroup(int group) {
        this.group = group;
    }


    public int getPublisher() {
        return publisher;
    }


    public void setPublisher(int publisher) {
        this.publisher = publisher;
    }


    public int getTitle() {
        return title;
    }


    public void setTitle(int title) {
        this.title = title;
    }


    public int getCheckdigit() {
        return checkdigit;
    }


    public void setCheckdigit(int checkdigit) {
        this.checkdigit = checkdigit;
    }


    public String getName() {
        return name;
    }


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


    static class EmbeddedISBN implements Serializable {
    // identical to EmbeddedISBN from Listing 6-8
    }
}

Regardless of which of these approaches we take to declare our compound primary key, the table that will be used to represent it will require the same set of columns. Listing 6-10 shows the DDL that will be generated from Listings 6-7, 6-8, and 6-9; the other table names would differ, but the DDL for each table would be the same.

Listing 6-10. The DDL Generated from the CPKBook Class
create table CPKBook (
    checkdigit integer not null,
    group_number integer not null,
    publisher integer not null,
    title integer not null,
    name varchar(255),
    primary key (checkdigit, group, publisher, title)
)

Database Table Mapping with @Table and @SecondaryTable

By default, table names are derived from the entity names. Therefore, given a class Book with a simple @Entity annotation, the table name would be “book,” adjusted for the database’s configuration.

If the entity name is changed (by providing a different name in the @Entity annotation, such as @Entity(“BookThing”)), the new name will be used for the table name. (Queries would need to use the entity name; from the user’s perspective, the table name would be irrelevant.)

The table name can be customized further, and other database-related attributes can be configured via the @Table annotation. This annotation allows you to specify many of the details of the table that will be used to persist the entity in the database. As already pointed out, if you omit the annotation, Hibernate will default to using the class name for the table name, so you need only provide this annotation if you want to override that behavior.

The @Table annotation provides four attributes, allowing you to override the name of the table, its catalog, and its schema, and to enforce unique constraints on columns in the table. Typically, you would only provide a substitute table name thus: @Table(name="ORDER_HISTORY"). The unique constraints will be applied if the database schema is generated from the annotated classes, and will supplement any column-specific constraints (see discussions of @Column and @JoinColumn later in this chapter). They are not otherwise enforced.

The @SecondaryTable annotation provides a way to model an entity bean that is persisted across several different database tables. Here, in addition to providing an @Table annotation for the primary database table, your entity can have a @SecondaryTable annotation, or a @SecondaryTables annotation, in turn, containing zero or more @SecondaryTable annotations. The @SecondaryTable annotation takes the same basic attributes as the @Table annotation, with the addition of the join attribute. The join attribute defines the join column for the primary database table. It accepts an array of javax.persistence.PrimaryKeyJoinColumn objects. If you omit the join attribute, then it will be assumed that the tables are joined on identically named primary key columns.

When an attribute in the entity is drawn from the secondary table, it must be marked with the @Column annotation , with a table attribute identifying the appropriate table. Listing 6-11 shows how a property of the Customer entity could be drawn from a second table mapped in this way.

Listing 6-11. An Example of a Field Access Entity Mapped Across Two Tables
package chapter06.twotables;

import javax.persistence.*;

@Entity
@Table(name = "customer")
@SecondaryTable(name = "customer_details")
public class Customer {
    @Id
    public int id;
    public String name;
    @Column(table = "customer_details")
    public String address;


    public Customer() {
    }
}

Columns in the primary or secondary tables can be marked as having unique values within their tables by adding one or more appropriate @UniqueConstraint annotations to @Table or @SecondaryTable’s uniqueConstraints attribute. You may also set uniqueness at the field level with the unique attribute on the @Column attribute. For example, to mark the name field in the preceding declaration as being unique, use the following:

@Entity
@Table(
   name="customer",
   uniqueConstraints={@UniqueConstraint(columnNames="name")}
)
@SecondaryTable(name="customer_details")
public class Customer {
   ...
}

Persisting Basic Types with @Basic

By default, properties and instance variables in your POJO are persistent; Hibernate will store their values for you. The simplest mappings are therefore for the “basic” types. These include primitives, primitive wrappers, arrays of primitives or wrappers, enumerations, and any types that implement Serializable but are not themselves mapped entities. These are all mapped implicitly — no annotation is needed. By default, such fields are mapped to a single column, and eager fetching is used to retrieve them (i.e., when the entity is retrieved from the database, all the basic fields and properties are retrieved8). Also, when the field or property is not a primitive, it can be stored and retrieved as a null value.

This default behavior can be overridden by applying the @Basic annotation to the appropriate class member. The annotation takes two optional attributes, and is itself entirely optional. The first attribute is named optional and takes a Boolean. Defaulting to true, this can be set to false to provide a hint to schema generation that the associated column should be created NOT NULL. The second is named fetch and takes a member of the enumeration FetchType. This is EAGER by default, but can be set to LAZY to permit loading on access of the value.

Lazy loading means that the values of the references aren’t actually necessarily initialized when the object is loaded by the Session. This has the potential advantage of performance – you can see if an object is persisted or not, without having to set all of its attributes – but it also means that your object might not be initialized fully at any given moment. (It’s initialized when you actually start accessing the data.) This means that for lazily loaded data, the originating Session has to be active when you access the data, or else you will get a LazyInitializationException exception.

Lazy initialization is most valuable when you’re actually loading relationships from the database. If you had a PublishingHouse object, with thousands of Books being published, you wouldn’t necessarily want to load all of the books just because you are working with the PublishingHouse reference. Therefore, you’d want the books loaded lazily (because they probably have their own references to Authors, and those authors might have more than one book, and so forth and so on, ad infinitum).

The @Basic attribute is usually omitted, with the @Column attribute being used where the @Basic annotation’s optional attribute might otherwise be used to provide the NOT NULL behavior.

Omitting Persistence with @Transient

Some fields, such as calculated values, may be used at runtime only, and they should be discarded from objects as they are persisted into the database. The JPA specification provides the @Transient annotation for these transient fields. The @Transient annotation does not have any attributes — you just add it to the instance variable or the getter method as appropriate for the entity bean’s property access strategy.

The @Transient annotation highlights one of the more important differences between using annotations with Hibernate and using XML mapping documents. With annotations, Hibernate will default to persisting all of the fields on a mapped object. When using XML mapping documents, Hibernate requires you to tell it explicitly which fields will be persisted.9

For our example, if we wanted to add a Date field named publicationDate, not be stored in the database to our Book class, we could mark this field transient thus:

@Transient
Date publicationDate;

If we are using a property access strategy for our Book class, we would need to put the @Transient annotation on the accessor instead.

Mapping Properties and Fields with @Column

The @Column annotation is used to specify the details of the column to which a field or property will be mapped. Some of the details are schema related, and therefore apply only if the schema is generated from the annotated files. Others apply and are enforced at runtime by Hibernate (or the JPA 2 persistence engine). It is optional, with an appropriate set of default behaviors, but is often useful when overriding default behavior, or when you need to fit your object model into a preexisting schema. It is more commonly used than the similar @Basic annotation, with the following attributes commonly being overridden:

  • name permits the name of the column to be explicitly specified — by default, this would be the name of the property. However, it is often necessary to override the default behavior when it would otherwise result in an SQL keyword being used as the column name (e.g., user).

  • length permits the size of the column used to map a value (particularly a String value) to be explicitly defined. The column size defaults to 255, which might otherwise result in truncated String data, for example.

  • nullable permits the column to be marked NOT NULL when the schema is generated. The default is that fields should be permitted to be null; however, it is common to override this when a field is, or ought to be, mandatory.

  • unique permits the column to be marked as containing only unique values. This defaults to false, but commonly would be set for a value that might not be a primary key but would still cause problems if duplicated (such as username).

We have marked up the title field of our Book entity using the @Column entity to show how three of these attributes would be applied:

@Column(name="working_title",length=200,nullable=false)
String title;

The remaining attributes, less commonly used, are as follows:

  • table is used when the owning entity has been mapped across one or more secondary tables. By default, the value is assumed to be drawn from the primary table, but the name of one of the secondary tables can be substituted here (see the @SecondaryTable annotation example earlier in this chapter).

  • insertable defaults to true, but if set to false, the annotated field will be omitted from insert statements generated by Hibernate (i.e., it won’t be persisted).

  • updatable defaults to true, but if set to false, the annotated field will be omitted from update statements generated by Hibernate (i.e., it won’t be altered once it has been persisted).

  • columnDefinition can be set to an appropriate DDL fragment to be used when generating the column in the database. This can only be used during schema generation from the annotated entity, and should be avoided if possible, since it is likely to reduce the portability of your application between database dialects.

  • precision permits the precision of decimal numeric columns to be specified for schema generation, and will be ignored when a nondecimal value is persisted. The value given represents the number of digits in the number (usually requiring a minimum length of n+1, where n is the scale).

  • scale permits the scale of decimal numeric columns to be specified for schema generation and will be ignored where a nondecimal value is persisted. The value given represents the number of places after the decimal point.

Modeling Entity Relationships

Naturally, annotations also allow you to model associations between entities. JPA 2 supports one-to-one, one-to-many, many-to-one, and many-to-many associations. Each of these has its corresponding annotation.

We discussed the various ways in which these mappings can be established in the tables in Chapter 5. In this section, we will show how the various mappings are requested using the annotations.

Mapping an Embedded (Component) One-to-One Association

When all the fields of one entity are maintained within the same table as another, the enclosed entity is referred to in Hibernate as a component. The JPA standard refers to such an entity as being embedded.

The @Embedded and @Embeddable attributes are used to manage this relationship. In this chapter’s example of primary keys, we associate an ISBN class with a Book class in this way.

The ISBN class is marked with the @Embeddable annotation. An embeddable entity must be composed entirely of basic fields and attributes. An embeddable entity can only use the @Basic, @Column, @Lob, @Temporal, and @Enumerated annotations. It cannot maintain its own primary key with the @Id tag because its primary key is the primary key of the enclosing entity.

The @Embeddable annotation itself is purely a marker annotation, and it takes no additional attributes, as demonstrated in Listing 6-12. Typically, the fields and properties of the embeddable entity need no further markup.10

Listing 6-12. Marking an Entity for Embedding Within Other Entities
@Embeddable
public class AuthorAddress {
...
}

The enclosing entity then marks appropriate fields or getters in entities, making use of the embeddable class with the @Embedded annotation, as shown in Listing 6-13.

Listing 6-13. Marking an Embedded Property
@Embedded
AuthorAddress address;

The @Embedded annotation draws its column information from the embedded type, but permits the overriding of a specific column or columns with the @AttributeOverride and @AttributeOverrides tags (the latter to enclose an array of the former if multiple columns are being overridden). For example, Listing 6-14 shows how to override the default column names of the address and country attributes of AuthorAddress with columns named ADDR and NATION.

Listing 6-14. Overriding Default Attributes of an Embedded Property
@Embedded
@AttributeOverrides({
    @AttributeOverride(name="address",column=@Column(name="ADDR")),
    @AttributeOverride(name="country",column=@Column(name="NATION"))
})
AuthorAddress address;

Neither Hibernate nor the JPA standard supports mapping an embedded object across more than one table. In practice, if you want this sort of persistence for your embedded entity, you will usually be better off making it a first-class entity (i.e., not embedded) with its own @Entity marker and @Id annotations, and then mapping it via a conventional one-to-one association, as explained in the next section.11

Mapping a Conventional One-to-One Association

There is nothing intrinsically wrong with mapping a one-to-one association between two entities where one is not a component of (i.e., embedded into) the other. The relationship is often somewhat suspect, however. You should give some thought to using the embedded technique described previously before using the @OneToOne annotation.

You can have a bidirectional relationship with a one-to-one association. One side will need to own the relationship and be responsible for updating a join column with a foreign key to the other side. The nonowning side will need to use the mappedBy attribute to indicate the entity that owns the relationship.

Assuming that you are resolute on declaring the association in this way (perhaps because you anticipate converting it to a one-to-many or many-to-one relationship in the foreseeable future), applying the annotation is quite simple — all of the attributes are optional. Listing 6-15 shows how simply a relationship like this might be declared.

Listing 6-15. Declaring a Simple One-to-One Relationship
@OneToOne
Address address;

The @OneToOne annotation permits the following optional attributes to be specified:

  • targetEntity can be set to the class of an entity storing the association. If left unset, the appropriate type will be inferred from the field type, or the return type of the property’s getter.

  • cascade can be set to any of the members of the javax.persistence.CascadeType enumeration. It defaults to none being set. See the “Cascading Operations” sidebar for a discussion of these values.

  • fetch can be set to the EAGER or LAZY members of FetchType.

  • optional indicates whether the value being mapped can be null.

  • orphanRemoval indicates that if the value being mapped is deleted, this entity will also be deleted.

  • mappedBy indicates that a bidirectional one-to-one relationship is owned by the named entity.12 The owning entity contains the primary key of the subordinate entity.

Mapping a Many-to-One or One-to-Many Association

A many-to-one association and a one-to-many association are the same association seen from the perspective of the owning and subordinate entities, respectively.

The simplest way to maintain a many-to-one relationship between two entities is by managing the foreign key of the entity at the “one” end of the one-to-many relationship as a column in the “many” entity’s table.

The @OneToMany annotation can be applied to a field or property value for a collection or an array representing the mapped “many” end of the association.

Listing 6-16. Mapping a One-to-Many Relationship from the Book Entity to the Publisher Entity
@OneToMany(cascade = ALL,mappedBy = "publisher")
Set<Book> books;

The many-to-one end of this relationship is expressed in similar terms to the one-to-many end, as shown in Listing 6-17.

Listing 6-17. Mapping a Many-to-One Relationship from the Publisher Entity to the Book Entity
@ManyToOne
@JoinColumn(name = "publisher_id")
Publisher publisher;

The @ManyToOne annotation takes a similar set of attributes to @OneToMany. The following list describes the attributes, all of which are optional.

  • cascade indicates the appropriate cascade policy for operations on the association; it defaults to none.

  • fetch indicates the fetch strategy to use; it defaults to LAZY.

  • optional indicates whether the value can be null; it defaults to true.

  • targetEntity indicates the entity that stores the primary key — this is normally inferred from the type of the field or property (Publisher, in the preceding example).

We have also supplied the optional @JoinColumn attribute to name the foreign key column required by the association something other than the default (publisher) — this is not necessary, but it illustrates the use of the annotation.

When a unidirectional one-to-many association is to be formed, it is possible to express the relationship using a link table. This is achieved by adding the @JoinTable annotation, as shown in Listing 6-18.13

Listing 6-18. A Simple Unidirectional One-to-Many Association with a Join Table
@OneToMany(cascade = ALL)
@JoinTable
Set<Book> books;

The @JoinTable annotation provides attributes that allow various aspects of the link table to be controlled. These attributes are as follows:

  • name is the name of the join table to be used to represent the association.

  • catalog is the name of the catalog containing the join table.

  • schema is the name of the schema containing the join table.

  • joinColumns is an array of @JoinColumn attributes representing the primary key of the entity at the “one” end of the association.

  • inverseJoinColumns is an array of @JoinColumn attributes representing the primary key of the entity at the “many” end of the association.

Listing 6-19 shows a fairly typical application of the @JoinTable annotation to specify the name of the join table and its foreign keys into the associated entities.

Listing 6-19. A Unidirectional One-to-Many Association with a More Fully Specified Join Table
@OneToMany(cascade = ALL)
@JoinTable(
        name="PublishedBooks",
        joinColumns = { @JoinColumn( name = "publisher_id") },
        inverseJoinColumns = @JoinColumn( name = "book_id")
)
Set<Book> books;

Mapping a Many-to-Many Association

When a many-to-many association does not involve a first-class entity joining the two sides of the relationship, a link table must be used to maintain the relationship. This can be generated automatically, or the details can be established in much the same way as with the link table described in the earlier “Mapping a Many-to-One or One-to-Many Association” section of the chapter.

The appropriate annotation is naturally @ManyToMany, and takes the following attributes:

  • mappedBy is the field that owns the relationship — this is only required if the association is bidirectional. If an entity provides this attribute, then the other end of the association is the owner of the association, and the attribute must name a field or property of that entity.

  • targetEntity is the entity class that is the target of the association. Again, this may be inferred from the generic or array declaration, and only needs to be specified if this is not possible.

  • cascade indicates the cascade behavior of the association, which defaults to none.

  • fetch indicates the fetch behavior of the association, which defaults to LAZY.

The example maintains a many-to-many association between the Book class and the Author class. The Book entity owns the association, so its getAuthors() method must be marked with an appropriate @ManyToMany attribute, as shown in Listing 6-20.

Listing 6-20. The Book Side of the Many-to-Many Association
@ManyToMany(cascade = ALL)
Set321250_4_En authors;

The Author entity is managed by the Book entity. The link table is not explicitly managed, so, as shown in Listing 6-21, we mark it with a @ManyToMany annotation and indicate that the foreign key is managed by the author’s attribute of the associated Book entity.

Listing 6-21. The Author Side of the Many-to-Many Association
@ManyToMany(mappedBy = "authors")
Set<Book> books;

Alternatively, we could specify the link table in full, as in Listing 6-22.

Listing 6-22. Specifying the Link Table in Full Using the Book Entity Annotations
@ManyToMany(cascade = ALL)
@JoinTable(
        name="Books_to_Author",
        joinColumns={@JoinColumn(name="book_ident")},
        inverseJoinColumns={@JoinColumn(name="author_ident")}
)
Set<Author> authors;

Inheritance

The JPA 2 standard and Hibernate both support three approaches to mapping inheritance hierarchies into the database. These are as follows:

  1. Single table (SINGLE_TABLE): One table for each class hierarchy

  2. Joined (JOINED): One table for each subclass (including interfaces and abstract classes)

  3. Table-per-class (TABLE_PER_CLASS): One table for each concrete class implementation

Persistent entities that are related by inheritance must be marked up with the @Inheritance annotation. This takes a single strategy attribute, which is set to one of three javax.persistence. InheritanceType enumeration values corresponding to these approaches (shown in brackets in the preceding numbered list).

Single Table

The single-table approach manages one class for the superclass and all its subtypes. There are columns for each mapped field or property of the superclass, and for each distinct field or property of the derived types. When following this strategy, you will need to ensure that columns are appropriately renamed when any field or property names collide in the hierarchy.

To determine the appropriate type to instantiate when retrieving entities from the database, a @DiscriminatorColumn annotation should be provided in the root (and only in the root) of the persistent hierarchy.14 This defines a column containing a value that distinguishes between the types used. The attributes permitted by the @DiscriminatorColumn annotation are as follows:

  • name is the name of the discriminator column.

  • discriminatorType is the type of value to be stored in the column as selected from the javax.persistence.DiscriminatorType enumeration of STRING, CHAR, or INTEGER.

  • columnDefinition is a fragment of DDL defining the column type. Using this is liable to reduce the portability of your code across databases.

  • length is the column length of STRING discriminator types. It is ignored for CHAR and INTEGER types.

All of these (and the annotation itself) are optional, but we recommend supplying at least the name attribute. If no @DiscriminatorColumn is specified in the hierarchy, a default column name of DTYPE and type of STRING will be used.

Hibernate will supply an appropriate discriminator value for each of your entities. For example, if the STRING discriminator type is used, the value this column contains will be the name of the entity (which defaults to the class name). You can also override this behavior with specific values using the @DiscriminatorValue annotation. If the discriminator type is INTEGER, any value provided via the @DiscriminatorValue annotation must be convertible directly into an integer.

In Listing 6-23, we specify that an INTEGER discriminator type should be stored in the column named DISCRIMINATOR. Rows representing Book entities will have a value of 1 in this column, whereas the following mapping in Listing 6-24 requires that rows representing ComputerBook entities should have a value of 2 in the same column.

Listing 6-23. The Root of the Inheritance Hierarchy Mapped with the SINGLE_TABLE Strategy
@Entity
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(
    name="DISCRIMINATOR",
    discriminatorType=INTEGER
)
@DiscriminatorValue("1")
public class Book {
...
}
Listing 6-24. A Derived Entity in the Inheritance Hierarchy
@Entity
@DiscriminatorValue("2")
public class ComputerBook extends Book {
...
}

Joined Table

An alternative to the monolithic single-table approach is the otherwise similar joined-table approach. Here a discriminator column is used, but the fields of the various derived types are stored in distinct tables. Other than the differing strategy, this inheritance type is specified in the same way (as shown in Listing 6-25).

Listing 6-25. The Root of the Inheritance Hierarchy Mapped with the JOINED Strategy
@Entity
@Inheritance(strategy = JOINED)
@DiscriminatorColumn(
    name="DISCRIMINATOR"
)
public class Book {
...
}

Table per Class

Finally, there is the table-per-class approach , in which all of the fields of each type in the inheritance hierarchy are stored in distinct tables. Because of the close correspondence between the entity and its table, the @DiscriminatorColumn annotation is not applicable to this inheritance strategy. Listing 6-26 shows how our Book class could be mapped in this way.

Listing 6-26. The Root of the Inheritance Hierarchy Mapped with the TABLE_PER_CLASS Strategy
@Entity
@Inheritance(strategy = TABLE_PER_CLASS)
public class Book {
...
}

Choosing Between Inheritance Types When Modeling Inheritance

Each of these different inheritance types has trade-offs. When you create a database schema that models a class hierarchy, you have to weigh performance and database maintainability to decide which inheritance type to use.

It is easiest to maintain your database when using the joined-table approach . If fields are added or removed from any class in the class hierarchy, only one database table needs to be altered to reflect the changes. In addition, adding new classes to the class hierarchy only requires that a new table be added, eliminating the performance problems of adding database columns to large data sets. With the table-per-class approach, a change to a column in a parent class requires that the column change be made in all child tables. The single-table approach can be messy, leading to many columns in the table that aren’t used in every row, as well as a rapidly horizontally growing table.

Read performance will be best with the single-table approach. A select query for any class in the hierarchy will only read from one table, with no joins necessary. The table-per-class type has great performance if you only work with the leaf nodes in the class hierarchy. Any queries related to the parent classes will require joins on a number of tables to get results. The joined-table approach will also require joins for any select query, so this will affect performance. The number of joins will be related to the size of the class hierarchy — large, deep class hierarchies may not be good candidates for the joined-table approach.

We recommend using the joined-table approach unless performance could be a problem because of the size of the data set and the depth of the class hierarchy.

Other JPA 2 Persistence Annotations

Although we have now covered most of the core JPA 2 persistence annotations, there are a few others that you will encounter fairly frequently. We cover some of these in passing in the following sections.

Temporal Data

Fields or properties of an entity that have java.util.Date or java.util.Calendar types represent temporal data. By default, these will be stored in a column with the TIMESTAMP data type, but this default behavior can be overridden with the @Temporal annotation.

The annotation accepts a single value attribute from the javax.persistence.TemporalType enumeration. This offers three possible values: DATE, TIME, and TIMESTAMP. These correspond, respectively, to java.sql.Date, java.sql.Time, and java.sql.Timestamp. The table column is given the appropriate data type at schema generation time. Listing 6-27 shows an example mapping a java.util.Date property as a TIME type—the java.sql.Date and java.sql.Time classes are both derived from the java.util.Date class, so confusingly, both are capable of representing dates and times!

Listing 6-27. A Date Property Mapped as a Time Temporal Field
@Temporal(TemporalType.TIME)
java.util.Date startingTime;

Element Collections

In addition to mapping collections using one-to-many mappings, JPA 2 introduced an @ElementCollection annotation for mapping collections of basic or embeddable classes. You can use the @ElementCollection annotation to simplify your mappings. Listing 6-28 shows an example where you use the @ElementCollection annotation to map a java.util.List collection of string objects.

Listing 6-28. An Example of ElementCollections
@ElementCollection
List<String> passwordHints;

There are two attributes on the @ElementCollection annotation: targetClass and fetch. The targetClass attribute tells Hibernate which class is stored in the collection. If you use generics on your collection, you do not need to specify targetClass because Hibernate will infer the correct class. The fetch attribute takes a member of the enumeration, FetchType. This is EAGER by default, but can be set to LAZY to permit loading when the value is accessed.

Large Objects

A persistent property or field can be marked for persistence as a database-supported large object type by applying the @Lob annotation .

The annotation takes no attributes, but the underlying large object type to be used will be inferred from the type of the field or parameter. String- and character-based types will be stored in an appropriate character-based type. All other objects will be stored in a BLOB. Listing 6-29 maps a String–a title of some kind15 - into a large object column type.

Listing 6-29. An Example of a Large Object Property
@Lob
String title; // a very, very long title indeed

The @Lob annotation can be used in combination with the @Basic or the @ElementCollection annotation.

Mapped Superclasses

A special case of inheritance occurs when the root of the hierarchy is not itself a persistent entity, but various classes derived from it are. Such a class can be abstract or concrete. The @MappedSuperclass annotation allows you to take advantage of this circumstance.

The class marked with @MappedSuperclassis not an entity, and is not queryable (it cannot be passed to methods that expect an entity in the Session or EntityManager objects). It cannot be the target of an association.

The mapping information for the columns of the superclass will be stored in the same table as the details of the derived class (in this way, the annotation resembles the use of the @Inheritance tag with the SINGLE_TABLE strategy).

In other respects, the superclass can be mapped as a normal entity, but the mappings will apply to the derived classes only (since the superclass itself does not have an associated table in the database). When a derived class needs to deviate from the superclass’s behavior, the @AttributeOverride annotation can be used (much as with the use of an embeddable entity).

For example, if in our example, model Book was a superclass of ComputerBook, but Book objects themselves were never persisted directly, then Book could be marked as @MappedSuperclass, as shown in Listing 6-30.

Listing 6-30. Marking the Book Class as a Mapped Superclass
@MappedSuperclass
public class BookSuperclass {
...
}

The fields of the ComputerBook entity derived from Book would then be stored in the ComputerBook entity class’s table. Classes derived directly from Book but not mapped as entities in their own right, such as a hypothetical MarketingBook class, would not be persistable. In this respect alone, the mapped superclass approach behaves differently from the conventional @Inheritance approach with a SINGLE_TABLE strategy.

Ordering Collections with @OrderColumn

While @OrderBy allows data to be ordered once it has been retrieved from the database, JPA 2 also provides an annotation that allows the ordering of appropriate collection types (e.g., List) to be maintained in the database; it does so by maintaining an order column to represent that order. Here’s an example:

@OneToMany
@OrderColumn(
   name="employeeNumber"
)
List<Employee> employees;

Here, we are declaring that an employeeNumber column will maintain a value, starting at 0 and incrementing as each entry is added to the list. The default starting value can be overridden by the base attribute. By default, the column can contain null (unordered) values. The nullability can be overridden by setting the nullable attribute to false. By default, when the schema is generated from the annotations, the column is assumed to be an integer type; however, this can be overridden by supplying a columnDefinition attribute specifying a different column definition string.

Named Queries (HQL or JPQL)

@NamedQuery and @NamedQueriesallow one or more Hibernate Query Language or Java Persistence Query Language (JPQL) queries to be associated with an entity. The required attributes are as follows:

  • name is the name by which the query is retrieved.

  • query is the JPQL (or HQL) query associated with the name.

Listing 6-31 shows an example associating a named query with the Author entity. The query would retrieve Author entities by name, so it is natural to associate it with that entity; however, there is no actual requirement that a named query be associated in this way with the entity that it concerns.

Listing 6-31. A JPQL Named Query Annotation
@Entity
@NamedQuery(
        name="findAuthorsByName",
        query="from Author where name = :author"
)
public class Author {
...
}

There is also a hints attribute, taking a QueryHint annotation name/value pair, which allows caching mode, timeout value, and a variety of other platform-specific tweaks to be applied (this can also be used to comment the SQL generated by the query).

You do not need to directly associate the query with the entity against which it is declared, but it is normal to do so. If a query has no natural association with any of the entity declarations, it is possible to make the @NamedQuery annotation at the package level.16

There is no natural place to put a package-level annotation, so Java annotations allow for a specific file, called package-info.java, to contain them. Listing 6-32 gives an example of this.

Listing 6-32. A package-info.java File
@javax.annotations.NamedQuery(
   name="findBooksByAuthor",
   query="from Book b where b.author.name = :author"
)
package chapter06.annotations;

Hibernate’s session allows named queries to be accessed directly, as shown in Listing 6-33.

Listing 6-33. Invoking a Named Query via the Session
Query query = session.getNamedQuery("findBooksByAuthor", Book.class);
query.setParameter("author", "Dave");
List<Book> booksByDave = query.list();
System.out.println("There is/are " + booksByDave.size()
        + " books by Dave in the catalog");

If you have multiple @NamedQuery annotations to apply to an entity, they can be provided as an array of values of the @NamedQueries annotation.

Named Native Queries (SQL)

Hibernate also allows the database’s native query language (usually a dialect of SQL) to be used in place of HQL or JPQL. You risk losing portability here if you use a database-specific feature, but as long as you choose reasonably generic SQL, you should be okay. The @NamedNativeQuery annotation is declared in almost exactly the same manner as the @NamedQuery annotation. The following block of code shows a simple example of the declaration of a named native query:

@NamedNativeQuery(
  name="nativeFindAuthorNames",
  query="select name from author"
)

All queries are used in the same way; the only difference is how they’re accessed, whether by Session.getNamedQuery(), Session.createQuery(), or Session.createSQLQuery(); the results can be retrieved as a List through Query.list(), or a scrollable result set can be accessed via Query.scroll(), Query.iterate() provides an Iterator (surprise!), and if the Query has only one object returned, Query.uniqueResult() can be used.

Multiple @NamedNativeQuery annotations can be grouped with the @NamedNativeQueries annotation.

Configuring the Annotated Classes

Once you have an annotated class, you will need to provide the class to your application’s Hibernate configuration, just as if it were an XML mapping. With annotations, you can either use the declarative configuration in the hibernate.cfg.xml XML configuration document, or programmatically add annotated classes to Hibernate’s org.hibernate.cfg.AnnotationConfiguration object. Your application may use both annotated entities and XML mapped entities in the same configuration.

To provide declarative mapping, we use a normal hibernate.cfg.xml XML configuration file and add the annotated classes to the mapping using the mapping element (see Listing 6-34). Notice that we have specified the name of the annotated classes as mappings.

Listing 6-34. A Hibernate XML Configuration File with Annotated Classes
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!--  Database connection settings  -->
        <property name="connection.driver_class">org.h2.Driver</property>
        <property name="connection.url">jdbc:h2:./db6</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"/>
        <property name="dialect">org.hibernate.dialect.H2Dialect</property>
        <!-- set up c3p0 for use -->
        <property name="c3p0.max_size">10</property>
        <!--  Echo all executed SQL to stdout  -->
        <property name="show_sql">true</property>
        <!--  Drop and re-create the database schema on startup  -->
        <property name="hbm2ddl.auto">create</property>


        <mapping class="chapter06.primarykey.after.Book"/>
        <mapping class="chapter06.compoundpk.CPKBook"/>
        <mapping class="chapter06.compoundpk.EmbeddedPKBook"/>
        <mapping class="chapter06.compoundpk.IdClassBook"/>
        <mapping class="chapter06.twotables.Customer"/>


        <mapping class="chapter06.mappedsuperclass.ComputerBook"/>

        <mapping class="chapter06.naturalid.Employee"/>
        <mapping class="chapter06.naturalid.SimpleNaturalIdEmployee"/>
    </session-factory>
</hibernate-configuration>

You can also add an annotated class to your Hibernate configuration programmatically. The annotations toolset comes with an org.hibernate.cfg.AnnotationConfiguration object that extends the base Hibernate Configuration object for adding mappings. The methods on AnnotationConfiguration for adding annotated classes to the configuration are as follows:

addAnnotatedClass(Class persistentClass) throws MappingException
addAnnotatedClasses(List<Class> classes)
addPackage(String packageName) throws MappingException

Using these methods, you can add one annotated class, a list of annotated classes, or an entire package (by name) of annotated classes. As with the Hibernate XML configuration file, the annotated entities are interoperable with XML mapped entities.17

Hibernate-Specific Persistence Annotations

Hibernate has various annotations that extend the standard persistence annotations. They can be very useful, but you should keep in mind that their use will constrict your application to Hibernate; this won’t affect any of the code we’ve written so far, since most of it uses Hibernate-specific classes already.

Tip

It is possible to overstate the importance of portability – most bespoke applications are never deployed to an environment other than the one for which they were originally developed. As a mature product, Hibernate has numerous features to offer above and beyond the base JPA 2 specification. You should not waste too much time trying to achieve a portable solution in preference to these proprietary features unless you have a definite requirement for portability.

@Immutable

The @Immutable annotation marks an entity as being, well, immutable. This is useful for situations in which your entity represents reference data – things like lists of states, genders, or other rarely mutated data.

Since things like states tend to be rarely changed, someone usually updates the data manually, via SQL or an administration application. Hibernate can cache this data aggressively, which needs to be taken into consideration; if the reference data changes, you’d want to make sure that the applications using it are notified or restarted somehow.

What the annotation tells Hibernate is that any updates to an immutable entity should not be passed on to the database. It’s a “safe” object; one probably shouldn’t update it very often, if only to avoid confusion.

@Immutable can be placed on a collection; in this case, changes to the collection (additions, or removals) will cause a HibernateException to be thrown.

Natural IDs

The first part of this chapter spent a lot of pages discussing primary keys, including generated values. Generated values are referred to as “artificial primary keys,” and are very much recommended18 as a sort of shorthand reference for a given row.

However, there’s also the concept of a “natural ID ,” which provides another convenient way to refer to an entity, apart from an artificial or composite primary key.

An example might be a Social Security number or a Tax Identification Number in the United States. An entity (being a person or a corporation) might have an artificial primary key generated by Hibernate, but it also might have a unique tax identifier. This might be annotated with @Column(unique=true, nullable=false, updatable=false), which would create a unique, immutable index,19 but a natural ID also provides a loadable mechanism that we’ve not seen yet in any of our previous code, plus an actual optimization.

The Session provides the concept of a loader mechanism, known as a “load access.” There are three loaders contained in Hibernate: able to load by ID, natural ID, and simple natural ID.

Loading by ID refers to an internal reference for a given instance. For example, if an object with an ID of 1 is already referred to by Hibernate, Hibernate doesn’t need to go to the database to load that object – it can look the object up through its ID, and return that reference.

A natural ID is another form of that ID; in the case of a tax identifier, the system could look it up by the actual object ID (which would be an artificial key in most cases) or by the tax ID number itself – and if the tax ID is a “natural ID,” then the library is able to look that object up internally instead of building a query for the database.

Just as there are simple identifiers and composite identifiers comprising single fields and multiple fields, respectively – there are two forms of natural ID, similarly being made up of single fields or multiple fields.

In the case of the simple IDs, the load process provides a simple load() method, with the ID in question being the parameter. If no instance with the ID exists, load() returns null. The loader also provides an alternative, a getReference() method, which will throw an exception if no object with that natural ID is in the database.

For natural IDs, there are two forms of load mechanisms; one uses the simple natural ID (where the natural ID is one and only one field), and the other uses named attributes as part of a composite natural ID.

Now let’s look at some actual code. First, let’s create a class representing an employee in Listing 6-35; our employee will have a name (everyone has a name); an artificial ID (an employee number) assigned by the database; and a natural ID, representing a manually assigned badge number.

Listing 6-35. SimpleNaturalIdEmployee.java, without Accessors, Mutators, or Ancillary Methods
package chapter06.naturalid;

import org.hibernate.annotations.NaturalId;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


@Entity
public class SimpleNaturalIdEmployee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    @NaturalId
    Integer badge;
    String name;


    public SimpleNaturalIdEmployee() {
    }  
}

The simple natural ID is declared by annotating a single field, badge, with @NaturalId.

To use the loader mechanism, you would get a reference through the use of Session.byId(), Session.byNaturalId(), or Session.bySimpleNaturalId(), with the type of the entity being passed in. The simple loaders (for the ID and for the simple natural ID) follow the same form: you acquire the loader, then either load or get the reference, using the key value as a parameter. Let’s see how that would look, shown in Listing 6-36.

Listing 6-36. A Simple Test Showing the Use of Both ID and Natural ID Loaders
@Test
public void testSimpleNaturalId() {
    Integer id = createSimpleEmployee("Sorhed", 5401).getId();


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


        SimpleNaturalIdEmployee employee =
                session
                        .byId(SimpleNaturalIdEmployee.class)
                        .load(id);
        assertNotNull(employee);
        SimpleNaturalIdEmployee badgedEmployee =
                session
                        .bySimpleNaturalId(SimpleNaturalIdEmployee.class)
                        .load(5401);
        assertEquals(badgedEmployee, employee);


        tx.commit();
    }
}


private SimpleNaturalIdEmployee createSimpleEmployee(String name, int badge) {
    SimpleNaturalIdEmployee employee = new SimpleNaturalIdEmployee();
    employee.setName(name);
    employee.setBadge(badge);


    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();
        session.save(employee);
        tx.commit();
    }
    return employee;
}

This code creates a new employee, with a specific badge number (5401). It then uses Session.byId(SimpleNaturalIdEmployee.class) to acquire a loader for the entity, and calls load(id), with the ID returned by the createSimpleEmployee() method.

There’s an interesting thing happening here, though, that the code demonstrates without it necessarily being obvious from the code level.

When we run this method, we actually load two references – or else the test for equivalency wouldn’t make any sense.20 However, if we look at the actual SQL executed in the Session, we see only one call being issued.

This is because Hibernate will cache the natural IDs in objects that it loads in a session. When we use the natural ID in the load accessor, Hibernate looks in the session cache and finds that natural ID – and knows that this is the reference for which we’re asking. It doesn’t need to go to the database because it already has it in memory.

This helps make the class more self-documenting, as well as slightly more efficient; it means that if we have a data about a person from the real world, the API is more efficient. We can find a given employee by using a naturally indexed badge number instead of relying on other indexes, even if at the database level the other indexes do come into play.

An entity with a compound natural ID merely has more fields annotated with @NaturalId. Let’s create an employee for whom a section and department are a natural ID,21 as shown in Listing 6-37.

Listing 6-37. An Entity with a Compound Natural ID
package chapter06.naturalid;

import org.hibernate.annotations.NaturalId;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    @NaturalId
    Integer section;
    @NaturalId
    Integer department;
    String name;


    public Employee() {
    }


    // accessors and mutators removed for brevity
}

Next, let’s look at a test that demonstrates the use of the natural ID loader, shown in Listing 6-38.

Listing 6-38. The Natural ID Loader in Action
@Test
public void testLoadByNaturalId() {
    Employee initial = createEmployee("Arrowroot", 11, 291);
    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        Employee arrowroot = session
                .byNaturalId(Employee.class)
                .using("section", 11)
                .using("department", 291)
                .load();
        assertNotNull(arrowroot);
        assertEquals(initial, arrowroot);


        tx.commit();
    }
}


private Employee createEmployee(String name, int section, int department) {
    Employee employee = new Employee();
    employee.setName(name);
    employee.setDepartment(department);
    employee.setSection(section);
    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();
        session.save(employee);
        tx.commit();
    }
    return employee;
}

This is very similar to our previous test for natural ID usage: we create an employee, and then search for the ID. The object returned by Session.byNaturalId() has a using() method that takes a field name and the field value, as opposed to using a single reference for the identifier. If we don’t include every field making up the natural ID, we’ll get an exception.

Note that we’re using the load() method; if the natural ID is not present in the database, load() will return a signal value of null.

Now let’s look at another test in Listing 6-39. This one, which uses getReference(), will throw an exception if the ID isn’t present, so we don’t need the check for null.

Listing 6-39. A Test That Uses getReference()Instead of load()
@Test
public void testGetByNaturalId() {
    Employee initial = createEmployee("Eorwax", 11, 292);
    try (Session session = SessionUtil.getSession()) {
        Transaction tx = session.beginTransaction();


        Employee eorwax = session
                .byNaturalId(Employee.class)
                .using("section", 11)
                .using("department", 292)
                .getReference();
        System.out.println(initial.equals(eorwax));
        assertEquals(initial, eorwax);


        tx.commit();
    }
}

Summary

In this chapter, we used JPA 2 annotations to add metadata to our POJOs for Hibernate, and we looked at some Hibernate-specific annotations that can enhance these at the cost of reduced portability.

In the next chapter, we’re going to discuss JPA configuration for Hibernate, more of the object life cycle, and data validation.

Footnotes

1 Most IDEs can generate XML mappings for you; also see JBoss Tools ( http://tools.jboss.org/ ), XDoclet ( http://xdoclet.sourceforge.net/xdoclet/index.html ), and MyEclipse ( https://www.genuitec.com/products/myeclipse/ ) for other possibilities. With that said, most people prefer the annotations, for good reason.

2 There’s probably value in creating tests for your object model, which would, one hopes, eliminate this as a concern, but there’s no point in creating extra work for its own sake.

3 The JPA configuration was largely derived from Hibernate’s configuration specification, which preceded JPA.

4 Naturally, there’s a way around this. The normal requirement is to have a no-argument constructor; interceptors allow you to not have to do this.

5 The reasoning here is that if you needed something to be part of the object’s state, you’d include it in the database, which would mean you wouldn’t need to set it when the object was instantiated. With that said, your mileage may vary; do what works for you.

6 Actually, the default generator is the “assigned” generator, which means the application is responsible for assigning the primary key before save() is called. This ends up having the same effect as if no key generation is specified.

7 Serializable relies on Java’s introspection to serialize and deserialize data. Externalizable forces the author to implement explicit serialization mechanisms. Externalizable can be far faster, but this doesn’t give you any real benefits in this case.

8 However, the data for the class might not be initialized yet. When an instance is loaded from the session, the data might have been retrieved eagerly, but the object won’t be initialized until something in it has been requested. This can yield some “interesting” behaviors, some of which were exposed in the previous chapters.

9 This is not an endorsement of XML configuration. Use @Transient instead.

10 The ISBN example required an additional annotation for “group” because group is a reserved word in SQL.

11 You could also use this situation – where you want an embedded object spread across multiple tables – as a sign that maybe you’re not meant to use object-relational mapping for those entities. But the truth is that you’re probably mapping it incorrectly. Probably.

12 An association is bidirectional if each entity maintains a property or field representing its end of the same relationship. For example, if our Address class maintained a reference to the Publisher located there, and the Publisher class maintained a reference to its Address, then the association would be bidirectional.

13 When a join table is being used, the foreign key relationship is maintained within the join table itself – it is therefore not appropriate to combine the mappedBy attribute of the @OneToMany annotation with the use of a @JoinTable annotation.

14 That is to say, the highest class in the hierarchy that is mapped to the database as an entity should be annotated in this way.

15 Before you think this example is entirely contrived, some titles can be amazingly long. Check out   http://oldbooktitles.tumblr.com/ for some examples that might not fit in traditional-size columns.

16 Note that something being possible is very different from something being preferable. Your author knows of exactly zero instances of this feature having been used in production; this doesn’t mean it’s not ever been used, but it’s far from common.

17 Again, this is not an endorsement of the Hibernate XML configuration. It’s also not exactly a condemnation, but…

18 Note that there are different views of this. Most anecdotal data would suggest artificial keys as primary keys because they’re short and immutable by nature; however, you can find many who advocate natural keys because they naturally map to the data model instead of adding to it. With that said, there are some data-oriented applications (data warehousing, for example) in which artificial keys are advocated without opposition. With this in mind, the recommendation stands: use artificial keys. Your data is likely to be warehoused at some point.

19 The @UniqueConstraints annotation, mentioned earlier in this chapter, can do the same for a compound index. With that said, we’re trying to look at a better way to do at least some ordering and indexing.

20 Let’s assume that we usually make sense.

21 In the previous footnote, we said that we usually make sense. This is a good example of when we really don’t; this is horribly contrived and would earn a solid scolding in an actual project. With that said, the code works and is fairly demonstrative of the concept.

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

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