What is new in Jakarta Persistence 3.2

This tutorial provides an overview of some of the new features that are available in the upcoming Jakarta Persistence API 3.2, which is part of Jakarta EE 11 bundle.

Jakarta Persistence defines a standard for management of persistence and ORM Java(R) Enterprise environments. If you are new to Jakarta Persistence API we recommend checking this article for a quick introduction: Java Persistence (JPA) Tutorial with WildFly
Here, we would like to mention some of the most interesting features available in the new release. For a full list, check the specification at: https://jakarta.ee/specifications/persistence/3.2/

Optional persistence.xml

Firstly, in non-managed environments, it would be simpler to reduce the amount of files to set up a Jakarta Persistence application. In the new API specification, you can skip the creation of the persistence.xml file and just define an EntityManagerFactory programmatically:

Here is an example:

@Produces @ApplicationScoped 
EntityManagerFactory configure() {
    return new PersistenceConfiguration("CustomerData").jtaDataSource("java:global/jdbc/MyDatasource")
    .managedClass(Customer.class)
    .property(PersistenceConfiguration.LOCK_TIMEOUT, 5000)
    .createEntityManagerFactory();
}

Then, if you need the injection of an EntityManager, you can use a CDI Producer method/Disposer to make the EntityManager available as a CDI managed bean.

@Produces 
EntityManager create(EntityManagerFactory factory) {
    return factory.createEntityManager();
}

void close(@Disposes EntityManager entityManager) {
    entityManager.close();
}

Programmatic schema export

Besides the programmatic creation of the EntityManagerFactory, you can also create your schema programmatically as follows:

factory.getSchemaManager().create(true);  

The SchemaManager API has also a truncate() method which is ideal for cleaning up before or after tests.

emf.getSchemaManager().truncate(); // destroy all my data

Using Java Records as Embeddable Entities in Jakarta Persistence 3.2

The introduction of Java records in Java 14 (as a preview feature, finalized in Java 16) brought a concise and immutable way to define data-carrying classes. Jakarta Persistence 3.2 takes full advantage of this feature by allowing Java records to be used as embeddable entities, simplifying the management of complex entity state while adhering to modern Java practices.
What are Embeddable Entities?

Embeddable entities are lightweight classes used to encapsulate a portion of an entity’s state. Unlike regular entities, embeddables:

  • Do not have a persistent identity. They exist only as part of the parent entity.
  • Are strictly bound to their owning entity and cannot be shared across multiple entities.
  • Can contain relationships to entities, collections, or even other embeddables, with some restrictions (e.g., no relationships for embedded IDs or map keys).

Why Use Java Records for Embeddables?

Java records provide:

  • Immutability: By design, records are immutable, ensuring the integrity of the state encapsulated in the embeddable.
  • Conciseness: Records require less boilerplate compared to traditional classes, as they automatically generate constructors, equals, hashCode, and toString methods.

Finally, here’s an example of using a record as an Embeddable in Jakarta Persistence:

import jakarta.persistence.Embeddable;

@Embeddable
public record Employee(String name, String department, long salary) {
}

In this example:

  • The Employee record encapsulates fields (name, department, salary) representing part of an entity’s state.
  • The @Embeddable annotation makes it suitable for use as an embedded component in an entity.

Using the Record in an Entity

Then, you can embed the Employee record in an Entity as follows:

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Company {

    @Id
    private Long id;

    private Employee employee;

    // Constructors, getters, and setters (if needed)
}

The employee field is of type Employee, seamlessly integrating the record into the entity state.

New Type-Safe Options for Entity Operations in Jakarta Persistence 3.2

Jakarta Persistence 3.2 introduces a type-safe and more readable approach to specifying options for common entity operations like find(), lock(), and refresh(). This enhancement replaces the older, error-prone pattern of using string-based hints (maps of string keys and values) with marker interfaces and pre-defined option implementations.
Why This Change?

Previously, when passing hints to methods like find(), developers used a Map to specify options. This method was:

  • Error-prone: Mistyping keys like “jakarta.persistence.cache.retrieveMode” could silently cause runtime issues.
  • Verbose: Even for simple options, you had to define a full map.
  • Hard to read: Options expressed as key-value pairs lacked clarity.

Now, the new marker interfaces (FindOption, LockOption, RefreshOption) introduce a type-safe, modern, and concise way to specify these options.

For example, here is how to use the em.find() with Hints (Before Jakarta 3.2)

Here’s how you might bypass the cache, set a query timeout, and mark a query as read-only using the older API:

var user = em.find(User.class, userId, 
    Map.of("jakarta.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS,
           "jakarta.persistence.query.timeout", 500,
           "org.hibernate.readOnly", true));

Issues with this approach:

  • Strings like “jakarta.persistence.cache.retrieveMode” are prone to typos.
  • The meaning of each key-value pair is not immediately clear.
  • Developers must manually look up the supported keys for the provider they’re using.

On the other hand, here is how to use em.find() with Type-Safe Options (Jakarta 3.2)

In Jakarta Persistence 3.2, you can implement the same functionality in a more concise and type-safe manner:

var user = em.find(User.class, userId, 
    CacheRetrieveMode.BYPASS, 
    Timeout.milliseconds(500), 
    READ_ONLY);

Advantages:

  • Type safety: Options like CacheRetrieveMode.BYPASS and Timeout.milliseconds(500) are strongly typed, reducing runtime errors.
  • Improved readability: The code clearly communicates the options being set.
  • Extensibility: Providers can add their own custom implementations of FindOption, LockOption, and RefreshOption.

New Transaction Management Methods in Jakarta Persistence 3.2: runInTransaction and callInTransaction

Jakarta Persistence 3.2 introduces two new methods in the EntityManagerFactory interface: runInTransaction and callInTransaction. These methods simplify transaction management when working with application-managed EntityManager instances by handling transaction boundaries and resource cleanup transparently. They improve code readability and reduce boilerplate for both read and write operations.

The following test demonstrates the usage of these methods to perform a database write operation followed by a read operation:

@Test
public void example() {
    // Perform a write operation using runInTransaction
    emf.runInTransaction(em -> em.runWithConnection(connection -> {
    try (var stmt = ((Connection) connection).createStatement()) {
        stmt.execute("INSERT INTO product (id, name, category, price) VALUES (101, 'Laptop', 'Electronics', 1200.00)");
    } catch (Exception e) {
        Assertions.fail("JDBC operation failed");
    }
    }));

    // Perform a read operation using callInTransaction
    var product = emf.callInTransaction(em -> em.find(Product.class, 101L));

    // Validate the results
    assertNotNull(product);
    assertEquals("Laptop", product.getName());
    assertEquals("Electronics", product.getCategory());
    assertEquals(1200.00, product.getPrice());
}

Key Points in the Example

runInTransaction for Write Operations:

  • Inserts a new product record into the database.
  • Uses the EntityManager.runWithConnection to perform a raw JDBC operation.
  • Automatically commits the transaction if no exception is thrown.

callInTransaction for Read Operations:
*Retrieves the inserted Product entity by its ID.
*Automatically commits the transaction after the query execution and returns the Employee object.

Automatic Resource Management:

  • The EntityManager is automatically closed at the end of each method call.
  • Transactions are properly committed or rolled back based on the success or failure of the provided lambda.

Conclusion

In conclusion, Jakarta Persistence 3.2 introduces a range of features designed to streamline development and enhance code readability. By adopting these new features, developers can reduce boilerplate code, increase productivity, and ensure their applications are both efficient and maintainable.

Was this article helpful? We need your support to keep MasterTheBoss alive!