Continued from Part 1:
Generating Primary Keys
One thing Hibernate is good at is automatically generating primary keys. The Hibernate/EBJ 3 annotations also provide a rich support for automatic key generation, allowing a variety of strategies. The following example illustrates a frequently used approach, where Hibernate will decide on an appropriate key generation strategy depending on the underlying database:
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Long getId() {
return id;
}
Customizing Table and Field Mappings
By default, Hibernate will map persistent classes to tables and fields with matching names. For example, the above class would map to a table along the following lines:
CREATE TABLE MODELPLANE
(
ID long,
NAME varchar
)
This is fine if you are generating and maintaining the database yourself, and makes your code a lot easier to maintain if you can get away with it. However, it doesn't suit everyone's needs. Some applications need to access external databases, and others may need to confirm to company database naming conventions. If necessary, you can use the @Table and @Column annotations to tailor your persistence mappings, as shown here:
@Entity
@Table(name="T_MODEL_PLANE")
public class ModelPlane {
private Long id;
private String name;
@Id
@Column(name="PLANE_ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name="PLANE_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
This would map to the following table:
CREATE TABLE T_MODEL_PLANE
(
PLANE_ID long,
PLANE_NAME varchar
)
You can also customize your mappings using other table and column attributes. This lets you specify details such as column length, non-null constraints and so forth. Hibernate supports a large number of attributes for these annotations. Here are just a few:
...
@Column(name="PLANE_ID", length=80, nullable=true)
public String getName() {
return name;
}
...
Mapping Relationships
One of the most important, and complex, parts of Java persistence mapping is determining how to map relationships between tables. As elsewhere, Hibernate provides a great deal of flexibility in this area, arguably at the cost of a certain degree of complexity. We will look at a few common cases to get an idea of how this is done using annotations.
One of the most commonly-used relationships are many-to-one relationships. Suppose, in the above example, that each ModelPlane is associated with a PlaneType via a many-to-one relationship (in other words, each model plane is associated with exactly one plane type, although a given plane type can be associated with several model planes). You could map this as follows:
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
public PlaneType getPlaneType() {
return planeType;
}
The CascadeType values indicate how Hibernate should handle cascading operations.
Another commonly-used relationship is the opposite of the above: the one-to-many-to-one relationship, also known as a collection. Collections are complex beasts, both in old-style Hibernate mapping and when using annotations, and we'll just be skimming the surface here to give you an idea of hown it's done, For example, in the above example, each PlaneType object may contain a collection of ModelPlanes. You could map this as follows:
@OneToMany(mappedBy="planeType",
cascade=CascadeType.ALL,
fetch=FetchType.EAGER)
@OrderBy("name")
public List<ModelPlane> getModelPlanes() {
return modelPlanes;
}
Named Queries
One of the nice features of Hibernate is the ability to declare named queries within your mapping files. These queries can then be invoked by name from within the code, which lets you centralize queries and avoids having SQL or HQL code scattered throughout the application.
You can do this with annotations too, using the @NamedQueries and @NamedQuery annotations, as shown here:
@NamedQueries(
{
@NamedQuery(
name="planeType.findById",
query="select p from PlaneType p left join fetch p.modelPlanes where id=:id"
),
@NamedQuery(
name="planeType.findAll",
query="select p from PlaneType p"
),
@NamedQuery(
name="planeType.delete",
query="delete from PlaneType where id=:id"
)
}
)
Once defined, you can call them just as you would any other named query.
Conclusion:
Hibernate 3 annotations provides a powerful and elegant API to simplify your Java database persistence code, and we really have only scratched the surface here. You can either choose to stick to the standards, and use the Java Persistence API, or take advantage of the Hibernate-specific extensions, which provide more power and flexibility, at the cost of a certain loss of portability. In any case, by eliminating the need for XML mapping files, using Hibernate annotations allows you to simplify your application maintenance, with the additional advantage of giving you a gentle introduction into the world of EJB 3.