实体模型
假设我们有以下 Post
实体:
![Post Domain]()
如果你需要修改实体,则需要抓取整个实体;但是如果你只是对其中的几列感兴趣,则使用 DTO 会更有效。
如果我们只是想选择 Post
的 id
和 title
,如果抓取整个实体会比较浪费资源,接下来我们看下 JPA 和 Hibernate 怎么实现我们的目标。
使用 JPA 映射 DTO
在使用 JPA 或 Hibernate 查询实体的时候,你可以通过执行 JPQL 或着 Criteria API 以及原生的 SQL 查询。
如果你不想将映射应用到 DTO,你可以使用 JPA 的 Tuple
, 如果使用 Tuple
映射,你的 JPQL 查询看起来是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 类。
如果你想使用特定的类来映射 DTO,你可以使用构造函数来 New 一个你想要的参数列表的对象。
DTO 类必须提供一个全参的构造函数来映射结果
DTO 映射如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 查询如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @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 映射:
1 2 3 4 5 6
| 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
允许您以任何方式自定义结果集,以便您可以使用它将典型的 Object [] 数组投影转换为 DTO 结果集。
这次,您不需要提供构造函数来匹配查询选择的实体属性。
虽然你甚至不需要在你的DTO类中提供 setter,但是我们需要 setter,因为 id 列在数据库映射时会返回 BigInteger,而我们需要将它强制转换为 Long。
Hibernate 可以使用 Reflection 设置适当的字段,因此它比以前的 JPA 构造函数替代方案更灵活。
考虑下面的 DTO 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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
解析。
1 2 3 4 5 6 7 8 9 10 11 12
| 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();
|
如果你想用原生 SQL 查询,你不需要经历声明SqlResultSetMapping
的所有麻烦,因为你可以使用 AliasToBeanResultTransformer
,就像前面提到的 JPQL 示例的情况一样。
1 2 3 4 5 6 7 8 9 10 11 12
| 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();
|