Chapter 3. Working with Annotations

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:

  • Mapping and association
  • Behavior
  • The SQL/DDL modifier

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.

Mapping and association

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.

@Any and @ManyToAny

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:

@Any and @ManyToAny

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.

Note

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:

@Any and @ManyToAny

@MapsId

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:

@MapsId

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.

@Fetch

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.

@OrderBy

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.

@ElementCollection and @CollectionTable

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.

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

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