如何使用 JPA EntityManager 将未跟踪的 SQL 视图的结果映射到 DTO/POJO?

How to map results of an untracked SQL view to a DTO/POJO using JPA EntityManager?

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

这个要求很容易解释:我想查询一个 SQL 视图,并且视图中连接的表 没有被任何实体跟踪 不应该。 我想使用 EntityManager 和 map 结果自动到我的 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);

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

第二个选项 是照你说的去做,使用 JPA 的结果映射工具。总而言之,您必须使用本机查询,因为 JPA 不知道 tables/views。然后使用 @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 已知的 class(例如实体):

@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());
}