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:
(For more resources related to this topic, see here.)
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:
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:
Let's take a look at how the L1 cache works.
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;
}
}
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.
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:
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());
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
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:
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:
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:
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());
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.
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:
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());
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.
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.
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:
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`)
);
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();
}
}
In this section, we will take a look at how to insert a record step by step.
Using the following code, we will insert an Employee record with a Detail object:
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();
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.
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:
Ultimately, the third table, employee_detail, is created with two columns: one is "employee_id" and the other is "detail_id".
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.
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.
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`)
);
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;
}
}
Here, we will consider how to work with Map and its manipulation operations, such as inserting, retrieving, deleting, and updating.
Here, we will create one employee record with two e-mail addresses:
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();
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.
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.
Employee employee = (Employee) session.get(Employee.class, 1l);
System.out.println(employee.toString());
System.out.println("Business email: " +
employee.getEmails().get("Business email"));
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.
Here, we will try to add one more e-mail address to Employee#1:
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());
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.
Here again, we will try to delete the records of Employee#1 using the following code:
Employee employee = new Employee();
employee.setId(1);
session.getTransaction().begin();
session.delete(employee);
session.getTransaction().commit();
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.
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.
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.