more HQL doc rewriting

much better documentation for HQL statement types
rewrite section on predicates and logical operators
rewrite from/join section
stop shouting so much
document that H6 auto-removes non-distinct entity results
document limit/offset
document set operators and 'cross join'
slight reorg of Query API chapter, and new title
use "ordinal" instead of "positional" for parameters
This commit is contained in:
Gavin King 2022-01-01 12:14:07 +01:00
parent df72d7db3d
commit 0966c7be1d
10 changed files with 691 additions and 536 deletions

View File

@ -1,5 +1,5 @@
[[hql]]
== HQL and JPQL
== Java API for HQL and JPQL
:modeldir: ../../../../../../main/java/org/hibernate/userguide/model
:sourcedir: ../../../../../../test/java/org/hibernate/userguide/hql
:extrasdir: extras
@ -21,20 +21,47 @@ However, HQL is the most convenient option for most people most of the time.
The actual query language itself is discussed the <<chapters/query/criteria/QueryLanguage.adoc#query-language,next chapter>>.
This chapter describes the Java APIs for executing HQL and JPQL queries.
[[hql-examples-domain-model]]
=== Example domain model
The code examples featured in this chapter, and the next, make use of the following annotated domain model.
[[hql-examples-domain-model-example]]
.Examples domain model
====
[source, JAVA, indent=0]
----
include::{modeldir}/Person.java[tags=hql-examples-domain-model-example]
include::{modeldir}/AddressType.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Partner.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Phone.java[tags=hql-examples-domain-model-example]
include::{modeldir}/PhoneType.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Call.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Payment.java[tags=hql-examples-domain-model-example]
include::{modeldir}/CreditCardPayment.java[tags=hql-examples-domain-model-example]
include::{modeldir}/WireTransferPayment.java[tags=hql-examples-domain-model-example]
----
====
[[hql-getting-started]]
=== HQL query basics
=== Obtaining a `Query` object
A query may be provided to Hibernate as either:
* an _inline query_: the text of the query is passed as a string to the session at runtime, or
* a _named query_: the query is specified in an annotation or XML file, and identified by name at runtime.
The API for actually executing the query is the same in both cases.
A `Query` object is obtained from the `EntityManager` or Hibernate `Session` by calling `createQuery()` or `createNamedQuery()`.
[TIP]
====
One big advantage to named queries is that they are parsed by Hibernate at startup time, and so some sorts of errors are reported much earlier.
====
The API for actually executing the query is the same in both cases, as we're about to see <<query-execution,below>>.
[[named-queries]]
==== Declaring named queries
@ -64,34 +91,9 @@ include::{modeldir}/Phone.java[tags=jpql-api-hibernate-named-query-example, inde
//include::{sourcedir}/HQLTest.java[tags=jpql-api-hibernate-named-query-example, indent=0]
====
[[hql-examples-domain-model]]
==== Example domain model
The code examples featured in this chapter, and the next, make use of the following annotated domain model.
[[hql-examples-domain-model-example]]
.Examples domain model
[TIP]
====
[source, JAVA, indent=0]
----
include::{modeldir}/Person.java[tags=hql-examples-domain-model-example]
include::{modeldir}/AddressType.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Partner.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Phone.java[tags=hql-examples-domain-model-example]
include::{modeldir}/PhoneType.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Call.java[tags=hql-examples-domain-model-example]
include::{modeldir}/Payment.java[tags=hql-examples-domain-model-example]
include::{modeldir}/CreditCardPayment.java[tags=hql-examples-domain-model-example]
include::{modeldir}/WireTransferPayment.java[tags=hql-examples-domain-model-example]
----
One big advantage to named queries is that they are parsed by Hibernate at startup time, and so some sorts of errors are reported much earlier.
====
[[query-api]]
@ -174,10 +176,10 @@ Since `org.hibernate.query.Query` inherits `TypedQuery`, which in turn inherits
[[jpql-query-parameters]]
==== Binding arguments to query parameters
A query may have named parameters or positional parameters:
A query may have named parameters or ordinal parameters:
* named parameters are specified using the syntax `:name`, and
* positional parameters are specified using the syntax `?1`, `?2`, etc.
* ordinal parameters are specified using the syntax `?1`, `?2`, etc.
If the query has parameters, arguments must be bound to each parameter before the query is executed.
@ -190,10 +192,10 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-parameter-example]
----
====
JPQL-style positional parameters are numbered from `1`.
Just like with named parameters, a positional parameter may appear multiple times in a query.
JPQL-style ordinal parameters are numbered from `1`.
Just like with named parameters, a ordinal parameter may appear multiple times in a query.
[[jpql-api-positional-parameter-example]]
[[jpql-api-ordinal-parameter-example]]
.Positional parameter binding
====
[source, JAVA, indent=0]
@ -204,7 +206,7 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-positional-parameter-example]
[NOTE]
====
It's not a good idea to mix named and positional parameters in a single query.
It's not a good idea to mix named and ordinal parameters in a single query.
====
[[jpql-query-execution]]
@ -283,28 +285,30 @@ Jakarta Persistence defines some standard hints with the prefix `jakarta.persist
Using provider-specific hints limits your program's portability to only a small degree.
|===
| `jakarta.persistence.query.timeout` | Defines the query timeout, in milliseconds.
| `jakarta.persistence.fetchgraph` | Defines a _fetchgraph_ EntityGraph.
Attributes explicitly specified as `AttributeNodes` are treated as `FetchType.EAGER` (via join fetch or subsequent select).
For details, see the EntityGraph discussions in <<chapters/fetching/Fetching.adoc#fetching,Fetching>>.
| `jakarta.persistence.loadgraph` | Defines a _loadgraph_ EntityGraph.
Attributes explicitly specified as AttributeNodes are treated as `FetchType.EAGER` (via join fetch or subsequent select).
Attributes that are not specified are treated as `FetchType.LAZY` or `FetchType.EAGER` depending on the attribute's definition in metadata.
For details, see the EntityGraph discussions in <<chapters/fetching/Fetching.adoc#fetching,Fetching>>.
| `org.hibernate.cacheMode` | Defines the `CacheMode` to use. See `org.hibernate.query.Query#setCacheMode`.
| `org.hibernate.cacheable` | Defines whether the query is cacheable. true/false. See `org.hibernate.query.Query#setCacheable`.
| `org.hibernate.cacheRegion` | For queries that are cacheable, defines a specific cache region to use. See `org.hibernate.query.Query#setCacheRegion`.
| `org.hibernate.comment` | Defines the comment to apply to the generated SQL. See `org.hibernate.query.Query#setComment`.
| `org.hibernate.fetchSize` | Defines the JDBC fetch-size to use. See `org.hibernate.query.Query#setFetchSize`.
| `org.hibernate.flushMode` | Defines the Hibernate-specific `FlushMode` to use. See `org.hibernate.query.Query#setFlushMode.` If possible, prefer using `jakarta.persistence.Query#setFlushMode` instead.
| `org.hibernate.readOnly` | Defines that entities and collections loaded by this query should be marked as read-only. See `org.hibernate.query.Query#setReadOnly`.
|===
| Hint name | Interpretation | Equivalent Hibernate API
For complete details, see the `Query` {jpaJavadocUrlPrefix}Query.html[Javadocs].
| `jakarta.persistence.query.timeout` | The query timeout, in milliseconds. | `Query#setTimeout()`
| `jakarta.persistence.fetchgraph` | An `EntityGraph` to be interpreted as a _fetchgraph_, as defined by the Jakarta Persistence specification.
| See <<chapters/fetching/Fetching.adoc#fetching,Fetching>>.
| `jakarta.persistence.loadgraph` | An `EntityGraph` to be interpreted as a _loadgraph_, as defined by the Jakarta Persistence specification.
| See <<chapters/fetching/Fetching.adoc#fetching,Fetching>>.
| `org.hibernate.cacheMode` | The `CacheMode` to use. | `Query#setCacheMode()`
| `org.hibernate.cacheable` | `true` if the query is cacheable. | `Query#setCacheable()`
| `org.hibernate.cacheRegion` | For a cacheable query, the name of a cache region to use. | `Query#setCacheRegion()`
| `org.hibernate.comment` | A comment to apply to the generated SQL. | `Query#setComment()`
| `org.hibernate.fetchSize` | The JDBC fetch size to use. | `Query#setFetchSize()`
| `org.hibernate.flushMode` | The Hibernate-specific `FlushMode` to use.
(Where possible, prefer `jakarta.persistence.Query#setFlushMode()`.)
| `Query#setFlushMode()`
| `org.hibernate.readOnly` | `true` if entities and collections loaded by this query should be marked as read-only.
| `Query#setReadOnly()`
|===
[TIP]
====
For named queries, query hints may be specified using the Jakarta Persistence {jpaJavadocUrlPrefix}QueryHint.html[`@QueryHint`] annotation.
For named queries, query hints may be specified using the `@QueryHint` annotation.
====
[[hql-api-execution]]
@ -323,8 +327,18 @@ Whereas we needed to specify some information using query hints when working wit
| `Query#setLockMode()` | Overrides the session-level flush mode. Locking is covered in detail in <<chapters/locking/Locking.adoc#locking,Locking>>.
| `Query#setReadOnly()` | Overrides the session-level default for read-only state. The concept of read-only state is covered in <<chapters/pc/PersistenceContext.adoc#pc,Persistence Contexts>>.
| `Query#setComment()` | Adds a comment to the generated SQL.
| `Query#addQueryHint()` | Add a hint to the generated SQL.
|===
[IMPORTANT]
====
`addQueryHint()` allows specification of a hint intended for the database query planner.
A hint is added directly to the generated SQL according to `Dialect#getQueryHintString()`.
On the other hand, `setHint()` refers to the Jakarta Persistence notion of a query hint, a hint that targets the provider (Hibernate).
This is a completely different concept.
====
For complete details, see the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Query.html[Query] Javadocs.
[[hql-api-basic-usage-example]]
@ -336,15 +350,6 @@ include::{sourcedir}/HQLTest.java[tags=hql-api-basic-usage-example]
----
====
[IMPORTANT]
====
`addQueryHint()` allows specification of a database query hint.
A hint is added directly to the generated SQL according to `Dialect#getQueryHintString`.
On the other hand, `setHint()` refers to the Jakarta Persistence notion of a query hint, a hint that targets the provider (Hibernate).
This is a completely different concept.
====
[[hql-api-result-transformers]]
=== Query result transformers
@ -404,9 +409,9 @@ See the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hi
//
//[IMPORTANT]
//====
//Traditionally, Hibernate used to support a JDBC positional parameter syntax form via a `?` symbol without a following ordinal.
//Traditionally, Hibernate used to support a JDBC ordinal parameter syntax form via a `?` symbol without a following ordinal.
//
//There was no way to relate two such positional parameters as being "the same" aside from binding the same value to each and, for this reason, this form is no longer supported.
//There was no way to relate two such ordinal parameters as being "the same" aside from binding the same value to each and, for this reason, this form is no longer supported.
//
//[source, JAVA, indent=0]
//----

View File

@ -1,7 +1,7 @@
in_expression ::=
single_valued_expression [NOT] IN single_valued_list
expression NOT? IN inList
single_valued_list ::=
constructor_expression | (subquery) | collection_valued_input_parameter
constructor_expression ::= (expression[, expression]*)
inList
: (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN
| LEFT_PAREN (expressionOrPredicate (COMMA expressionOrPredicate)*)? RIGHT_PAREN
| LEFT_PAREN subquery RIGHT_PAREN
| parameter

View File

@ -1,4 +1 @@
like_expression ::=
string_expression
[NOT] LIKE pattern_value
[ESCAPE escape_character]
expression NOT? (LIKE | ILIKE) expression (ESCAPE character)?

View File

@ -1,5 +1 @@
delete_statement ::=
delete_clause [where_clause]
delete_clause ::=
DELETE FROM entity_name [[AS] identification_variable]
deleteStatement : DELETE FROM? targetEntity whereClause?

View File

@ -1,8 +1 @@
insert_statement ::=
insert_clause select_statement
insert_clause ::=
INSERT INTO entity_name (attribute_list)
attribute_list ::=
state_field[, state_field ]*
insertStatement : INSERT INTO? targetEntity targetFields (queryExpression | valuesList)

View File

@ -1,7 +1,15 @@
select_statement :: =
[select_clause]
[from_clause]
[where_clause]
[groupby_clause]
[having_clause]
[orderby_clause]
selectStatement
: queryExpression
queryExpression
: orderedQuery (setOperator orderedQuery)*
orderedQuery
: (query | LEFT_PAREN queryExpression RIGHT_PAREN) queryOrder?
query
: selectClause fromClause? whereClause? (groupByClause havingClause?)?
| fromClause whereClause? (groupByClause havingClause?)? selectClause?
queryOrder
: orderByClause limitClause? offsetClause? fetchClause?

View File

@ -1,12 +1 @@
update_statement ::=
update_clause [where_clause]
update_clause ::=
UPDATE entity_name [[AS] identification_variable]
SET update_item {, update_item}*
update_item ::=
[identification_variable.]{state_field | single_valued_object_field} = new_value
new_value ::=
scalar_expression | simple_entity_expression | NULL
updateStatement : UPDATE VERSIONED? targetEntity setClause whereClause?

View File

@ -8,7 +8,6 @@ package org.hibernate.userguide.model;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -23,21 +22,15 @@ import jakarta.persistence.FieldResult;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.MapKeyEnumerated;
import jakarta.persistence.NamedNativeQueries;
import jakarta.persistence.NamedNativeQuery;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderColumn;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.QueryHint;
import jakarta.persistence.SqlResultSetMapping;
import jakarta.persistence.SqlResultSetMappings;
import jakarta.persistence.StoredProcedureParameter;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.persistence.Version;
/**

View File

@ -153,8 +153,8 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
//tag::hql-select-simplest-example-alt[]
LocalDateTime datetime = session.createQuery(
"select local datetime",
LocalDateTime.class )
"select local datetime",
LocalDateTime.class )
.getSingleResult();
//end::hql-select-simplest-example-alt[]
});
@ -166,9 +166,41 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
//tag::hql-select-simplest-jpql-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p", Person.class )
"from Person p",
Person.class )
.getResultList();
//end::hql-select-simplest-jpql-example[]
Session session = entityManager.unwrap( Session.class );
//tag::hql-select-last-example[]
List<String> datetimes = session.createQuery(
"from Person p select p.name",
String.class )
.getResultList();
//end::hql-select-last-example[]
});
}
@Test
public void hql_update_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-update-example[]
entityManager.createQuery(
"update Person set nickName = 'Nacho' " +
"where name = 'Ignacio'" )
.executeUpdate();
//end::hql-update-example[]
});
}
@Test
public void hql_insert_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-insert-example[]
entityManager.createQuery(
"insert Person (id, name) values (100L, 'Jane Doe')" )
.executeUpdate();
//end::hql-insert-example[]
});
}
@ -200,6 +232,21 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
});
}
@Test
public void test_hql_cross_join_jpql_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-cross-join-jpql-example[]
List<Object[]> persons = entityManager.createQuery(
"select distinct pr, ph " +
"from Person pr cross join Phone ph " +
"where ph.person = pr and ph is not null",
Object[].class)
.getResultList();
//end::hql-cross-join-jpql-example[]
assertEquals(3, persons.size());
});
}
@Test
public void test_hql_multiple_same_root_reference_jpql_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
@ -1676,7 +1723,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
doInJPA( this::entityManagerFactory, entityManager -> {
Call call = entityManager.createQuery( "select c from Call c", Call.class).getResultList().get( 0 );
Phone phone = call.getPhone();
//tag::hql-collection-expressions-example[]
//tag::hql-collection-expressions-some-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
@ -1685,7 +1732,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
Person.class )
.setParameter( "phone", phone )
.getResultList();
//end::hql-collection-expressions-example[]
//end::hql-collection-expressions-some-example[]
assertEquals(1, persons.size());
});
}
@ -1693,7 +1740,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void test_hql_collection_expressions_example_6() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-collection-expressions-example[]
//tag::hql-collection-expressions-exists-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
@ -1701,7 +1748,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
"where exists elements ( p.phones )",
Person.class )
.getResultList();
//end::hql-collection-expressions-example[]
//end::hql-collection-expressions-exists-example[]
assertEquals(2, persons.size());
});
}
@ -1727,7 +1774,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@SkipForDialect(value = DerbyDialect.class, comment = "Comparisons between 'DATE' and 'TIMESTAMP' are not supported")
public void test_hql_collection_expressions_example_8() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-collection-expressions-example[]
//tag::hql-collection-expressions-all-example[]
List<Phone> phones = entityManager.createQuery(
"select p " +
@ -1735,7 +1782,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
"where current_date() > all elements( p.repairTimestamps )",
Phone.class )
.getResultList();
//end::hql-collection-expressions-example[]
//end::hql-collection-expressions-all-example[]
assertEquals(3, phones.size());
});
}
@ -1743,7 +1790,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void test_hql_collection_expressions_example_9() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-collection-expressions-example[]
//tag::hql-collection-expressions-in-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
@ -1751,7 +1798,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
"where 1 in indices( p.phones )",
Person.class )
.getResultList();
//end::hql-collection-expressions-example[]
//end::hql-collection-expressions-in-example[]
assertEquals(1, persons.size());
});
}
@ -1892,15 +1939,13 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void test_simple_case_expressions_example_2() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-simple-case-expressions-example[]
// same as above
//tag::hql-coalesce-example[]
List<String> nickNames = entityManager.createQuery(
"select coalesce(p.nickName, '<no nick name>') " +
"from Person p",
String.class )
.getResultList();
//end::hql-simple-case-expressions-example[]
//end::hql-coalesce-example[]
assertEquals(3, nickNames.size());
});
}
@ -1932,15 +1977,14 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void test_searched_case_expressions_example_2() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-searched-case-expressions-example[]
//tag::hql-coalesce-example[]
// coalesce can handle this more succinctly
List<String> nickNames = entityManager.createQuery(
"select coalesce( p.nickName, p.name, '<no nick name>' ) " +
"from Person p",
String.class )
.getResultList();
//end::hql-searched-case-expressions-example[]
//end::hql-coalesce-example[]
assertEquals(3, nickNames.size());
});
}