【问题标题】:How to map results of an untracked SQL view to a DTO/POJO using JPA EntityManager?如何使用 JPA EntityManager 将未跟踪的 SQL 视图的结果映射到 DTO/POJO?
【发布时间】:2022-01-04 10:53:13
【问题描述】:

我遇到了一个特殊的用例。

这个需求很容易解释:我想查询一个 SQL 视图,并且视图中连接的表没有被任何实体跟踪,也不应该被跟踪。 我想使用 EntityManager 并将结果自动映射到我的 DTO/POJO

到目前为止我没有成功的尝试:

  1. 使用em.createNativeQuery("select .... from MyView",MyDto.class);

    Results in Caused by: org.hibernate.MappingException: Unknown entity: MyDto.class

  2. 另外使用SqlResultSetMapping

    @Data
    @SqlResultSetMapping(name = "MyDto", classes = {
            @ConstructorResult(targetClass = MyDto.class,
                    columns = {
                            @ColumnResult(name = "id")
                            })
    })
    
    @AllArgsConstructor
    public class MyDto implements Serializable {
    
    

    Does not work either

  3. 将 unwrap 与 createNativeQuery Transformers.aliasToBean 一起使用

    em.createNativeQuery("")
      .unwrap(SQLQuery.class)
      .setResultTransformer(Transformers.aliasToBean(MyDto.class));
    

    Results in syntax error near "." for whatever reason.

  4. 将 unwrap 与 createNativeQuery 和 AliasToEntityMapResultTransformer 一起使用

    em.createNativeQuery("")
      .unwrap(SQLQuery.class)
      .setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE)
    

    Results in syntax error near "." for whatever reason as well.

除此之外,setResultTransformer 方法似乎已弃用

我目前针对较小 DTO 的方法是手动映射,但如何自动执行此操作?

 em.createNativeQuery("")
   .getResultStream()
   .map(o -> {
       Object[] cols = (Object[]) o;
       //Do some ugly error-prone mapping here and return a new DTO object
       return new MyDto();
      })
   .filter(Objects::nonNull);

【问题讨论】:

    标签: hibernate jpa projection entitymanager sql-view


    【解决方案1】:

    一种选择是将视图映射到实体。只要确保永远不要插入或更新它; JPA 不支持它,JPA 提供者可能支持具有提供者特定注释的只读实体。您可能能够通过技巧(如实体侦听器、insert/update=false 主键等)强制执行不插入或更新策略。我会尝试这个,因为它很简单。

    第二个选项是照你说的去使用 JPA 的结果映射工具。总而言之,您必须使用本机查询,因为 JPA 不知道表/视图。然后使用@SqlResultSetMapping将结果的列映射到DTO。

    我在一个临时项目中实现了它,并用我的模型进行展示,以便示例具体:

    首先是 DTO:

    public class UserProjectDto {
        private long userId;
    
        private long projectId;
    
        private String userName;
    
        private String email;
    
        private String projectName;
    
        // default constructor, probably optional
        public UserProjectDto() {}
    
        // all-args constructor, mandatory
        public UserProjectDto(long userId, long projectId, String userName, String email, String projectName) {
            ...
        }
    
        // getters, setters
    }
    

    然后是(繁琐的)结果集映射;这必须转到 JPA 已知的类(例如实体):

    @SqlResultSetMappings({
        @SqlResultSetMapping(
            name = "UserProjectDto", // <- the name is used to reference the mapping
            classes = {
                @ConstructorResult(
                    targetClass = UserProjectDto.class,
                    columns = {
                        // order of column results matches the arguments of the constructor
                        // name matches the column in the result set (I like using the property name, but that's my preference)
                        @ColumnResult(name = "userId", type = long.class),
                        @ColumnResult(name = "projectId", type = long.class),
                        @ColumnResult(name = "userName"),
                        @ColumnResult(name = "email"),
                        @ColumnResult(name = "projectName")
                    }
                )
            }
        )
    })
    public class SomeClassKnownToJpa {
        ...
    }
    

    最后,运行它的代码:

    EntityManager em = ...
    Query query = em.createNativeQuery(
            // note the "AS" names match the names of the @ColumnResult
            "SELECT " +
            "  u.id AS userId," +
            "  p.id AS projectId," +
            "  u.name AS userName," +
            "  u.email AS email," +
            "  p.name AS projectName " +
            "FROM APP_USER u, APP_PROJECT p",
            // reference the name of the @SqlResultSetMapping
            "UserProjectDto"
    );
    for( Object o : query.getResultList() ) {
        UserProjectDto u = (UserProjectDto) o;
        System.out.println(u.getUserName() + " - " + u.getProjectName());
    }
    
    

    【讨论】:

    • 首先非常感谢您的详细解答!我会选择第一个解决方案,但是,当我有一个被跟踪的实体并直接用@Column 注释对列进行注释时,我不能使用 em.createQuery 方法而不是 createNativeQuery 吗?我已经尝试过了,但它似乎找不到列,所以不确定这是一个实现问题还是根本不可能。除了您的评论以使实体为只读之外,还想提一下,还有@Immutable 注释也可能用于强制执行 RO。
    • 您好!我很确定我过去使用过第一个解决方案,因此它很有可能(最终)起作用。在这种情况下,我会同时使用@Table@Column 注释,当然还有@Entity。如果您使用 JPA 生成模式,这会使事情变得复杂。此外,如果它似乎没有找到列,请尝试激活 Hibernate 的 SQL 日志记录并查看它试图执行什么。哦,是的,如果你对 Hibernate 的 @Immutable 没意见,那就用吧!
    • 现在可以使用了,谢谢。我已经使用了@Entity@Table 注释。 @Column 注释是可选的,但如果您想防止因重构 DTO 字段名称而引起的麻烦,则它是有意义的。编辑:你只比我快几秒钟:P
    • 很高兴知道 :) 顺便说一句,由于您提到的原因,我总是尝试使用“物理”注释(@Table@Column 等)。
    猜你喜欢
    • 2022-01-11
    • 1970-01-01
    • 2014-10-01
    • 2020-04-07
    • 2021-02-21
    • 2011-09-20
    • 1970-01-01
    • 2021-08-13
    • 1970-01-01
    相关资源
    最近更新 更多