Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Java Hibernate Collections, Associations, and Advanced Concepts

Save for later
  • 16 min read
  • 15 Sep 2015

article-image

In this article by Yogesh Prajapati and Vishal Ranapariya, the author of the book Java Hibernate Cookbook, he has provide a complete guide to the following recipes:

  • Working with a first-level cache
  • One-to-one mapping using a common join table
  • Persisting Map

(For more resources related to this topic, see here.)


Working with a first-level cache


Once we execute a particular query using hibernate, it always hits the database. As this process may be very expensive, hibernate provides the facility to cache objects within a certain boundary.

The basic actions performed in each database transaction are as follows:

  1. The request reaches the database server via the network.
  2. The database server processes the query in the query plan.
  3. Now the database server executes the processed query.
  4. Again, the database server returns the result to the querying application through the network.
  5. At last, the application processes the results.


This process is repeated every time we request a database operation, even if it is for a simple or small query. It is always a costly transaction to hit the database for the same records multiple times. Sometimes, we also face some delay in receiving the results because of network routing issues. There may be some other parameters that affect and contribute to the delay, but network routing issues play a major role in this cycle.

To overcome this issue, the database uses a mechanism that stores the result of a query, which is executed repeatedly, and uses this result again when the data is requested using the same query. These operations are done on the database side. Hibernate provides an in-built caching mechanism known as the first-level cache (L1 cache).

Following are some properties of the first-level cache:

  • It is enabled by default. We cannot disable it even if we want to.
  • The scope of the first-level cache is limited to a particular Session object only; the other Session objects cannot access it.
  • All cached objects are destroyed once the session is closed.
  • If we request for an object, hibernate returns the object from the cache only if the requested object is found in the cache; otherwise, a database call is initiated.
  • We can use Session.evict(Object object) to remove single objects from the session cache.
  • The Session.clear() method is used to clear all the cached objects from the session.

Getting ready


Let's take a look at how the L1 cache works.

Creating the classes


For this recipe, we will create an Employee class and also insert some records into the table:

Source file: Employee.java

@Entity
@Table
public class Employee {

@Id
@GeneratedValue
private long id;

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

// getters and setters

@Override
public String toString() {
   return "Employee: " +
       "nt Id: " + this.id +
       "nt Name: " + this.name;
}
}

Creating the tables


Use the following table script if the hibernate.hbm2ddl.auto configuration property is not set to create:

Use the following script to create the employee table:

CREATE TABLE `employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);


We will assume that two records are already inserted, as shown in the following employee table:






id name
1 Yogesh
2 Aarush


Now, let's take a look at some scenarios that show how the first-level cache works.

How to do it…


Here is the code to see how caching works. In the code, we will load employee#1 and employee#2 once; after that, we will try to load the same employees again and see what happens:

Code

System.out.println("nLoading employee#1...");
/* Line 2 */ Employee employee1 = (Employee)
session.load(Employee.class, new Long(1));
System.out.println(employee1.toString());

System.out.println("nLoading employee#2...");
/* Line 6 */ Employee employee2 = (Employee)
session.load(Employee.class, new Long(2));
System.out.println(employee2.toString());

System.out.println("nLoading employee#1 again...");
/* Line 10 */ Employee employee1_dummy = (Employee)
session.load(Employee.class, new Long(1));
System.out.println(employee1_dummy.toString());

System.out.println("nLoading employee#2 again...");
/* Line 15 */ Employee employee2_dummy = (Employee)
session.load(Employee.class, new Long(2));
System.out.println(employee2_dummy.toString());

Output

Loading employee#1...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 1
Name: Yogesh

Loading employee#2...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 2
Name: Aarush

Loading employee#1 again...
Employee:
Id: 1
Name: Yogesh

Loading employee#2 again...
Employee:
Id: 2
Name: Aarush

How it works…


Here, we loaded Employee#1 and Employee#2 as shown in Line 2 and 6 respectively and also the print output for both. It's clear from the output that hibernate will hit the database to load Employee#1 and Employee#2 because at startup, no object is cached in hibernate. Now, in Line 10, we tried to load Employee#1 again. At this time, hibernate did not hit the database but simply use the cached object because Employee#1 is already loaded and this object is still in the session. The same thing happened with Employee#2.

Hibernate stores an object in the cache only if one of the following operations is completed:

  • Save
  • Update
  • Get
  • Load
  • List

There's more…


In the previous section, we took a look at how caching works. Now, we will discuss some other methods used to remove a cached object from the session.

There are two more methods that are used to remove a cached object:

  • evict(Object object): This method removes a particular object from the session
  • clear(): This method removes all the objects from the session

evict (Object object)


This method is used to remove a particular object from the session. It is very useful. The object is no longer available in the session once this method is invoked and the request for the object hits the database:

Code

System.out.println("nLoading employee#1...");
/* Line 2 */ Employee employee1 = (Employee)
session.load(Employee.class, new Long(1));
System.out.println(employee1.toString());

/* Line 5 */ session.evict(employee1);
System.out.println("nEmployee#1 removed using evict(…)...");

System.out.println("nLoading employee#1 again...");
/* Line 9*/ Employee employee1_dummy = (Employee)
session.load(Employee.class, new Long(1));
System.out.println(employee1_dummy.toString());

Output

Loading employee#1...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 1
Name: Yogesh

Employee#1 removed using evict(…)...

Loading employee#1 again...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 1
Name: Yogesh


Here, we loaded an Employee#1, as shown in Line 2. This object was then cached in the session, but we explicitly removed it from the session cache in Line 5. So, the loading of Employee#1 will again hit the database.

clear()


This method is used to remove all the cached objects from the session cache. They will no longer be available in the session once this method is invoked and the request for the objects hits the database:

Code

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
System.out.println("nLoading employee#1...");
/* Line 2 */ Employee employee1 = (Employee)
session.load(Employee.class, new Long(1));
System.out.println(employee1.toString());

System.out.println("nLoading employee#2...");
/* Line 6 */ Employee employee2 = (Employee)
session.load(Employee.class, new Long(2));
System.out.println(employee2.toString());

/* Line 9 */ session.clear();
System.out.println("nAll objects removed from session cache using
clear()...");

System.out.println("nLoading employee#1 again...");
/* Line 13 */ Employee employee1_dummy = (Employee)
session.load(Employee.class, new Long(1));
System.out.println(employee1_dummy.toString());

System.out.println("nLoading employee#2 again...");
/* Line 17 */ Employee employee2_dummy = (Employee)
session.load(Employee.class, new Long(2));

System.out.println(employee2_dummy.toString());

Output

Loading employee#1...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 1
Name: Yogesh

Loading employee#2...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 2
Name: Aarush

All objects removed from session cache using clear()...

Loading employee#1 again...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 1
Name: Yogesh

Loading employee#2 again...
Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from Employee employee0_ where employee0_.id=?
Employee:
Id: 2
Name: Aarush


Here, Line 2 and 6 show how to load Employee#1 and Employee#2 respectively. Now, we removed all the objects from the session cache using the clear() method. As a result, the loading of both Employee#1 and Employee#2 will again result in a database hit, as shown in Line 13 and 17.

One-to-one mapping using a common join table


In this method, we will use a third table that contains the relationship between the employee and detail tables. In other words, the third table will hold a primary key value of both tables to represent a relationship between them.

Getting ready


Use the following script to create the tables and classes. Here, we use Employee and EmployeeDetail to show a one-to-one mapping using a common join table:

Creating the tables


Use the following script to create the tables if you are not using hbm2dll=create|update:

Use the following script to create the detail table:

CREATE TABLE `detail` (
`detail_id` bigint(20) NOT NULL AUTO_INCREMENT,
`city` varchar(255) DEFAULT NULL,
PRIMARY KEY (`detail_id`)
);


Use the following script to create the employee table:

CREATE TABLE `employee` (
`employee_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`employee_id`)
);


Use the following script to create the employee_detail table:

CREATE TABLE `employee_detail` (
`detail_id` BIGINT(20) DEFAULT NULL,
`employee_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`employee_id`),
KEY `FK_DETAIL_ID` (`detail_id`),
KEY `FK_EMPLOYEE_ID` (`employee_id`),
CONSTRAINT `FK_EMPLOYEE_ID`
   FOREIGN KEY (`employee_id`)
   REFERENCES `employee` (`employee_id`),
CONSTRAINT `FK_DETAIL_ID`
   FOREIGN KEY (`detail_id`)
   REFERENCES `detail` (`detail_id`)
);

Creating the classes


Use the following code to create the classes:

Source file: Employee.java

@Entity
@Table(name = "employee")
public class Employee {

@Id
@GeneratedValue
@Column(name = "employee_id")
private long id;

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

@OneToOne(cascade = CascadeType.ALL)
@JoinTable(
   name="employee_detail"
   , joinColumns=@JoinColumn(name="employee_id")
   , inverseJoinColumns=@JoinColumn(name="detail_id")
)
private Detail employeeDetail;

public long getId() {
   return id;
}

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

public String getName() {
   return name;
}

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

public Detail getEmployeeDetail() {
   return employeeDetail;
}

public void setEmployeeDetail(Detail employeeDetail) {
   this.employeeDetail = employeeDetail;
}

@Override
public String toString() {
   return "Employee"
     +"n Id: " + this.id
     +"n Name: " + this.name
   +"n Employee Detail "
     + "nt Id: " + this.employeeDetail.getId()
     + "nt City: " +
       this.employeeDetail.getCity();
}
}


Source file: Detail.java

@Entity
@Table(name = "detail")
public class Detail {

@Id
@GeneratedValue
@Column(name = "detail_id")
private long id;

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

@OneToOne(cascade = CascadeType.ALL)
@JoinTable(
   name="employee_detail"
   , joinColumns=@JoinColumn(name="detail_id")
   , inverseJoinColumns=@JoinColumn(name="employee_id")
)
private Employee employee;

public Employee getEmployee() {
   return employee;
}

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

public String getCity() {
   return city;
}

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

public long getId() {
   return id;
}

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

@Override
public String toString() {
   return "Employee Detail"
     +"n Id: " + this.id
     +"n City: " + this.city
     +"n Employee "
     + "nt Id: " + this.employee.getId()
     + "nt Name: " + this.employee.getName();
}
}

How to do it…


In this section, we will take a look at how to insert a record step by step.

Inserting a record


Using the following code, we will insert an Employee record with a Detail object:

Code

Detail detail = new Detail();
detail.setCity("AHM");

Employee employee = new Employee();
employee.setName("vishal");
employee.setEmployeeDetail(detail);

Transaction transaction = session.getTransaction();
transaction.begin();

session.save(employee);
transaction.commit();

Output

Hibernate: insert into detail (city) values (?)
Hibernate: insert into employee (name) values (?)
Hibernate: insert into employee_detail (detail_id, employee_id)
values (?,?)
Hibernate saves one record in the detail table and one in the employee table and then inserts a record in to the third table, employee_detail, using the primary key column value of the detail and employee tables.

How it works…


From the output, it's clear how this method works. The code is the same as in the other methods of configuring a one-to-one relationship, but here, hibernate reacts differently. Here, the first two statements of output insert the records in to the detail and employee tables respectively, and the third statement inserts the mapping record in to the third table, employee_detail, using the primary key column value of both the tables.

Let's take a look at an option used in the previous code in detail:

  • @JoinTable: This annotation, written on the Employee class, contains the name="employee_detail" attribute and shows that a new intermediate table is created with the name "employee_detail"
  • joinColumns=@JoinColumn(name="employee_id"): This shows that a reference column is created in employee_detail with the name "employee_id", which is the primary key of the employee table
  • inverseJoinColumns=@JoinColumn(name="detail_id"): This shows that a reference column is created in the employee_detail table with the name "detail_id", which is the primary key of the detail table


Ultimately, the third table, employee_detail, is created with two columns: one is "employee_id" and the other is "detail_id".

Persisting Map


Map is used when we want to persist a collection of key/value pairs where the key is always unique. Some common implementations of java.util.Map are java.util.HashMap, java.util.LinkedHashMap, and so on. For this recipe, we will use java.util.HashMap.

Getting ready


Now, let's assume that we have a scenario where we are going to implement Map<String, String>; here, the String key is the e-mail address label, and the value String is the e-mail address. For example, we will try to construct a data structure similar to <"Personal e-mail", "emailaddress2@provider2.com">, <"Business e-mail", "emailaddress1@provider1.com">. This means that we will create an alias of the actual e-mail address so that we can easily get the e-mail address using the alias and can document it in a more readable form. This type of implementation depends on the custom requirement; here, we can easily get a business e-mail using the Business email key.

Use the following code to create the required tables and classes.

Creating tables


Use the following script to create the tables if you are not using hbm2dll=create|update. This script is for the tables that are generated by hibernate:

Use the following code to create the email table:

CREATE TABLE `email` (
`Employee_id` BIGINT(20) NOT NULL,
`emails` VARCHAR(255) DEFAULT NULL,
`emails_KEY` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`Employee_id`,`emails_KEY`),
KEY `FK5C24B9C38F47B40` (`Employee_id`),
CONSTRAINT `FK5C24B9C38F47B40` FOREIGN KEY (`Employee_id`)
REFERENCES `employee` (`id`)
);


Use the following code to create the employee table:

CREATE TABLE `employee` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);

Creating a class


Source file: Employee.java

@Entity
@Table(name = "employee")
public class Employee {

@Id
@GeneratedValue
@Column(name = "id")
private long id;

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

@ElementCollection
@CollectionTable(name = "email")
private Map<String, String> emails;

public long getId() {
   return id;
}
public void setId(long id) {
   this.id = id;
}

public String getName() {
   return name;
}

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

public Map<String, String> getEmails() {
   return emails;
}

public void setEmails(Map<String, String> emails) {
   this.emails = emails;
}

@Override
public String toString() {
   return "Employee"
       + "ntId: " + this.id
       + "ntName: " + this.name
       + "ntEmails: " + this.emails;
}
}

How to do it…


Here, we will consider how to work with Map and its manipulation operations, such as inserting, retrieving, deleting, and updating.

Inserting a record


Here, we will create one employee record with two e-mail addresses:

Code

Employee employee = new Employee();
employee.setName("yogesh");

Map<String, String> emails = new HashMap<String, String>();
emails.put("Business email", "emailaddress1@provider1.com");
emails.put("Personal email", "emailaddress2@provider2.com");
employee.setEmails(emails);

session.getTransaction().begin();
session.save(employee);
session.getTransaction().commit();

Output

Hibernate: insert into employee (name) values (?)
Hibernate: insert into email (Employee_id, emails_KEY, emails)
values (?,?,?)
Hibernate: insert into email (Employee_id, emails_KEY, emails)
values (?,?,?)


When the code is executed, it inserts one record into the employee table and two records into the email table and also sets a primary key value for the employee record in each record of the email table as a reference.

Retrieving a record


Here, we know that our record is inserted with id 1. So, we will try to get only that record and understand how Map works in our case.

Code

Employee employee = (Employee) session.get(Employee.class, 1l);
System.out.println(employee.toString());
System.out.println("Business email: " +  
employee.getEmails().get("Business email"));

Output

Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from employee employee0_ where employee0_.id=?
Hibernate: select emails0_.Employee_id as Employee1_0_0_,
emails0_.emails as emails0_, emails0_.emails_KEY as emails3_0_
from email emails0_ where emails0_.Employee_id=?
Employee
Id: 1
Name: yogesh
Emails: {Personal email=emailaddress2@provider2.com, Business  
email=emailaddress1@provider1.com}
Business email: emailaddress1@provider1.com


Here, we can easily get a business e-mail address using the Business email key from the map of e-mail addresses. This is just a simple scenario created to demonstrate how to persist Map in hibernate.

Updating a record


Here, we will try to add one more e-mail address to Employee#1:

Code

Employee employee = (Employee) session.get(Employee.class, 1l);
Map<String, String> emails = employee.getEmails();
emails.put("Personal email 1", "emailaddress3@provider3.com");
session.getTransaction().begin();
session.saveOrUpdate(employee);
session.getTransaction().commit();
System.out.println(employee.toString());

Output

Hibernate: select employee0_.id as id0_0_, employee0_.name as
name0_0_ from employee employee0_ where employee0_.id=?
Hibernate: select emails0_.Employee_id as Employee1_0_0_,
emails0_.emails as emails0_, emails0_.emails_KEY as emails3_0_
from email emails0_ where emails0_.Employee_id=?
Hibernate: insert into email (Employee_id, emails_KEY, emails)
values (?, ?, ?)
Employee
Id: 2
Name: yogesh
Emails: {Personal email 1= emailaddress3@provider3.com, Personal  
email=emailaddress2@provider2.com, Business  
email=emailaddress1@provider1.com}


Here, we added a new e-mail address with the Personal email 1 key and the value is emailaddress3@provider3.com.

Deleting a record


Here again, we will try to delete the records of Employee#1 using the following code:

Code

Employee employee = new Employee();
employee.setId(1);
session.getTransaction().begin();
session.delete(employee);
session.getTransaction().commit();

Output

Hibernate: delete from email where Employee_id=?
Hibernate: delete from employee where id=?


While deleting the object, hibernate will delete the child records (here, e-mail addresses) as well.

How it works…


Here again, we need to understand the table structures created by hibernate:

Hibernate creates a composite primary key in the email table using two fields: employee_id and emails_KEY.

Summary


In this article you familiarized yourself with recipes such as working with a first-level cache, one-to-one mapping using a common join table, and persisting map.

Resources for Article:





Further resources on this subject: