In this chapter, we will look at various annotations and how they are used. Most Hibernate and JPA annotations were created for a specific purpose. For that reason, annotations discussed in this chapter are grouped together according to what they are used for. Some annotations are specified in JPA and some are only available in Hibernate. We will mostly focus on Hibernate annotations, specifically the ones that are not documented well or may be tricky to use. The discussion on annotations is grouped based on the following:
You may have noticed that so far we have avoided the Hibernate configuration file and mostly focused on annotations. This is actually the goal throughout this book, since most developers prefer using annotations. In this chapter, we will pay closer attention to some important annotations.
In the previous chapter, we demonstrated many annotations that defined mapping and associations between objects; most of those are JPA annotations. In this section, we will look at more annotations that affect mapping and associations. Most of these are only available in Hibernate, but we also cover some very useful JPA annotations that you may not have used.
The @Any
and @ManyToAny
annotations are only available in Hibernate. They are defined to support polymorphism in Java, in the sense that a discriminator column determines the subclass of a parent class (or interface).
For instance, if you have a table with a discriminator column that helps distinguish one class from the other, you can use the @Any
annotation to map the rows to the correct class. We will discuss @ManyToAny
here, as @Any
is well documented in Hibernate API JavaDoc, but in short, @Any
is used for a one-to-one association.
Consider the following relations that keep track of a person's mode of transportation throughout the travel itinerary, using a single table strategy:
The DTYPE
column helps Hibernate determine which class to instantiate when it reads the data from the database, and this can be implemented as shown in the following listing:
@Entity public class Traveler { @Id @GeneratedValue private long id; private String firstname; private String lastname; @ManyToAny(metaColumn=@Column(name="dtype")) private Set<Transportation> transportation = new HashSet<Transportation>(); // getters and setters } @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="dtype") public class Transportation { @Id @GeneratedValue private long id; private double cost; } @Entity @DiscriminatorValue(value="C") public class Car extends Transportation { private String pickupLocation; private String dropOffLocation; } @Entity @DiscriminatorValue(value="A") public class Airplane extends Transportation { private String departureAirport; private String arrivalAirport; }
You might have noticed that this looks awfully similar to the way we implemented inheritance, and you don't see the value in having the @ManyToAny
annotation. In this case, the @ManyToAny
annotation really acts like a @OneToMany
, while supporting inheritance, as described in the preceding chapter. But the true purpose of the @Any
annotations is actually to support polymorphism.
In Java, the notions of inheritance and polymorphism are closely related, but they are not quite the same. Think of polymorphism as implementing one or more interfaces, and inheritance is simply a class extending another, which is different from implementing an interface. Since Java doesn't support multiple-inheritance, and in order for your class to respond to multiple method calls, it needs to implement multiple Interfaces to be polymorphic.
So, how do you actually support polymorphic behavior using Hibernate? The answer is using the @Any
annotations, as shown in the listing. Here, since we are dealing with a one-to-many association, we use @ManyToAny
. If this were a one-to-one association, we could simply use the @Any
annotation, as described in the JavaDoc:
@Entity public class Traveler { @Id @GeneratedValue private long id; private String firstname; private String lastname; @ManyToAny(metaColumn=@Column(name="trans_mode")) @AnyMetaDef( idType="long", metaValues = { @MetaValue( value="C", targetEntity=Car.class ), @MetaValue( value="A", targetEntity=Airplane.class ), }, metaType = "char" ) @JoinTable( name="traveler_transportation", joinColumns = @JoinColumn( name="traveler_id"), inverseJoinColumns = @JoinColumn( name="mode_id") ) private Set<Transportation> transportation = new HashSet<Transportation>(); // getters and setters } public interface Transportation { public double calculateCost(); } @Entity public class Car implements Transportation { @Id @GeneratedValue private long id; private String pickupLocation; private String dropOffLocation; // don't forget to implement equals and hashCode } @Entity public class Airplane implements Transportation { @Id @GeneratedValue private long id; private String departureAirport; private String arrivalAirport; // don't forget to implement equals and hashCode }
The
trans_mode
column is now the discriminator column. So, the tables now look different, as shown here:
This annotation is a JPA annotation and is very useful to associate two entities (in separate tables) while one of them has a composite ID that includes the ID of the other entity. It can be used for one-to-one or many-to-one relationships. But there is a catch! In some cases, Hibernate doesn't populate the ID fields. Consider the following tables where you have a join table, called ENROLMENT
, which associates a student with a course:
You can use the @MapsId
annotation to map these relations and associations in Hibernate, as shown in the following listing:
@Entity public class Student { @Id @GeneratedValue private long id; private String name; } @Entity public class Course { @Id @GeneratedValue private long id; private String title; } @Embeddable public class EnrolmentId implements Serializable { private long studentId; private long courseId; // don't forget equals() and hashCode() methods. } @Entity public class Enrolment { @EmbeddedId private EnrolmentId id; private String grade; @MapsId("id") @OneToOne @JoinColumn(name="studentId") private Student student; @MapsId("id") @OneToOne @JoinColumn(name="courseId") private Course course; }
But when you try to save these entities, you need to explicitly set the ID for the Enrolment
entity before attempting to save it:
session.save(student); session.save(course); EnrolmentId id = new EnrolmentId(); id.setCourseId(course.getId()); enrollment.setId(id); session.save(enrollment);
This solution is not desired, and in fact, you may discover that @MapsId
has caused a lot of grief to Hibernate users. Luckily, there is another solution that is only supported by Hibernate and doesn't work in JPA. You can declare your association in the embedded ID
class; in this case, you can add the Student
and Course
(one-to-one) associations to the EnrolmentId
class instead of the Enrolment
class:
@Embeddable public class EnrolmentId implements Serializable { @OneToOne @JoinColumn(name = "studentId") private Student student; @OneToOne @JoinColumn(name = "courseId") private Course course; // don't forget equals() and hashCode() }
Note that the composite IDs, studentId
and courseId
, are no longer needed, and when you save your entities, you don't have to manually set the IDs, as they are generated for the Student
and Course
entities. Hibernate will map enrolments correctly based on those IDs.
Another useful Hibernate annotation is @Fetch
, which can be used for instructing Hibernate to use a certain strategy when fetching the associated entities/collections. This is slightly different from the FetchType
modifier (LAZY
, EAGER
) that you can specify, for example, on a @OneToMany
annotation, in that you can specify three different fetching modes, JOIN
, SELECT
, and SUBSELECT
. We will cover this in the next chapter.
This annotation exists in both Hibernate and JPA. But in the JPA version, the syntax needs to be in JPQL. The Hibernate version of this annotation expects the orderBy
clause to be in SQL. There are several annotations that exist in both Hibernate and JPA, such as Entity
. So when you import these annotations, carefully choose the correct one from the package you want.
These two annotations are new and were introduced in JPA 2.0. They are worth mentioning here because they can be used in lieu of the @OneToMany
annotation when the owned entity is a primitive type or an embeddable object.
If your entity is associated with a collection of primitive data type, you only need to use @ElementCollection
. In this case, Hibernate will create an additional table to store the associated data, for instance, for the following example, Hibernate will create a table called person
for the Person
entity, another table called person_aliases
to store the aliases for each person, and a table called person_addresses
to store the addresses for a person as shown here. (An address is simply an embeddable class):
@Entity public class Person { @Id @GeneratedValue private long id; private String firstname; private String lastname; @ElementCollection private List<String> aliases = new ArrayList<String>(); @ElementCollection private List<Address> addresses = new ArrayList<Address>(); // getters and setters }
If you want additional control over the table name or the join columns, you can use @CollectionTable
. The JPA documentation is quite good on this.
18.117.71.211