Запрос JPA Criteria для извлечения данных из главной и дочерней таблиц с помощью entitygraph - PullRequest
1 голос
/ 27 мая 2020

Я пытаюсь получить данные от родителя к потомку на основе критериев фильтрации с использованием запроса критериев JPA, чтобы можно было избежать множественных запросов к БД, но не смог достичь желаемого результата. Ниже приведены мои образцы сущностей (без геттеров / сеттеров)

@Entity
public class ParentTable implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    private String id;

    @Column(name = "KEY_COLUMN",length = 30)
    private String keyColumn;

    @Column(name = "CODE",length = 30)
    private String code;

    @Column(name = "KEY_DESC",length = 240)
    private String desc;

    @OneToMany(mappedBy = "parentTable",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<ChildTable> childTableList;
}


@Entity
public class ChildTable implements Serializable{

    private static final long serialVersionUID = 1L;

    public ChildTable() {
        super();
    }

    @Id
    @Column(name = "ID",length = 80)
    private String id;

    @Column(name = "PARENT_KEY_COLUMN",length = 30,insertable = false,updatable = false)
    private String parentKeyColumn;

    @Column(name = "CHILD_CODE",length = 30)
    private String childCode;

    @Column(name = "CHILD_DESC",length = 240)
    private String chldDesc;


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PARENT_KEY_COLUMN", referencedColumnName = "KEY_COLUMN")
    private ParentTable parentTable;

}

Фрагмент построителя критериев -

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> query = cb.createQuery(ParentTable.class);
Root<ParentTable> fromParent = query.from(ParentTable.class);
Join<ParentTable, ChildTable> details = fromParent.join("childTableList");

List<Predicate> conditions = new ArrayList();
conditions.add(cb.equal(details.get("childCode"), childCode));
conditions.add(cb.equal(details.get("chldDesc"),chldDesc));

TypedQuery<ParentTable> typedQuery = em.createQuery(query.select(fromParent).where(conditions.toArray(new Predicate[] {})));
List<ParentTable> parentTableList = typedQuery.getResultList();

Это выполняется и дает результат только для родительской таблицы, если я получаю данные дочерней таблицы, я могу увидеть, что запрос JPA снова исключается, можно ли этого избежать и получить список дочерних объектов, который соответствует 3 параметрам динамического c? 1. ParentTable.code, 2. ChildTable.childCode, 3. ChildTable.chldDes c.

Может ли кто-нибудь помочь мне построить запрос JPA, как показано ниже, который выполняется в одном обращении к базе данных вместо нескольких циклов приема-передачи, что происходит в приведенном выше фрагменте кода?

select * from ParentTable p,ChildTable c where p.KEY_COLUMN=c.PARENT_KEY_COLUMN and p.CODE=? and c.CHILD_CODE=? and c.CHILD_DESC=?

Обновление:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> cq = builder.createQuery(ParentTable.class);
Root<ParentTable> root = cq.from(ParentTable.class);
Join<ParentTable, ChildTable> join = root.join("childTableList");

Predicate p1=builder.equal(root.get("code"), "code");
Predicate p2=builder.like(join.get("chldDesc"), "%chldDesc%");
Predicate p3=builder.equal(join.get("childCode"), "childCode");
Predicate andPredicate = builder.and(p1,p2, p3);
cq.select(root).where(andPredicate);

EntityGraph<ParentTable> fetchGraph = entityManager.createEntityGraph(ParentTable.class);
fetchGraph.addSubgraph("childTableList");
List<ParentTable> parentTableList=entityManager.createQuery(cq).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
parentTableList.forEach(System.out::println);

Этот подход формирует ожидаемый запрос, как указано выше, но снова формируется еще один запрос, как показано ниже, почему запускается второй запрос даже если это не требуется?

select * from ParentTable  parentTable0_ where parentTable0_.code=?

1 Ответ

0 голосов
/ 29 мая 2020

JPA требуется для получения результатов управляемого объекта, которые отражают данные в базе данных. Фильтр, который вы устанавливаете для запроса, не фильтрует внутренние отношения.

Ваше предложение 'join' влияет только на фильтр, применяемый к возвращаемым объектам ParentTable. Каждая возвращенная сущность будет иметь полную коллекцию childTableList, поэтому, даже если вы возвращаете экземпляры ParentTable с указанными c кодами и описаниями, childTableList показывает всех своих дочерних элементов. Это JPA для вас, и у конкретных поставщиков c есть способ фильтровать эти сопоставленные коллекции ( AdditionalCriteria ). ИМО, это плохой маршрут со многими проблемами.

Если вы хотите имеют дочерние элементы таблицы, соответствующие указанным c кодам и описаниям, ваш запрос должен иметь форму (с использованием JPQL)

"Select c, p from ChildTable c join c.parentTable p where c.childCode = :code and c.chldDesc = :desc"

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

В противном случае дополнительный запрос вызывается доступом к childTableList в записях parentTable, поскольку они помечены как ленивые. В вашем запросе критериев указано «соединение», поскольку вы хотите использовать записи childTableList при фильтрации сущностей parentTable. Если вы хотите, чтобы childTableList извлекался с помощью parentTables, вам необходимо использовать fetchJoins. Root реализует FetchParent , что позволит вам указать «выборку» в childTableList в дополнение к определенному вами соединению. В JPQL это примерно так:

 "select p from ParentTable p fetch join p.childTableList, join p.childTableList c where c.childCode = :code and c.chldDesc = :desc"
...