Hibernate 6中的一个较小的变化很容易被忽视,Steve Ebersole在最近的持久性中心的专家会议上介绍了这个变化,即引入了MutationQuery 和SelectionQuery 接口。它允许改变数据的查询和从数据库中选择数据的查询分开。
旧的Hibernate版本和JPA规范使用查询 接口来处理这两种类型的查询。它扩展了SelectionQuery 和MutationQuery接口。当然,你仍然可以在Hibernate 6中使用该接口。但这两个新的接口要干净得多,因为每个接口都只定义了你可以在相应类型的查询中使用的方法。而且,正如我将在本文中向你展示的那样,它还能对提供的语句进行更严格的验证。
内容
- 1解除Hibernate会话的束缚
- 2SelectionQuery - 从数据库中查询数据
- 3MutationQuery - 实现修改查询
- 4结论
解除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中找到所有方法的定义,几个例子是:
- getResultList 和getResultStream 方法来获取包含多条记录的查询结果。
- getSingleResult方法来获得一个只有一条记录的查询结果。
- 不同版本的setParameter方法来设置查询中使用的绑定参数的值。
- setFirstResult和setMaxResult 方法来定义分页。
- setHint方法来提供一个查询提示和
- 各种方法来配置缓存处理。
MutationQuery - 实现修改查询
你可以为修改查询定义的东西要少得多,这就是为什么MutationQuery 接口从分离中获益最大。MutationQuery接口通过排除所有特定于选择的方法,比Query接口要干净和容易使用, 它只定义了:
- executeUpdate 方法来执行修改查询。
- 多个版本的setParameter 方法来提供绑定的参数值。
- 2个方法来定义JPA和Hibernate特有的FlushMode,以及
- 一个方法来设置查询超时、查询注释和查询提示。
定义一个MutationQuery
你可以用类似于SelectionQuery的方式来实例化MutationQuery 。如果你想使用JPQL或Criteria语句,你必须调用Hibernate会话 接口上的createMutationQuery 方法,并提供一个带有JPQL语句的字符串 ,或者一个CriteriaUpdate或CriteriaDelete 对象:
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带来了大量的变化。SelectionQuery 和MutationQuery 接口是其中一些较小的变化。它们提供了比经常使用的Query接口更简洁的API,因为它们专注于特定类型的操作:
- SelectionQuery 接口定义了创建、配置和执行一个从数据库中选择数据的查询所需的方法。
- MutationQuery 接口为UPDATE和DELETE语句做了同样的工作。
为特定类型的操作设计一个接口,可以删除所有不必要的方法,设计出更简洁的接口。