Getting Started With JPA/Hibernate
Tags: java, jpa, hibernate, database
Published: 2022-01-31 13:00:07


JPA was born as the acronym for Java Persistence API. When Java EE was donated to the Eclipse Foundation under the [Jakarta EE](https://jakarta.ee/) project, the name of the API changed to Jakarta Persistence but the term JPA is still in use. JPA solves the object-relational impedance mismatch by allowing you to map Java objects to database tables and is one of the most (if not the most) used persistence frameworks for Java.

JPA is an API specification that someone can implement–JPA providers or implementations. The most popular JPA implementation is Hibernate ORM. You can use Hibernate ORM without JPA or through JPA. One potential advantage of using it with JPA is that you can move between implementations if you want (something I have never seen happening, though). Another advantage is that someone with experience in, say EclipseLink or Apache OpenJPA, then they can use at least part of that experience when moving to Hibernate.

This article gets you started with the Hibernate implementation of JPA. We’ll use only standard features and cover only the basics. If you are looking for a comprehensive course on JPA or Hibernate, I recommend reading the [JPA spec](https://jakarta.ee/specifications/persistence/3.0/) and the official [Hibernate docs](https://hibernate.org/orm/).

Here’s a video version of this article, in case you want to see the concepts in action:

[![# JPA/Hibernate tutorial for beginners](https://img.youtube.com/vi/UVo2SRR-ZRM/hqdefault.jpg)](https://www.youtube.com/watch?v=UVo2SRR-ZRM) 

## Adding JPA and Hibernate to a Maven Project

Here are the [Maven](https://maven.apache.org/) dependencies you need to add to the **pom.xml** file:

```XML
<dependency>
  <groupId>jakarta.persistence</groupId>
  <artifactId>jakarta.persistence-api</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core-jakarta</artifactId>
  <version>5.6.4.Final</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>3.0.0</version>
</dependency>
```

You also need a [JDBC](https://dev.to/alejandro_du/jdbc-tutorial-part-1-connecting-to-a-database-kfc) driver. For example, if you are using a MariaDB database or SkySQL instance, add the following as well (check the latest version and see [what’s new](https://mariadb.com/resources/blog/whats-new-in-mariadb-connector-j-3-0) in the 3.0 series):

```XML
<dependency>
  <groupId>org.mariadb.jdbc</groupId>
  <artifactId>mariadb-java-client</artifactId>
  <version>3.0.3</version>
</dependency>
```

If you use Gradle, you can use an online Maven to Gradle dependency [converter](https://sagioto.github.io/maven2gradle).

## Creating a Database

You can use any database you want, but let’s continue with MariaDB.

> **Note:** You can create a MariaDB database in the cloud using SkySql. It's [free](https://mariadb.com/resources/blog/new-mariadb-skysql-release-makes-getting-started-free-fast-and-easy/) and it doesn’t require credit card details. Create an account and take a look at the [documentation](https://mariadb.com/products/skysql/docs/) for more information on how to get started.

Connect to the database using an SQL client and create the following database:

```SQL
CREATE DATABASE jpa_demo;
```

## Defining a Persistence Unit

A Persistence Unit is the logical grouping of a set of classes of which objects can be persisted, plus configuration parameters like database connection and pooling options. A Persistence Unit is defined in a **persistence.xml** file in the **src/main/resources/META-INF/** directory. Here’s the one we’ll use in this article:

```XML
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
       version="2.0">
  <persistence-unit name="jpa-demo-local" transaction-type="RESOURCE_LOCAL">
    <properties>
      <property name="jakarta.persistence.jdbc.url" value="jdbc:mariadb://localhost:3306/jpa_demo"/>
      <property name="jakarta.persistence.jdbc.user" value="user"/>
      <property name="jakarta.persistence.jdbc.password" value="password"/>
      <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
    </properties>
  </persistence-unit>
</persistence>
```

Pay close attention to the `name` and `transaction-type` parameters. Every Persistence Unit needs a name that we can use later in the code. The transaction type indicates who manages the transactions, JPA or the server. In this example we want JPA to manage the transactions, but in more complex scenarios that require distributed transactions across multiple databases or services like JMS and JCA, we would use JTA instead. This is an advanced topic we won’t cover in this article.

We added properties to specify the JDBC connection URL and the database user and password. We also activated a database action to drop and create the database schema automatically. This will make JPA drop the database (deleting all the tables and schema) and recreate them any time we start the application. This obviously is not a good idea in production environments, but very useful during development, especially when you are starting to define the database objects through JPA Entities.

## Implementing an Entity

An _Entity_ is a class of which instances we want to persist in the database. You can configure them through Java annotations or XML. Study the following example paying close attention to the annotations:

```Java
package com.example;
 
import jakarta.persistence.*;
 
import java.util.Objects;
 
@Entity
@Table(name = "programming_language")
public class ProgrammingLanguage {
 
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "pl_id")
  private Integer id;
 
  @Column(name = "pl_name")
  private String name;
 
  @Column(name = "pl_rating")
  private Integer rating;
 
  public ProgrammingLanguage() {
  }
 
  public ProgrammingLanguage(String name, Integer rating) {
    this.name = name;
    this.rating = rating;
  }
 
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    ProgrammingLanguage that = (ProgrammingLanguage) o;
    return Objects.equals(id, that.id);
  }
 
  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
 
  public Integer getId() {
    return id;
  }
 
  public void setId(Integer id) {
    this.id = id;
  }
 
  public String getName() {
    return name;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public Integer getRating() {
    return rating;
  }
 
  public void setRating(Integer rating) {
    this.rating = rating;
  }
}
```

At a minimum, a JPA Entity class is marked by `@Entity`, has a field annotated with `@Id`, a default constructor, and field accessors (getters). In the previous example, we added extra configuration. For example, we configured the names of the SQL table and column names, and a generated value to let the database assign primary keys. We also added a custom constructor (additional to the required non-arg constructor), `equals(Object)` and `hashCode()` methods, and field mutators (setters).

The entry point of the JPA API is the `EntityManagerFactory` class. Frameworks like Spring Framework and JakartaEE might provide instances of this type to your application. If you are not using them, you can create an instance specifying the name Persistence Unit to use:

```Java
EntityManagerFactory entityManagerFactory =
    Persistence.createEntityManagerFactory("jpa-demo-local");
```

Ideally, you should have only one instance of `EntityManagerFactory` for each Persistence Unit per application and create `EntityManager` objects (much cheaper to create) as needed. Persistence operations must be inside a transaction boundary:

```Java
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
 
... persistence logic here ...
 
transaction.commit(); // or transaction.rollback();
```

The `EntityManager` class includes methods to query and update data. For example, if we want to save a `ProgrammingLanguage` object in the database, we can add the following persistence logic to the previous snippet of code:

```Java
entityManager.persist(new ProgrammingLanguage("Java", 10));
```

Or if we want to get all the programming languages stored in the database as a list of `ProgrammingLanguage` objects:

```Java
List<ProgrammingLanguage> list = entityManager.createQuery(
    "select p from ProgrammingLanguage p where p.rating > 5",
    ProgrammingLanguage.class
).getResultList();
```

Notice that the query in the string is not SQL. If you recall from where we implemented the Entity (`ProgrammingLanguage`) we used the `@Table` annotation to configure the name of the table as `programming_language`. The query language is called [Jakarta Persistence Query Language](https://jakarta.ee/specifications/persistence/3.0/jakarta-persistence-spec-3.0.html#a4665) (JPQL), a platform-independent object-oriented language that is part of the JPA specification. This makes your database queries portable between databases.

[Check](https://jakarta.ee/specifications/persistence/3.0/apidocs/jakarta.persistence/jakarta/persistence/entitymanager) some of the methods available in the `EntityManager` interface to get an idea of all the persistence operations available. This is the interface that you’ll use the most when using JPA directly.

## Implementing a Simple JPA Service Class

Even though we used the most simple use case and ignored things such as transaction rollback in case of errors or controlled conditions, we need 5 lines of boilerplate code to run a simple query or to save, delete or update an Entity. In a Java SE application like the one we are implementing in this article, it’d be useful to have a utility class to encapsulate all the boilerplate code and make the usage of JPA easier. This utility class should include the logic to create one `PersistenceManagerFactory` per application, create a new `EntityManager` and begin, close, and commit transactions automatically.

Here’s an example of such a utility class implemented as a singleton that takes care of rolling back a transaction in case of a failure. Take into account that if you are using frameworks like Jakarta EE or Spring Data, you don’t really need this kind of class:

```Java
package com.example;
 
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
 
import java.util.function.Function;
 
public class JPAService {
 
  private static JPAService instance;
  private EntityManagerFactory entityManagerFactory;
 
  private JPAService() {
    entityManagerFactory = Persistence.createEntityManagerFactory("jpa-demo-local");
  }
 
  public static synchronized JPAService getInstance() {
    return instance == null ? instance = new JPAService() : instance;
  }
 
  public void shutdown() {
    if (entityManagerFactory != null) {
      entityManagerFactory.close();
      instance = null;
    }
  }
 
  public <T> T runInTransaction(Function<EntityManager, T> function) {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();
    boolean success = false;
    try {
      T returnValue = function.apply(entityManager);
      success = true;
      return returnValue;
 
    } finally {
      if (success) {
        transaction.commit();
      } else {
        transaction.rollback();
      }
    }
  }
}
```

By using this class we can simplify the code required to use an `EntityManager`. For example, to run a JQL query we only need to enclose the logic in a `runInTransaction(Function<EntityManager>, T)` call:

```Java
List<ProgrammingLanguage> programmingLanguages = jpaService.runInTransaction(entityManager ->
    entityManager.createQuery(
        "select p from ProgrammingLanguage p where p.rating > 5",
        ProgrammingLanguage.class
    ).getResultList());
```

Here’s an example of a full Java application that uses this class to create programming languages with random ratings and show the ones with a rating greater than 5:

```Java
package com.example;
 
import java.util.Arrays;
import java.util.List;
 
public class Application {
 
  private static final JPAService jpaService = JPAService.getInstance();
 
  public static void main(String[] args) {
    try {
      createProgrammingLanguages();
      printTopProgrammingLanguages();
 
    } finally {
      jpaService.shutdown();
    }
  }
 
  private static void createProgrammingLanguages() {
    jpaService.runInTransaction(entityManager -> {
      Arrays.stream("Java,C++,C#,JavaScript,Rust,Go,Python,PHP".split(","))
          .map(name -> new ProgrammingLanguage(name, (int) (Math.random() * 10)))
          .forEach(entityManager::persist);
      return null;
    });
  }
 
  private static void printTopProgrammingLanguages() {
    List<ProgrammingLanguage> programmingLanguages = jpaService.runInTransaction(entityManager ->
        entityManager.createQuery(
            "select p from ProgrammingLanguage p where p.rating > 5",
            ProgrammingLanguage.class
        ).getResultList());
 
    programmingLanguages.stream()
        .map(pl -> pl.getName() + ": " + pl.getRating())
        .forEach(System.out::println);
  }
}
```

## Useful Resources

We merely scratched the surface of JPA and Hibernate in this article. Fortunately, being one of the most popular Java specifications, a ton of useful resources are available online. Here’s a list of some of them:

-  [The Jakarta Persistence 3.0 official page](https://jakarta.ee/specifications/persistence/3.0/)
-  [The spec itself](https://jakarta.ee/specifications/persistence/3.0/jakarta-persistence-spec-3.0.html)
-  [The Javadoc](https://jakarta.ee/specifications/persistence/3.0/apidocs/overview-tree.html)
-  [An intro to JDBC](https://dzone.com/articles/jdbc-tutorial-part-1-connecting-to-a-database)
-  [The MariaDB JDBC driver documentation](https://mariadb.com/kb/en/mariadb-connector-j/)
-  [This article as a video](https://youtu.be/UVo2SRR-ZRM)
-  [JPA with Spring boot guide](https://spring.io/guides/gs/accessing-data-jpa/)
-  [JPA with Jakarta EE guide](https://blog.payara.fish/getting-started-with-jakarta-ee-9-jakarta-persistence-api-jpa)