Update Interacting.adoc for JPA 3.2
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
63d3d32f42
commit
5fca5255ca
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue