Modern Hibernate and Jakarta Persistence CRUD

A current Java 25 persistence application should use the Jakarta Persistence namespace consistently. The older version of this tutorial mixed javax.persistence and jakarta.persistence APIs and created a new EntityManagerFactory inside every CRUD method. This version fixes both problems.

Maven dependencies

<properties>
    <maven.compiler.release>25</maven.compiler.release>
</properties>

<dependencies>
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>7.4.5.Final</version>
    </dependency>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>9.4.0</version>
    </dependency>
</dependencies>

Jakarta Persistence 3.2 persistence.xml

<?xml version="1.0" encoding="UTF-8"?>

<persistence
    xmlns="https://jakarta.ee/xml/ns/persistence"
    xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        https://jakarta.ee/xml/ns/persistence
        https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
    version="3.2">

    <persistence-unit
        name="jpa-tutorial"
        transaction-type="RESOURCE_LOCAL">

        <properties>
            <property
                name="jakarta.persistence.jdbc.driver"
                value="com.mysql.cj.jdbc.Driver"/>

            <property
                name="jakarta.persistence.jdbc.url"
                value="jdbc:mysql://localhost:3306/hibernate_examples"/>

            <property
                name="jakarta.persistence.jdbc.user"
                value="root"/>

            <property
                name="jakarta.persistence.jdbc.password"
                value="password"/>

            <property
                name="jakarta.persistence.schema-generation.database.action"
                value="create"/>

            <property
                name="hibernate.show_sql"
                value="true"/>

            <property
                name="hibernate.format_sql"
                value="true"/>
        </properties>

    </persistence-unit>

</persistence>

JPA entity

package com.mcnz.jpa.examples;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Player {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String password;

    protected Player() {
    }

    public Player(String password) {
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Create, retrieve, update and delete

Create the EntityManagerFactory once and reuse it. Each CRUD operation gets its own EntityManager, and write operations use a transaction with rollback on failure.

package com.mcnz.jpa.examples;

import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public final class JpaCrudExample {

    private static final EntityManagerFactory EMF =
        Persistence.createEntityManagerFactory("jpa-tutorial");

    public static void main(String[] args) {
        try {
            var id = createRecord();

            retrieveRecord(id);
            updateRecord(id);
            retrieveRecord(id);
            deleteRecord(id);
        } finally {
            EMF.close();
        }
    }

    static Long createRecord() {
        var entityManager = EMF.createEntityManager();
        var transaction = entityManager.getTransaction();

        try {
            transaction.begin();

            var player = new Player("my-password");
            entityManager.persist(player);

            transaction.commit();

            IO.println("Created player: " + player.getId());
            return player.getId();

        } catch (RuntimeException e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            throw e;
        } finally {
            entityManager.close();
        }
    }

    static void retrieveRecord(Long id) {
        var entityManager = EMF.createEntityManager();

        try {
            var player = entityManager.find(Player.class, id);

            if (player != null) {
                IO.println("Password: " + player.getPassword());
            }
        } finally {
            entityManager.close();
        }
    }

    static void updateRecord(Long id) {
        var entityManager = EMF.createEntityManager();
        var transaction = entityManager.getTransaction();

        try {
            transaction.begin();

            var player = entityManager.find(Player.class, id);

            if (player != null) {
                player.setPassword("123def");
            }

            transaction.commit();

        } catch (RuntimeException e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            throw e;
        } finally {
            entityManager.close();
        }
    }

    static void deleteRecord(Long id) {
        var entityManager = EMF.createEntityManager();
        var transaction = entityManager.getTransaction();

        try {
            transaction.begin();

            var player = entityManager.find(Player.class, id);

            if (player != null) {
                entityManager.remove(player);
            }

            transaction.commit();

        } catch (RuntimeException e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            throw e;
        } finally {
            entityManager.close();
        }
    }
}

The entity manager factory is relatively expensive and is intended to be reused. An EntityManager, by contrast, represents a persistence context for a unit of work and should be closed when that work is complete.

The update example also demonstrates JPA dirty checking. Once a managed Player is loaded inside the active persistence context, changing its password is enough. Hibernate detects the change and issues the SQL update when the transaction is committed.

You can find the source code for related examples on GitHub.

Cameron McKenzie Cameron McKenzie is an AWS Certified AI Practitioner, Machine Learning Engineer, Solutions Architect and author of many popular books in the software development and Cloud Computing space. His growing YouTube channel training devs in Java, Spring, AI and ML has well over 30,000 subscribers.