将JPA或Hibernate投影查询映射到DTO(数据传输对象)

时间:2014-05-18 06:33:51

标签: java spring hibernate jpa dozer

在我的DAO图层中,我有一个像这样的查找功能

public List<?> findCategoryWithSentenceNumber(int offset, int maxRec) {
  Criteria crit = getSession().createCriteria(Category.class, "cate");
    crit.createAlias("cate.sentences", "sent");

    crit.setProjection(Projections.projectionList().
    add(Projections.property("title"), "title").
    add(Projections.count("sent.id"), "numberOfSentence").
    add(Projections.groupProperty("title"))
  );

  crit.setFirstResult(offset);
  crit.setMaxResults(maxRec);

  return crit.list();
}

所以,为了读取数据,我必须使用一个循环(Iterator

List<?> result = categoryDAO.findCategoryWithSentenceNumber(0, 10);
// List<DQCategoryDTO> dtoList = new ArrayList<>(); 

for (Iterator<?> it = result.iterator(); it.hasNext(); ) {
  Object[] myResult = (Object[]) it.next();

  String  title = (String) myResult[0];
  Long count = (Long) myResult[1];


  assertEquals("test", title); 
  assertEquals(1, count.intValue()); 

  // dQCategoryDTO = new DQCategoryDTO();
  // dQCategoryDTO.setTitle(title);
  // dQCategoryDTO.setNumberOfSentence(count);
  // dtoList.add(dQCategoryDTO);

}

我的问题是:是否有任何api,框架可以轻松地将List<?> result转换为DTO对象列表(例如,DQCategoryDTO),而无需使用任何循环,迭代器和调用setter / getter填这个价值?

4 个答案:

答案 0 :(得分:11)

正如我在this article中所解释的那样,您有很多选项可以将投影映射到DTO结果集:

使用Tuple和JPQL进行DTO预测

tsconfig.json

使用Constructor Expressionand和JPQL

进行DTO投影
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" ) 
);

使用Tuple和本机SQL查询的DTO投影

这个可以从Hibernate 5.2.11获得,这是升级的另一个原因。

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

使用ConstructorResult进行DTO投影

如果我们使用之前介绍的相同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(); 类类型,我们必须提供以下PostDTO

@SqlResultSetMapping

现在,名为native query的SQL投影执行如下:

@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")
        }
    )
)

使用ResultTransformer和JPQL进行DTO投影

这一次,你的DTO需要让你需要Hibernate的属性的setter从底层的JDBC List<PostDTO> postDTOs = entityManager.createNamedQuery("PostDTO") .setParameter( "fromTimestamp", Timestamp.from( LocalDateTime.of( 2016, 1, 1, 0, 0, 0 ) .toInstant( ZoneOffset.UTC ) )) .getResultList(); 中填充。

DTO预测如下:

ResultSet

使用ResultTransformer和Native SQL查询进行DTO投影

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

答案 1 :(得分:4)

您可以使用可以从别名转换为bean(DTO)属性的ResultTransformer。有关用法,请参阅第13.1.5节

中的Hibernate docs here

答案 2 :(得分:1)

以下是使用Projection根据街道名称将地址组合在一起的完整示例。

Criteria criteria = getCurrentSession().createCriteria(Address.class);
// adding condition
criteria.add(Restrictions.eq("zip", "12345"));
// adding projection
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("streetName"), "streetName")
.add(Projections.count("apartment"), "count"));
// set transformer
criteria.setResultTransformer(new AliasToBeanResultTransformer(SomeDTO.class));

List<SomeDTO> someDTOs = criteria.list();
  

someDTOs列表将包含streetName的结果组数。每个SomeDTO对象都包含该街道的街道名称和公寓数量。

SomeDTO.java

public class SomeDTO{

private String streetName;
private Long count;

public void setStreetName(String streetName){
    this.streetName=streetName;
}
public String getStreetName(){
    return this.streetName;
}
public Long getCount() {
    return count;
}
public void setCount(Long count) {
    this.count = count;
}
}

答案 3 :(得分:0)

这正是为其创建Blaze-Persistence Entity Views的用例!

您的DTO看起来像

@EntityView(Category.class)
interface DQCategoryDTO  {
  String getTitle();
  @Mapping("SIZE(sentences)")
  int getCount();
}

,如果您使用Spring Data,则可以在类似的存储库中使用它

interface CategoryRepository extends Repository<Category, Long> {
  List<DQCategoryDTO> findAll(Pageable pageable);
}