JPQL подсчитывает родительские объекты при совпадении нескольких дочерних элементов в отношениях OneToMany - PullRequest
1 голос
/ 30 сентября 2019

В веб-приложении JavaEE JPA объект Feature имеет двунаправленную связь ManyToOne с объектом Patient. Я хочу написать запрос для подсчета количества пациентов с одним или несколькими критериями соответствия. Я использую EclipseLink в качестве поставщика постоянства.

Например, я хочу подсчитать количество пациентов, у которых есть функция с «variableName» = «Sex» и «variableData» = «Female», а другая функция с «variableName '=' Smoking 'и' variableData '=' yes '.

Как мне написать JPQL-запрос, чтобы получить количество пациентов?

После первого ответа я попробовал этот запросне дает никаких ожидаемых результатов.

public void querySmokingFemales(){
    String j = "select count(f.patient) from Feature f "
            + "where ((f.variableName=:name1 and f.variableData=:data1)"
            + " and "
            + " (f.variableName=:name2 and f.variableData=:data2))";
    Map m = new HashMap();
    m.put("name1", "sex");
    m.put("data1", "female");
    m.put("name2", "smoking");
    m.put("data2", "yes");
    count = getFacade().countByJpql(j, m);
}

Объект Patient выглядит следующим образом.

@Entity
public class Patient implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    @OneToMany(mappedBy = "patient")
    private List<Feature> features;

    public Long getId() {
        return id;
    }

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



    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Patient)) {
            return false;
        }
        Patient other = (Patient) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "entity.Patient[ id=" + id + " ]";
    }

    public String getName() {
        return name;
    }

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

    public List<Feature> getFeatures() {
        return features;
    }

    public void setFeatures(List<Feature> features) {
        this.features = features;
    }

}

Это объект Feature.

@Entity
public class Feature implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String variableName;
    private String variableData;
    @ManyToOne
    private Patient patient;



    public Long getId() {
        return id;
    }

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

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Feature)) {
            return false;
        }
        Feature other = (Feature) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "entity.Feature[ id=" + id + " ]";
    }

    public String getVariableName() {
        return variableName;
    }

    public void setVariableName(String variableName) {
        this.variableName = variableName;
    }

    public String getVariableData() {
        return variableData;
    }

    public void setVariableData(String variableData) {
        this.variableData = variableData;
    }

    public Patient getPatient() {
        return patient;
    }

    public void setPatient(Patient patient) {
        this.patient = patient;
    }

}

Ответы [ 3 ]

2 голосов
/ 30 сентября 2019

Для отдельных функций Вы можете использовать это

select count(f.patient) from Feature f where f.variableName=:name and f.variableData:=data

Два функции

select count(distinct p) from Patient p, Feature f1, Feature f2 
where 
  p.id=f1.patient.id and p.id=f2.patient.id and 
  f1.variableName=:name1 and f1.variableData:=data1 and 
  f2.variableName=:name2 and f2.variableData:=data2

Несколько функций решение немного сложнее. org.springframework.data.jpa.domain.Specification можно использовать

    public class PatientSpecifications {
      public static Specification<Patient> hasVariable(String name, String data) {
        return (root, query, builder) ->  {
                    Subquery<Fearure> subquery = query.subquery(Fearure.class);
                    Root<Fearure> feature = subquery.from(Fearure.class);

                    Predicate predicate1 = builder.equal(feature.get("patient").get("id"), root.get("id"));

                    Predicate predicate2 = builder.equal(feature.get("variableName"), name);
                    Predicate predicate3 = builder.equal(feature.get("variableData"), data);

                    subquery.select(operation).where(predicate1, predicate2, predicate3);

                    return builder.exists(subquery);
        }
      }
    }

Тогда ваш репозиторий PatientRepository должен продлить org.springframework.data.jpa.repository.JpaSpecificationExecutor<Patient>

@Repository
public interface PatientRepository 
    extends JpaRepository<Patient, Long>, JpaSpecificationExecutor<Patient> {

}

Ваш метод обслуживания:

@Service
public class PatientService {    

   @Autowired
   PatientRepository patientRepository;

   //The larger map is, the more subqueries query would involve. Try to avoid large map
   public long countPatiens(Map<String, String> nameDataMap) {
         Specification<Patient> spec = null;

         for(Map.Entry<String, String> entry : nameDataMap.entrySet()) {
            Specification<Patient> tempSpec = PatientSpecifications.hasVariable(entry.getKey(), entry.getValue());
            if(spec != null)
              spec = Specifications.where(spec).and(tempSpec);
            else spec = tempSpec;

         }

         Objects.requireNonNull(spec);

         return patientRepository.count(spec);        
    }
}
1 голос
/ 01 октября 2019

Возможно, потребуется изменить структуру классов, чтобы упростить запросы.

1 голос
/ 01 октября 2019

Мы также обрабатывали одну и ту же ситуацию для двух функций, и после извлечения идентификаторов мы использовали вложенные циклы после подсчета общего числа. Это было ресурсоемким, и эти два функциональных запроса в ответе очень помогли.

...