使用JPA CriteriaQuery和Hibernate的DTO查询,使用实体作为DTO构造函数会创建许多选择

时间:2018-10-08 13:14:25

标签: java hibernate jpa criteria

休眠5.2.10.Final

jpa 2.1

我想使用JPA Criteria Query和Hibernate将投影查询映射到DTO(数据传输对象)。我指定了一个构造函数,该构造函数将应用于查询执行的结果。

如果构造函数用于整个实体类,则我有多个selects而不是一个(这是一个运行很长时间的过程,用于处理数千条记录)。如果构造函数用于实体的一组参数,则在控制台中只能看到一个选择。我不明白自己在哪里弄错了或者是个错误?

public class ServiceDAO {

      public List<ServicesDTO> getAllServicesByFilter(ServicesFilter filter) {

          CriteriaBuilder cb = entityManager.getCriteriaBuilder();
          CriteriaQuery<ServicesDTO> criteria = cb.createQuery(ServicesDTO.class);
          Root<ServicesEntity> serviceEntity = criteria.from(ServicesEntity.class);

          // here is only one select to get list of services    
          criteria.select(cb.construct(ServicesDTO.class, serviceEntity.get("active"), serviceEntity.get("providerId"), serviceEntity.get("serviceId")));

          // in this case I have multiple selects
          //criteria.select(cb.construct(ServicesDTO.class, serviceEntity)); 
          if(filter != null) {
             List<Predicate> pcl = new ArrayList<Predicate>();

             if(filter.getActive() != null)
                pcl.add(cb.equal(serviceEntity.get("active"), filter.getActive()));
             if(filter.getProviderId() != null)
                pcl.add(cb.equal(serviceEntity.get("providerId"), filter.getProviderId()));
             if(filter.getServiceId() != null)
                pcl.add(cb.equal(serviceEntity.get("serviceId"), filter.getServiceId()));

             criteria.where(pcl.toArray(new Predicate[pcl.size()]));
          }

          return entityManager.createQuery(criteria).getResultList();
      }

}

-

public class ServicesDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Boolean active;
    private Integer providerId;
    private Integer serviceId;

    public ServicesDTO() {}

    public ServicesDTO(Boolean active, String providerId, Integer serviceId) {

         this.active = active;
         this.providerId = Integer.parseInt(providerId);
         this.serviceId = serviceId;
    }


    public ServicesDTO(ServicesEntity service) {

       if(service != null) {
           this.active = service.isActive();
           this.providerId = Integer.parseInt(service.getProviderId());
           this.serviceId = service.getServiceId();
       }

    // getters & setters

}

-

@Entity
@Table
public class ServicesEntity {

   @Id
   @Column(name = "id", unique = true)
   @GeneratedValue(strategy=GenerationType.IDENTITY)
   private Long id;

   @Column(name = "serviceId", nullable = false)
   private int serviceId;

   @Column(nullable = false)
   private String providerId;

   @ManyToOne(fetch=FetchType.LAZY)
   @JoinColumn(name="categoryId")
   private Categories categoryId;

   private boolean active;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "service", cascade = CascadeType.ALL)
   private List<Service_Area_Ref> areas = new ArrayList<Service_Area_Ref>();

   @ManyToOne(fetch=FetchType.LAZY, optional = true)
   @JoinColumn(name="parentCatId")
   private Categories parentCatId;

   public ServicesEntity() {}

   public ServicesEntity(int serviceId) {
       this.serviceId = serviceId;
   }

   // getters & setters

   // equals & hashcode

}

2 个答案:

答案 0 :(得分:1)

是的,确实如此。可能没有太多用例。给定

@Entity
public class A {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private Integer value;

public class ADto {
    private Integer va;

    public ADto(A a) {
        this.va = a.getValue();
    }
    public ADto(Integer va) {
        this.va = va;
    }

然后

tx.begin();

A a1 = new A();
a1.setValue(1);
A a2 = new A();
a1.setValue(2);
em.persist(a1);
em.persist(a2);

tx.commit();

em.clear();

System.out.println("As usual");
em.createQuery("select new dto.ADto(a.value) from A a where a.value <= 2", ADto.class).getResultList();

System.out.println("As A");
em.createQuery("select new dto.ADto(a) from A a where a.value <= 2", ADto.class).getResultList();

给你

create table A (id integer generated by default as identity (start with 1), value integer, primary key (id))
create table B (id integer generated by default as identity (start with 1), value integer, primary key (id))
insert into A (id, value) values (default, ?)
insert into A (id, value) values (default, ?)
As usual
select a0_.value as col_0_0_ from A a0_ where a0_.value<=2
As A
select a0_.id as col_0_0_ from A a0_ where a0_.value<=2
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?

您不喜欢每次为新的ADto实例选择实体A的事实。之所以这样做是因为您可以创建一个具有多个实体的DTO,而不仅仅是多个实体,例如A,B和C,因此JPA / Hibernate如何在单个select语句中方便地做到这一点?尽管它可以选择所有属性,然后跟踪哪些属性属于哪些实体,然后构造它们并将它们传递给DTO,所以您可以对它们进行解构,这似乎是一件很罕见的事情,需要做很多工作。如果您选择所需的属性,然后从任何内容中构造一个构造器,则可能更高效,更好,如第一种情况。

答案 1 :(得分:0)

我正在使用Hibernate 5.3,也遇到了这种现象。但是我发现,如果将JPA Tuple用作DTO容器和multiselect,则不会发生此问题。因此,我的最终解决方案是使用Tuple首先查询结果集,然后手动将其转换为DTO,例如:

CriteriaQuery<Tuple> criteria = cb.createTupleQuery();

.......

criteria.multiselect(serviceEntity);

List<ServicesDTO>  result = entityManager.createQuery(criteria).getResultList().stream()
.map(t->new ServicesDTO(t.get(0,ServicesEntity.class)))
.collect(toList());