Hibernate 6中的MutationQuery和SelectionQuery介绍

457 阅读5分钟

Hibernate 6中的一个较小的变化很容易被忽视,Steve Ebersole在最近的持久性中心的专家会议上介绍了这个变化,即引入了MutationQuerySelectionQuery 接口。它允许改变数据的查询和从数据库中选择数据的查询分开。

旧的Hibernate版本和JPA规范使用查询 接口来处理这两种类型的查询。它扩展了SelectionQueryMutationQuery接口。当然,你仍然可以在Hibernate 6中使用该接口。但这两个新的接口要干净得多,因为每个接口都只定义了你可以在相应类型的查询中使用的方法。而且,正如我将在本文中向你展示的那样,它还能对提供的语句进行更严格的验证。

内容

解除Hibernate会话的包装

在我们仔细研究这两个新的接口之前,我想快速地告诉你,如果你使用Hibernate作为你的JPA实现,如何获得一个HibernateSession。在这种情况下,你通常会注入一个EntityManager 实例,而不是Hibernate的专有Session。你可以通过调用unwrap 方法获得底层Session

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Session s = em.unwrap(Session.class);

SelectionQuery - 从数据库中查询数据

获得Hibernate的SelectionQuery 接口是非常简单的。在你得到一个Session实例后,你可以用JPQL查询字符串CriteriaQuery 对象调用createSelectionQuery方法:

SelectionQuery<Book> q = s.createSelectionQuery("SELECT b FROM Book b WHERE b.title = :title", Book.class);
q.setParameter("title", "Hibernate Tips - More than 70 solutions to common Hibernate problems");
List<Book> books = q.getResultList();

你也可以用一个*@NamedQuery的名字来调用createNamedSelectionQuery*方法。

SelectionQuery<Book> q = s.createNamedSelectionQuery("Book.selectByTitle", Book.class);
q.setParameter("title", "Hibernate Tips - More than 70 solutions to common Hibernate problems");
List<Book> books = q.getResultList();

改进的语句验证

SelectionQuery接口的优势之一是改进的验证。当Hibernate实例化SelectionQuery时,它会立即验证查询语句:

Session s = em.unwrap(Session.class);
SelectionQuery<Book> q = s.createSelectionQuery("UPDATE Book SET title = upper(title)", Book.class);
List<Book> books = q.getResultList();

如果你提供了一个修改而不是一个选择语句,Hibernate会抛出一个IllegalSelectQueryException

10:27:35,288 INFO  [com.thorben.janssen.model.TestQueryInterfaces] - Hibernate threw the expected IllegalSelectQueryException.
10:27:35,289 INFO  [com.thorben.janssen.model.TestQueryInterfaces] - org.hibernate.query.IllegalSelectQueryException: Expecting a selection query, but found `UPDATE Book SET title = upper(title)` [UPDATE Book SET title = upper(title)]

更干净的API

SelectionQuery 接口的另一个优点是接口更简洁。它只提供了配置和执行选择数据的查询的方法。但是没有专门针对更新操作的方法,比如executeUpdate 方法。你可以在Javadoc中找到所有方法的定义,几个例子是:

  • getResultListgetResultStream 方法来获取包含多条记录的查询结果。
  • getSingleResult方法来获得一个只有一条记录的查询结果。
  • 不同版本的setParameter方法来设置查询中使用的绑定参数的值。
  • setFirstResultsetMaxResult 方法来定义分页。
  • setHint方法来提供一个查询提示
  • 各种方法来配置缓存处理。

MutationQuery - 实现修改查询

你可以为修改查询定义的东西要少得多,这就是为什么MutationQuery 接口从分离中获益最大。MutationQuery接口通过排除所有特定于选择的方法,比Query接口要干净和容易使用, 它只定义了:

  • executeUpdate 方法来执行修改查询。
  • 多个版本的setParameter 方法来提供绑定的参数值。
  • 2个方法来定义JPA和Hibernate特有的FlushMode,以及
  • 一个方法来设置查询超时、查询注释查询提示

定义一个MutationQuery

你可以用类似于SelectionQuery的方式来实例化MutationQuery 。如果你想使用JPQL或Criteria语句,你必须调用Hibernate会话 接口上的createMutationQuery 方法,并提供一个带有JPQL语句的字符串 ,或者一个CriteriaUpdateCriteriaDelete 对象:

Session s = em.unwrap(Session.class);
MutationQuery q = s.createMutationQuery("UPDATE Book SET title = upper(title)");
q.executeUpdate();

如果你为你的语句定义了一个*@NamedQuery*,你可以通过调用createNamedMutationQuery 方法,用你的*@NamedQuery*的名字来实例化它:

Session s = em.unwrap(Session.class);
MutationQuery q = s.createNamedMutationQuery("Book.updateTitle");
q.executeUpdate();

通过调用createNativeMutationQuery,你也可以使用本地SQL语句实例化一个MutationQuery接口:

Session s = em.unwrap(Session.class);
MutationQuery q = s.createNativeMutationQuery("UPDATE Book SET title = upper(title)");
q.executeUpdate();

在所有3种情况下,Hibernate都会返回同一个MutationQuery接口的实例,然后你可以用它来配置和执行你的修改语句。

改进的验证

SelectionQuery接口一样,当你实例化MutationQuery时,Hibernate会验证所提供的语句。如果你提供的语句是选择数据而不是修改数据,Hibernate会抛出一个IllegalMutationQueryException

Session s = em.unwrap(Session.class);
try {
	MutationQuery q = s.createMutationQuery("SELECT b FROM Book b");
	fail("Expected an IllegalMutationQueryException");
} catch (IllegalMutationQueryException e) {
	log.info("Hibernate threw the expected IllegalMutationQueryException.");
	log.info(e);
}

正如你在日志输出中所看到的,异常信息清楚地描述了这个问题:

10:42:47,778 INFO  [com.thorben.janssen.model.TestQueryInterfaces] - Hibernate threw the expected IllegalMutationQueryException.
10:42:47,779 INFO  [com.thorben.janssen.model.TestQueryInterfaces] - org.hibernate.query.IllegalMutationQueryException: Expecting a mutation query, but found `SELECT b FROM Book b`

结论

Hibernate 6带来了大量的变化。SelectionQueryMutationQuery 接口是其中一些较小的变化。它们提供了比经常使用的Query接口更简洁的API,因为它们专注于特定类型的操作:

  • SelectionQuery 接口定义了创建、配置和执行一个从数据库中选择数据的查询所需的方法。
  • MutationQuery 接口为UPDATE和DELETE语句做了同样的工作。

为特定类型的操作设计一个接口,可以删除所有不必要的方法,设计出更简洁的接口。