One to One relations in JPA 2.0 22 Mar 2012
Recently I came across the need to map a one 2 one relationship from the object model to the database using JPA 2.0. The case was pretty simple as the database was nicely organized but it raised the question: what if? What if I have to deal to a legacy system or a database administrator that has to follow strict company rules. Other reasons such as security or performances may interfere with simple designs. Hence, I had a look to the diverse ways to map a one 2 one relationship. I probably forgot several cases so please do not hesitate to discuss them in the comments.
A one to one relationship consider that the objects involved in the relation are highly dependent. In Object Orientation, this corresponds to the an aggregation or a composition. In a relational model, the data can be either:
- in the same table,
- split over two (or more) tables (one per object) and linked by a foreign key, or
- split over two (or more) tables and linked by a join table.
The rest of the articles these describes different situations. Please note that for the sake of the explanation, I explicitly map all the fields even if most of the time the default mapping policy would work.
Data in the same class: Embedded class
This is especially useful for legacy code where the database design is a bit to flat for your taste.
The following figure presents the concept of an embedded class. The class Address
is embedded in the class Student
.
The idea is that Address
is an entity per se, it exists only in the context of the class Student
.
To declare a embedded class, the class itself must be annotated with @Embeddable
and its reference must be annotated with
@Embedded
.
@Embeddable
public class Address implements Serializable {
...
@Column(name = "NUMBER")
private String number;
@Column(name = "STREET")
private String street;
...
}
@Embedded
and @Basic
cannot be used together. Therefore, if required, lazy fetching must be declared field by field in the embedded class. Remember that outside of a JEE container, the actual behavior of lazy loading on @Basic
and @OneToOne
depend on the actual implementation. Eclipse link, for instance, does not perform lazy loading by default on @Basic
, @OneToOne
, and @ManyToOne
mappings. For more detail, please refer to the Eclipse-link specification.
@Entity
@Table(name = "STUDENTS")
public class Student implements Serializable {
...
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mId;
...
@Embedded
private Address mAddress;
...
}
The following SQL statement shows the code generated by the previous mapping.
SELECT ID, FIRST_NAME, PHONE_NUMBER, BIRTH_DATE, LAST_NAME, NUMBER,
CITY, STREET, POSTAL_CODE
FROM STUDENTS
Data in different classes: Secondary Tables
In the second scenario, the OO model is composed on only one class but the relational model is split over several tables.
The foreign key is in the secondary table
In this case, the concepts of secondary tables is very useful. A secondary table is basically a table that hosts
important data that are one to one related to the data of the primary table.
Unlike the first case, a join is required and thus a key mapping is required.
In the following mapping, the secondary table PICTURES
is mapped using its STUDENT_ID
field to the ID
field
of the main table.
@SecondaryTable(name = "PICTURES",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "STUDENT_ID",
referencedColumnName = "ID"))
public class Student implements Serializable {
...
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mId;
/** A picture of the student. */
@Lob
@Basic(optional = true, fetch = FetchType.EAGER)
@Column(table = "PICTURES", name = "PICTURE", nullable = true)
private byte[] mPicture;
...
The following SQL statement shows the code generated by the previous mapping.
It joins the two tables according to the @PrimaryKeyJoinColumn
annotation.
SELECT t0.ID, t1.STUDENT_ID, t1.PICTURE, t0.FIRST_NAME, t0.PHONE_NUMBER, t0.BIRTH_DATE,
t0.LAST_NAME, t0.NUMBER, t0.CITY, t0.STREET, t0.POSTAL_CODE
FROM STUDENTS t0, PICTURES t1
WHERE (t1.STUDENT_ID = t0.ID)
The foreign key is in the host table
Similarly to the previous case, the data is split over several tables. The difference lies in the foreign key position. Here the foreign key is hosted in the primary table.
From a mapping point of view, it is very similar to the previous case. Indeed only the key column names have to be changed to reflect the organization.
@SecondaryTable(name = "PICTURES",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "PICTURE_ID",
referencedColumnName = "PICTURE_ID"))
public class Student implements Serializable {
The following SQL statement shows the code generated by the previous mapping.
Again, the data are joined according to the content of the @PrimaryKeyJoinColumn
annotation.
SELECT t0.ID, t0.PICTURE_ID, t1.PICTURE_ID, t1.PICTURE, t0.FIRST_NAME, t0.PHONE_NUMBER, t0.BIRTH_DATE,
t0.LAST_NAME,t0.NUMBER, t0.CITY, t0.STREET, t0.POSTAL_CODE
FROM STUDENTS t0, PICTURES t1
WHERE (t1.PICTURE_ID = t0.PICTURE_ID)
First and second class JPA citizens
In the previous examples, the table PICTURES
does not have a business existence in itself. It is a secondary table because its data only have meaning in relation to the data of the primary table. Sometimes, we may want to treat the second objects as first class JPA citizens and thus we must put the @Entity
annotation on it. In this case, we have to use the @OneToOne
annotation for the mapping. Unlike the previous mappings, @OneToOne
enable bidirectional mapping.
In the last example, the tables STUDENTS
and BADGES
have a one to one relationship modeled with a foreign in the BADGES
table to the STUDENTS
table. In this case, the owning side is the Badge
entity has it contains the foreign key.
As mentioned before, the class Badge
is now an entity has it has a business existence without Student
. The interesting part is the @JoinColumn
annotation that specifies the local column that is the foreign key (STUDENT_ID
) as well as to which column of the foreign table its corresponds (ID
).
@Entity
@Table(name = "BADGES")
public class Badge implements Serializable {
...
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mId;
@Column(name = "SECURITY_LEVEL")
private Long mSecurityLevel;
@OneToOne
@JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID")
private Student mStudent;
...
As we want the other side to be aware of the relation (bidirectional), it is required to add the mappedBy
attribute to the @OneToOne
annotation. This attribute references the (Java) property in the entity that is the owner of the relationship.
@Entity
...
public class Student implements Serializable {
...
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mId;
/** The Student's badge. */
@OneToOne(mappedBy = "mStudent")
private Badge mBadge;
...
}
Using the previous mapping, JPA 2.0 produces the following SQL statements to load the Student object.
SELECT t0.ID, t1.STUDENT_ID, t1.PICTURE, t0.FIRST_NAME, t0.PHONE_NUMBER,
t0.BIRTH_DATE, t0.LAST_NAME, t0.NUMBER, t0.CITY, t0.STREET,
t0.POSTAL_CODE
FROM STUDENTS t0, PICTURES t1
WHERE (t1.STUDENT_ID = t0.ID)
SELECT ID, SECURITY_LEVEL, STUDENT_ID
FROM BADGES
WHERE (STUDENT_ID = ?)
Conclusion
JPA 2.0 offers many way to represent one to one relationships. This flexibility allows to handle many different scenarios that may happen when integrating legacy databases. The examples used in this blog are to be found in the JEE-6-Demo project on Google code hosting.