Hibernate 4、5、6中的ResultTransformer

674 阅读5分钟

获得我所有的视频课程,每月2次问答电话,每月的编码挑战,一个志同道合的开发者社区,以及定期的专家会议。

加入持久性中心!


Hibernate实现了JPA的标准化构造函数表达式@SqlResultSetMappings来映射你的查询结果。而且它还支持专有的ResultTransformers。它们提供了一种强大而灵活的方式,将你的JPQL、Criteria和本地SQL查询的结果映射到一个特定的对象结构。这可以是实体或DTO对象,java.util.Listjava.util.Map对每条记录的表示,或自定义的数据结构。

ResultTransformers 在Hibernate 4中特别流行,但在Hibernate 5中被弃用。不幸的是,Hibernate 5并没有为它们提供一个替代方案。由于这个原因,看起来ResultTransformer 会在Hibernate 6中被移除。但事实并非如此!

Hibernate 6将提供一个基于2个功能接口的改进版ResultTranformers 。基于Hibernate 6资源库中的当前代码,迁移你现有的ResultTransformer 应该不是什么大问题。

内容

Hibernate 4和5中的ResultTransformer

Hibernate 4和5包括几个内置的ResultTransformer。此外,你也可以提供你自己的实现。但在我们谈论不同的ResultTransformers之前,让我告诉你如何将它们应用于你的查询。

你只需要向Hibernate查询接口的setResultTransformer方法提供一个你的ResultTransformer 实例。如果你使用JPA的EntityManager查询 接口,你需要将它们解包。如果你解开EntityManager,你会得到相关的HibernateSession。如果你解开JPA的查询 接口,你会得到Hibernate的查询接口。我在《Hibernate Tip: How to access Hibernate APIs from JPA》中详细解释了这一点。

好了,让我们来看看Hibernate 4和5中一些常用的ResultTransformers

AliasToBeanResultTransformer

JPA使你能够将查询结果中的每条记录映射到一个非管理的DTO对象。你可以使用JPQL中的构造函数表达式或本地查询的@ConstructorResult来定义这些映射。这两个选项都要求你在你的DTO类中添加一个构造函数,以设置所有的属性。如果你的DTO类有大量的属性,这可能是个问题。

Hibernate的AliasToBeanResultTransformer 提供了另一种基于bean规范的方法。它使用DTO类的默认构造函数来实例化一个新对象。在下一步,Hibernate使用反射来为查询中的每个别名值调用一个setter方法。这使得它非常适用于作为标准Java类但不作为Java记录实现的DTOs。

Query query = session.createQuery("select p.id as personId,p.firstName as firstName, p.lastName as lastName from Person p")
    .setResultTransformer(new AliasToBeanResultTransformer(PersonDTO.class));
List<PersonDTO> personDTOS = query.list();

在这个例子中,AliasToBeanResultTransformer 使用默认构造函数,为查询返回的每条记录实例化一个新的PersonDTO 对象。在下一步,Hibernate用查询返回的值调用setPersonIdsetFirstNamesetLastName 方法。

ToListResultTransformer 和 AliasToEntityMapResultTransformer

如果你不想改变选中的数据,又没有匹配的DTO类,你可以使用Hibernate的ToListResultTransformerAliasToEntityMapResultTransformerToListResultTransformer 将查询返回的Object[]及其所有元素映射为java.util.ListAliasToEntityMapResultTransformer 将查询结果转换为一个java.util.Map ,其中包含结果集的所有别名值。每个值的别名被用作Map的键。

这里你可以看到一个AliasToEntityMapResultTransformer的例子。你可以以同样的方式使用ToListResultTransformer

Query selectPerson = session.createQuery(
    "Select p.id as id, " +
    "p.firstName as firstName, " +
    "p.lastName as lastName " +
    "from Person p")
    .setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map> list = selectPerson.list();

实现你自己的 ResultTransformer

如果你想用Hibernate 4或5实现自己的ResultTransformer ,你需要实现Hibernate的ResultTransformer 接口。该接口定义了2个方法:transformTupletransformList方法。

一个普通的ResultTransformer 实现在transformTuple 方法中实现每个记录的映射。transformList 方法只返回提供的图元列表。

我在下面的代码片段中使用了这种方法来实现我自己的ResultTransformer ,将每条记录映射到一个PersonDTO 对象。

Query query = session.createNativeQuery("select id as personId, first_name as firstName, last_name as lastName, city from Person p")
    .setResultTransformer(new ResultTransformer(){
            @Override
            public Object transformTuple(Object[] tuples, String[] aliases) {
                PersonDTO personDTO = new PersonDTO();
                personDTO.setPersonId((int)tuples[0]);
                personDTO.setFirstName((String)tuples[1]);
                personDTO.setLastName((String)tuples[2]);
                return personDTO;
            }
 
            @Override
            public List transformList(List list) {
                return list;
            }
        });
List<PersonDTO> list = query.list();

Hibernate 6中的ResultTransformer

当我描述Hibernate 4和5中自定义ResultTransformer 的实现时,我也提到了ResultTransformer 接口的一个缺点。它定义了transformTupletransformList 方法,这两个方法都需要被实现。大多数应用程序只以有意义的方式实现这两个方法中的一个。但是因为这两个方法都是接口定义的一部分,所以你需要实现这两个方法,并且不能在lambda表达式中把ResultTransformer 作为一个功能接口。

这在Hibernate 6中有所改变。Hibernate团队已经将ResultTransformer 接口拆分为2个功能接口。TupleTransformerResultListTransformer。你可以通过调用Hibernate的查询 接口上的setTupleTransformersetResultListTransformer 方法来设置它们。

Hibernate团队还将Hibernate 4和5提供的ResultTransformer 实现转换为Hibernate 6中的TupleTransformerResultListTransformer实现。因此,在将你的应用程序迁移到Hibernate 6时,所需的改变应该是最小的。

Query query = session.createQuery("select p.id as personId,p.firstName as firstName, p.lastName as lastName from Person p")
    .setTupleTransformer(new AliasToBeanResultTransformer<PersonDTO>(PersonDTO.class)).getSingleResult();
List<PersonDTO> personDTOS = query.list();

正如你在下面的代码片段中所看到的,Hibernate 6中的自定义转换器的实现要简洁得多。

PersonDTO person = (PersonDTO) session
        .createQuery("select id as personId, first_name as firstName, last_name as lastName, city from Person p", Object[].class)
        .setTupleTransformer((tuples, aliases) -> {
                log.info("Transform tuple");
                PersonDTO personDTO = new PersonDTO();
                personDTO.setPersonId((int)tuples[0]);
                personDTO.setFirstName((String)tuples[1]);
                personDTO.setLastName((String)tuples[2]);
                return personDTO;
        }).getSingleResult();

总结

Hibernate的ResultTransformers 提供了各种方法来将查询结果映射到不同的数据结构。它们在Hibernate 4中被普遍使用,在Hibernate 5中被废弃,在Hibernate 6中被功能接口TupleTransformerResultListTransformer取代。

下面的列表显示了Hibernate 4和5中最常用的3种ResultTransformers。这些在Hibernate 6中仍然可用,现在实现了TupleTransformer和/或ResultListTransformer 接口。

  • AliasToBeanResultTransformer- 基于查询中定义的别名,对DTO对象进行实例化和设置属性。
  • ToListResultTransformer--将查询结果中的每条记录映射到java.util.List中。
  • AliasToEntityMapResultTransformer--将查询结果中每条记录的别名值映射到一个java.util.Map。

你也可以实现你自己的转换。

  • 在Hibernate 4和5中,你需要实现ResultTransformer 接口并在transformTuple方法中处理每个结果集记录的映射。

  • 在Hibernate 6中,你需要实现TupleTransformerResultListTransformer的功能接口。

  • 分享

  • 分享

  • 分享

  • 电子邮件