Basics of Hibernate (architecture + example)
Do not miss this exclusive book on Binary Tree Problems. Get it now for free.
Hibernate is a Java framework that maps Java classes and types into the relational databases, which solves problems of "paradigm mismatches" between how data is represented in application objects versus relational databases. This article explains the basics of Hibernate and provides a simple example with the MySQL database.
Table of Contents
- Overview
- Architecture of Hibernate
- Mapping datatypes
- Example of Hibernate 6 with MySQL database
- Analogs to Hibernate
- Conclusion
Overview
Hibernate is an open-source Object/Relational persistence and query service for Java applications. As the logic of relational databases and their data types vary from the structures in object-oriented languages, there are inevitable problems when trying to convert data between them. The Hibernate Object-Relational-Mapping (ORM) helps to automate communication between Java applications and databases and takes care of the mapping from Java classes to database tables, and from Java data types to SQL data types.
Why Hibernate?
If the application written in object-oriented language needs to store data in a database, the possible approaches are:
- Write Classes that reflect the relational model and thus minimize mapping
- Write Classes that don't reflect the relational model and do mapping manually or with ORMs like Hibernate
- Use NoSQL databases
What approach to choose depends completely on the application logic: for example, aggregates or graphs problems work well with the NoSQL data model, while the data that is mostly for reading can be handled with a simple SQL database without complex mapping. So, by no means Hibernate is the best solution for all Java applications, but it is a great tool to use when mapping is needed.
Architecture of Hibernate
Hibernate works as an additional layer between Java application and database, above the Java Database Connectivity (JDBC) API:
To isolate dependencies, Hibernate's functionality is split into a number of modules. Some of the important modules include:
- hibernate-core - ORM features and APIs
- hibernate-encache - integrates Encache as a second-level cache provider
- hibernate-spatial - integrates GIS data type support
- hibernate-osgi - support for running in OSGi containers
As Hibernate implements Java Persistence API (JPA), it is important to understand the lifecycle of a JPA persistence object.
A newly created object is not associated with the Hibernate Session (not Persistent context) and is not mapped to the database yet. To make the object Managebale - include it into the mapping with the persist() method. The changes in the manageable object are seen by the Hibernate layer but not by the database, the flush() method writes changes to the database.
Mapping datatypes
Hibernate introduces org.hibernate.type.Type
datatypes as an intermediary between application and database data types that has additional functionality to do equality checks, clone values, etc.
Basic data types are denoted with @Basic
(default) annotation and include the following Java data types:
- Java primitives
- Primitive wrappers
- Strings
- BigInteger, BigDecimal
- Date/time types
- Byte and character arrays
- Enums
- Serializable types
Embeddable @Embeddable
data types are a composition of values that is not an entity (is a part of a table in a database).
Collections @ElementCollection
is a way of mapping Java's collections - java.util.Collection and java.util.Map subclasses.
Entity type @Entity
describes the mapping between the actual persistable domain model object and a database table row.
Example of Hibernate 6 with MySQL database
Installation
Hibernate abstracts away the underlying APIs (it uses Java APIs like JTA, JNDI, and JDBC) so the only installation needed is Hibernate itself and the JDBC connectivity driver for the database.
Hibernate release bundles are hosted on the SourceForge File Release System for manual installation.
For Mavel installation add Hibernate and database connector (MySQL database in the example) dependency to the pom.xml file, as shown below.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>hibernate-example</groupId>
<artifactId>hibernate-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- Hibernate ORM dependency (developed by Hibernate) -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.0.1.Final</version>
</dependency>
<!-- MySQL database connection (develoved by MySQL) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
</project>
Configuration
Before setting up the connection to the database, Hibernate needs to know the database properties and location of the mapping files, such information is usually provided in the hibernate.cfg.xml
file.
Below is the complete hibernate.cfg.xml
file that contains main database settings and references to two classes that I am going to map to the database:
<?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 -->
<!-- JDBC driver for MySQL database (requires MySQL database connection installed) -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<!-- Location and name of the database: local MySQL database named "mydatabase" -->
<property name="connection.url">jdbc:mysql://localhost/mydatabase</property>
<!-- Username and login are configured in the database manager -->
<property name="connection.username">hibernate_user</property>
<property name="connection.password">123456</property>
<!-- JDBC connection pool settings (use the built-in connection pool for testing) - optional -->
<!-- Limit the number of connections waiting in the connection pool -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect - optional (Hibernate can determine which dialect to use)-->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Disable the second-level cache
(second-level cache is applied to multiple sessions from the single factory object) -->
<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
<!-- Print all executed SQL queries to the terminal -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup
(this option should be removed after the development is finished) -->
<property name="hbm2ddl.auto">create</property>
<!-- List of XML mapping files and annotated classes -->
<mapping class="com.og.hibernate.example.Contact"/>
<mapping class="com.og.hibernate.example.Address"/>
</session-factory>
</hibernate-configuration>
Annotated classes
The hibernate.cfg.xml
file includes a list of XML mapping files or, in newer versions of the Hibernate, annotated classes that are going to be mapped to the database.
The mapping in form of [Class name].hbm.xml files is considered deprecated and will no longer be supported beyond Hibernate 6.x.
The Java class below represents a contact, @annotations
help Hibernate automatically map class fields to columns in the database.
package com.og.hibernate.example;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import jakarta.persistence.*;
@Entity(name = "Contact") // Name of the Entity on the application side
@Table(name = "Contacts") // Corresponding name of the table on the database side
public class Contact {
@Id // The primary key on the database side
@GeneratedValue (strategy = GenerationType.IDENTITY) // Generate id automatically
private Integer id;
private Name name; // Example of embedded class
private String phone;
// Many-to-Many relation between two entities,
// persisting or merging operations propagate from parent (Contact) to child (Address) entities
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Address> addresses = new ArrayList<>();
@Convert(converter = org.hibernate.type.YesNoConverter.class) // Convert boolean value to Y-N in database
private boolean active;
private Date registrationDate;
@Embeddable
static class Name {
String firstName;
String lastName;
// Default constructor is required by the Hibernate
public Name(){};
public Name (String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
// Default constructor is required by the Hibernate
public Contact(){};
// User-defined constructors
public Contact(Name name, String phone, Address address, boolean active, Date registrationDate) {
this.name = name;
this.phone = phone;
this.addresses.add(address);
this.active = active;
this.registrationDate = registrationDate;
}
// Each class field needs getter and setter methods for Hibernate to use
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
// Reduced getters and setters for other fields for brevity, can automatically generate them in IDE
}
Another entity in this example is addresses: each client can have multiple addresses, and the same address can be shared by multiple clients.
package com.og.hibernate.example;
import jakarta.persistence.*;
@Entity(name = "Address")
@Table(name = "Addresses")
public class Address {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "ZipPostcode")
private int zip_postcode;
private String area;
private String city;
private String street;
private String building;
private String details;
// Default constructor is required by the Hibernate
public Address(){};
public Address (int zip_postcode, String area, String city, String street, String building, String details){
this.zip_postcode = zip_postcode;
this.area = area;
this.city = city;
this.street = street;
this.building = building;
this.details = details;
}
// Reduced getters and setters for brevity
}
Database tables
The configuration file and mappings are ready, now the Hibernate can automatically create tables in the database with hibernate.hbm2ddl.auto
property in the configuration file. For a more flexible setup of the database schema and data types, it is recommended to create databases manually. Below is the code to add contacts, addresses, and contact-addresses tables in the MySQL database:
CREATE TABLE Contacts (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
LastName VARCHAR(30),
FirstName VARCHAR(30),
Phone VARCHAR(15),
RegistrationDate DATETIME(6),
Active CHAR(1)
);
CREATE TABLE Addresses (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
ZipPostcode INT,
Area VARCHAR(20),
City VARCHAR(20),
Street VARCHAR(30),
Building VARCHAR(30),
Details VARCHAR(100)
);
CREATE TABLE Contacts_Addresses (
Contact_id INT,
Addresses_id INT,
PRIMARY KEY (Contact_id , Addresses_id),
FOREIGN KEY (Contact_id)
REFERENCES Contacts (id),
FOREIGN KEY (Addresses_id)
REFERENCES Addresses (id)
);
Hibernate Session
Now everything is set up and we can run Hibernate Session in the Java application to work with the database. The locations of the files in the Maven Project folder in the Eclipse IDE view:
In this example, the main application logic is in the Main.java file, where the objects are instantiated and persisted in the database. The general workflow with the Hibernate includes work with the following classes:
SessionFactory (org.hibernate.SessionFactory
) is a thread-safe heavy object that contains Hibernate configurations and allows to create Sessions with the database. Working with multiple databases will require multiple SessionFactory objects.
Session (org.hibernate.Session
) object is created by SessionFactory. It represents a physical connection to a database, as the java.sql.Connection
JDBC is hidden underneath. Additionally, Session holds the persistence context of the application domain model (first level cache).
Transaction (org.hibernate.Transaction
) object is created by Session. It is a single-threaded, short-lived object optionally used to set physical transaction boundaries.
package com.og.hibernate.example;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import com.og.hibernate.example.Contact.Name;
public class Main {
public static void main(String[] args) {
// Set up a SessionFactory - once for application, one factory per database
SessionFactory sessionFactory;
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure("hibernate.cfg.xml") // load settings from hibernate.cfg.xml
.build();
try {
sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory();
}
catch (Exception e) {
System.out.println("Could not create session factory: " + e.getMessage());
StandardServiceRegistryBuilder.destroy( registry ); // Clear up registry object
return; // Exit application
}
// Create new contact objects
Contact contact1 = new Contact(
new Name("Jon", "Snow"),
"(743)355-1661",
new Address(33025, "US", "Miramar", "Trails End Road", "1423", ""),
true,
new Date()
);
Contact contact2 = new Contact(
new Name("Aleksandar", "Tionge"),
"(549)392-9128",
new Address(46268, "US", "Indianapolis", "Elk City Road", "23-533", ""),
false,
new Date()
);
// Add one common address to both contacts
Address secondAddress = new Address(2303, "Australia", "New South Wales", "Silsoe Street", "10", "");
contact1.getAddresses().add(secondAddress);
contact2.getAddresses().add(secondAddress);
// Save (persist) new objects to the database in a short session
Session session = sessionFactory.openSession();
session.beginTransaction();
session.persist(contact1);
session.persist(contact2);
session.getTransaction().commit();
session.close();
// Close session factory
sessionFactory.close();
}
}
Running code in Main.java file adds the objects to the database, to check results run direct SQL queries to MySQL.
Contacts table:
mysql> SELECT * FROM contacts;
+----+----------+------------+---------------+----------------------------+--------+
| id | LastName | FirstName | Phone | RegistrationDate | Active |
+----+----------+------------+---------------+----------------------------+--------+
| 1 | Snow | Jon | (743)355-1661 | 2022-05-14 15:14:29.889000 | Y |
| 2 | Tionge | Aleksandar | (549)392-9128 | 2022-05-14 15:14:29.889000 | N |
+----+----------+------------+---------------+----------------------------+--------+
2 rows in set (0.00 sec)
Addresses table:
mysql> SELECT * FROM addresses;
+----+-------------+-----------+-----------------+-----------------+----------+---------+
| id | ZipPostcode | Area | City | Street | Building | Details |
+----+-------------+-----------+-----------------+-----------------+----------+---------+
| 1 | 33025 | US | Miramar | Trails End Road | 1423 | |
| 2 | 2303 | Australia | New South Wales | Silsoe Street | 10 | |
| 3 | 46268 | US | Indianapolis | Elk City Road | 23-533 | |
+----+-------------+-----------+-----------------+-----------------+----------+---------+
3 rows in set (0.00 sec)
Intermediary table between contacts and addresses:
mysql> SELECT * FROM contacts_addresses;
+------------+--------------+
| Contact_id | Addresses_id |
+------------+--------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
+------------+--------------+
4 rows in set (0.00 sec)
The data was automatically mapped and added to the multiple relations in a single session, the next example will show how to retrieve data back.
Hibernate Queries
Hibernate supports two types of queries: native queries and JPQL queries. Native queries are run in database-specific SQL language, thus providing more database-specific functionality at the price of potential transferability problems. The second option is queries written in Java Persistence Query Language (JPQL) which is closer to data in the format of Java objects and lets Java Persistence API validate queries against the Java application model.
The example below shows how to retrieve Java objects from the database with Hibernate JPQL query.
// Fetch data from the database
Session session = sessionFactory.openSession();
String hSQL = "FROM Contact"; // Query to load complete persistent objects into memory
// Query the database
// Note: since Hibernate 6.0 String queries without resulting Class name are deprecated,
// so in this example method createQuery(String queryString, Class<Contact> resultClass) is used
List<Contact> result = session.createQuery(hSQL, Contact.class).list();
// Print results to the terminal
for (Contact contact : result ) {
System.out.println( "Contact (" + contact.getName().firstName + ") : " + contact.getPhone() );
// Additional query to the database for second entity
List <Address> addresses = contact.getAddresses();
for (Address address: addresses) {
System.out.println( "\tCity: " + address.getCity());
}
}
// Close session after all data is fetched
session.close();
The output to the terminal prints retrieved information from contacts and addresses tables:
Contact (Jon) : (743)355-1661
City: Miramar
City: New South Wales
Contact (Aleksandar) : (549)392-9128
City: New South Wales
City: Indianapolis
To run more complex queries involving search over multiple tables, it is easier to use native SQL queries with session.nativeQuery() method. For example, the query below searches for the cities by the contact's name.
Starting from Hibernate 6.0 it is required to provide the type of search result beforehand - in the example below the city name will be returned as an object of
String.class
.
// Open session with database
Session session = sessionFactory.openSession();
// Write Native SQL query
String sql = "SELECT A.city " +
"FROM contacts C " +
"JOIN contacts_addresses CA ON C.id = CA.contact_id " +
"JOIN addresses A ON CA.Addresses_id = A.id " +
"WHERE C.LastName = :secondname";
// Pass parameters to query and run it
List<String> cities = session.createNativeQuery(sql, String.class)
.setParameter("secondname", "Tionge")
.list();
// Print results
System.out.println(cities); // Output: [New South Wales, Indianapolis]
Analogs to Hibernate
Overall, the Hibernate ORM is a reliable open-source product with over 20-yeares of history that has beginner-friendly and easy-to-follow documentation and helps developers to map data between the application and database. The more lightweight analogs to Hibernate are:
- DataNucleus is another popular ORM framework that complies with the Java Persistence API standard.
- MyBatis is a lightweight SQL Mapping Framework for Java.
- jOOQ Java Object Oriented Querying is a light database-mapping framework that aims to utilize the best of SQL queries.
Conclusion
This article explained the basics of Hibernate ORM framework and showed a simple example of Java Class mapping to the MySQL database tables using Annotations with Hibernate 6. The Hibernate abstracts away datatypes mapping, as well as embedded classes mapping and the relations between entities. The tradeoff is the necessity to comply with the Hibernate requirements for Classes.
Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.