如何以类型安全的方式运行 Hibernate NativeQuery 而不是返回 Object[]

Posted

技术标签:

【中文标题】如何以类型安全的方式运行 Hibernate NativeQuery 而不是返回 Object[]【英文标题】:How to run a Hibernate NativeQuery in a type-safe manner instead of returning an Object[] 【发布时间】:2018-09-06 09:04:27 【问题描述】:

我正在将应用程序从 Hibernate 4.x 迁移到 Hibernate 5.3.6。应用程序有这样的查询:

SQLQuery query = getSession().createSQLQuery("SELECT a.a, a.b, a.c FROM aTable");

由于 createSQLQuery 方法已被弃用,我首先将方法调用替换为 Hibernate Javadoc 中建议的替代方法,即使用 createNativeQuery:

NativeQuery query = getSession().createNativeQuery("SELECT a.a, a.b, a.c FROM aTable");

问题在于它会产生编译器警告“NativeQuery 是原始类型。对泛型类型 NativeQuery 的引用应该被参数化”。此外,我当然想从类型化查询中受益,因为它们是可用的。所以我将查询更改为

NativeQuery<Object[]> query = getSession().createNativeQuery("SELECT a.a, a.b, a.c FROM aTable", Object[].class);

现在的问题是执行查询

List<Object[]> retList = query.list();

产生错误

javax.persistence.PersistenceException: org.hibernate.MappingException:未知实体:[Ljava.lang.Object;

研究问题seems to indicate 在使用类型化的本机查询时无法使用非映射实体(这似乎是一个严重且不必要的限制,但我在这里离题了)。

问题:有没有办法使用 Hibernate 执行返回一个对象数组的本机 SQL 查询而不产生编译器警告同时实现类型安全?如果没有,是否有任何明智的选择?

【问题讨论】:

【参考方案1】:

这种投影有更好的替代方案,而不是默认的Object[]

    您可以使用 JPA javax.persistence.Tuple 结果集,因为 Hibernate ORM 5.2.11 适用于本机 SQL:

     List<Tuple> postDTOs = entityManager
     .createNativeQuery(
         "SELECT " +
         "       p.id AS id, " +
         "       p.title AS title " +
         "FROM Post p " +
         "WHERE p.created_on > :fromTimestamp", Tuple.class)
     .setParameter( "fromTimestamp", Timestamp.from(
         LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
             .toInstant( ZoneOffset.UTC ) ))
     .getResultList();
    

    您可以使用特定于 Hibernate 的 ResultTransformer,它允许您构建非常复杂的 DTO 结构(例如图形):

     List postDTOs = entityManager
     .createNativeQuery(
         "select " +
         "       p.id as \"id\", " +
         "       p.title as \"title\" " +
         "from Post p " +
         "where p.created_on > :fromTimestamp")
     .setParameter( "fromTimestamp", Timestamp.from(
         LocalDateTime.of( 2016, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) ))
     .unwrap( org.hibernate.query.NativeQuery.class )
     .setResultTransformer( Transformers.aliasToBean( PostDTO.class ) )
     .getResultList();
    

    您也可以使用命名的原生查询:

     List<PostDTO> postDTOs = entityManager
     .createNamedQuery("PostDTO")
     .setParameter( "fromTimestamp", Timestamp.from(
         LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
             .toInstant( ZoneOffset.UTC ) ))
     .getResultList();
    

    PostDTO 查询是一个命名的本机 SQL 查询,如下所示:

     @NamedNativeQuery(
         name = "PostDTO",
         query =
             "SELECT " +
             "       p.id AS id, " +
             "       p.title AS title " +
             "FROM Post p " +
             "WHERE p.created_on > :fromTimestamp",
         resultSetMapping = "PostDTO"
     )
     @SqlResultSetMapping(
         name = "PostDTO",
         classes = @ConstructorResult(
             targetClass = PostDTO.class,
             columns = 
                 @ColumnResult(name = "id"),
                 @ColumnResult(name = "title")
             
         )
     )
    

很酷,对吧?

【讨论】:

【参考方案2】:

只需通过调用创建它

createNativeQuery("SELECT a.a, a.b, a.c FROM aTable");

它会默认返回一行Object[]

该警告与您的情况无关,因此请取消它。

【讨论】:

【参考方案3】:

如果没有,是否有任何明智的选择?

由于您要求替代方案(可能包括不涉及 Hibernate 的替代方案),我认为在这里提及jOOQ 是公平的,您可以在其中编写您的查询:

Result<Record3<Integer, String, Long>> result =
ctx.select(A.A, A.B, A.C)
   .from(A)
   .fetch();

只用var就可以避免表示泛型:

var result =
ctx.select(A.A, A.B, A.C)
   .from(A)
   .fetch();

或者,如果您想继续使用查询的字符串版本,您可以使用转换来降低类型安全性:

Result<?> result = ctx.fetch("SELECT a.a, a.b, a.c FROM aTable");
for (Record record : result) 
    int a = record.get(0, int.class);
    String b = record.get(1, String.class);
    long c = record.get(2, long.class);

或者,您使用 DTO / POJO

class POJO 
  int a;
  String b;
  long c;


List<POJO> list = ctx.fetch("SELECT a.a, a.b, a.c FROM aTable").into(POJO.class);

如果您的结果是实体,you can execute that query easily on an EntityManager, using jOOQ and Hibernate together

(免责声明,我在 jOOQ 背后的公司工作)

【讨论】:

以上是关于如何以类型安全的方式运行 Hibernate NativeQuery 而不是返回 Object[]的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式更改发布类型?

如何以编程方式将“@Cascade”添加到 POJO 字段(Spring mvc + Hibernate)

Hibernate/JPA 中的类型安全主键

如何使用 Hibernate Named Query (HQL) 在 MYSQL 的时间戳字段中搜索当前日期?

JPA/Hibernate 类型安全删除查询

Hibernate学习笔记 --- 映射基本数据类型的List集合