DEV Community

Cover image for Getting Started With JPA/Hibernate
Alejandro Duarte
Alejandro Duarte

Posted on • Originally published at dzone.com

Getting Started With JPA/Hibernate

JPA was born as the acronym for Java Persistence API. When Java EE was donated to the Eclipse Foundation under the 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 and the official Hibernate docs.

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

# JPA/Hibernate tutorial for beginners

Adding JPA and Hibernate to a Maven Project

Here are the Maven dependencies you need to add to the pom.xml file:

<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>
Enter fullscreen mode Exit fullscreen mode

You also need a JDBC 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 in the 3.0 series):

<dependency>
  <groupId>org.mariadb.jdbc</groupId>
  <artifactId>mariadb-java-client</artifactId>
  <version>3.0.3</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

If you use Gradle, you can use an online Maven to Gradle dependency converter.

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 and it doesn’t require credit card details. Create an account and take a look at the documentation for more information on how to get started.

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

CREATE DATABASE jpa_demo;
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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:

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

EntityManagerFactory entityManagerFactory =
    Persistence.createEntityManagerFactory("jpa-demo-local");
Enter fullscreen mode Exit fullscreen mode

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:

EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();

... persistence logic here ...

transaction.commit(); // or transaction.rollback();
Enter fullscreen mode Exit fullscreen mode

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:

entityManager.persist(new ProgrammingLanguage("Java", 10));
Enter fullscreen mode Exit fullscreen mode

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

List<ProgrammingLanguage> list = entityManager.createQuery(
    "select p from ProgrammingLanguage p where p.rating > 5",
    ProgrammingLanguage.class
).getResultList();
Enter fullscreen mode Exit fullscreen mode

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 (JPQL), a platform-independent object-oriented language that is part of the JPA specification. This makes your database queries portable between databases.

Check 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:

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();
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

Discussion (0)