Skip to content

Commit

Permalink
Update Interacting.adoc for JPA 3.2
Browse files Browse the repository at this point in the history
Signed-off-by: Gavin King <gavin@hibernate.org>
  • Loading branch information
gavinking committed Oct 20, 2024
1 parent 63d3d32 commit 5fca525
Showing 1 changed file with 134 additions and 32 deletions.
166 changes: 134 additions & 32 deletions documentation/src/main/asciidoc/introduction/Interacting.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 -> {
Expand All @@ -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

Expand Down Expand Up @@ -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)`
Expand All @@ -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
|===

Expand All @@ -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.

Expand Down Expand Up @@ -380,29 +441,36 @@ 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:

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:
[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
Author authorRef = session.getReference(Author.class, authorId); // obtain an unfetched proxy
boolean isByAuthor = Hibernate.contains(book.getAuthors(), authorRef); // no fetching
Hibernate.initialize(book.getAuthors()); // fetch the Authors
----

This code fragment leaves both the set `book.authors` and the proxy `authorRef` unfetched.
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.

Finally, `Hibernate.initialize()` is a convenience method that force-fetches a proxy or collection:
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:

[source,java]
----
Book book = session.find(Book.class, bookId); // fetch just the Book, leaving authors unfetched
Hibernate.initialize(book.getAuthors()); // fetch the Authors
Author authorRef = session.getReference(Author.class, authorId); // obtain an unfetched proxy
boolean isByAuthor = Hibernate.contains(book.getAuthors(), authorRef); // no fetching
----

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.
This code fragment leaves both the set `book.authors` and the proxy `authorRef` unfetched.

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()`.
Expand All @@ -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`.

Expand All @@ -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]
Expand All @@ -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]
====
Expand Down Expand Up @@ -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]]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -1270,22 +1358,36 @@ 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]
----
session.doWork(connection -> {
entityManager.runWithConnection((Connection connection) -> {
try (var callable = connection.prepareCall("{call myproc(?)}")) {
callable.setLong(1, argument);
callable.execute();
}
});
----

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.
To return a value, use `callWithConnection()` instead of `runWithConnection()`.

The Hibernate `Session` has an older, slightly simpler API:

[source,java]
----
session.doWork(connection -> {
try (var callable = connection.prepareCall("{call myproc(?)}")) {
callable.setLong(1, argument);
callable.execute();
}
});
----

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.
Expand Down

0 comments on commit 5fca525

Please sign in to comment.