如何通过Hibernate/JPA的SqlResultSetMapping生成需要数据的DTO?


获取比你实际所需要的更多数据并不好,此外,当您不打算修改实体时,获取实体(通过在持久化上下文中加入的方式获取实体)是最常见的错误之一,它隐含性能损失。
因此,使用DTO可允许我们仅提取所需的数据。在这个应用程序中,我们依赖  @SqlResultSetMapping和EntityManager来实现。

假设三个实体TopCategory和MiddleCategory是一对多关系,MiddleCategory和BottomCategory 是一对多关系,现在需要分别从TopCategory、MiddleCategory和BottomCategory 中提取各自的namet、namem和nameb字段组成CategoryDto:
TopCategory代码如下,我们使用了注释@SqlResultSetMapping构造获得CategoryDto的字段,TopCategory是父级根实体(DDD聚合中的实体根)。


@SqlResultSetMapping(name = "CategoryDtoMapping",
        classes = {
            @ConstructorResult(
                    targetClass = CategoryDto.class,
                    columns = {
                        @ColumnResult(name =
"namet"),
                        @ColumnResult(name =
"namem"),
                        @ColumnResult(name =
"nameb")
                    }
            )}
)
@Entity
@Table(name =
"top_category")
public class TopCategory implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL,
            mappedBy =
"topCategory", orphanRemoval = true)
    private List<MiddleCategory> middleCategories = new ArrayList<>();

   
// helper methods
    public void addMiddleCategory(MiddleCategory middleCategory) {
        middleCategories.add(middleCategory);
        middleCategory.setTopCategory(this);
    }

    public void removeMiddleCategory(MiddleCategory middleCategory) {
        middleCategory.setTopCategory(null);
        middleCategories.remove(middleCategory);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<MiddleCategory> getMiddleCategories() {
        return middleCategories;
    }

    public void setMiddleCategories(List<MiddleCategory> middleCategories) {
        this.middleCategories = middleCategories;
    }

}


然后实现自己的DAO仓储,在其中调用EntityManager的createNativeQuery,其他方式点击标签#DTO可看到更多EntityManager调用方式。

@Repository
@Transactional
public class Dao<T, ID extends Serializable> implements GenericDao<T, ID> {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public <S extends T> S persist(S entity) {
        
        Objects.requireNonNull(entity, "Cannot persist a null entity");
        
        entityManager.persist(entity);

        return entity;
    }

    @Transactional(readOnly=true)
    public List<CategoryDto> fetchCategories() {
        Query query = entityManager.createNativeQuery(
               
"SELECT t.name AS namet, m.name AS namem, b.name AS nameb "
                +
"FROM middle_category m "
                +
"INNER JOIN top_category t ON t.id=m.top_category_id "
                +
"INNER JOIN bottom_category b ON m.id=b.middle_category_id", "CategoryDtoMapping");
        List<CategoryDto> result = query.getResultList();

        return result;
    }

    protected EntityManager getEntityManager() {
        return entityManager;
    }
}

下面是Dao客户端Service的调用方式:

@Service
public class CategoryService {

    private final Dao dao;

    public CategoryService(Dao dao) {
        this.dao = dao;
    }

  
    public List<CategoryDto> fetchCategories() {
        return dao.fetchCategories();
    }
}

源代码可以在这里找到