Books

Thursday, October 24, 2013

Understanding Entity Relationship Mapping, Part 2: Annotating Entity Relationships

In Understanding Entity Relationship Mapping, Part 1: The basic concepts I glazed over some of the basic concepts that would be encountered when dealing with establishing relationships between Entities. This post would take a look at how to use Annotations to specify these relationships.

The relationships that would be annotated are the four types that you have in relational data model:

  1. One-To-One annotated with @OneToOne
  2. Many-To-One annotated with @ManyToOne
  3. One-To-Many annotated with @OneToMany
  4. Many-To-Many annotated with @ManyToMany


If concepts like Cardinality, Direction of Relationship, Cascades, Owning side and Inverse side of Entity relationship is new to you, then it is would be good to first read Understanding Entity Relationship Mapping, Part 1: The basic concepts

The relationship would be classified into two: Single Valued Mappings and Collection Valued Mappings. It is worth stating that the examples were written with Hibernate's implementation of JPA in mind, but would work with any other standard implementation of JPA 2, as no Hibernate specific annotation is included.

So let us begin.




1. Single Valued Mappings (OneToOne and ManyToOne)


@OneToOne Unidirectional
An example would be the relationship between a Person and Life. A person can only have one life, and from the person information you can get to the life information. In JPA this would be represented as

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

    @Basic
    private String name;

    @OneToOne
     private Life life
}

and

@Entity
class Life {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id

    @Basic
    private Long lifetime;

    @Basic
    private Collection accomplishments;

}

With the above code, the @OneToOne annotation defines the One-To-One relationship and when the Person table is created, it would have a column that would serve as foreign key that holds the primary key from the Life Table. By default this column name would be [reference_table]_[primarykey of reference_table] so with the code above, this would be life_id

In case you do not want to go with the default naming convention, you can use @JoinColumn to specify the name for the join column, or in relational palace, foreign key column that is created. That is:

.....
@OneToOne
@JoinColumn(name="lifetime_id")
private Life life
.....


Another thing you can have is to explicitly specify the column that would be created and made the join column in your entity definition. As you can see, from the example above, the life_id (or lifetime_id) column found in the created table is no where defined as property in the Person Entity. In some situation, (or based on personal preference) it might be desired to have the explicit definition of the column.

To have this kind of explicit specification, you annotate the field as you would any column, but in other for that set up to work, you have to set the explicitly defined foreign key column to insertable false and updatable false. So for example the above Person entity would look thus:

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

    @Basic
    private String name;

    @Column(insertable=false, updatable=false)
    private Long life_id

    @OneToOne
    @JoinColumn(name="life_id")
    private Life life;
}


For more explicit definition the @JoinColumn annotation has another field: referencedColumn which points to the Primary Key, the Join Column references. We see the usage of referencedColumn in the @ManyToOne mapping example below.


@OneToOne Bidirectional
In some instances you would want to reach the table that has the foreign key from the referenced table. In more JPA specific term you want to reach the Owning Side of the relationship from the Inverse Side.

For example from the Life table you may want to get the Person that lived that life. This is possible using the mappedBy property.

It should be noted that In a One-To-One Bidirectional set up, nothing is reflected in the Tables that is created for the Inverse side. The ability to navigate back to the Owning Entity is taken care of in the ORM layer. So if you want to be able to access the Person from the Life Entity, you annotate it thus:

@Entity
class Life {
     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long id

     @Basic
     private Long lifetime;
     .........

     @OneToOne(mappedBy="life")
     private Person person;
}

Where the value of mappedBy points to the field that possess the mapping relationship in the Owning Entity. In this case “life” the field that possess the mapping relationship in Person Entity

@ManyToOne Unidirectional
Relationship between Planets and Solar Systems is an example of Many-To-One. Many planets can belong to One Solar System.

To map this relationship you use @ManyToOne annotation. Just like in the previously @OneToOne annotation mapping, @JoinColumn and mappedBy have the same functionality.

In this example we would use referencedColumn which is used to specifically state the column holding the primary key in the referenced entity.

It is also worth mentioning when you have @ManyToOne, the Many side is designated as the Owning side.

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

    @Basic
    private String planetName;

    @ManyToOne
    @JoinColumn(name="solar_system_id", referencedColumn="solar_system_primary_id")
    Private String solarSystem;
....
}

and

@Entity
class SolarSystem {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long solar_system_primary_id;

    @Basic
    private String solarSystemName;
    ....
}

So from the code above, the Planet table that would be created would have a column named solar_system_id that would reference the primary key in the solar_system_primary_id column in the SolarSystem table.

You would notice that this relationship is unidirectional. Planet has the information about the Solar System it belongs but looking at the SolarSystem Entity, you can not tell its planets. To make it possible to navigate to the planets in a Solar System we make use of the mappedBy field previously described. Using mappedBy this way would then be a case of Collection Valued Mapping. Which would be discussed next:

2. Collection Valued Mappings (OneToMany and ManyToMany)


@OneToMany
Since the opposite side of @OneToMany (i.e. the @ManyToOne side) usually serves as the owning side and thus holds the mapping relationship, the @OneToMany side is, most of the time mapped with @OneToMany and qualified with mappedBy. The only difference from the examples we have seen so far is that in a Collection valued mapping, the entity fields it annotates needs to reference a collection instance. So moving on with the Planet/Solar system from above, if we want the SolarSystem Entity to be aware of the relationship, we annotate it thus:

@Entity
class SolarSystem {
     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long solar_system_primary_id;
   
     @Basic
     private String solarSystemName;

     @OneToMany(mappedBy=solarSystem)
     private Collection planetList = new ArrayList<>();
....
}

Specifying Cascade and Fetch Type
Cascade and Fetch Type were two concepts explained in Understanding Entity Relationship Mapping, Part 1: The basic concepts. We can now show how they are put to use via annotations. We continue using our Planet and SolarSystem entity to illustrate.

As previously defined:
Cascading is used to indicate if actions performed on an Entity in a relationship should be propagated to the other entity at the end of a relationship.
So if we want to define what happens to planets when their corresponding solar system has actions performed on them, we include the cascade property in the relationship mapping annotation.

@Entity
class SolarSystem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long solar_system_primary_id;

@Basic
private String solarSystemName;

@OneToMany(mappedBy=solarSystem, cascade=CascadeType.REMOVE, fetch=FetchType.EAGER)
private Collection planetList = new ArrayList<>();
...
}

The above annotation states that when a Solar System is removed, its associated planets should be removed also. The cascade property can take any of these Enum constants: CascadeType.ALL, CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH.

Again, note that cascade properties can be placed on any side of the relationship as long as it makes sense.

For instance you would not want the Planet side of the relationship to be annotated with CascadeType.REMOVE since you would not want to destroy the whole solar system just because one planet got destroyed.

The above example also has the fetch property added. As stated in the previous post:

Fetch is used to define how data from a related entity is gotten. An Eager Fetch states that the related/target Entity should be fetched at the point where the Source Entity is fetched. A Lazy Fetch on the other hand, states that the target entity should only be fetched at the point where the field referencing it from the source entity is being accessed.

In the code above, setting the fetch property to FetchType.EAGER means that once a SolarSystem Entity is fetched, the corresponding planets would also be fetched from the database and planetList would hold the reference to them. The other option you can have is FetchType.LAZY

@ManyToMany
@ManyToMany is the kind of entity relationship where entities are associated with collections of other entities they are related too, and the entities have overlapping associations with the same target entities.

A tacky example, from previous post, being a set of boys and girls in open relationships.

In previous types of relationship, we have being using the @JoinColumn annotation, together with the corresponding relationship annotation to specify the entity relationships. With @ManyToMany, we use @JoinTable instead, and this is because to establish a Many-To-Many relationship, a Table is normally created to contain the relationships.

So our Boy/Girl Many-To-Many relationship can thus be annotated as such:

@Entity
class Boy {
     @Id
      @Column(name="BOY_ID")
     private Long id;

     @Basic
     private String name;

     @ManyToMany(fetch = FetchType.EAGER)
     @JoinTable(name="BOY_GIRL", 
joinColumns=@JoinColumn(name="BOY_ID"), 
inverseJoinColumns=@JoinColumn(name="GIRL_ID"))
     private Collection girls;
...
}

And the Girl Entity

@Entity
class Girl {
     @Id
     @Column(name="GIRL_ID")
     private long id;

    @Basic
    private String name;
   
     @ManyToMany(mappedBy=girls, fetch = FetchType.EAGER)
     private Collection boys;
...
}

In the above example, the Boy Entity is the Owning side of the relationship since it contains the @JoinTable annotation while the Girl is the Inverse Side. The name of the join table that would be created would be BOY_GIRL as specified in the @JoinTable annotation.

JoinColumn value of the @JoinTable annotation uses @JoinColumn annotation to specify the column which contains the foreign_keys in the join table which references the primary key in the owning entity. For example, in the join table, BOY_GIRL that is created, BOY_ID would be the column that holds the foreign key that references the primary key in the BOY Table. inverseJoinColumn value of the @JoinTable does the same but for the Inverse side of the relationship.

All the previous described function of mappedBy, fetch, cascade holds true also in a @ManyToMany mapping.

No comments:

Post a Comment