If our application database is having a large number of tables ,in most of the applications , data model will be complex. So the tables needs to be related each other. In conventional Java applications , this is achieved by writing complex join queries. But it is less effective , since the debugging is also getting complex.JPA provides options to map database tables through mapping between entities. The various relationships are :One To One, One To Many ,Many To One and many To Many. In this chapter we are discussing a One To One Relationship in JPA with suitable example.
JPA One To One Mapping
If a source object has an attribute that references to a target object . Then , that is a one to one relationship. In this case inverse relation ship can also One To One. But there is no guarantee for the inverse relationship.
Consider an Employee class which has an attribute of class Passport. Each employee is having a unique Passport object in it. And an Employee object has one and only one Passport object in it. The class diagram can be represented as .
When we consider the database , these two entities can be represented in two separate database tables.We can relate those two tables by storing a unique value from second table as foreign key in each row of first table.Our Employee and Passport entities are mapped in the example given below.
JPA One To One Mapping Example
Tools & Softwares Required
1)Eclipse Indigo – Download Link
2)Java EE SDK 1.6 –Download Link
3)MySQL Server 5.6 Community edition – Download Link
4)SQLYog Community Edition-Download Link
5)OpenJPA libray 2.1 – Download Link
In this example we are using OpenJPA as provider implementation. The steps in creating a JPA project in eclipse is explained in an earlier chapter.We are using the same set up here also.
Let us create the database JPASAMPLE and tables EMPLOYEE_TABLE and PASSPORT_TABLE in it.We can either use SQLYOG or the command line client of MySQL for creating the database and tables.
CREATE DATABASE JPASAMPLEDB;
USE JPASAMPLEDB;
CREATE TABLE EMPLOYEE_TABLE( `EMP_ID` INT NOT NULL, `EMP_NAME` VARCHAR(50) NOT NULL, `PASSPORT_NUMBER` INT NOT NULL, PRIMARY KEY (`EMP_ID`) );
CREATE TABLE PASSPORT_TABLE( `PASSPORT_NUMBER` INT NOT NULL, `ADDRESS_LINE1` VARCHAR(100) NOT NULL, `ADDRESS_LINE2` VARCHAR(100) NOT NULL,`STATE_NAME` VARCHAR(50) NOT NULL, `COUNTRY_NAME` VARCHAR(50) NOT NULL, PRIMARY KEY (`PASSPORT_NUMBER`) );
Let us see the Employee.java and Passport.java entity classes.We are doing the entity to table mapping through annotations.Primary key generation strategy is specified as AUTO.Database column mapping is done above the getter method.
Employee.java
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity(name = "Employee")
@Table(name = "EMPLOYEE_TABLE")
public class Employee implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int employeeId;
private String name;
private Passport passportDetails;
public Employee() {
}
@Id
@Column(name = "EMP_ID")
@GeneratedValue(strategy = GenerationType.AUTO)
public int getEmployeeId() {
return employeeId;
}
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
@Column(name = "EMP_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "PASSPORT_NUMBER")
public Passport getPassportDetails() {
return passportDetails;
}
public void setPassportDetails(Passport passportDetails) {
this.passportDetails = passportDetails;
}
public String toString() {
return "ID = " + getEmployeeId() + " ; Name = " + getName()
+ " ; Passport Details : " + getPassportDetails();
}
}
The passport details are storing in a separate table. For doing the one to one mapping the PASSPORT_NUMBER is storing as a foreign key in EMPLOYEE_TABLE.And the mapping is done through the OneToOne annotation.In case of xml mapping , this can be done using element . The @JoinColumn annotation is doing the mapping between the two tables.
The fetch type is specified as LAZY.This means that the object is fetched lazily from the database. Other option is EAGER.If we specify the fetch type as EAGER , then the data will be fetched from database eagerly.
The cascade value is specified as ALL. It means that the Passport object will be cascaded with parent object for all operations : detach , merge,persist,remove and refresh operations.The CascadeType enum defines all possible options.
The various options are:
CascadeType.DETACH :- Target object will be cascaded with parent object for detach operation.
CascadeType.MERGE : – Target object will be cascaded with parent object for merge operation.
CascadeType.PERSIST :- Target object will be cascaded with parent object for persist operation.
CascadeType.REFRESH :-Target object will be cascaded with parent object for refresh operation.
cascadeType.REMOVE :- Target object will be cascaded with parent object for remove operation.
CascadeType.ALL :- Target object will be cascaded with the parent object for the above operations.
Passport.java
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity(name = "Passport")
@Table(name = "PASSPORT_TABLE")
public class Passport implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int passportNumber;
private String addressLine1;
private String addressLine2;
private String state;
private String country;
public Passport() {
}
@Id
@Column(name = "PASSPORT_NUMBER")
@GeneratedValue(strategy = GenerationType.AUTO)
public int getPassportNumber() {
return passportNumber;
}
public void setPassportNumber(int passportNumber) {
this.passportNumber = passportNumber;
}
@Column(name = "ADDRESS_LINE1")
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
@Column(name = "ADDRESS_LINE2")
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
@Column(name = "STATE_NAME")
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
@Column(name = "COUNTRY_NAME")
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String toString() {
return "Passport Number = " + getPassportNumber()
+ " ; Address LIne 1 = " + getAddressLine1()
+ " ; Address Line 2 = " + getAddressLine2()
+ " ; State Name = " + getState() + " ; Country Name = "
+ getCountry();
}
}
persistence.xml
Now lets see the class with main. There are two methods : one is for inserting the data and the other is for fetching the data from database
OnetoOneSample.java
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import com.jpa.entity.Address;
import com.jpa.entity.Employee;
import com.jpa.entity.Passport;
public class OnetoOneSample {
public OnetoOneSample() {
}
public void insertRecord() {
EntityManagerFactory entityManagerFactory = Persistence
.createEntityManagerFactory("OpenJPASample");
EntityManager entitymanager = entityManagerFactory
.createEntityManager();
if (null != entitymanager) {
Employee employee = new Employee();
employee.setName("Bijoy");
Passport passportDetails = new Passport();
passportDetails.setAddressLine1("SKM");
passportDetails.setAddressLine2("Trivandrum");
passportDetails.setState("Kerala");
passportDetails.setCountry("India");
employee.setPassportDetails(passportDetails);
EntityTransaction transaction = entitymanager.getTransaction();
transaction.begin();
entitymanager.persist(employee);
transaction.commit();
System.out.println("The object " + employee + " is persisted");
}
}
public void readRecord() {
EntityManagerFactory entityManagerFactory = Persistence
.createEntityManagerFactory("OpenJPASample");
EntityManager entitymanager = entityManagerFactory
.createEntityManager();
if (null != entitymanager) {
EntityTransaction readTransaction = entitymanager.getTransaction();
readTransaction.begin();
Query query = entitymanager
.createQuery("select employee FROM Employee employee");
List list = query.getResultList();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Employee employee = (Employee) iterator.next();
System.out.println("Employee Details : " + employee);
}
readTransaction.commit();
System.out.println("Read is over..");
}
}
public static void main(String[] args) {
OnetoOneSample sample = new OnetoOneSample();
sample.insertRecord();
sample.readRecord();
}
}
Output
The object : ID = 501 ; Name = Bijoy ; Passport Details : Passport Number = 551 ; Address LIne 1 = SKM ; Address Line 2 = Trivandrum ; State Name = Kerala ; Country Name = India : is persisted
Employee Details : ID = 501 ; Name = Bijoy ; Passport Details : Passport Number = 551 ; Address LIne 1 = SKM ; Address Line 2 = Trivandrum ; State Name = Kerala ; Country Name = India
Read is over..
Bidirectional One To One Mapping
If each Employee object has a unique Passport object in it and if each Passport object has a unique Employee object in it , then it is a bidirectional relation.So the Passport.java entity class should have the inverse mapping.There is no need for separate foreign keys in each table. Foreign key is needed only in the source objects table. But the mapping using the @MappedBy annotation in the target object is important.
Employee.java is more or less same as the one already explained. The only change is in toString() method
Employee.java
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity(name = "Employee")
@Table(name = "EMPLOYEE_TABLE")
public class Employee implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int employeeId;
private String name;
private Passport passportDetails;
public Employee() {
}
@Id
@Column(name = "EMP_ID")
@GeneratedValue(strategy = GenerationType.AUTO)
public int getEmployeeId() {
return employeeId;
}
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
@Column(name = "EMP_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "PASSPORT_NUMBER")
public Passport getPassportDetails() {
return passportDetails;
}
public void setPassportDetails(Passport passportDetails) {
this.passportDetails = passportDetails;
}
public String toString() {
return "[ ID = " + getEmployeeId() + " ; Name = " + getName()
+ " ; Passport Details : [" + " Passport Number = "
+ getPassportDetails().getPassportNumber()
+ " ; Address Line 1 = "
+ getPassportDetails().getAddressLine1()
+ " ; Address Line 2 = "
+ getPassportDetails().getAddressLine2() + " ; State = "
+ getPassportDetails().getState() + "; Country = "
+ getPassportDetails().getCountry() + "]]";
}
}
The Passport.java contains an additional attribute of Employee.java.It is mapped using @OneToOne. The @MappedBy annotation is using for indicating the inverse relation.
Passport.java
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity(name = "Passport")
@Table(name = "PASSPORT_TABLE")
public class Passport implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int passportNumber;
private String addressLine1;
private String addressLine2;
private String state;
private String country;
private Employee employee;
public Passport() {
}
@Id
@Column(name = "PASSPORT_NUMBER")
@GeneratedValue(strategy = GenerationType.AUTO)
public int getPassportNumber() {
return passportNumber;
}
public void setPassportNumber(int passportNumber) {
this.passportNumber = passportNumber;
}
@Column(name = "ADDRESS_LINE1")
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
@Column(name = "ADDRESS_LINE2")
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
@Column(name = "STATE_NAME")
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
@Column(name = "COUNTRY_NAME")
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@OneToOne(mappedBy = "passportDetails", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public String toString() {
return "[ Passport Number = " + getPassportNumber()
+ " ; Address LIne 1 = " + getAddressLine1()
+ " ; Address Line 2 = " + getAddressLine2()
+ " ; State Name = " + getState() + " ; Country Name = "
+ getCountry() + " ; Employee Details : [" + "Id = "
+ getEmployee().getEmployeeId() + " ; Name = "
+ getEmployee().getName() + "]]";
}
}
Now lets see the class with main. We are using the same set of tables created in the above example.We are trying to fetch the same data inserted there .There are two methods : the first method fetches the Employee details from the table. The second method fetches the Passport details from the database. when we query for Employee , it should contain the mapped Passport details within it. Also when we query for Passport details , it should contain the mapped Employee object.
OnetoOneReadSample.java
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import com.jpa.entity.Employee;
import com.jpa.entity.Passport;
public class OnetoOneReadSample {
public OnetoOneReadSample() {
}
public void readPassportDetails() {
EntityManagerFactory entityManagerFactory = Persistence
.createEntityManagerFactory("OpenJPASample");
EntityManager entitymanager = entityManagerFactory
.createEntityManager();
if (null != entitymanager) {
EntityTransaction readTransaction = entitymanager.getTransaction();
readTransaction.begin();
Query query = entitymanager
.createQuery("select passport FROM Passport passport");
List list = query.getResultList();
Iterator iterator = list.iterator();
System.out.println("Passport Details fetched from database : ");
while (iterator.hasNext()) {
Passport passport = (Passport) iterator.next();
System.out.println(passport);
}
readTransaction.commit();
System.out.println("Passport Details over..");
}
}
public void readEmployeeDetails() {
EntityManagerFactory entityManagerFactory = Persistence
.createEntityManagerFactory("OpenJPASample");
EntityManager entitymanager = entityManagerFactory
.createEntityManager();
if (null != entitymanager) {
EntityTransaction readTransaction = entitymanager.getTransaction();
readTransaction.begin();
Query query = entitymanager
.createQuery("select employee FROM Employee employee");
List list = query.getResultList();
Iterator iterator = list.iterator();
System.out.println("Employee Details fetched from database : ");
while (iterator.hasNext()) {
Employee employee = (Employee) iterator.next();
System.out.println(employee);
}
readTransaction.commit();
System.out.println("Employee Details over");
}
}
public static void main(String[] args) {
OnetoOneReadSample sample = new OnetoOneReadSample();
sample.readEmployeeDetails();
sample.readPassportDetails();
}
}
output
Employee Details fetched from database :
[ ID = 501 ; Name = Bijoy ; Passport Details : [ Passport Number = 551 ; Address Line 1 = SKM ; Address Line 2 = Trivandrum ; State = Kerala; Country = India]]
Employee Details over
Passport Details fetched from database :
[ Passport Number = 551 ; Address LIne 1 = SKM ; Address Line 2 = Trivandrum ; State Name = Kerala ; Country Name = India ; Employee Details : [Id = 501 ; Name = Bijoy]]
Passport Details over..
The Employee object retrieved contains Passport details. The Passport object retrieved contains the mapped Employee object also.In this way bidirectional one to one relation ship can be implemented.
See Related Discussions
JPQL
Caching
Locking in JPA
JPA Mapping Schemes