Update Interacting.adoc for JPA 3.2

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-10-20 22:25:12 +02:00
parent 63d3d32f42
commit 5fca5255ca
1 changed files with 136 additions and 34 deletions

View File

@ -172,7 +172,19 @@ finally {
}
----
Using Hibernate's native APIs we might write something really similar,
But this code is extremely tedious, so there's a cleaner option:
[source,java]
----
entityManagerFactory.runInTransaction(entityManager -> {
// do the work
...
});
----
When we need to return a value from within the anonymous function, we use `callInTransaction()` instead of `runInTransaction()`.
Using Hibernate's native APIs we can write something very similar:
// [source,java]
// ----
// Session session = sessionFactory.openSession();
@ -191,8 +203,6 @@ Using Hibernate's native APIs we might write something really similar,
// session.close();
// }
// ----
but since this sort of code is extremely tedious, we have a much nicer option:
[source,java]
----
sessionFactory.inTransaction(session -> {
@ -207,13 +217,22 @@ In a container environment, the container itself is usually responsible for mana
In Java EE or Quarkus, you'll probably indicate the boundaries of the transaction using the `@Transactional` annotation.
****
JPA doesn't have a standard way to set the transaction timeout, but Hibernate does:
The `EntityTransaction` interface provides a standard way to set the transaction timeout:
[source,java]
----
session.getTransaction().setTimeout(30); // 30 seconds
entityManager.getTransaction().setTimeout(30); // 30 seconds
----
`EntityTransaction` also provides a way to set the transaction to rollback-only mode:
[source,java]
----
entityManager.getTransaction().setRollbackOnly();
----
A transaction in rollback-only mode will be rolled back when it completes.
[[persistence-operations]]
=== Operations on the persistence context
@ -256,6 +275,8 @@ On the other hand, except for `getReference()`, the following operations all res
| Obtain a persistent object given its type and its id
| `find(Class,Object,LockModeType)`
| Obtain a persistent object given its type and its id, requesting the given <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock mode>>
| `find(EntityGraph,Object)`
| Obtain a persistent object given its id and an `EntityGraph` specifying its type and associations which should be eagerly fetched
| `getReference(Class,id)`
| Obtain a reference to a persistent object given its type and its id, without actually loading its state from the database
| `getReference(Object)`
@ -264,7 +285,7 @@ On the other hand, except for `getReference()`, the following operations all res
| Refresh the persistent state of an object using a new SQL `select` to retrieve its current state from the database
| `refresh(Object,LockModeType)`
| Refresh the persistent state of an object using a new SQL `select` to retrieve its current state from the database, requesting the given <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock mode>>
| `lock(Object, LockModeType)`
| `lock(Object,LockModeType)`
| Obtain an <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock>> on a persistent object
|===
@ -280,6 +301,46 @@ The persistence context is fragile.
If you receive an exception from Hibernate, you should immediately close and discard the current session. Open a new session if you need to, but throw the bad one away first.
====
Four of these operations accept _options_, allowing influence over their behavior.
[%breakable,cols="50,~"]
|===
| Method name and parameters | Effect
| `find(Class,Object,FindOption...)`
| Obtain a persistent object given its type and its id, using the specified options
| `find(EntityGraph,Object,FindOption...)`
| Obtain a persistent object given its id and an `EntityGraph` specifying its type and associations which should be eagerly fetched, using the specified options
| `refresh(Object,LockModeType,RefreshOption...)`
| Refresh the persistent state of an object using a new SQL `select` to retrieve its current state from the database, requesting the given <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock mode>>, using the specified options
| `lock(Object,LockModeType,LockOption...)`
| Obtain an <<optimistic-and-pessimistic-locking,optimistic or pessimistic lock>> on a persistent object, using the specified options
|===
For example, JPA provides the `Timeout` class which is a `FindOption`, a `RefreshOption`, and a `LockOption`.
[source,java]
----
var book = entityManger.find(Book.class, isbn, Timeout.ms(100), CacheStoreMode.BYPASS);
----
Finally, the Hibernate `Session` offers the following method, which is capable of efficiently loading multiple entity instances in parallel:
[%breakable,cols="50,~"]
|===
| Method name and parameters | Effect
| `findMultiple(Class,List<Object>,FindOption...)`
| Obtain a list of persistent objects given their type and their ids, using the specified options
|===
The following code results in a single SQL `select` statement:
[source,java]
----
List<Book> books = session.findMultiple(Book.class, bookIds);
----
Each of the operations we've seen so far affects a single entity instance passed as an argument.
But there's a way to set things up so that an operation will propagate to associated entities.
@ -380,8 +441,25 @@ Hibernate has a slightly easier way to do it:
boolean authorsFetched = Hibernate.isInitialized(book.getAuthors());
----
But the static methods of the link:{doc-javadoc-url}org/hibernate/Hibernate.html[`Hibernate`] class let us do a lot more, and it's worth getting a bit familiar with them.
Similarly, `PersistenceUnitUtil.load()` force-fetches a proxy or collection:
[source,java]
----
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
entityManagerFactory.getPersistenceUnitUtil().load(book.getAuthors());
----
Again, `Hibernate.initialize()` is slightly more convenient:
[source,java]
----
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
Hibernate.initialize(book.getAuthors()); // fetch the Authors
----
On the other hand, the above code is very inefficient, requiring two trips to the database to obtain data that could in principle be retrieved with just one query.
The static methods of the link:{doc-javadoc-url}org/hibernate/Hibernate.html[`Hibernate`] class let us do a lot more, and it's worth getting a bit familiar with them.
Of particular interest are the operations which let us work with unfetched collections without fetching their state from the database.
For example, consider this code:
@ -394,16 +472,6 @@ boolean isByAuthor = Hibernate.contains(book.getAuthors(), authorRef); // no fet
This code fragment leaves both the set `book.authors` and the proxy `authorRef` unfetched.
Finally, `Hibernate.initialize()` is a convenience method that force-fetches a proxy or collection:
[source,java]
----
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
Hibernate.initialize(book.getAuthors()); // fetch the Authors
----
But of course, this code is very inefficient, requiring two trips to the database to obtain data that could in principle be retrieved with just one query.
It's clear from the discussion above that we need a way to request that an association be _eagerly_ fetched using a database `join`, thus protecting ourselves from the infamous N+1 selects.
One way to do this is by passing an `EntityGraph` to `find()`.
@ -413,24 +481,20 @@ One way to do this is by passing an `EntityGraph` to `find()`.
When an association is mapped `fetch=LAZY`, it won't, by default, be fetched when we call the `find()` method.
We may request that an association be fetched eagerly (immediately) by passing an `EntityGraph` to `find()`.
The JPA-standard API for this is a bit unwieldy:
[source,java]
----
var graph = entityManager.createEntityGraph(Book.class);
graph.addSubgraph(Book_.publisher);
Book book = entityManager.find(Book.class, bookId, Map.of(SpecHints.HINT_SPEC_FETCH_GRAPH, graph));
Book book = entityManager.find(graph, bookId);
----
This is untypesafe and unnecessarily verbose.
Hibernate has a better way:
[source,java]
----
var graph = session.createEntityGraph(Book.class);
graph.addSubgraph(Book_.publisher);
Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
----
//
// [source,java]
// ----
// var graph = session.createEntityGraph(Book.class);
// graph.addSubgraph(Book_.publisher);
// Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
// ----
This code adds a `left outer join` to our SQL query, fetching the associated `Publisher` along with the `Book`.
@ -441,10 +505,17 @@ We may even attach additional nodes to our `EntityGraph`:
var graph = session.createEntityGraph(Book.class);
graph.addSubgraph(Book_.publisher);
graph.addPluralSubgraph(Book_.authors).addSubgraph(Author_.person);
Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
Book book = entityManager.find(graph, bookId);
----
// [source,java]
// ----
// var graph = session.createEntityGraph(Book.class);
// graph.addSubgraph(Book_.publisher);
// graph.addPluralSubgraph(Book_.authors).addSubgraph(Author_.person);
// Book book = session.byId(Book.class).withFetchGraph(graph).load(bookId);
// ----
This results in a SQL query with _four_ ``left outer join``s.
[NOTE]
@ -460,8 +531,13 @@ JPA specifies that any given `EntityGraph` may be interpreted in two different w
Any association not belonging to the entity graph is proxied and loaded lazily only if required.
- A _load graph_ specifies that the associations in the entity graph are to be fetched in addition to the associations mapped `fetch=EAGER`.
An `EntityGraph` passed directly to `find()` is always interpreted as a load graph.
[TIP]
====
You're right, the names make no sense.
But don't worry, if you take our advice, and map your associations `fetch=LAZY`, there's no difference between a "fetch" graph and a "load" graph, so the names don't matter.
====
[NOTE]
====
@ -580,6 +656,13 @@ A second way to reduce the cost of flushing is to load entities in _read-only_ m
- `SelectionQuery.setReadOnly(true)` specifies that every entity returned by a given query should be loaded in read-only mode, and
- `Session.setReadOnly(Object, true)` specifies that a given entity already loaded by the session should be switched to read-only mode.
Hibernate's `ReadOnlyMode` is a custom `FindOption`:
[source,java]
----
var book = entityManager.find(Book.class, isbn, ReadOnlyMode.READ_ONLY);
----
It's not necessary to dirty-check an entity instance in read-only mode.
[[queries]]
@ -1224,6 +1307,11 @@ Therefore, Hibernate has some APIs that streamline certain more complicated look
| `byMultipleIds()` | Lets us load a _batch_ of ids at the same time
|===
[WARNING]
====
Since the introduction of `FindOption` in JPA 3.2, `byId()` is now much less useful.
====
Batch loading is very useful when we need to retrieve multiple instances of the same entity class by id:
[source,java]
@ -1270,7 +1358,21 @@ Notice that this code fragment is completely typesafe, again thanks to the <<met
=== Interacting directly with JDBC
From time to time we run into the need to write some code that calls JDBC directly.
Unfortunately, JPA offers no good way to do this, but the Hibernate `Session` does.
The `EntityManager` now offers a convenient way to do this:
[source,java]
----
entityManager.runWithConnection((Connection connection) -> {
try (var callable = connection.prepareCall("{call myproc(?)}")) {
callable.setLong(1, argument);
callable.execute();
}
});
----
To return a value, use `callWithConnection()` instead of `runWithConnection()`.
The Hibernate `Session` has an older, slightly simpler API:
[source,java]
----
@ -1282,10 +1384,10 @@ session.doWork(connection -> {
});
----
The `Connection` passed to the work is the same connection being used by the session, and so any work performed using that connection occurs in the same transaction context.
If the work returns a value, use `doReturningWork()` instead of `doWork()`.
The `Connection` passed to the work is the same connection being used by the session, and so any work performed using that connection occurs in the same transaction context.
[TIP]
====
In a container environment where transactions and database connections are managed by the container, this might not be the easiest way to obtain the JDBC connection.