1. Overview

Before we dive into Hibernate and Jakarta Persistence API (JPA), the titular topics of this course, we first need to understand what an Object-Relational Mapping (ORM) framework is.

In this lesson, we’ll explore the concept of an ORM and understand how it relates to Hibernate and JPA.

 

There is no code we need to check out to follow along in this lesson.

 

2. Understanding Object-Relational Mapping (ORM)

The most effective way to understand a concept is to understand the problem statement that gave birth to it.

Similarly, before we look at what an ORM is, let’s understand the fundamental problems it aims to solve.

 

2.1. Object-Relational Impedance Mismatch

Simply put, the Object-Oriented Programming (OOP) paradigm used in Java and the Relational paradigm used in a Relational Database Management System (RDBMS) don’t work well together.

This incompatibility leads to five main mismatch problems, known as Object-Relational Impedance Mismatch, when attempting to bridge between them:

  • Granularity: In a relational model, the granularity, i.e., how we represent data, is limited to just tables and columns. Meanwhile, in OOP, objects often contain rich data structures with nested objects. The granularity problem occurs when we try to map an object with varied levels of granularity to a database table with limited granularity.
  • Inheritance: Inheritance, a fundamental concept in OOP, doesn’t have a direct equivalent in the relational model.
  • Identity: To evaluate whether two records have the same identity, we use the concept of primary keys in the relational model. In contrast, we use the == operator and equals() method in Java to check for object identity and object equality, respectively. We say that two objects have the same identity if they refer to the same memory location, and consider them to be equal based on the implementation of the equals() method.
  • Associations: In OOP, an object can form an association with other objects, via object references. Meanwhile, in the relational model, the relationship between two tables is represented through a foreign key.
  • Data Navigation: Object networks in OOP are directly navigable using object references. However, in a relational model, we typically use SQL joins when querying data from multiple tables.

It’s important to note that although this course is centered around the Java ecosystem, the Object-Relational Impedance Mismatch problems are language-agnostic.

 

2.2. Solving Impedance Mismatch Through an ORM

An ORM framework provides a solution to the Object-Relational Impedance Mismatch problems by acting as a bridge between our object-oriented code and the underlying relational database. It allows us to perform operations on our persistence layer using the already familiar object-oriented paradigm instead of writing SQL queries directly:

 

Using the above rudimentary architecture of a wizard management system, let’s understand the two fundamental ORM concepts of entities and relationships.

An entity in ORM represents a table in the database. The class properties map to table columns, and each entity instance corresponds to a row in the table. In our example, the House and Wizard classes are entities that map to the houses and wizards tables, respectively.

We provide metadata, typically through annotations on our entity class, to tell the ORM framework how it should map our entity class to the corresponding database table. Using this metadata, the ORM framework is able to generate the underlying CRUD SQL queries.

Similarly, using an ORM framework, we’re able to represent relationships between entities through object references. Here, our House class has a list of wizards, representing a one-to-many relationship. On the other hand, our Wizard class has a reference to the house it belongs to, representing the inverse many-to-one relationship. At the database level, this translates to the wizards table having a foreign key column that references the primary key of the houses table.

The ORM framework takes care of managing these database relationships and allows us to navigate between related entities using the defined references. For example, to retrieve all wizards belonging to the Gryffindor house, we’ll simply call gryffindor.getWizards(), without having to worry about the underlying SELECT SQL statement.

Throughout the course, we’ll explore more fundamental and advanced ORM concepts and implement them practically.

 

3. Jakarta Persistence API (JPA)

Jakarta Persistence API (JPA), formerly known as Java Persistence API, is a specification that defines a standard API for ORM frameworks. It dictates how an ORM should be implemented in the Java ecosystem.

Before JPA, each ORM vendor had their own proprietary APIs, which made it difficult to switch between vendors or maintain code that used multiple ORMs.

However, with JPA, we can write our persistence code against the standard API, and it’ll work with any JPA-compliant ORM implementation. It eliminates the problem of vendor lock-in and helps us create a consistent and portable codebase.

At its core, JPA defines a set of interfaces in the jakarta.persistence package, such as EntityManager, EntityManagerFactoryQuery, etc. Any ORM vendor that wishes to be JPA compliant must provide implementations for these interfaces.

Additionally, JPA provides a set of annotations, such as @Entity, @Id, @OneToMany, etc., that we can use to define how our entity classes should map to our database tables, specify relationships between entities, and configure various persistence settings. The JPA runtime, provided by the ORM vendor we choose, then uses this metadata to generate the appropriate SQL queries and manage the persistence of our entities.

Through standardization, it becomes easy for us to learn and use ORM in our Java projects. JPA abstracts away vendor-specific details and provides a clean way of interacting with our relational databases using the object-oriented paradigm.

While JPA is the most widely used ORM specification in the Java ecosystem, it’s important to note that other ORMs exist, such as jOOQ and MyBatis, that are not JPA-compliant.

 

4. The de Facto JPA Implementation: Hibernate

Now, while JPA provides the standard specification for ORM in Java, it’s the different implementations that bring the specification to life.

Several JPA implementations exist, such as EclipseLink, Apache OpenJPA, etc. However, there’s one implementation that has cemented itself as the most widely used and popular choice for nearly two decades – Hibernate. It’s not an exaggeration to say that Hibernate has become synonymous with JPA itself.

Let’s visualize the relationship between the three terminologies we’ve discussed so far:

 

The above diagram illustrates Hibernate serving as a JPA-compliant implementation, building upon the specification to deliver ORM capabilities in practice.

Hibernate predates the JPA specification and was already a mature and feature-rich ORM solution. After JPA’s introduction, Hibernate quickly adapted to implement the standard as well, while continuing to support and enrich its proprietary APIs.

Apart from the technical aspects, Hibernate also excels in the area of documentation and community support, the two things that are crucial for the success and adoption of any framework. Many popular Java frameworks, such as Spring and Quarkus, offer out-of-the-box integration with Hibernate, further cementing its position as the de facto JPA implementation.

Throughout the course, we’ll be using Hibernate as our JPA provider. However, it’s important to note that the JPA concepts and code we’ll learn aren’t unique to Hibernate and can be applied to any JPA-compliant ORM. We’ll explicitly call out lessons that cover Hibernate-specific features.