Java persistence with JPA and Hibernate: Persisting data to a database

Write, build, and run an example application that persists data to and from a relational database using Hibernate, JPA, and the repository pattern.

JPA and Hibernate: Persisting data to a database
Galina-Photo/Shutterstock

In this second half of the Java persistence with JPA and Hibernate tutorial, we move past concepts and start writing code that persists data to and from a relational database using JPA with Hibernate. We'll start by configuring an example application to use Hibernate as the JPA provider, then quickly configure the EntityManager and write two classes that we want to persist to the database: Book and Author. Finally, we'll write a simple application that pulls together all the application components and successfully persists our two entities to the database.

You'll learn how to:

  • Configure a Java application to use Hibernate as your JPA provider.
  • Configure JPA's EntityManager.
  • Create a simple JPA domain model representing two classes with a one-to-many relationship.
  • Use repositories to cleanly separate persistence logic from your application code.
  • Write, build, and run the example application using JPA to connect with a relational database.

You'll also get started with JPA Query Language (JPQL) and use it to execute a few simple database operations on the example application.

The complete source code for the example application can be found here. If you have not read the first half of this tutorial, we recommend doing that before you continue.

Configuring Hibernate

To keep things simple, we're going to use the embedded H2 database for both development and runtime examples. You can change the JDBC URL in the EntityManager to point to any database you wish.

Start by reviewing the Maven POM file for the project, shown in Listing 1.

Listing 1. pom.xml


<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 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.infoworld</groupId>
    <artifactId>jpa-example</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>jpa-example</name>
    <url>http://maven.apache.org</url>
    <properties>
        <java.version>21</java.version>
        <hibernate.version>6.3.1.Final</hibernate.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.infoworld.jpa.JpaExample</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>${hibernate.version}</version>
</dependency>
<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>9.1.0</version>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>jakarta.json.bind</groupId>
    <artifactId>jakarta.json.bind-api</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.eclipse</groupId>
    <artifactId>yasson</artifactId>
    <version>3.0.3</version>
    <scope>runtime</scope>
</dependency>

    </dependencies>
</project>

We'll build this project using Java 21 and Hibernate version 6.3, which was the latest version as of this writing. Plugins in the build node will set the Java compilation version, make the resulting JAR file executable, and ensure that all dependencies are copied to a lib folder so that the executable JAR can run. We include the following dependencies:

  • Hibernate's core functionality: hibernate-core
  • The JPA API: hibernate-jpa-2.1-api
  • The embedded H2 database: h2

Note that H2's scope is set to runtime so that we can use it when we run our code.

Configuring the EntityManager

Recall that our JPA's EntityManager is driven by the persistence.xml file. Listing 2 shows the contents of this file.

Listing 2. /resources/persistence.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="Books" transaction-type="RESOURCE_LOCAL">
        <!-- Persistence provider -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver" />
            <property name="jakarta.persistence.jdbc.url"    value="jdbc:h2:mem:bookstore" />
            <property name="jakarta.persistence.jdbc.user" value="sa" />
            <property name="jakarta.persistence.jdbc.password" value="" />

            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="show_sql" value="true"/>
            <property name="hibernate.temp.use_jdbc_metadata_defaults" value="false"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Although we are using persistence.xml, you could achieve a similar configuration with an annotated EntityManager. In that case, you would use the @PersistenceContext(unitName = "Books") annotation.

The persistence.xml file begins with a persistence node that can contain one or more persistence units. A persistence-unit has a name, which we'll use later when we create the EntityManager, and it defines the attributes of that unit. In this case, we configure properties in this unit to do the following:

  • Specify HibernatePersistenceProvider, so the application knows we're using Hibernate as our JPA provider.
  • Define the database configuration via JDBC. In this case, we're using an in-memory H2 instance.
  • Configure Hibernate, including setting the Hibernate dialect to H2Dialect, so that Hibernate knows how to communicate with the H2 database.

Note that JPA will automatically scan and discover your annotated entity classes.

The domain model

For this application, we're modeling a Book class and an Author class. These entities have a one-to-many relationship, meaning that a book can only be written by a single author, but an author can write many books. Figure 1 shows this domain model.

JPA and Hibernate--Example of a domain model with two entities. Steven Haines

Figure 1. Domain model for a JPA/Hibernate application with two entities.

When we talk about database tables, we typically speak about a "data model," but when we talk about Java entities and their relationships, we typically refer to it as a "domain model."

Modeling the Book class

Let's begin with our entities. Listing 3 shows the source code for the Book class.

Listing 3. Book.java


package com.infoworld.jpa.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;

@Entity
@Table(name = "BOOK")
@NamedQueries({
        @NamedQuery(name = "Book.findByName",
                query = "SELECT b FROM Book b WHERE b.name = :name"),
        @NamedQuery(name = "Book.findAll",
                query = "SELECT b FROM Book b")
})
public class Book {
    @Id
    @GeneratedValue
    private Integer id;
    private String name;

    @ManyToOne
    @JoinColumn(name="AUTHOR_ID")
    private Author author;

    public Book() {
    }

    public Book(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Book(String name) {
        this.name = name;
    }

    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 Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author=" + author.getName() +
                '}';
    }
}

This Book is a simple POJO (plain old Java object) that manages three properties:

  • id: The primary key, or identifier, of the book.
  • name: The name, or title, of the book.
  • author: The author who wrote the book.

The class itself is annotated with three annotations:

  • @Entity: Identifies the Book as a JPA entity.
  • @Table: Overrides the name of the table to which this entity will be persisted. In this case we define the table name as BOOK.
  • @NamedQueries: Allows you to define JPA Query Language queries that can later be retrieved and executed by the EntityManager.

About JPA Query Language (JPQL)

JPQL is similar to SQL, but rather than operating on database column names, it operates on entities, their fields, and their relationships. JPA refers to these queries as running on top of an "abstract schema," which gets translated to a proper database schema. Entities are mapped to that database schema. Here's the format for a simple JPQL query:


SELECT returnedEntity FROM entityName var WHERE whereClause

The Book.findAll query is defined as SELECT b FROM Book b, in which "Book" is the name of the entity and "b" is the variable name assigned to "Book." This is equivalent to the SQL SELECT * FROM BOOK.

The Book.findByName uses a named parameter, "name," as in:


SELECT b FROM Book b WHERE b.name = :name

Parameters can be referenced by name or by position. When referencing a parameter by name, you specify the name your query expects, such as "name" or "bookName," prefaced by a ":". If, instead, you wanted to reference the name by position, you could replace ":name" with "?1". When executing the query, you would set the parameter with position of "1". Binding the book name to "MyBook" is equivalent to the following SQL:


SELECT * FROM BOOK WHERE name='MyBook'

The Book's id attribute is annotated with both @Id and @GeneratedValue. The @Id annotation identifies the id as the primary key of the Book, which will resolve to the primary key of the underlying database. The @GeneratedValue annotation tells JPA that the database should generate the primary key when the entity is persisted to the database. Because we have not specified a @Column annotation, the id will be mapped to the same column name, "id."

1 2 3 Page 1
Page 1 of 3