实体模型
假设我们有以下 Post
实体:
如果你需要修改实体,则需要抓取整个实体;但是如果你只是对其中的几列感兴趣,则使用 DTO 会更有效。
如果我们只是想选择 Post
的 id
和 title
,如果抓取整个实体会比较浪费资源,接下来我们看下 JPA 和 Hibernate 怎么实现我们的目标。
使用 JPA 映射 DTO
在使用 JPA 或 Hibernate 查询实体的时候,你可以通过执行 JPQL 或着 Criteria API 以及原生的 SQL 查询。
使用 Tuple 和 JPQL 映射 DTO
如果你不想将映射应用到 DTO,你可以使用 JPA 的 Tuple
, 如果使用 Tuple
映射,你的 JPQL 查询看起来是这样的:
List<Tuple> postDTOs = entityManager
.createQuery(
"select " +
" p.id as id, " +
" p.title as title " +
"from Post p " +
"where p.createdOn > :fromTimestamp", Tuple.class)
.setParameter( "fromTimestamp", Timestamp.from(
LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ))
.getResultList();
assertFalse( postDTOs.isEmpty() );
Tuple postDTO = postDTOs.get( 0 );
assertEquals(
1L,
postDTO.get( "id" )
);
assertEquals(
"High-Performance Java Persistence",
postDTO.get( "title" )
);
如您所见,
tuple
是一种获取 DTO 投影的便捷方式,因为您不需要为需要支持的每种类型的投影指定 DTO 类。
使用构造函数和 JPQL 映射 DTO
如果你想使用特定的类来映射 DTO,你可以使用构造函数来 New 一个你想要的参数列表的对象。
DTO 类必须提供一个全参的构造函数来映射结果
DTO 映射如下:
public class PostDTO {
private Long id;
private String title;
public PostDTO(Number id, String title) {
this.id = id.longValue();
this.title = title;
}
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
}
因此,使用构造函数的 JPQL 查询如下:
List<PostDTO> postDTOs = entityManager
.createQuery(
"select new " +
" com.vladmihalcea.book.hpjp.hibernate.query.dto.projection.jpa.PostDTO(" +
" p.id, " +
" p.title " +
" ) " +
"from Post p " +
"where p.createdOn > :fromTimestamp", PostDTO.class)
.setParameter( "fromTimestamp", Timestamp.from(
LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ))
.getResultList();
使用 Tuple 和原生 SQL 查询 映射 DTO
从 Hibernate ORM 5.2.11开始,由于 HHH-11897 Jira 问题得到修复,您可以使用 Tuple 进行原生 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();
assertFalse( postDTOs.isEmpty() );
Tuple postDTO = postDTOs.get( 0 );
assertEquals(
1L,
((Number) postDTO.get( "id" )).longValue()
);
assertEquals(
"High-Performance Java Persistence",
postDTO.get( "title" )
);
使用 ConstructorResult 映射 DTO
对于原生的 SQl 查询,你不能使用构造函数,所以你需要使用一个命名原生查询(NamedNativeQuery)和配置一个 SqlResultSetMapping
,这样你就可以通过构造函数或字段来填充 DTO 类:
@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")
}
)
)
使用下面的代码执行 SQL 映射:
List<PostDTO> postDTOs = entityManager
.createNamedQuery("PostDTO")
.setParameter( "fromTimestamp", Timestamp.from(
LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ))
.getResultList();
使用 Hibernate 映射 DTO
当然您可以将所有 JPA 特性在 Hibernate 上使用,因为 Hibernate 提供的特性比标准 Java Persistence 规范要多得多。
使用 ResultTransformer 和 JPQL 来映射 DTO
如前所述,ResultTransformer
允许您以任何方式自定义结果集,以便您可以使用它将典型的 Object [] 数组投影转换为 DTO 结果集。
这次,您不需要提供构造函数来匹配查询选择的实体属性。
虽然你甚至不需要在你的DTO类中提供 setter,但是我们需要 setter,因为 id 列在数据库映射时会返回 BigInteger,而我们需要将它强制转换为 Long。
Hibernate 可以使用 Reflection 设置适当的字段,因此它比以前的 JPA 构造函数替代方案更灵活。
考虑下面的 DTO 类:
public class PostDTO {
private Long id;
private String title;
public Long getId() {
return id;
}
public void setId(Number id) {
this.id = id.longValue();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
我们可以使用 Hibernate 特定 org.hibernate.query.Query
接口的 setResultTransformer
方法转换结果集,该接口可以从 JPA Query
解析。
List<PostDTO> postDTOs = entityManager
.createQuery(
"select " +
" p.id as id, " +
" p.title as title " +
"from Post p " +
"where p.createdOn > :fromTimestamp")
.setParameter( "fromTimestamp", Timestamp.from(
LocalDateTime.of( 2016, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) ))
.unwrap( org.hibernate.query.Query.class )
.setResultTransformer( Transformers.aliasToBean( PostDTO.class ) )
.getResultList();
使用 ResultTransformer 和 原生 SQL 查询映射 DTO
如果你想用原生 SQL 查询,你不需要经历声明SqlResultSetMapping
的所有麻烦,因为你可以使用 AliasToBeanResultTransformer
,就像前面提到的 JPQL 示例的情况一样。
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();