From 18e88b7358d026d0ec04def29b5078f1af55b5df Mon Sep 17 00:00:00 2001 From: Gavin Date: Fri, 12 May 2023 12:50:20 +0200 Subject: [PATCH] executing queries --- .../asciidoc/introduction/Interacting.adoc | 186 +++++++++++++++++- 1 file changed, 185 insertions(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/introduction/Interacting.adoc b/documentation/src/main/asciidoc/introduction/Interacting.adoc index 36e5701b57..c7a972f9b3 100644 --- a/documentation/src/main/asciidoc/introduction/Interacting.adoc +++ b/documentation/src/main/asciidoc/introduction/Interacting.adoc @@ -296,8 +296,192 @@ Hibernate features three complementary ways to write queries: The query language is discussed in great detail below in <>. +Here we want to see how to execute a query via the `Session` or `EntityManager` API. +The method we call depends on what kind of query it is: + +- _selection queries_ return a result list, but do not modify the data, but +- _mutation queries_ modify data, and return the number of modified rows. + +Selection queries usually start with the keyword `select` or `from`, whereas mutation queries begin with the keyword `insert`, `update`, or `delete`. + +.Executing HQL +|=== +| Kind of query | `Session` method | `EntityManager` method | `Query` execution method + +| Selection query | `createSelectionQuery(String,Class)` | `createQuery(String,Class)` | `getResultList()`, `getSingleResult()`, or `getSingleResultOrNull()` +| Mutation query | `createMutationQuery(String)` | `createQuery(String,Class)` | `executeUpdate()` +|=== + +So for the `Session` API we would write: + +[source,java] +---- +List matchingBooks = + s.createSelectionQuery("from Book where title like :titleSearchPattern", Book.class) + .setParameter("titleSearchPattern", titleSearchPattern) + .getResultList(); +---- + +Or, if we're sticking to the JPA-standard APIs: + +[source,java] +---- +List matchingBooks = + s.createQuery("from Book where title like :titleSearchPattern", Book.class) + .setParameter("titleSearchPattern", titleSearchPattern) + .getResultList(); +---- + +The only difference between `createSelectionQuery()` and `createQuery()` is that `createSelectionQuery()` throw an exception if passed a mutation query. + +In the query above, `:titleSearchPattern` is called a _named parameter_. +We may also identify parameters by a number. +These are called _ordinal parameters_. + +[source,java] +---- +List matchingBooks = + s.createSelectionQuery("from Book where title like ?1", Book.class) + .setParameter(1, titleSearchPattern) + .getResultList(); +---- + +When a query has multiple parameters, named parameters tend to be easier to read, even if slightly more verbose. + +[IMPORTANT] +.Using parameters to avoid injection attacks +==== +_Never_ concatenate user input with HQL and pass the concatenated string to `createSelectionQuery()`. +This would open up the possibility for an attacker to execute arbitrary code on your database server. +==== + +If we're expecting a query to return a single result, we can use `getSingleResult()`. + +[source,java] +---- +Book book = + s.createSelectionQuery("from Book where isbn = ?1", Book.class) + .setParameter(1, isbn) + .getSingleResult(); +---- + +Or, if we're expecting it to return at most one result, we can use `getSingleResultOrNull()`. + +[source,java] +---- +Book bookOrNull = + s.createSelectionQuery("from Book where isbn = ?1", Book.class) + .setParameter(1, isbn) + .getSingleResult(); +---- + +Occasionally we need to build a query at runtime, from a set of optional conditions. +For this, JPA offers an API which allows programmatic construction of a query. + +[NOTE] +.HQL is implemented in terms of criteria queries +==== +Actually, in Hibernate 6, every HQL query is compiled to a criteria query before being translated to SQL. +This ensures that the semantics of HQL and criteria queries are identical. +==== + [[criteria-queries]] === Criteria queries +Imagine we're implementing some sort of search screen, where the user of our system is offered several different ways to constrain the query result set. +For example, we might let them search for books by title and/or the author name. +Of course, we could construct a HQL query by string concatenation, but this is a bit fragile, so it's quite nice to have an alternative. + +First we need an object for building criteria queries. +Using the JPA-standard APIs, this would be a `CriteriaBuilder`, and we get it from the `EntityManagerFactory`: + +[source,java] +---- +CriteriaBuilder cb = emf.getCriteriaBuilder(); +---- + +But if we have a `SessionFactory`, we get something much better, a `HibernateCriteriaBuilder`: + +[source,java] +---- +HibernateCriteriaBuilder cb = sf.getCriteriaBuilder(); +---- + +The `HibernateCriteriaBuilder` extends `CriteriaBuilder` and adds many operations that JPQL doesn't have. + +[TIP] +.Getting a `HibernateCriteriaBuilder` in JPA +==== +If you're using `EntityManagerFactory`, don't despair, you have two perfectly good ways to obtain the `HibernateCriteriaBuilder` associated with that factory. +Either: + +[source,java] +---- +HibernateCriteriaBuilder cb = emf.unwrap(SessionFactory.class).getCriteriaBuilder(); +---- + +Or simply: + +[source,java] +---- +HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) emf.getCriteriaBuilder(); +---- +==== + +We're ready to create a criteria query. + +[source,java] +---- +CriteriaQuery query = cb.createQuery(Book.class); +Root book = query.from(Book.class); +Predicate where = conjunction(); +if (titlePattern != null) { + where = cb.and(where, cb.like(book.get(Book_.title), titlePattern)); +} +if (namePattern != null) { + Join author = book.join(Book_.author); + where = cb.and(where, cb.like(author.get(Author_.name), namePattern)); +} +query.select(book).where(where) + .orderBy(cb.asc(book.get(Book_.title))); +---- + +:generator: https://hibernate.org/orm/tooling/ +:generator-guide: https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html#tooling-modelgen + +Here, the classes `Book_` and `Author_` are classes generated by Hibernate's {generator}[JPA Metamodel Generator], which is documented in the {generator-guide}[User Guide]. + +[NOTE] +.Injection attacks and criteria queries +==== +Notice that we did not bother treating `titlePattern` and `namePattern` as parameters. +That's safe because, _by default_, Hibernate automatically and transparently handles any literal string passed to the `CriteriaBuilder` as a JDBC parameter. + +But this behavior is controlled by the configuration setting `hibernate.criteria.value_handling_mode`. +If you change the default behavior, and set the property to `INLINE` instead of `BIND`, you _must_ pass user-input via a JPA `ParameterExpression`. +==== + +Execution of a criteria query works almost exactly like execution of HQL. + +.Executing criteria queries +|=== +| Kind of query | `Session` method | `EntityManager` method | `Query` execution method + +| Selection query | `createSelectionQuery(CriteriaQuery)` | `createQuery(CriteriaQuery)` | `getResultList()`, `getSingleResult()`, or `getSingleResultOrNull()` +| Mutation query | `createMutationQuery(CriteriaUpdate)` or `createMutationQuery(CriteriaDelte)` | `createQuery(CriteriaUpdate)` or `createQuery(CriteriaDelte)` | `executeUpdate()` +|=== + +For example: + +[source,java] +---- +List matchingBooks = + s.createSelectionQuery(query) + .getResultList(); +---- + +When all else fails, and sometimes even before that, we're left with the option of writing a query in SQL. + [[native-queries]] -=== Native SQL queries \ No newline at end of file +=== Native SQL queries +