In the previous chapter, we saw that an entity can be associated with another through a relationship, which can be one-to-one, one-to-many, many-to-one, or many-to-many. Relationships under JPA are polymorphic, which means you can take advantage of object oriented inheritance, if required.
JPA requires for all relationship mapping that there is one side that is identified as the target and therefore the opposite side is the owner. The primary key of the target entity is always referenced in the mapping of entity relationships.
In Java and JPA, entity relationships are by default unidirectional, which means that it is straightforward to traverse from the master-the owning entity-to the detail-the target entity. The Java developer must explicitly supply the inverse direction in order to facilitate bidirectional entity relationships.
Relationships are defined by association of primary keys in database tables (and views) to foreign keys in other tables (and views) inside a relational database. In the owning database table, the master, there is a database column containing the foreign keys of the target database table, the detail, which defines the primary keys. Associations are defined by the possibility to traverse to individual rows of the target database table from the source database table using the foreign key to primary mapping.
The source and target entity in the relationship model how the engineer can navigate from one entity to the other.
The one-to-one mapping relationship maps one entity directly to another entity. The annotation is @javax.persistence.OneToOne
.
The property or accessor method may be further annotated with the @javax.persistence.JoinColumn
to further identify the target database column that contains the primary key in the target entity.
Let us look at the full attributes of the @OneToOne
annotation:
We already saw some examples of one-to-one mapping in the previous chapter.
Let us examine in detail the relationship between the employee and the address:
@Entity public class Employee implements java.io.Serializable { /*...*/ @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name="EMPLOYEE_ADDRESS_FK", referencedColumnName="ADDRESS_ID") private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } /*...*/ }
We have established a one-to-one unidirectional relationship between the Employee
entity and the Address
entity. The owner of the relationship is Employee
and this determines the navigability.
Here, we configure the relationship with the @JoinColumn
annotation in order to customize the database column name EMPLOYEE_ADDRESS_FK
and also to specify the reference database column name in the target entity ADDRESS_ID
.
Here is a table that describes the attributes for @JoinColumn
annotation.
It is easy to save one-to-one related entities using JPA. All you need are the entity beans themselves and then use a method to create the dependency, and thus the association.
Here is an example that creates and saves both Employee
and Address
:
@PersistenceContext EntityManager em; public void create() { Employee employee = new Employee(); Address address = new Address(); employee.setAddress(address); em.persist(employee); }
This will work because we have chosen CascadeType.ALL
and thus saving the Employee entity, cascades the operation from the owning entity to the dependent entity, which is the Address
.
We have chosen to allow nullable references in the relationship, and therefore we can adopt a method that removes the address from the employee record, shown as follows:
@PersistenceContext EntityManager em; public void clearAddress( int empId ) { Employee employee = em.find(Employee.class, id ); employee.setAddress( null ); em.persist(employee); }
Now, let us move on to the bidirectional relationship.
Now let us make this one-to-one relationship between an employee and an address bidirectional. In order to achieve this, we still have to inform JPA which one of these entities is the owner of the relationship. In this case, we still want the employee entity to be owner of the address.
Here is the code to achieve a bidirectional relationship:
@Entity public class Address implements java.io.Serializable { /*...*/ @OneToOne(mappedBy="address") private Employee employee public Employee getEmployee() { return employee; } public void setEmployee(Employee employee) { this.employee = employee; } /*...*/ }
In the Address
entity, we add a brand new field employee and annotate it with @OneToOne
. Here, we supplied the inverse property mappedBy="address"
in order to complete the inverse relationship.
In Java code, persisting the bidirectional entities must always be built from both sides of the relationship. Entities in Java behave like any other Java object that has a relationship. As a developer, you are required to configure both sides of the relationship in memory.
Here is a code that creates the Employee
and Address
records for a bidirectional association:
@PersistenceContext EntityManager em; public void create() { Employee employee = new Employee(); Address address = new Address(); employee.setAddress(address); em.persist( employee ); }
Failure to configure both sides of the relationship in bidirectional mappings can result in peculiar data management errors.
Let's move on to mapping composite foreign keys.
JPA can map entities with composite keys into one-to-one relationships. For this task, we need to annotate the field or property accessor with @javax.persistence.JoinColumns
, which accepts an array of @JoinColumn
declarations.
Here is an example of a record, a shipping record, with a set of properties:
@Entity public class Shipping implements Serializable{ private long id; private String delivery; private String carrier; private String costCenter; /*...*/ }
Let's suppose we are only interested in the one-to-one relationship between the Invoice
and the Shipping
entities, except we only want to associate across id
and costCenter
.
Here is some example code, which demonstrates what we can achieve with a composite key:
@Entity public class Invoice implements java.io.Serializable { /*...*/ @ManyToOne @JoinColumns({ @JoinColumn(name="SHIPPING_ID", referencedColumnName="ID"), @JoinColumn(name="COST_CENTER_REF", referencedColumnName="COST_CENTER") }) private Shipping shipping; public Shipping getShipping() { return shipping; } public void setShipping( Shipping shipping) { this.shipping = shipping; } }
The code in the Invoice
class creates a one-to-one relationship, which may be a null
reference, meaning an invoice can have no shipping record at all.
JPA maps the entity Invoice
into the database table called INVOICE
and with the additional database columns, which are the foreign key for referencing the shipping entity, namely SHIPPING_ID
and COST_CENTER
.
The one-to-many mapping relationship maps one entity directly to multiple entitles. The annotation is @javax.persistence.OneToMany
. The source entity is owner of a collection of target entities.
The inverse relationship of a one-to-many is a many-to-one. By contrast to either to one-to-many or many-to-one relationships, the inverse of many-to-many relationship is itself.
Let us look at the full attributes of the @OneToMany
annotation:
Let's see an example of a one-to-many in action. Employee records can have multiple telephone numbers for work, home, fax, mobile, and so on. Here is a simple phone entity bean.
@Entity public class Phone implements java.io.Serializable { @Id @Column(name="PHONE_ID") private int id; private String type; // home,work,main,fax private String interCode; // international prefix private String areaCode; private String localCode; }
The Phone
entity has a primary id, a phone type, and international, area, and local dialing codes. We assume that the application has an automatic parser for deciphering a full telephone number into its constituent dialing code parts.
Here is the Employee
modified to accept a list collection of telephone entities:
@Entity public class Employee implements java.io.Serializable { @Id @Column(name="EMP_ID") private long id; /*...*/ @OneToMany(cascade = CascadeType.ALL) private List<Phone> phones; public List<Phone> getPhones() { return phones; } public void setPhones(List<Phone> phone) { this.phones = phones; } }
In this example, the JPA provider creates a join table linking the Employee
to Phone
entities, which by default is EMPLOYEE_PHONE
. The table has two database columns: the first is the primary key of the Employee
entity, which is called Employee_EMP_ID
, and the second is the foreign key of the Phone
entity, which is called Phone_PHONE_ID
.
Let us see this in tabular form, as shown next:.
The table EMPLOYEE:
EMP_ID |
EMP_TYPE |
FIRST_NAME |
LAST_NAME |
---|---|---|---|
32368 |
Perm |
Vernon |
Reid |
40201 |
Contractor |
Paul |
Gilbert |
The table PHONE
PHONE_ID |
TYPE |
INTER_CODE |
AREA_CODE |
LOCAL_CODE |
---|---|---|---|---|
1002 |
Work |
+44 |
207 |
5459923 |
1004 |
Work Fax |
+44 |
207 |
5451234 |
1006 |
Home |
+1 |
812 |
1237884 |
The table EMPLOYEE_PHONE
Employee_EMP_ID |
Phone_PHONE_ID |
---|---|
32668 |
1002 |
32368 |
1004 |
40201 |
1008 |
Of course, we assume that the underlying database allows lower and upper case names for its entities.
A @OneToMany
can be defined with either a join table, or foreign key in the target object's table referencing the source object table's primary key. If we choose the second option, with the foreign key in the target entity then to uphold the relationship, we need to add a @JoinColumn
annotation to the list collection field or property accessor.
Let's add a join column to the employee entity:
@Entity public class Employee implements java.io.Serializable { /*...*/ @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name="EMP_FK_ID", referencedColumnName="EMP_ID") private List<Phone> phones; }
Here the join column is actually created as part of the dependent entity Phone
. In other words, the extra column is a foreign key in the target entity, which the entity directly has no responsibility to take care of and there is no equivalent in Java!
Consequently, a unidirectional one-to-many mapping is only officially supported in JPA 2.0 or better. Such a standard conforming persistence provider creates a database table PHONE
with an additional foreign key column EMPLOYEE_FK_ID
. The join table is unnecessary in this relationship type.
The table PHONE
looks like the following:
PHONE_ID |
TYPE |
INTER_CODE |
AREA_CODE |
LOCAL_CODE |
EMP_FK_ID |
---|---|---|---|---|---|
1002 |
Work |
+44 |
207 |
5459923 |
32368 |
1004 |
Work Fax |
+44 |
207 |
5451234 |
32368 |
1006 |
Home |
+1 |
812 |
1237884 |
40201 |
The solution to the update problem for an unknown foreign key, which has existed since JPA 1.0, is to make the one-to-many relationship bidirectional, or use an explicit join table with an explicit foreign constraint that always enforces a one-to-many relationship in the database.
Let's look at the bidirectional form first.
The target entity requires a many-to-one mapping.
The Phone
entity takes a new property, the employee, and references to the owning entity.
@Entity public class Phone implements java.io.Serializable { /* ... as before ... */ @ManyToOne @JoinColumn(name="EMP_ID_FK") private Employee employee; }
Now the Phone
entity knows about the database column as a foreign key. We can certainly access the employee in Java programming.
In order to be bidirectional the owning entity must be configured with an inverse mapping. It needs to know what the field is or the property accessor that provides the inverse reference back to the source entity.
For the Employee
entity, we provide this information with the mappedBy
attribute.
@Entity public class Employee implements java.io.Serializable { /*... */ @OneToMany(cascade=CascadeType.ALL, mappedBy="employee") private List<Phone> phones; /*...*/ }
The @OneToMany
signifies that the list collection of Phone
entities is managed and owned by the Employee and there is an inverse relationship through the method call chain Employee.getPhones().get(N).getEmployee()
, where N
is some primitive integer type.
Therefore to add a new Phone
and to remove a Phone
entity record, we can have methods in the Employee
entity like the following:
@Entity public class Employee implements java.io.Serializable { /*...*/ public boolean addPhone( Phone phone) { if ( ! phones.contains( phone) ) { Employee oldEmployee = old.getEmployee(); if ( oldEmployee != null ) { removePhone( oldEmployee ) } phones.add( phone ); return true; } else { return false; } } public boolean removePhone( Phone phone ) { if ( phones.contains( phone) ) { phones.remove(phone); phone.setEmployee(null); return true; } else { return false; } } }
It is very important to ensure that in the Java application both references link to each other.
The removePhone()
safely demonstrates how to delete the Phone
entity from the employee entity and sets the reference to null; there is a check that the record is part of the collection beforehand.
The addPhone()
adds a new phone entity to the employee's list collection provided it is not already contained. We sanity check the method by removing the Phone
entity from the old Employee
record, if any, and check if it was managed beforehand by reusing the removePhone()
method. The new phone entity is added to the employee's current list collection, and the inverse relationship is also set in the phone entity.
JPA allows developers to explicitly define a join table with the candidate keys from the respective database tables. The @javax.persistence.JoinTable
annotation configures join table for all of the types of relationship: @OneToOne
, @OneToMany
, @ManyToOne
, and @ManyToMany
. The @JoinTable
annotation specifies the owning side of the relationship in Java.
It requires an array of join columns and for inverse relationship an array of inverse join columns.
In the following code,, we have our two entities Employee
and Phone
. The Employee
entity-side is annotated explicitly as a join table:
@Entity public class Employee implements java.io.Serializable { /*...*/ @OneToMany(cascade = CascadeType.ALL) @JoinTable(name="EMP_PHONE_LINK", joinColumns= @JoinColumn( name="EMP_FK", referencedColumnName="EMP_ID"), inverseJoinColumns= @JoinColumn( name="PHONE_FK", referencedColumnName="PHONE_ID", unique=true ) ) private List<Phone> phones; }
The phone list collection field is annotated with @JoinTable
, with a single join column and a single inverse join column. Of course, we could have additional columns in a combination of primary and foreign keys for advanced situations.
With the code above, the JPA provider creates a database join table called EMP_PHONE_LINK
with two foreign columns, namely EMP_FK
and PHONE_FK
. Excuse the badly named database column, but I think it illustrates the point—the reference column names points back to the constituent entities and therefore database tables: EMP_ID
and PHONE_ID
.
In order to enforce the constraint of the one-to-many relationship, we set the unique
attribute, which is on the @JoinColumn
, to true
on the inverse part of the relationship.
If you choose not to specify a table explicitly, then the default join table name of the join table is a concatenation of the owning entity and the dependent names, delimited with an underscore.
This concludes the section on one-to-many relationship in JPA. We will now move on to the inverse mapping.
The many-to-one mapping relationship associates a collection of multiple entities directly to one single entity. The annotation is @javax.persistence.ManyToOne
. The source entities collectively share ownership of a single entity.
The inverse relationship of a many-to-one is a one-to-many. In comparison to a many-to-many relationship, remember that inverse of a many-to-many relationship is itself.
Let us look at the full attributes of the @ManyToOne
annotation:
Let us take two different entities for this example: many projects have an association to a particular project type, or expressed in the reverse: for a given type there can be zero or more projects.
Here is the ProjectType
entity:
@Entity public class ProjectType implements java.io.Serializable { @Id private int id; private String name; private String description; /* ... */ }
Here is the Project
entity, which is the owner of the relationship:
@Entity public class Project implements java.io.Serializable { @Id private int id; private String name; private String description; @ManyToOne(cascade=CascadeType.ALL) @JoinColumn(name="PROJ_TYPE_FK") private ProjectType type; public ProjectType getType() { return type; } public void setType(ProjectType type) { this.type = type; } /* ... */ }
In the above class Project
, the @JoinColumn
annotation configures the additional database column with a name PROJ_TYPE_FK
. By default, the persistence provider will concatenate the entity name with the foreign key name delimited with an underscore character (the database table column PROJECT_TYPE_ID
).
Most applications will use @ManyToOne
with the @JoinColumn
in order to configure an alternative database column name.
The relationship between the Project
and ProjectType
is unidirectional in the example and here are the possible states of the database tables:
PROJECT_TYPE
(table):
ID |
Name |
Description |
---|---|---|
579 |
|
FX Trading |
404 |
|
Trading Sales and Marketing |
PROJECT
(table):
ID |
Name |
Description |
PROJ_TYPE_FK |
---|---|---|---|
1 |
|
FX daily trading report |
579 |
2 |
|
New customer onboarding project |
404 |
3 |
|
FX Fix Income Upgrade |
579 |
The bidirectional many-to-one relationship is the exact mirror on the one-to-many relationship. The only difference is that the entity is ultimately the owner of the relationship in terms of a Java programming. See the section One-to-many mapping.
Be careful with cascade operations in a many-to-one mapping, especially in circumstances where the single entity is used like a database enumeration type. Removing an entire collection of project records could delete the project type too. You probably want to manage the cascade types with a set of defined operations.
The many-to-many mapping relationship associates a collection of multiple entities to another collection of multiple entities. In the Java Persistence, this annotation is called @javax.persistence.ManyToMany
. The Java developer must define which side of the relationship is the owner regardless of whether the true association is unidirectional or bidirectional. In database terms, all many-to-many associations are by definition bidirectional.
The inverse relationship of a many-to-many is a many-to-many, which is different to the associations of one-to-many and many-to-one.
All @ManyToMany
associations require a @JoinTable
as this is the practical way to represent this relationship in a relational database.
Let us look at the full attributes of the @ManyToMany
annotation:
In the example code, we extend the Project
entities with a many-to-many association of Employee
entities. That is, multiple projects have zero or one employee and zero or one project have multiple employees.
Here is the revised class for Project
:
@Entity public class Project implements java.io.Serializable { @Id private int id; /*...*/ @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE,CascadeType.DETACH)} @JoinTable(name="PROJECT_EMPLOYEE", joinColumns={ @JoinColumn(name="PROJ_ID_FK", referencedColumnName="ID" ) }, inverseJoinColumns={ @JoinColumn(name="EMPL_ID_FK", referencedColumnName="EMP_ID" ) } private List<Employee> employees; public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } /* ... */ }
Because this class Project
declared the list of employees with a join table, it is the owner of the relationship. The database table is explicitly defined as PROJECT_EMPLOYEE
; the join columns are appropriately named as PROJ_ID_FK
and EMP_ID_FK
, which are foreign keys to their respective tables.
Project configures a limited set of cascade operations from the Project
to the Employee
. By default, the case operations do not take place, but we allow general persistence, merging, and detaching of entities.
Let us tie the other side of the relationship into the Employee
entity.
@Entity public class Employee implements java.io.Serializable { /*...*/ @ManyToMany(mappedBy="employees") private List<Project> projects; public List<Project> getProjects() { return projects; } public void setProjects(List<Project> projects) { this.projects = projects; } }
An employee has a set of projects or not, and we complete the definition of bidirectional relationship by declaring projects are owned by the Employee
entity. We do this with the mappedBy
attribute. It is important to define the mappedBy
attribute in order to prevent duplicate join table being added to the database.
The JPA provider assumes that there are actually two separate relationships: a one-to-many and many-to-one, when the mappedBy
attribute is not used. This could actually be the requirement for your business application, but it might be better to annotate the entities as properly separate @OneToMany
and @ManyToOne
with defined @JoinTable
to avoid confusion.
Here are the sample database tables for the previous code:
PROJECT
(table)
ID |
Name |
Description |
---|---|---|
1 |
FXDAILYREP |
FX daily trading report |
2 |
ONBOARD |
New customer onboarding project |
3 |
FXFIXINCOME |
FX Fix Income Upgrade |
EMPLOYEE
(table)
EMP_ID |
FIRST_NAME |
LAST_NAME |
---|---|---|
32368 |
Vernon |
Reid |
40201 |
Paul |
Gilbert |
50203 |
Jennifer |
Batten |
60205 |
Joe |
Satriani |
PROJECT_EMPLOYEE
EMP_ID_FK |
PROJ_ID_FK |
---|---|
32368 |
3 |
40201 |
2 |
50203 |
1 |
50203 |
3 |
40201 |
2 |
60205 |
3 |
Mapping a unidirectional many-to-many relationship is fairly straightforward. The association has to be declared on the owner side with a @ManyToMany
. The target association has no direct relationship to the owner: no @ManyToMany
is required.
Let's take another example with the project entity. Every project has zero or more milestones, and some milestones are shared between projects.
Here is the Project
code with a new field, a list collection of Milestone
entities:
@Entity public class Project implements java.io.Serializable { @Id private int id; /*...*/ @ManyToMany(cascade=CascadeType.ALL, fetch=LAZY ) @JoinTable(name="PROJECT_HAS_MILESTONE", joinColumns={ @JoinColumn(name="PROJ_ID_FK", referencedColumnName="ID" ) }, inverseJoinColumns={ @JoinColumn(name="MILESTONE_ID_FK", referencedColumnName="ID" ) } private List<Milestone> milestones; public List<Milestone> getMilestones() { return milestones; } public void setMilestones(List<Milestones> milestones) { this.milestones = milestones; } /* ... */ }
We use a join table as always for many-to-many association. The @JoinTable
annotation declares the table name as PROJECT_HAS_MILESTONE
, and the join columns at PROJ_ID_FK
and MILESTONE_ID_FK
respectively. So this code is semantically the same as the previous bidirectional example between projects and types.
Here is the code for the Milestone entity bean:
@Entity public class Milestone implements java.io.Serializable { @Id private int id; @Column(nullable=false) private String name; private float tolerance; @Column(nullable=false) private String requirements; @Temporal(TIMESTAMP) private Date startDate; @Temporal(TIMESTAMP) private Date finishDate; /* ... */ }
In a unidirectional @ManyToMany
association we do not declare the target side of the relationship and therefore there is no need for the annotation in the Milestone object.
As with all bidirectional relationships, Java Persistence requires the developer to connect object instances with each other. There is no secret sauce in the current JPA 2.1 specification that automates these connections.
Finally, we switch the cascade operation on the owner side of the many-to-many relationship back to CascadeType.ALL
. So persistence will perform operations such as CascadeType.PERSIST
and CascadeType.REMOVE
on the dependent Milestone records, if the master Project
entity is affected. As with all performance questions, always take measurements before and after, multiple times within a micro-benchmark—don't guess! Use the measurements to reason about the bottlenecks before modifying application code.
This concludes the entire section on entity relationship mapping.
18.191.189.23