web-dev-qa-db-ja.com

JPAまたはHibernateプロジェクションクエリのDTO(データ転送オブジェクト)へのマッピング

私の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);

}

私の質問は:ループ、イテレータを使用せずにList<?> resultDTOオブジェクト(たとえば、DQCategoryDTO)のリストに簡単に変換するAPI、フレームワークはありますか?値?

16
Thai Tran

エイリアスからBean(DTO)プロパティに変換できる ResultTransformer を使用できます。使用方法については、セクション13.1.5のHibernate docs here を参照してください。

4
Shailendra

この記事 で説明したように、プロジェクションをDTO結果セットにマッピングするための非常に多くのオプションがあります。

タプルと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" ) 
);

コンストラクター式とJPQLを使用したDTO投影

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

JPAコンストラクター式からDTOパッケージ名を省略し、その単純なJavaクラス名(例:PostDTO))でDTOを参照することもできます。

List<PostDTO> postDTOs = entityManager.createQuery(
  "select new 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();

詳しくは この記事 をご覧ください。

タプルとネイティブSQLクエリを使用したDTOプロジェクション

これはHibernate 5.2.11から利用できるため、アップグレードする理由がもう1つあります。

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

ConstructorResultを使用したDTO投影

以前に導入した同じPostDTOクラスタイプを使用する場合、次の@SqlResultSetMappingを提供する必要があります。

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

ResultTransformerとJPQLを使用したDTO投影

今回、DTOは、Hibernateが基礎となるJDBC ResultSetからデータを取り込むために必要なプロパティのセッターを持つ必要があります。

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

ResultTransformerとネイティブSQLクエリを使用したDTOプロジェクション

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();
15
Vlad Mihalcea

それがまさに Blaze-Persistenceエンティティビュー が作成されたユースケースです!

あなたの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);
}
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();

someDTOリストには、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;
}
}
1
Sumit Sundriyal