如何使用 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.
到目前为止我尝试失败的是:
使用em.createNativeQuery("select .... from MyView",MyDto.class);
Results in Caused by: org.hibernate.MappingException: Unknown entity: MyDto.class
另外使用 SqlResultSetMapping
@Data
@SqlResultSetMapping(name = "MyDto", classes = {
@ConstructorResult(targetClass = MyDto.class,
columns = {
@ColumnResult(name = "id")
})
})
@AllArgsConstructor
public class MyDto implements Serializable {
Does not work either
将 unwrap 与 createNativeQuery 一起使用 Transformers.aliasToBean
em.createNativeQuery("")
.unwrap(SQLQuery.class)
.setResultTransformer(Transformers.aliasToBean(MyDto.class));
Results in syntax error near "." for whatever reason.
将 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());
}
我遇到了一个特殊的用例。
这个要求很容易解释:我想查询一个 SQL 视图,并且视图中连接的表 没有被任何实体跟踪 不应该。 我想使用 EntityManager 和 map 结果自动到我的 DTO/POJO.
到目前为止我尝试失败的是:
使用
em.createNativeQuery("select .... from MyView",MyDto.class);
Results in Caused by: org.hibernate.MappingException: Unknown entity: MyDto.class
另外使用 SqlResultSetMapping
@Data @SqlResultSetMapping(name = "MyDto", classes = { @ConstructorResult(targetClass = MyDto.class, columns = { @ColumnResult(name = "id") }) }) @AllArgsConstructor public class MyDto implements Serializable {
Does not work either
将 unwrap 与 createNativeQuery 一起使用 Transformers.aliasToBean
em.createNativeQuery("") .unwrap(SQLQuery.class) .setResultTransformer(Transformers.aliasToBean(MyDto.class));
Results in syntax error near "." for whatever reason.
将 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:
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());
}