fix broken code examples, and make 'em more readable

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-10-24 18:37:22 +02:00
parent 7002ee8d0d
commit 7bf6004ef8
2 changed files with 58 additions and 36 deletions

View File

@ -447,10 +447,14 @@ We might start with something like this, a mix of UI and persistence logic:
[source,java] [source,java]
---- ----
@Path("/") @Produces("application/json") @Path("/")
@Produces("application/json")
public class BookResource { public class BookResource {
@GET @Path("book/{isbn}") private final SessionFactory sessionfactory = .... ;
@GET
@Path("book/{isbn}")
public Book getBook(String isbn) { public Book getBook(String isbn) {
var book = sessionFactory.fromTransaction(session -> session.find(Book.class, isbn)); var book = sessionFactory.fromTransaction(session -> session.find(Book.class, isbn));
return book == null ? Response.status(404).build() : book; return book == null ? Response.status(404).build() : book;
@ -473,16 +477,23 @@ Let's now consider a slightly more complicated case.
[source,java] [source,java]
---- ----
@Path("/") @Produces("application/json") @Path("/")
@Produces("application/json")
public class BookResource { public class BookResource {
private static final int RESULTS_PER_PAGE = 20; private static final int RESULTS_PER_PAGE = 20;
@GET @Path("books/{titlePattern}/{page:\\d+}") private final SessionFactory sessionfactory = .... ;
public List<Book> findBooks(String titlePattern, int page) {
var books = sessionFactory.fromTransaction(session -> { @GET
return session.createSelectionQuery("from Book where title like ?1 order by title", Book.class) @Path("books/{titlePattern}/{pageNumber:\\d+}")
public List<Book> findBooks(String titlePattern, int pageNumber) {
var page = Page.page(RESULTS_PER_PAGE, pageNumber);
var books =
sessionFactory.fromTransaction(session -> {
var findBooksByTitle = "from Book where title like ?1 order by title";
return session.createSelectionQuery(findBooksByTitle, Book.class)
.setParameter(1, titlePattern) .setParameter(1, titlePattern)
.setPage(Page.page(RESULTS_PER_PAGE, page)) .setPage(page)
.getResultList(); .getResultList();
}); });
return books.isEmpty() ? Response.status(404).build() : books; return books.isEmpty() ? Response.status(404).build() : books;
@ -498,9 +509,9 @@ Let's hit the code with our favorite thing, the Extract Method refactoring. We o
[source,java] [source,java]
---- ----
static List<Book> findBooksTitled(Session session, static List<Book> findBooksTitled(Session session, String titlePattern, Page page) {
String titlePattern, Page page) { var findBooksByTitle = "from Book where title like ?1 order by title";
return session.createSelectionQuery("from Book where title like ?1 order by title", Book.class) return session.createSelectionQuery(findBooksByTitle, Book.class)
.setParameter(1, titlePattern) .setParameter(1, titlePattern)
.setPage(page) .setPage(page)
.getResultList(); .getResultList();
@ -522,13 +533,13 @@ We need a place to put the annotation, so let's move our query method to a new c
query = "from Book where title like :title order by title") query = "from Book where title like :title order by title")
class Queries { class Queries {
static List<Book> findBooksTitled(Session session, static List<Book> findBooksTitled(Session session, String titlePattern, Page page) {
String titlePattern, Page page) {
return session.createQuery(Queries_._findBooksByTitle_) //type safe reference to the named query return session.createQuery(Queries_._findBooksByTitle_) //type safe reference to the named query
.setParameter("title", titlePattern) .setParameter("title", titlePattern)
.setPage(page) .setPage(page)
.getResultList(); .getResultList();
} }
} }
---- ----
@ -546,11 +557,13 @@ Whatever the case, the code which orchestrates a unit of work usually just calls
[source,java] [source,java]
---- ----
@GET @GET
@Path("books/{titlePattern}") @Path("books/{titlePattern}/{pageNumber:\\d+}")
public List<Book> findBooks(String titlePattern) { public List<Book> findBooks(String titlePattern, int pageNumber) {
var books = sessionFactory.fromTransaction(session -> var page = Page.page(RESULTS_PER_PAGE, pageNumber);
Queries.findBooksTitled(session, titlePattern, var books =
Page.page(RESULTS_PER_PAGE, page))); sessionFactory.fromTransaction(session ->
// call handwritten query method
Queries.findBooksTitled(session, titlePattern, page));
return books.isEmpty() ? Response.status(404).build() : books; return books.isEmpty() ? Response.status(404).build() : books;
} }
---- ----
@ -567,7 +580,9 @@ Suppose we simplify `Queries` to just the following:
[source,java] [source,java]
---- ----
// a sort of proto-repository, this interface is never implemented
interface Queries { interface Queries {
// a HQL query method with a generated static "implementation"
@HQL("where title like :title order by title") @HQL("where title like :title order by title")
List<Book> findBooksTitled(String title, Page page); List<Book> findBooksTitled(String title, Page page);
} }
@ -579,11 +594,13 @@ We can call it just like we were previously calling our handwritten version:
[source,java] [source,java]
---- ----
@GET @GET
@Path("books/{titlePattern}") @Path("books/{titlePattern}/{pageNumber:\\d+}")
public List<Book> findBooks(String titlePattern) { public List<Book> findBooks(String titlePattern, int pageNumber) {
var books = sessionFactory.fromTransaction(session -> var page = Page.page(RESULTS_PER_PAGE, pageNumber);
Queries_.findBooksTitled(session, titlePattern, var books =
Page.page(RESULTS_PER_PAGE, page))); sessionFactory.fromTransaction(session ->
// call the generated query method "implementation"
Queries_.findBooksTitled(session, titlePattern, page));
return books.isEmpty() ? Response.status(404).build() : books; return books.isEmpty() ? Response.status(404).build() : books;
} }
---- ----
@ -592,32 +609,37 @@ In this case, the quantity of code eliminated is pretty trivial.
The real value is in improved type safety. The real value is in improved type safety.
We now find out about errors in assignments of arguments to query parameters at compile time. We now find out about errors in assignments of arguments to query parameters at compile time.
This is all quite nice so far, but at this point you're probably wondering whether we could use dependency injection to obtain an _instance_ of the `Queries` interface. This is all quite nice so far, but at this point you're probably wondering whether we could use dependency injection to obtain an _instance_ of the `Queries` interface, and have this object take care of obtaining its own `Session`.
Well, indeed we can. Well, indeed we can.
What we need to do is indicate the kind of session the `Queries` interface depends on, by adding a method to retrieve the session. What we need to do is indicate the kind of session the `Queries` interface depends on, by adding a method to retrieve the session.
Observe, again, that we're _still_ not attempting to hide the `Session` from the client code.
[source,java] [source,java]
---- ----
// a true repository interface with generated implementation
interface Queries { interface Queries {
EntityManager entityManager(); // declare the kind of session backing this repository
Session session();
// a HQL query method with a generated implementation
@HQL("where title like :title order by title") @HQL("where title like :title order by title")
List<Book> findBooksTitled(String title, Page page); List<Book> findBooksTitled(String title, Page page);
} }
---- ----
The `Queries` interface is now considered a _repository_, and we may use CDI to inject the repository implementation generated by Hibernate Processor: The `Queries` interface is now considered a _repository_, and we may use CDI to inject the repository implementation generated by Hibernate Processor.
Also, since I guess we're now working in some sort of container environment, we'll let the container manage transactions for us.
[source,java] [source,java]
---- ----
@Inject Queries queries; @Inject Queries queries; // inject the repository
@GET @GET
@Path("books/{titlePattern}") @Path("books/{titlePattern}/{pageNumber:\\d+}")
@Transactional @Transactional
public List<Book> findBooks(String titlePattern) { public List<Book> findBooks(String titlePattern, int pageNumber) {
var books = queries.findBooksTitled(session, titlePattern, var page = Page.page(RESULTS_PER_PAGE, pageNumber);
Page.page(RESULTS_PER_PAGE, page)); var books = queries.findBooksTitled(session, titlePattern, page); // call the repository method
return books.isEmpty() ? Response.status(404).build() : books; return books.isEmpty() ? Response.status(404).build() : books;
} }
---- ----

View File

@ -424,8 +424,8 @@ What if we would like to inject a `Queries` object instead of calling its constr
[%unbreakable] [%unbreakable]
[TIP] [TIP]
==== ====
As you <<architecture,recall>>, we don't think these things really need to be container-managed objects. As you <<organizing-persistence,recall>>, we don't think these things really need to be container-managed objects.
But if you _want_ them to be—if you're allergic to calling constructors, for some reason—then: But if you _want_ them to be--if you're allergic to calling constructors, for some reason--then:
- placing `jakarta.inject` on the build path will cause an `@Inject` annotation to be added to the constructor of `Queries_`, and - placing `jakarta.inject` on the build path will cause an `@Inject` annotation to be added to the constructor of `Queries_`, and
- placing `jakarta.enterprise.context` on the build path will cause a `@Dependent` annotation to be added to the `Queries_` class. - placing `jakarta.enterprise.context` on the build path will cause a `@Dependent` annotation to be added to the `Queries_` class.