Hibernate : Mappings – OneToOne/OneToMany/ManyToMany

Relational database design supports many different types of relationships between tables.  These relationships are in place to prevent the entry of inconsistent data and enforce referential integrity. Till now we have seen an Employee Entity which have a valuetype of Address. Lets consider a scenario of one to one mapping first where we have 2 entities and one Entity resides in another. Lets take Employee and BankDetails classes as entities.
One-to-one relationship – A row in a table is associated to one and only one row in another table.

BankDetails.java:


package com.hibernate.one2one.mapping;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class BankDetails {
    
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private int bankDetails_id;
    private String accountNumber;
    private String bankName;

    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public String getBankName() {
        return bankName;
    }

    public void setBankName(String bankName) {
        this.bankName = bankName;
    }

    public int getBankDetails_id() {
        return bankDetails_id;
    }

    public void setBankDetails_id(int bankDetails_id) {
        this.bankDetails_id = bankDetails_id;
    }

}

Employee.java:


package com.hibernate.one2one.mapping;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;

@Entity
public class Employee {

    @Id
    @Column(name = "Id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @Column(name = "Name")
    private String name;
    @OneToOne
    @JoinColumn(name="bank_id")
    private BankDetails bankDetails;    

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BankDetails getBankDetails() {
        return bankDetails;
    }

    public void setBankDetails(BankDetails bankDetails) {
        this.bankDetails = bankDetails;
    }    

}

HibernateOne2OneTest.java:


package com.hibernate.one2one.mapping;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateOne2OneTest {
static SessionFactory sessionFactory;
static Session session;

public static void main(String[] args) {
System.out.println("HibernateTest");
Employee aEmployee = new Employee();
aEmployee.setName("Employee Name1");

BankDetails aBankDetails=new BankDetails();
aBankDetails.setBankName("Bank1");
aBankDetails.setAccountNumber("AccountNumber123");

aEmployee.setBankDetails(aBankDetails);

sessionFactory = new Configuration().configure("/com/hibernate/one2one/mapping/hibernate.cfg.xml").buildSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
session.save(aEmployee);
session.save(aBankDetails);
session.getTransaction().commit();
session.close();

}

}

Notice the @OneToOne mapping in the Employee.java which refer to BankDetails. In the Test class we created 2 separate objects and setted the bank object to the employee object.  Notice now we are taking the hibernate-cfg.xml file for a specific path which contains the mapping details of the 2 Entity classes along with DB details.


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernateDB</property>
<property name="connection.username">root</property>
<property name="connection.password">admin</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<!-- Names the annotated entity class -->
<mapping class="com.hibernate.one2one.mapping.Employee"></mapping>
<mapping class="com.hibernate.one2one.mapping.BankDetails"></mapping>
</session-factory>
</hibernate-configuration>

Output:


Hibernate: insert into Employee (bank_id, Name) values (?, ?)
Hibernate: insert into BankDetails (accountNumber, bankName) values (?, ?)
Hibernate: update Employee set bank_id=?, Name=? where Id=?

 

Hibernate first created the Employee and BankDetails tables, and inserted values in these. Now since we are using Generated.Type as AUTO, while inserting in Employee table at the first time, hibernate didn’t knew what the bank_id is going to be. So only after inserting in BankDetails table it came back again to Employee table to update the bank_id.

Lets take another scenario – One-to-many relationships –  A row in a table in a database can be associated with one or more rows in another table.  And since relationships work both ways it is not uncommon to hear reference to many-to-one-relationships as well. Lets take the scenario with Employee and Address entities.


package com.hibernate.one2many.mapping;

import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;

@OneToMany
private Collection<Address> address=new ArrayList<Address>();

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Collection<Address> getAddress() {
return address;
}

public void setAddress(Collection<Address> address) {
this.address = address;
}
}

package com.hibernate.one2many.mapping;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Address {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String street;
private String city;

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

}

}

HibernateOne2ManyTest:


package com.hibernate.one2many.mapping;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateOne2ManyTest {
static SessionFactory sessionFactory;
static Session session;

public static void main(String[] args) {

Employee aEmployee = new Employee();
aEmployee.setName("Employee Name1");

Address address1=new Address();
address1.setStreet("Street1");
address1.setCity("City1");

Address address2=new Address();
address2.setStreet("Street2");
address2.setCity("City2");

aEmployee.getAddress().add(address1);
aEmployee.getAddress().add(address2);

sessionFactory = new Configuration().configure("/com/hibernate/one2many/mapping/hibernate.cfg.xml").buildSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
session.save(aEmployee);
session.save(address1);
session.save(address2);
session.getTransaction().commit();
session.close();

}

}

hibernate.cfg.xml is almost the same with the mapping of the new classes. Output:


Hibernate: insert into Employee (name) values (?)
Hibernate: insert into Address (city, street) values (?, ?)
Hibernate: insert into Address (city, street) values (?, ?)
Hibernate: insert into Employee_Address (Employee_id, address_id) values (?, ?)
Hibernate: insert into Employee_Address (Employee_id, address_id) values (?, ?)

Notice that along with the Employee and Address table hibernate created another table called Employee_Address which has 2 columns – Employee_Id and address_id which has the mapping details. Of course you can change these default names. Changing in Employee.java


@OneToMany
@JoinTable(name="Employee_Address_Mapping",
joinColumns=@JoinColumn(name="emp_id"),
inverseJoinColumns=@JoinColumn(name="add_id"))
private Collection<Address> address=new ArrayList<Address>();

Now the mapping table created will be “Employee_Address_Mapping” with coulms emp_id and add_id.
Employee to Address is one-to-many mapping, Address to Employee is many-to-one. We can annotate that in Address class The advantage of this would be that if someone directly takes the Address entity it can know which employee is associated with it. Adding following to Address.java:


@ManyToOne
private Employee employee;

public Employee getEmployee() {
return employee;
}

public void setEmployee(Employee employee) {
this.employee = employee;
}

What if you don’t want a separate table (Employee_Address) for mapping in one-to-many mapping cases, rather you want the mapping to be done in Address table itself. Note that since an employee can have multiple address, this mapping cannot be done in Employee table. Changing Employee.java:


@OneToMany(mappedBy="employee")
private Collection<Address> address=new ArrayList<Address>();

Changing Address.java:


@ManyToOne
@JoinColumn(name="EMP_ID")
private Employee employee;

Running HibernateOne2ManyTest again:


Hibernate: insert into Employee (name) values (?)
Hibernate: insert into Address (city, EMP_ID, street) values (?, ?, ?)
Hibernate: insert into Address (city, EMP_ID, street) values (?, ?, ?)

Now hibernate didn’t created new table for mapping rather used Address table.

Many-to-many relationships-When one or more rows in a table are associated with one or more rows in another table. Lets take this scenario with Employee and Project Entities. Since its many-to-many, you cannot have mapping in Employee or Project table. Make the necessary mappings in hibernate-cfg.xml for these entities.


package com.hibernate.many2many.mapping;

import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
@ManyToMany
private Collection<Project> project=new ArrayList<Project>();

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Collection<Project> getProject() {
return project;
}

public void setProject(Collection<Project> project) {
this.project = project;
}

}

package com.hibernate.many2many.mapping;

import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class Project {

@Id
@GeneratedValue
private int id;
private String ProjectName;
@ManyToMany
private Collection<Employee> employee=new ArrayList<Employee>();

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProjectName() {
return ProjectName;
}
public void setProjectName(String projectName) {
ProjectName = projectName;
}
public Collection<Employee> getEmployee() {
return employee;
}
public void setEmployee(Collection<Employee> employee) {
this.employee = employee;
}

}

HibernateMany2ManyTest.java:


package com.hibernate.many2many.mapping;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateMany2ManyTest {
static SessionFactory sessionFactory;
static Session session;

public static void main(String[] args) {

Employee aEmployee = new Employee();
aEmployee.setName("Employee Name1");

Project project1=new Project();
project1.setProjectName("Project1");

Project project2=new Project();
project2.setProjectName("Project2");

aEmployee.getProject().add(project1);
aEmployee.getProject().add(project2);
project1.getEmployee().add(aEmployee);
project2.getEmployee().add(aEmployee);

sessionFactory = new Configuration().configure("/com/hibernate/many2many/mapping/hibernate.cfg.xml").buildSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
session.save(aEmployee);
session.save(project1);
session.save(project2);
session.getTransaction().commit();
session.close();

}

}

Output:


Hibernate: insert into Employee (name) values (?)
Hibernate: insert into Project (ProjectName) values (?)
Hibernate: insert into Project (ProjectName) values (?)
Hibernate: insert into Employee_Project (Employee_id, project_id) values (?, ?)
Hibernate: insert into Employee_Project (Employee_id, project_id) values (?, ?)
Hibernate: insert into Project_Employee (Project_id, employee_id) values (?, ?)
Hibernate: insert into Project_Employee (Project_id, employee_id) values (?, ?)

We knew that in this case another table has to created with mappings, but hibernate is by default creating 2 tables, Employee_Project and Project_Employee for the mappings, i.e both the @ManyToMany are mapping for both entities. We can stop one of them by adding the attribute ‘mappedBy’. Lets stop the creation of Project_Employee table. Changing Project.java:


@ManyToMany(mappedBy="project")
private Collection<Employee> employee=new ArrayList<Employee>();

Now if we run our test class Project_Employee table will not be created, because we have informed hibernate that the mapping of the employee is done by the ‘project’ attribute in the ‘Employee’ class.

There could be a scenario in which you have a project but no employee is yet assigned to it. So if you get the Project object and try to retrieve employee associated, you will get an exception. In order to suppress such exception you can use the annotation @NotFound. Note that this is a hibernate annotation and not the java persistence one. Project.java:


@ManyToMany(mappedBy="project")
@NotFound(action=NotFoundAction.IGNORE)
private Collection<Employee> employee=new ArrayList<Employee>();

Till now we are saving every object that we are creating, i.e  session.save(aEmployee), session.save(project1),  session.save(project2) etc. We have to pass every thing to session. If you try to save just the employee object which has the reference of the projects, the project object wont get saved and will throw an exception. Hibernate did not saved these automatically because Project class is also and Entity. Had Project class been a valuetype , hibernate would have saved it. We can use the property ‘cascade’ for this. Changing Employee.java:


@ManyToMany(cascade=CascadeType.PERSIST)
private Collection<Project> project=new ArrayList<Project>();

In order to use this persist cascase, we have to use session.persist() method instead. Changing few lines in HibernateMany2ManyTest.java:


session.persist(aEmployee);
//session.save(project1);
//session.save(project2);

Now all associations will get saved in one .persist() call.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: