Friday, 3 August 2012

One to many mapping using bidirectional relationship – onetoMany as Owner

First let us look the hibernate documentation on this as follows

To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the mappedBy element and set the many to one @JoinColumn as insertable and updatable to false. This solution is not optimized and will produce some additional UPDATE statements.

http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-mapping
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

Now let us goto our example.

In this example, we will see one to many  as owning side i.e master is now owner. As per this example,
Department is  the owner.

image_thumb6

Step : 2

Let us set the environment. Follow this post to set up Hibernate with java in eclipse IDE.

Step : 3

Let us create all our java bean files

Department.java

package domain;


import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import javax.persistence.OneToMany;

@Entity
@Table(name = "department")
public class Department {

@Id
@GeneratedValue
@Column(name = "ID")
private int ID;

@Column(name = "DepartName")
private String departName;

/**
/* To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the mappedBy element and
* set the many to one @JoinColumn as insertable and updatable to false. This solution is not optimized and will produce some
* additional UPDATE statements.
* Using JoinColumn makes the join-table the many side of association.
* Using JoinColumn puts an association into the many side of the one-to-many and it is that column that is used to join the two entities,
* not an extra table.
* http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-mapping
*/

/** I am the owner**/
@OneToMany(cascade={CascadeType.ALL})
@JoinColumn(name="DepartmentID", nullable = false)
List<Employees> employees;

public int getID() {
return ID;
}

public void setID(int iD) {
ID = iD;
}

public String getDepartName() {
return departName;
}

public void setDepartName(String departName) {
this.departName = departName;
}

public List<Employees> getEmployees() {
return employees;
}

public void setEmployees(List<Employees> employees) {
this.employees = employees;
}



}


Employees.java


package domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;

import org.hibernate.annotations.ForeignKey;

@Entity
@Table(name = "employees")
public class Employees {

@Id
@GeneratedValue
private int ID;
@Column(name = "firstName")
private String firstName;

@Column(name = "lastName")
private String lastName;

@ManyToOne
@JoinColumn(name = "DepartmentID", insertable = false, updatable = false)
private Department department;

public int getID() {
return ID;
}

public void setID(int iD) {
ID = iD;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public Department getDepartment() {
return department;
}

public void setDepartment(Department department) {
this.department = department;
}

}

hibernate.cfg.xml


 


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//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/sampledb</property>
<property name="connection.username">root</property>
<property name="connection.password">123</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>

<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</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>



<!-- Mapping Classes -->
<mapping class="domain.Department" />
<mapping class="domain.Employees" />

</session-factory>
</hibernate-configuration>


Test Class


package test;

import java.util.ArrayList;
import java.util.List;
import org.hibernate.Session;
import domain.*;
import HibernateUtilities.*;

public class Test {

public static void main(String[] args) {

addNewMasterAndDetail();

}

public static void addNewMasterAndDetail()
{
// Now let us create Department
Department d1 = new Department();
d1.setDepartName("Accounts");

// Now let us some employees
List<Employees> empList = new ArrayList<Employees>();
Employees e1 = new Employees();

e1.setFirstName("John");
e1.setFirstName("Mike");
e1.setDepartment(d1);
empList.add(e1);

e1 = new Employees();
e1.setFirstName("Smith");
e1.setFirstName("Robert");
e1.setDepartment(d1);
empList.add(e1);

d1.setEmployees(empList);

Session session = HibernateUtil.beginTransaction();
session.save(d1);
HibernateUtil.CommitTransaction();

}

}


Now let us run our test.java and let us the look output.

Hibernate: insert into department (DepartName) values (?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)
Hibernate: update employees set DepartmentID=? where ID=?
Hibernate: update employees set DepartmentID=? where ID=?


Now let us do some research and understand the concept.


In this department.java, you can see how we declare the one to many relationship






/** I am the owner**/
    @OneToMany(cascade={CascadeType.ALL})
    @JoinColumn(name="DepartmentID", nullable = false)
    List<Employees> employees;


1. Why we used nullable = false ??  Well, we will remove that as follow and run our test.java once again.


/** I am the owner**/
    @OneToMany(cascade={CascadeType.ALL})
    @JoinColumn(name="DepartmentID")
    List<Employees> employees;


Now, hibernate will throw an error as follows:
Hibernate: insert into department (DepartName) values (?)
Hibernate: insert into employees (firstName, lastName) values (?, ?)
Aug 4, 2012 11:18:14 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1364, SQLState: HY000
Aug 4, 2012 11:18:14 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Field 'DepartmentID' doesn't have a default value
Exception in thread "main" org.hibernate.exception.GenericJDBCException: Field 'DepartmentID' doesn't have a default value
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:54)


This is because we have defined as not null column in employee table for the column departmentID.
Now just for testing, let us make employee.department column as null and will remove the entire joincolumn annotation itself as follows


/** I am the owner**/
    @OneToMany(cascade={CascadeType.ALL})
     List<Employees> employees;


Now we run the test.java, you will see some different error as follows


Aug 4, 2012 11:21:55 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1146, SQLState: 42S02
Aug 4, 2012 11:21:55 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Table 'sampledb.department_employees' doesn't exist
Exception in thread "main" org.hibernate.exception.SQLGrammarException: Table 'sampledb.department_employees' doesn't exist


Well, this is tricky, hibernate looking for the third table to make the relationship between department and employee. In the database model, in order to make one to many relationship, either we can have the third table or we can define the foreign key in the child table that refers to master.


Rough scheme


Department (ID Primary Key, Name varchar(100)
Employee(ID Primary Key, FirstName Varchar(100)
Department_Employee(Department_ID foreign key to department, Employee_ID foreign key to Employee)


But in our example, we are using the foreign key concept without the third table for mapping between department and employee. So let us roll back all our changes and will continue from there.


Rollback in the department.java as follows


/** I am the owner**/
    @OneToMany(cascade={CascadeType.ALL})
    @JoinColumn(name="DepartmentID", nullable = false)
    List<Employees> employees;


And also, make departmentid in the employee table as not null.


And also,


What does CascadeType.ALL do?


CascadeType.PERSIST: cascades the persist (create) operation to associated entities persist() is called or if the entity is managed
CascadeType.MERGE: cascades the merge operation to associated entities if merge() is called or if the entity is managed
CascadeType.REMOVE: cascades the remove operation to associated entities if delete() is called
CascadeType.REFRESH: cascades the refresh operation to associated entities if refresh() is called
CascadeType.DETACH: cascades the detach operation to associated entities if detach() is called
CascadeType.ALL: all of the above


Now again let us run the test.java.  You can see the sql query as follows


Hibernate: insert into department (DepartName) values (?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)
Hibernate: update employees set DepartmentID=? where ID=?
Hibernate: update employees set DepartmentID=? where ID=?


If you observe carefully, hibernate using the extra update statement to update the child with the parent table key. We can also optimize this by changing in the department.java as follows


/** I am the owner**/
@OneToMany(cascade={CascadeType.ALL})
@JoinColumn(name="DepartmentID", updatable = false,  nullable = false)
List<Employees> employees;


Now let us run test.java again. Now you can see the query as follows


Hibernate: insert into department (DepartName) values (?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)


 


Now let us move further updating master, deleting master, update child, etc


Just in the mysql, do the following


TRUNCATE department
TRUNCATE employees


Now let us update only master. Change the test.java as follows and we will run one by one.




package test;

import java.util.ArrayList;
import java.util.List;
import org.hibernate.Session;
import domain.*;
import HibernateUtilities.*;

public class Test {
public static void main(String[] args) {
addNewMasterAndDetail();
//updateOnlyMaster();
//updateonlyDetail();
//deleteOnlyDetail();
//deleteMaster();
}

public static void addNewMasterAndDetail()
{
// Now let us create Department
Department d1 = new Department();
d1.setDepartName("Accounts");

// Now let us some employees
List<Employees> empList = new ArrayList<Employees>();
Employees e1 = new Employees();

e1.setFirstName("John");
e1.setFirstName("Mike");
e1.setDepartment(d1);
empList.add(e1);

e1 = new Employees();
e1.setFirstName("Smith");
e1.setFirstName("Robert");
e1.setDepartment(d1);
empList.add(e1);

d1.setEmployees(empList);

Session session = HibernateUtil.beginTransaction();
session.save(d1);
HibernateUtil.CommitTransaction();

}

public static void updateOnlyMaster()
{
//Now Let us update the master alone i.e Department
Session session = HibernateUtil.beginTransaction();
Department d2 = (Department) session.createQuery("from Department where id = 1").uniqueResult();
d2.setDepartName("Accounts Modified");
session.save(d2);
HibernateUtil.CommitTransaction();
}


public static void updateonlyDetail()
{
//Now Let us update the detail alone i.e employee
Session session = HibernateUtil.beginTransaction();
Department d2 = (Department) session.createQuery("from Department where id = 1").uniqueResult();
d2.getEmployees().get(0).setFirstName("changed first name");
session.save(d2);
HibernateUtil.CommitTransaction();
}

public static void deleteOnlyDetail()
{
//Now Let us update the master alone i.e Department
Session session = HibernateUtil.beginTransaction();
Department d2 = (Department) session.createQuery("from Department where id = 2").uniqueResult();
d2.getEmployees().clear();
session.saveOrUpdate(d2);
HibernateUtil.CommitTransaction();
}

public static void deleteMaster()
{
//Now Let us update the master alone i.e Department
Session session = HibernateUtil.beginTransaction();
Department d2 = (Department) session.createQuery("from Department where id = 1").uniqueResult();
session.delete(d2);
HibernateUtil.CommitTransaction();
}


}



1. Now Run the test.java and observer the sql output as follows

Hibernate: insert into department (DepartName) values (?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)
Hibernate: insert into employees (firstName, lastName, DepartmentID) values (?, ?, ?)


2. Now in the test.java, comment the first method and uncomment the second method as follows


public static void main(String[] args) {
        //addNewMasterAndDetail();
        updateOnlyMaster();
        //updateonlyDetail();
        //deleteOnlyDetail();
        //deleteMaster();
    }


  SQL Output as follows:
Hibernate: select department0_.ID as ID0_, department0_.DepartName as DepartName0_ from department department0_ where department0_.ID=1
Hibernate: update department set DepartName=? where ID=?

3. Now in the test.java, comment all the method and uncomment updateonlyDetail and run test.java.

Hibernate: select department0_.ID as ID0_, department0_.DepartName as DepartName0_ from department department0_ where department0_.ID=1
Hibernate: select employees0_.DepartmentID as Departme4_0_1_, employees0_.ID as ID1_, employees0_.ID as ID1_0_, employees0_.DepartmentID as Departme4_1_0_, employees0_.firstName as firstName1_0_, employees0_.lastName as lastName1_0_ from employees employees0_ where employees0_.DepartmentID=?
Hibernate: update employees set firstName=?, lastName=? where ID=?


3. Now in the test.java, comment all the method and uncomment deleteOnlyDetail and run test.java. Here is the sql output

Hibernate: select department0_.ID as ID0_, department0_.DepartName as DepartName0_ from department department0_ where department0_.ID=1
Hibernate: select employees0_.DepartmentID as Departme4_0_1_, employees0_.ID as ID1_, employees0_.ID as ID1_0_, employees0_.DepartmentID as Departme4_1_0_, employees0_.firstName as firstName1_0_, employees0_.lastName as lastName1_0_ from employees employees0_ where employees0_.DepartmentID=?

Here is a problem, hibernate actually not deleting the detail. So we need to do something


orphanRemoval is an entirely ORM-specific thing. It marks "child" entity to be removed when it's no longer referenced from the "parent" entity, e.g. when you remove the child entity from the corresponding collection of the parent entity.

So let us change our department.java as follows

/** I am the owner**/
@OneToMany(cascade={CascadeType.ALL},  orphanRemoval=true)
@JoinColumn(name="DepartmentID", updatable = false,  nullable = false)
List<Employees> employees


Now again run test.java, you can see the query output as follows

Hibernate: select department0_.ID as ID0_, department0_.DepartName as DepartName0_ from department department0_ where department0_.ID=1
Hibernate: select employees0_.DepartmentID as Departme4_0_1_, employees0_.ID as ID1_, employees0_.ID as ID1_0_, employees0_.DepartmentID as Departme4_1_0_, employees0_.firstName as firstName1_0_, employees0_.lastName as lastName1_0_ from employees employees0_ where employees0_.DepartmentID=?
Hibernate: delete from employees where ID=?
Hibernate: delete from employees where ID=?


3. Finally, we will delete the master and then it will delete the detail too.
Hibernate: select department0_.ID as ID0_, department0_.DepartName as DepartName0_ from department department0_ where department0_.ID=1
Hibernate: select employees0_.DepartmentID as Departme4_0_1_, employees0_.ID as ID1_, employees0_.ID as ID1_0_, employees0_.DepartmentID as Departme4_1_0_, employees0_.firstName as firstName1_0_, employees0_.lastName as lastName1_0_ from employees employees0_ where employees0_.DepartmentID=?
Hibernate: delete from employees where ID=?
Hibernate: delete from employees where ID=?
Hibernate: delete from department where ID=?


 


That’s all.


3 comments: