Ответ 1
Вы можете использовать ResultTransformer, который может конвертировать из псевдонима в свойства bean (DTO). Для использования вы можете обратиться к документам Hibernate здесь в разделе 13.1.5
В моем слое DAO у меня есть функция Find, подобная этой
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();
}
Итак, чтобы прочитать данные, я должен использовать Loop (с 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, framework, который легко конвертирует List<?> result
в список объекта DTO
(скажем, DQCategoryDTO) без использования какого-либо цикла, итератора и вызывающего setter/getter для заполнения значения?
Вы можете использовать ResultTransformer, который может конвертировать из псевдонима в свойства bean (DTO). Для использования вы можете обратиться к документам Hibernate здесь в разделе 13.1.5
Как я объяснял в этой статье, у вас есть так много вариантов отображения проекции на набор результатов 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" )
);
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();
Вы также можете опустить имя пакета DTO в выражении конструктора JPA и ссылаться на DTO по его простому имени класса Java (например,
PostDTO
).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();
For more details, check out this article.
Эта версия доступна в Hibernate 5.2.11, так что это еще одна причина для обновления.
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
:
@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 с именем native query выполняется следующим образом:
List<PostDTO> postDTOs = entityManager.createNamedQuery("PostDTO")
.setParameter( "fromTimestamp", Timestamp.from(
LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ))
.getResultList();
На этот раз ваш 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();
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();
Ниже приведен полный пример того, как адреса группируются вместе по имени улицы с помощью 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;
}
}
Это именно тот вариант использования, для которого 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);
}