Hibernate ORM 中文操作指南
6. Compile-time tooling
元模型生成器是 JPA 的标准部分。我们实际上已经在代码示例 earlier中看到了它的杰作:它是类_Book__的作者,其中包含 entity class_Book_的静态元模型。
The Metamodel Generator is a standard part of JPA. We’ve actually already seen its handiwork in the code examples earlier: it’s the author of the class Book_, which contains the static metamodel of the entity class Book.
我们在之前看到的 Gradle build 中已经介绍过如何设置注释处理器。有关如何集成元模型生成器的更多详细信息,请查看用户指南中的 Static Metamodel Generator 部分。 |
We’ve already seen how to set up the annotation processor in the Gradle build we saw earlier. For more details on how to integrate the Metamodel Generator, check out the Static Metamodel Generator section in the User Guide. |
以下是一个实体类的生成代码示例,由 JPA 规范规定:
Here’s an example of the sort of code that’s generated for an entity class, as mandated by the JPA specification:
-
生成的代码
. Generated Code
@StaticMetamodel(Book.class)
public abstract class Book_ {
/**
* @see org.example.Book#isbn
**/
public static volatile SingularAttribute<Book, String> isbn;
/**
* @see org.example.Book#text
**/
public static volatile SingularAttribute<Book, String> text;
/**
* @see org.example.Book#title
**/
public static volatile SingularAttribute<Book, String> title;
/**
* @see org.example.Book#type
**/
public static volatile SingularAttribute<Book, Type> type;
/**
* @see org.example.Book#publicationDate
**/
public static volatile SingularAttribute<Book, LocalDate> publicationDate;
/**
* @see org.example.Book#publisher
**/
public static volatile SingularAttribute<Book, Publisher> publisher;
/**
* @see org.example.Book#authors
**/
public static volatile SetAttribute<Book, Author> authors;
public static final String ISBN = "isbn";
public static final String TEXT = "text";
public static final String TITLE = "title";
public static final String TYPE = "type";
public static final String PUBLICATION_DATE = "publicationDate";
public static final String PUBLISHER = "publisher";
public static final String AUTHORS = "authors";
}
对于实体的每个属性,Book_ 类都有:
For each attribute of the entity, the Book_ class has:
-
a String-valued constant like TITLE , and
-
a typesafe reference like title to a metamodel object of type Attribute.
我们已经在前面的章节中使用过 Book.authors_ 和 Book.AUTHORS 等元模型引用。所以现在让我们看看元模型生成器还能为我们做什么。
We’ve already been using metamodel references like Book.authors_ and Book.AUTHORS in the previous chapters. So now let’s see what else the Metamodel Generator can do for us.
元模型生成器提供 statically-typed 访问 JPA 的元素 Metamodel 。但 Metamodel 也可通过 EntityManagerFactory 以“反射”方式进行访问。 |
The Metamodel Generator provides statically-typed access to elements of the JPA Metamodel. But the Metamodel is also accessible in a "reflective" way, via the EntityManagerFactory. |
EntityType<Book> book = entityManagerFactory.getMetamodel().entity(Book.class); SingularAttribute<Book,Long> id = book.getDeclaredId(Long.class) EntityType<Book> book = entityManagerFactory.getMetamodel().entity(Book.class); SingularAttribute<Book,Long> id = book.getDeclaredId(Long.class) 这对于在框架或库中编写通用代码非常有用。例如,您可以使用它来创建自己的条件查询 API。
EntityType<Book> book = entityManagerFactory.getMetamodel().entity(Book.class); SingularAttribute<Book,Long> id = book.getDeclaredId(Long.class) EntityType<Book> book = entityManagerFactory.getMetamodel().entity(Book.class); SingularAttribute<Book,Long> id = book.getDeclaredId(Long.class) This is very useful for writing generic code in frameworks or libraries. For example, you could use it to create your own criteria query API.
自动生成 finder methods 和 query methods 是 Hibernate 元模型生成器实现的一项新功能,以及对 JPA 规范所定义的功能进行的扩展。在本章中,我们将探讨这些功能。
Automatic generation of finder methods and query methods is a new feature of Hibernate’s implementation of the Metamodel Generator, and an extension to the functionality defined by the JPA specification. In this chapter, we’re going to explore these features.
本章的其余部分中描述的功能依赖于 Entities 中描述的注释的使用。目前元模型生成器无法为完全在 XML 中声明的实体生成查找器方法和查询方法,它也无法验证查询此类实体的 HQL。(另一方面, O/R mappings 可以用 XML 指定,因为元模型生成器不需要它们。)
The functionality described in the rest of this chapter depends on the use of the annotations described in Entities. The Metamodel Generator is not currently able to generate finder methods and query methods for entities declared completely in XML, and it’s not able to validate HQL which queries such entities. (On the other hand, the O/R mappings may be specified in XML, since they’re not needed by the Metamodel Generator.)
我们将遇到三种不同的生成方法:
We’re going to meet three different kinds of generated method:
-
a named query method has its signature and implementation generated directly from a @NamedQuery annotation,
-
a query method has a signature that’s explicitly declared, and a generated implementation which executes a HQL or SQL query specified via a @HQL or @SQL annotation, and
-
a finder method annotated @Find has a signature that’s explicitly declared, and a generated implementation inferred from the parameter list.
我们还将了解可以调用这些方法的两种方式:
We’re also going to see two ways that these methods can be called:
-
as static methods of a generated abstract class, or
-
as instance methods of an interface with a generated implementation which may even be injected.
为了激发我们的兴趣,我们来看看这如何适用于 @NamedQuery。
To whet our appetites, let’s see how this works for a @NamedQuery.
6.1. Named queries and the Metamodel Generator
生成查询方法最简单的方法是将 @NamedQuery 注释放在我们喜欢的任何地方,其中 name 以神奇字符 # 开头。
The very simplest way to generate a query method is to put a @NamedQuery annotation anywhere we like, with a name beginning with the magical character #.
让我们把它贴在 Book 类上:
Let’s just stick it on the Book class:
@CheckHQL // validate the query at compile time
@NamedQuery(name = "#findByTitleAndType",
query = "select book from Book book where book.title like :title and book.type = :type")
@Entity
public class Book { ... }
现在,元模型生成器向元模型类 Book_ 添加了以下方法声明。
Now the Metamodel Generator adds the following method declaration to the metamodel class Book_.
-
生成的代码
. Generated Code
/**
* Execute named query {@value #QUERY_FIND_BY_TITLE_AND_TYPE} defined by annotation of {@link Book}.
**/
public static List<Book> findByTitleAndType(@Nonnull EntityManager entityManager, String title, Type type) {
return entityManager.createNamedQuery(QUERY_FIND_BY_TITLE_AND_TYPE)
.setParameter("title", title)
.setParameter("type", type)
.getResultList();
}
我们可以轻松地从任何我们喜欢的地方调用此方法,只要我们可以访问 EntityManager:
We can easily call this method from wherever we like, as long as we have access to an EntityManager:
List<Book> books =
Book_.findByTitleAndType(entityManager, titlePattern, Type.BOOK);
现在,这确实很好,但它在各个方面都有点儿不灵活,所以这很可能 isn’t 是生成查询方法的最佳方式。
Now, this is quite nice, but it’s a bit inflexible in various ways, and so this probably isn’t the best way to generate a query method.
6.2. Generated query methods
直接从 @NamedQuery 注释生成查询方法的主要问题在于它不允许我们明确指定返回类型或参数列表。在我们就看到的案例中,元模型生成器在推断查询返回类型和参数类型方面做得不错,但是我们常常需要更多的控制。
The principal problem with generating the query method straight from the @NamedQuery annotation is that it doesn’t let us explicitly specify the return type or parameter list. In the case we just saw, the Metamodel Generator does a reasonable job of inferring the query return type and parameter types, but we’re often going to need a bit more control.
解决方案是将查询方法 explicitly 的签名写为 Java 中的抽象方法。我们需要一个放置此方法的地方,由于我们的 Book 实体不是抽象类,我们将为此目的引入一个新接口:
The solution is to write down the signature of the query method explicitly, as an abstract method in Java. We’ll need a place to put this method, and since our Book entity isn’t an abstract class, we’ll just introduce a new interface for this purpose:
interface Queries {
@HQL("where title like :title and type = :type")
List<Book> findBooksByTitleAndType(String title, String type);
}
使用新的 @HQL 注释(我们将其直接放在查询方法上)指定 HQL 查询,而不是作为类型级别注释的 @NamedQuery。这将在 Queries_ 类中生成以下代码:
Instead of @NamedQuery, which is a type-level annotation, we specify the HQL query using the new @HQL annotation, which we place directly on the query method. This results in the following generated code in the Queries_ class:
-
生成的代码
. Generated Code
@StaticMetamodel(Queries.class)
public abstract class Queries_ {
/**
* Execute the query {@value #FIND_BOOKS_BY_TITLE_AND_TYPE_String_Type}.
*
* @see org.example.Queries#findBooksByTitleAndType(String,Type)
**/
public static List<Book> findBooksByTitleAndType(@Nonnull EntityManager entityManager, String title, Type type) {
return entityManager.createQuery(FIND_BOOKS_BY_TITLE_AND_TYPE_String_Type, Book.class)
.setParameter("title", title)
.setParameter("type", type)
.getResultList();
}
static final String FIND_BOOKS_BY_TITLE_AND_TYPE_String_Type =
"where title like :title and type = :type";
}
请注意,签名与我们在 Queries 接口中写下的签名略有不同:元模型生成器在前置了接受 EntityManager 的参数,并将其添加到了参数列表中。
Notice that the signature differs just slightly from the one we wrote down in the Queries interface: the Metamodel Generator has prepended a parameter accepting EntityManager to the parameter list.
如果我们想要明确指定此参数的名称和类型,我们可能明确声明它:
If we want to explicitly specify the name and type of this parameter, we may declare it explicitly:
interface Queries {
@HQL("where title like :title and type = :type")
List<Book> findBooksByTitleAndType(StatelessSession session, String title, String type);
}
Metamodel 生成器默认使用 EntityManager 作为会话类型,但允许使用其他类型:
The Metamodel Generator defaults to using EntityManager as the session type, but other types are allowed:
-
Session,
-
StatelessSession, or
-
Mutiny.Session from Hibernate Reactive.
这一切的真正价值在于可以在编译时执行的检查中。Metamodel 生成器验证我们抽象方法声明的参数是否与 HQL 查询的参数匹配,例如:
The real value of all this is in the checks which can now be done at compile time. The Metamodel Generator verifies that the parameters of our abstract method declaration match the parameters of the HQL query, for example:
-
for a named parameter :alice, there must be a method parameter named alice with exactly the same type, or
-
for an ordinal parameter ?2, the second method parameter must have exactly the same type.
查询还必须在语法上合法且在语义上类型良好,也就是说,查询中引用的实体、属性和函数必须实际存在并且具有兼容的类型。Metamodel 生成器通过在编译时检查实体类的注释来确定这一点。
The query must also be syntactically legal and semantically well-typed, that is, the entities, attributes, and functions referenced in the query must actually exist and have compatible types. The Metamodel Generator determines this by inspecting the annotations of the entity classes at compile time.
指导 Hibernate 验证命名查询的 @CheckHQL 注释对于带 @HQL 注释的查询方法是 not 必要的。 |
The @CheckHQL annotation which instructs Hibernate to validate named queries is not necessary for query methods annotated @HQL. |
@HQL 注释有一个名为 @SQL 的朋友,它允许我们指定用原生 SQL 编写的查询,而不是用 HQL 编写的查询。在这种情况下,元模型生成器可以对查询进行合法性和类型良好检查。
The @HQL annotation has a friend named @SQL which lets us specify a query written in native SQL instead of in HQL. In this case there’s a lot less the Metamodel Generator can do to check that the query is legal and well-typed.
我们想象你可能会疑惑,一个 static 方法是否真的是这里使用的方法。
We imagine you’re wondering whether a static method is really the right thing to use here.
6.3. Generating query methods as instance methods
我们刚刚看到的这部分内容中有一点不太理想,那就是我们不能透明地替换一个生成的 static 函数,将它替换为改进后的手写实现,而不影响客户端。现在,如果我们的查询只在一个地方被调用,这是很常见的情况,这不会成为一个大问题,因此我们倾向于认为 static 函数是没有问题的。
One thing not to like about what we’ve just seen is that we can’t transparently replace a generated static function of the Queries_ class with an improved handwritten implementation without impacting clients. Now, if our query is only called in one place, which is quite common, this isn’t going to be a big issue, and so we’re inclined to think the static function is fine.
但是,如果这个函数在许多地方都被调用,那么最好将它提升到某个类或接口的实例方法。幸运的是,这很简单。
But if this function is called from many places, it’s probably better to promote it to an instance method of some class or interface. Fortunately, this is straightforward.
我们所需要做的就是为我们 Queries 接口添加一个会话对象的抽象 getter 方法。(并且从方法参数列表中移除会话。)我们可以随心所欲地调用此方法:
All we need to do is add an abstract getter method for the session object to our Queries interface. (And remove the session from the method parameter list.) We may call this method anything we like:
interface Queries {
EntityManager entityManager();
@HQL("where title like :title and type = :type")
List<Book> findBooksByTitleAndType(String title, String type);
}
在这里,我们使用了 EntityManager 作为会话类型,但正如我们在上面看到的,允许使用其他类型。
Here we’ve used EntityManager as the session type, but other types are allowed, as we saw above.
现在,Metamodel 生成器的做法稍有不同:
Now the Metamodel Generator does something a bit different:
-
生成的代码
. Generated Code
@StaticMetamodel(Queries.class)
public class Queries_ implements Queries {
private final @Nonnull EntityManager entityManager;
public Queries_(@Nonnull EntityManager entityManager) {
this.entityManager = entityManager;
}
public @Nonnull EntityManager entityManager() {
return entityManager;
}
/**
* Execute the query {@value #FIND_BOOKS_BY_TITLE_AND_TYPE_String_Type}.
*
* @see org.example.Queries#findBooksByTitleAndType(String,Type)
**/
@Override
public List<Book> findBooksByTitleAndType(String title, Type type) {
return entityManager.createQuery(FIND_BOOKS_BY_TITLE_AND_TYPE_String_Type, Book.class)
.setParameter("title", title)
.setParameter("type", type)
.getResultList();
}
static final String FIND_BOOKS_BY_TITLE_AND_TYPE_String_Type =
"where title like :title and type = :type";
}
生成的类 Queries_ 现在实现了 Queries 接口,生成的查询方法直接实现了我们抽象方法。
The generated class Queries_ now implements the Queries interface, and the generated query method implements our abstract method directly.
当然,调用查询方法的协议必须改变:
Of course, the protocol for calling the query method has to change:
Queries queries = new Queries_(entityManager);
List<Book> books = queries.findByTitleAndType(titlePattern, Type.BOOK);
如果我们曾经需要用一个手动编写的查询方法来替换生成的查询方法,而不影响客户端,那么我们所需要做的就是用 default 方法替换 Queries 接口的抽象方法。例如:
If we ever need to swap out the generated query method with one we write by hand, without impacting clients, all we need to do is replace the abstract method with a default method of the Queries interface. For example:
interface Queries {
EntityManager entityManager();
// handwritten method replacing previous generated implementation
default List<Book> findBooksByTitleAndType(String title, String type) {
entityManager()
.createQuery("where title like :title and type = :type", Book.class)
.setParameter("title", title)
.setParameter("type", type)
.setFlushMode(COMMIT)
.setMaxResults(100)
.getResultList();
}
}
如果我们想要注入 Queries 对象,而不是直接调用它的构造函数怎么办?
What if we would like to inject a Queries object instead of calling its constructor directly?
正如你 recall 的所想,我们认为这些内容并非一定要成为容器管理对象。但是如果你 want 它们——如果你出于某种原因而不愿意调用这些构造函数,那么: |
As you 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: |
在构建路径上放置 jakarta.inject 会导致在 Queries_ 的构造函数中添加一个 @Inject 注解,并且
placing jakarta.inject on the build path will cause an @Inject annotation to be added to the constructor of Queries_, and
在构建路径上放置 jakarta.enterprise.context 会导致在 Queries_ 类中添加一个 @Dependent 注解。
placing jakarta.enterprise.context on the build path will cause a @Dependent annotation to be added to the Queries_ class.
因此, Queries 生成的实现将是一个功能完全正常的 CDI bean,无需执行其他操作。
Thus, the generated implementation of Queries will be a perfectly functional CDI bean with no extra work to be done.
Queries 接口是否开始看起来更像 DAO 风格的存储库对象?嗯,也许。你当然可以 decide to use 这个设施去创建一个 BookRepository,如果你愿意这么做的话。但与存储库不同,我们的 Queries 接口:
Is the Queries interface starting to look a lot like a DAO-style repository object? Well, perhaps. You can certainly decide to use this facility to create a BookRepository if that’s what you prefer. But unlike a repository, our Queries interface:
-
doesn’t attempt to hide the EntityManager from its clients,
-
doesn’t implement or extend any framework-provided interface or abstract class, at least not unless you want to create such a framework yourself, and
-
isn’t restricted to service a particular entity class.
我们可以随意使用任意数量的带有查询方法的接口。这些接口和实体类型之间没有一对一对应关系。这种方法非常灵活,我们甚至不知道如何称呼这些“带有查询方法的接口”。
We can have as many or as few interfaces with query methods as we like. There’s no one-one-correspondence between these interfaces and entity types. This approach is so flexible that we don’t even really know what to call these "interfaces with query methods".
6.4. Generated finder methods
在这一点上,人们通常开始质疑是否还有必要编写查询。是否可能仅从方法签名推断查询?
At this point, one usually begins to question whether it’s even necessary to write a query at all. Would it be possible to just infer the query from the method signature?
在一些简单的情况下,这实际上是可能的,这就是 finder methods 的目的。Finder 方法是一个带有 @Find 注解的方法。例如:
In some simple cases it’s indeed possible, and this is the purpose of finder methods. A finder method is a method annotated @Find. For example:
@Find
Book getBook(String isbn);
Finder 方法可能有多个参数:
A finder method may have multiple parameters:
@Find
List<Book> getBooksByTitle(String title, Type type);
查找器方法的名称是随意的,不带任何语义。但是:
The name of the finder method is arbitrary and carries no semantics. But:
-
the return type determines the entity class to be queried, and
-
the parameters of the method must match the fields of the entity class exactly, by both name and type.
考虑到第一个示例,Book 具有一持久域 String isbn,因此此查找器方法是合法的。如果 Book 中没有名为 isbn 的域,或其具有不同类型,则此方法声明将被拒绝,并在编译时出现有意义的错误。类似地,第二个示例是合法的,因为 Book 具有域 String title 和 Type type。
Considering our first example, Book has a persistent field String isbn, so this finder method is legal. If there were no field named isbn in Book, or if it had a different type, this method declaration would be rejected with a meaningful error at compile time. Similarly, the second example is legal, since Book has fields String title and Type type.
你可能会注意到,我们针对此问题的解决方案与其他人采取的方法非常不同。在 DAO 样式的存储库框架中,系统会要求你将查找器方法的语义编码到 name of the method 中。这个想法来自 Ruby 传到 Java,我们认为它不属于这里。它在 Java 中是完全不自然的,并且几乎在所有方面(除了 counting characters)都比在字符串文字中编写查询差。至少字符串文字可以容纳空格和标点符号。哦,你知道吗,能够重命名查找器方法 without changing its semantics 非常有用。🙄
You might notice that our solution to this problem is very different from the approach taken by others. In DAO-style repository frameworks, you’re asked to encode the semantics of the finder method into the name of the method. This idea came to Java from Ruby, and we think it doesn’t belong here. It’s completely unnatural in Java, and by almost any measure other than counting characters it’s objectively worse than just writing the query in a string literal. At least string literals accommodate whitespace and punctuation characters. Oh and, you know, it’s pretty useful to be able to rename a finder method without changing its semantics. 🙄
为该查找器方法生成的代码取决于与方法参数匹配的字段类型:
The code generated for this finder method depends on what kind of fields match the method parameters:
@Id field |
Uses EntityManager.find() |
All @NaturalId fields |
Uses Session.byNaturalId() |
Other persistent fields, or a mix of field types |
Uses a criteria query |
生成的代码还取决于我们拥有的会话类型,因为无状态会话和响应式会话的功能与常规状态会话的功能略有不同。
The generated code also depends on what kind of session we have, since the capabilities of stateless sessions, and of reactive sessions, differ slightly from the capabilities of regular stateful sessions.
将 EntityManager 作为会话类型,我们将获得:
With EntityManager as the session type, we obtain:
/**
* Find {@link Book} by {@link Book#isbn isbn}.
*
* @see org.example.Dao#getBook(String)
**/
@Override
public Book getBook(@Nonnull String isbn) {
return entityManager.find(Book.class, isbn);
}
/**
* Find {@link Book} by {@link Book#title title} and {@link Book#type type}.
*
* @see org.example.Dao#getBooksByTitle(String,Type)
**/
@Override
public List<Book> getBooksByTitle(String title, Type type) {
var builder = entityManager.getEntityManagerFactory().getCriteriaBuilder();
var query = builder.createQuery(Book.class);
var entity = query.from(Book.class);
query.where(
title==null
? entity.get(Book_.title).isNull()
: builder.equal(entity.get(Book_.title), title),
type==null
? entity.get(Book_.type).isNull()
: builder.equal(entity.get(Book_.type), type)
);
return entityManager.createQuery(query).getResultList();
}
甚至可以将查找器方法的参数与关联实体或可嵌入实体的属性进行匹配。自然语法将是 String publisher.name 这样的参数声明,但由于这在 Java 中是非法的,因此我们可以将其写为 String publisher$name,利用一个合法的 Java 标识符字符,而其他人永远不会将其用于其他任何用途:
It’s even possible to match a parameter of a finder method against a property of an associated entity or embeddable. The natural syntax would be a parameter declaration like String publisher.name, but because that’s not legal Java, we can write it as String publisher$name, taking advantage of a legal Java identifier character that nobody ever uses for anything else:
@Find
List<Book> getBooksByPublisherName(String publisher$name);
可以将 @Pattern 注释应用于类型为 String 的参数,表示参数是通配模式,将使用 like 进行比较。
The @Pattern annotation may be applied to a parameter of type String, indicating that the argument is a wildcarded pattern which will be compared using like.
@Find
List<Book> getBooksByTitle(@Pattern String title, Type type);
查找器方法可以指定 fetch profiles,例如:
A finder method may specify fetch profiles, for example:
@Find(namedFetchProfiles=Book_.FETCH_WITH_AUTHORS)
Book getBookWithAuthors(String isbn);
这使我们可以声明应该通过注释 Book 类来预先获取 Book 的哪些关联。
This lets us declare which associations of Book should be pre-fetched by annotating the Book class.
6.5. Paging and ordering
或者,查询方法(或返回多个结果的查找器方法)可能具有不映射到查询参数的其他“神奇”参数:
Optionally, a query method—or a finder method which returns multiple results—may have additional "magic" parameters which do not map to query parameters:
Parameter type |
Purpose |
Example argument |
Page |
Specifies a page of query results |
Page.first(20) |
Order<? super E> |
Specifies an entity attribute to order by, if E is the entity type returned by the query |
Order.asc(Book.title)_ |
List<Order? super E> (or varargs) |
Specifies entity attributes to order by, if E is the entity type returned by the query |
List.of(Order.asc(Book.title), Order.asc(Book_.isbn))_ |
Order<Object[]> |
Specifies a column to order by, if the query returns a projection list |
Order.asc(1) |
List<Object[]> (or varargs) |
Specifies columns to order by, if the query returns a projection list |
List.of(Order.asc(1), Order.desc(2)) |
因此,如果我们将较早的查询方法重新定义为:
Thus, if we redefine our earlier query method as follows:
interface Queries {
@HQL("from Book where title like :title and type = :type")
List<Book> findBooksByTitleAndType(String title, Type type,
Page page, Order<? super Book>... order);
}
那么我们可以这样调用它:
Then we can call it like this:
List<Book> books =
Queries_.findBooksByTitleAndType(entityManager, titlePattern, Type.BOOK,
Page.page(RESULTS_PER_PAGE, page), Order.asc(Book_.isbn));
或者,我们可以将此查询方法编写为查找器方法:
Alternatively, we could have written this query method as a finder method:
interface Queries {
@Find
List<Book> getBooksByTitle(String title, Type type,
Page page, Order<? super Book>... order);
}
这会对查询执行提供一些动态控制,但是如果我们希望对 Query 对象进行直接控制,该怎么办?嗯,让我们讨论一下返回类型。
This gives some dynamic control over query execution, but what if would like direct control over the Query object? Well, let’s talk about the return type.
6.6. Key-based pagination
生成的查询或查找器方法可以使用 key-based pagination。
A generated query or finder method can make use of key-based pagination.
@Query("where publicationDate > :minDate")
KeyedResultList<Book> booksFromDate(Session session, LocalDate minDate, KeyedPage<Book> page);
请注意此方法:
Note that this method:
-
accepts a KeyedPage, and
-
returns KeyedResultList.
可以这样使用这样的方法:
Such a method may be used like this:
// obtain the first page of results
KeyedResultList<Book> first =
Queries_.booksFromDate(session, minDate,
Page.first(25).keyedBy(Order.asc(Book_.isbn)));
List<Book> firstPage = first.getResultList();
...
if (!firstPage.isLastPage()) {
// obtain the second page of results
KeyedResultList<Book> second =
Queries_.booksFromDate(session, minDate,
firstPage.getNextPage());
List<Book> secondPage = second.getResultList();
...
}
6.7. Query and finder method return types
查询方法无需返回 List。它可能返回单个 Book。
A query method doesn’t need to return List. It might return a single Book.
@HQL("where isbn = :isbn")
Book findBookByIsbn(String isbn);
对于带有投影列表的查询,Object[] 或 List<Object[]> 是允许的:
For a query with a projection list, Object[] or List<Object[]> is permitted:
@HQL("select isbn, title from Book where isbn = :isbn")
Object[] findBookAttributesByIsbn(String isbn);
但是当 select 列表中只有一个项目时,应该使用该项目的类型:
But when there’s just one item in the select list, the type of that item should be used:
@HQL("select title from Book where isbn = :isbn")
String getBookTitleByIsbn(String isbn);
@HQL("select local datetime")
LocalDateTime getServerDateTime();
返回选择列表的查询可能有一个查询方法,该方法重新封装结果为记录,就像我们在 Representing projection lists 中看到的。
A query which returns a selection list may have a query method which repackages the result as a record, as we saw in Representing projection lists.
record IsbnTitle(String isbn, String title) {}
@HQL("select isbn, title from Book")
List<IsbnTitle> listIsbnAndTitleForEachBook(Page page);
查询方法甚至可以返回 TypedQuery 或 SelectionQuery:
A query method might even return TypedQuery or SelectionQuery:
@HQL("where title like :title")
SelectionQuery<Book> findBooksByTitle(String title);
这有时非常有用,因为它允许客户端进一步操作查询:
This is extremely useful at times, since it allows the client to further manipulate the query:
List<Book> books =
Queries_.findBooksByTitle(entityManager, titlePattern)
.setOrder(Order.asc(Book_.title)) // order the results
.setPage(Page.page(RESULTS_PER_PAGE, page)) // return the given page of results
.setFlushMode(FlushModeType.COMMIT) // don't flush session before query execution
.setReadOnly(true) // load the entities in read-only mode
.setCacheStoreMode(CacheStoreMode.BYPASS) // don't cache the results
.setComment("Hello world!") // add a comment to the generated SQL
.getResultList();
insert、update 或 delete 查询必须返回 int、boolean 或 void。
An insert, update, or delete query must return int, boolean, or void.
@HQL("delete from Book")
int deleteAllBooks();
@HQL("update Book set discontinued = true where discontinued = false and isbn = :isbn")
boolean discontinueBook(String isbn);
@HQL("update Book set discontinued = true where isbn = :isbn")
void discontinueBook(String isbn);
另一方面,查找方法当前的限制更大。查找方法必须返回实体类型(如 Book)或实体类型列表(如 List<Book>)。
On the other hand, finder methods are currently much more limited. A finder method must return an entity type like Book, or a list of the entity type, List<Book>, for example.
如你所料,对于反应式会话,所有查询方法和查找器方法都必须返回 Uni。 |
As you might expect, for a reactive session, all query methods and finder methods must return Uni. |
6.8. An alternative approach
如果你不喜欢我们在本章中提出的想法,而是希望直接调用 Session 或 EntityManager,但仍然需要对 HQL 进行编译时验证,该怎么办?或者,如果你 do 喜欢这些想法,但正在处理一个庞大的现有代码库,其中包含你不想更改的代码,该怎么办?
What if you just don’t like the ideas we’ve presented in this chapter, preferring to call the Session or EntityManager directly, but you still want compile-time validation for HQL? Or what if you do like the ideas, but you’re working on a huge existing codebase full of code you don’t want to change?
那么,也有一个可供你使用的解决方案。 Query Validator 是一款单独的注释处理器,它能够类型检查 HQL 字符串,不仅存在于注释中,甚至当它们作为 createQuery()、createSelectionQuery()_或 _createMutationQuery() 的参数出现时也是如此。它甚至能够检查对 setParameter() 的调用(有一定限制)。
Well, there’s a solution for you, too. The Query Validator is a separate annotation processor that’s capable of type-checking HQL strings, not only in annotations, but even when they occur as arguments to createQuery(), createSelectionQuery(), or createMutationQuery(). It’s even able to check calls to setParameter(), with some restrictions.
Query Validator 可在 javac、Gradle、Maven 和 Eclipse Java 编译器中运行。
The Query Validator works in javac, Gradle, Maven, and the Eclipse Java Compiler.
与元模型生成器(这是一个完全基于仅标准 Java API 的标准 Java 注释处理器)不同,查询验证器使用 javac 和 ecj 中的内部编译器 API。这意味着不能保证它能在所有 Java 编译器中都能正常工作。已知当前版本在 JDK 11 及更高版本中可用,但建议使用 JDK 15 或更高版本。
Unlike the Metamodel Generator, which is a completely bog-standard Java annotation processor based on only standard Java APIs, the Query Validator makes use of internal compiler APIs in javac and ecj. This means it can’t be guaranteed to work in every Java compiler. The current release is known to work in JDK 11 and above, though JDK 15 or above is preferred.