2016-01-05 3 views
0

Я хочу понять, как применять определенные условия с использованием API критериев вместо JPQL, особенно если критерии могут использоваться для рекурсивного получения дочерних объектов из Joins в том же способ, которым JPQL может, через иерархии Join.JPA2 - Получение дочерних сущностей объединенных сущностей рекурсивно с использованием API критериев в качестве динамического запроса

UPDATE

Комментарии от Крошки и Криса побудили меня сначала быть ясно, что я пытаюсь достичь:

Моего примера имеет 4 сущности согласно приведенной ниже схеме. Enitemshas a ManyToOne отношения с Ensources. Энтопика имеет отношения OneToMany с Enitems. Энтопика имеет отношения OneToMany с SegmentsNew.

enter image description here

Я строю страницу поиска, где пользователь может найти выбранный элемент, введя столько или как мало, в критериях поиска. В приведенном ниже примере поиск «Корпоративного права» должен вернуть все элементы сегмента корпоративного права (даже если ничего не введено).

enter image description here

Мои объекты:

Enitems:

@Entity 
@Table(name = "enitem") 

private static final long serialVersionUID = 1L; 
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
@Basic(optional = false) 
@Column(name = "itemid") 
private Integer itemid; 
@Size(max = 500) 
@Column(name = "itemname") 
private String itemname; 
@Column(name = "daterec") 
@Temporal(TemporalType.DATE) 
private Date daterec; 
@Lob 
@Size(max = 65535) 
@Column(name = "itemdetails") 
private String itemdetails; 
private String enteredby; 
@OneToMany(mappedBy = "items") 
private Collection<Endatamaster> endatamasterCollection; 
@JoinColumn(name = "topicid", referencedColumnName = "topicid") 
@ManyToOne 
private Entopic topics; 
@JoinColumn(name = "sourceid", referencedColumnName = "sourceid") 
@ManyToOne 
private Ensource source; 

Entopics:

public class Entopic implements Serializable { 

private static final long serialVersionUID = 1L; 
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
@Basic(optional = false) 
@Column(name = "topicid") 
private Integer topicid; 
@Size(max = 500) 
@Column(name = "topicname") 
private String topicname; 
@Size(max = 500) 
@Column(name = "description") 
private String description; 
@Column(name = "locksec") 
private boolean locksec; 
@JoinColumn(name = "segmentid", referencedColumnName = "SEGMENTID") 
@ManyToOne 
private Segmentnew segments; 
@JoinColumn(name = "marketid", referencedColumnName = "marketid") 
@ManyToOne 
private Enmarkets markets; 
@OneToMany(mappedBy = "topics") 
private Collection<Enitem> enitemCollection; 

Ensource:

@Entity 
@Table(name = "ensource") 
@NamedQueries({ 
@NamedQuery(name = "Ensource.findAll", query = "SELECT e FROM Ensource e")}) 
public class Ensource implements Serializable { 
private static final long serialVersionUID = 1L; 
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
@Basic(optional = false) 
@Column(name = "sourceid") 
private Integer sourceid; 
@Size(max = 500) 
@Column(name = "sourcename") 
private String sourcename; 
@Size(max = 500) 
@Column(name = "description") 
private String description; 

@JoinColumn(name = "typeid", referencedColumnName = "typeid") 
@ManyToOne 
private Enitemtype entype; 

Segmentsnew:

public class Segmentnew implements Serializable { 
private static final long serialVersionUID = 1L; 
@Id 
@Basic(optional = false) 
@NotNull 
@Column(name = "SEGMENTID") 
private Integer segmentid; 
@Size(max = 255) 
@Column(name = "SEGMENTNAME") 
private String segmentname; 
@OneToMany(mappedBy = "segments") 
private Collection<Entopic> entopicCollection; 
@JoinColumn(name = "sectorId", referencedColumnName = "SECTORID") 
@ManyToOne 
private Sectorsnew sectors; 

Желаемое JPQL строковое представление этого будет:

Select e FROM Enitems e WHERE e.topics.topicid = :topicid 
AND e.source.sourceid = :sourceid; AND e.topics.segments.segmentid = :segmentid 

Страница поиска использует Jsf Primefaces:

<p:panelGrid columns="2"> 

        <p:outputLabel value="Sectors: "/> 
        <p:selectOneMenu value="#{sectorBean.sectorid}" 
            filter="true"> 
         <f:selectItem itemValue="0" itemLabel="NULL"/> 
         <f:selectItems value="#{sectorBean.secList}" 
             var="sect" 
             itemLabel="#{sect.sectorname}" 
             itemValue="#{sect.sectorid}"/> 
         <f:ajax listener="#{segmentsBean.segFromSec}" render="segs"/> 
        </p:selectOneMenu> 

        <p:outputLabel value="Segments: "/> 
        <p:panel id="segs"> 

         <p:selectOneMenu value="#{queryBean.segmentid}" 
             rendered="#{not empty segmentsBean.menuNormList}"> 

          <f:selectItems value="#{segmentsBean.menuNormList}" 
              var="segs" 
              itemLabel="#{segs.segmentname}" 
              itemValue="#{segs.segmentid}"/> 
         </p:selectOneMenu> 

        </p:panel> 

        <p:outputLabel value="Topics: "/> 
        <p:selectOneMenu value="#{queryBean.topicid}" filter="true"> 
         <f:selectItem itemValue="" itemLabel="NULL"/> 
         <f:selectItems value="#{clienTopicBean.publicTopMenu}" 
             var="pubs" 
             itemLabel="#{pubs.topicname}" 
             itemValue="#{pubs.topicid}"/> 


        </p:selectOneMenu> 

        <p:outputLabel value="Type: "/> 
        <p:selectOneMenu value="#{typeBean.typeid}" filter="true"> 
         <f:selectItem itemValue="" itemLabel="NULL"/> 
         <f:selectItems value="#{typeBean.menuList}" 
             var="type" 
             itemLabel="#{type.typename}" 
             itemValue="#{type.typeid}"/> 

         <f:ajax listener="#{sourceBean.sourceFromType}" render="src"/> 
        </p:selectOneMenu> 

        <p:outputLabel value="Sources: "/> 
        <p:panel id="src"> 

         <p:selectOneMenu value="#{queryBean.sourceid}" 
             rendered="#{not empty sourceBean.sourceListNorm}"> 

          <f:selectItems value="#{sourceBean.sourceListNorm}" 
              var="srcs" 
              itemLabel="#{srcs.sourcename}" 
              itemValue="#{srcs.sourceid}"/> 
         </p:selectOneMenu> 

        </p:panel> 
       </p:panelGrid> 

Это то, что я пытаюсь с помощью CAPI:

public List<Enitem> superQ(Integer topicid, Integer sourceid, 
     Integer segmentid) { 
    em.clear(); 

    CriteriaBuilder cb = em.getCriteriaBuilder(); 
    CriteriaQuery cq = cb.createQuery(Enitem.class); 
    Root<Enitem> rt = cq.from(Enitem.class); 

    cq.select(rt); 
    cq.distinct(true); 

    List<Predicate> criteria = new ArrayList<Predicate>(); 
    Predicate whereClause = cb.conjunction(); 
    // if() { 
    Path<Entopic> topJoin = rt.get("topics"); 


    if (topicid != 0) { 

     ParameterExpression<Integer> p 
       = cb.parameter(Integer.class, "topicid"); 
     whereClause = cb.and(whereClause, cb.equal(topJoin.get("topicid"), p)); 
    } 
    if (segmentid != 0) { 

     ParameterExpression<Integer> p 
       = cb.parameter(Integer.class, "segmentid"); 
     whereClause = cb.and(whereClause, cb.equal(topJoin.get("segments").get("segmentid"), p)); 
    } 
    //} 

    if (sourceid != 0) { 
     ParameterExpression<Integer> p 
       = cb.parameter(Integer.class, "sourceid"); 
     whereClause = cb.and(whereClause, cb.equal(rt.get("source").get("sourceid"), p)); 
    } 

// if(whereClause.getExpressions().isEmpty()) { 
//  throw new RuntimeException("no criteria"); 
// } 
    cq.where(whereClause); 

    TypedQuery q = em.createQuery(cq); 
    if (topicid != 0) { 
     q.setParameter("topicid", topicid); 
    } 
    if (segmentid != 0) { 
     q.setParameter("segmentid", segmentid); 
    } 

    if (sourceid != 0) { 
     q.setParameter("sourceid", sourceid); 
    } 

    return q.getResultList(); 

} 

Важно указать if (entityName! = 0), а не if (entityName! = Null), который я ранее делал, и это заставило приложение требовать, чтобы все параметры были заполнены пользователем. Вероятно, это потому, что целочисленное значение null на самом деле является номером 0?

Сгенерированный SQL:

SELECT DISTINCT t1.itemid, t1.daterec, t1.ENTEREDBY, t1.itemdetails, t1.itemname, 
t1.sourceid, t1.topicid FROM enitem t1 LEFT OUTER JOIN entopic t0 ON (t0.topicid 
= t1.topicid) LEFT OUTER JOIN ensource t2 ON (t2.sourceid = t1.sourceid) WHERE 
(((t0.topicid = ?) AND (t2.sourceid = ?)) AND (t0.segmentid = ?)) 

Приложение ведет себя динамически в том, что пользователю нужно только ввести любое единичное значение на странице поиска и список возвращается, соответствующий этому значению. Проблема, с которой я сталкиваюсь, заключается в том, что те же результаты возвращаются, если я выполняю второй запрос, хотя я уже очистил EntityManager в начале метода. SO приложение работает только при перезапуске. Нужно ли мне ссылаться на объекты?

+1

"* Правильно ли вы используете соединение между' Entopics', 'Ensource',' Segmentsnew', чтобы получить связанные элементы в списке типа 'Enitems'? * 'Is это единственный реальный вопрос? Вы можете подтвердить это, внимательно посмотрев на сгенерированный оператор SQL. Сделайте это настойчиво для каждого запроса, который вы формулируете с использованием критериев или JPQL. (Кстати, два 'Root' не нужны. Вероятно, вы уже знаете, что' Join' может быть скован рекурсивно. Это зависит от функциональных требований вашего проекта). – Tiny

+0

Спасибо, и прости, если я не понимаю. Нет, это не единственный вопрос. Мой вопрос заключается в том, как рекурсивно цепочки в Criteria Api a d, если такой же синтаксис «entity.subentity.object» используется как в JPQL? Я отредактировал вопрос и уточнил, что я хочу. –

+1

. Вы все еще не ясно показываете, что хотите, и из-за исключения указывается, что в enitems нет атрибута «themes». Критерии могут быть сформированы из почти любого оператора JPQL, поэтому, возможно, вы увидите свой запрос JPQL, если он у вас есть. Использование «nameOfJoin.source» эквивалентно, например, использованию nameOfJoin.get («source»), поэтому «nameOfJoin.source.sourceid =: sourceid» может быть qb.equal (nameOfJoin.get («source»). Get (" sourceid "), cb.parameter (Integer.class," sourceid ")) – Chris

ответ

0

Итак, оказывается, что логика JPA была правильной, но проблема в веб-структуре, что означает, что я должен соответствующим образом адаптировать JPA. JSF не принимает «null» как определение целого числа на странице поиска и принимает только числовое значение или просто «». Изменение, если заявление от:

if(entity != null); 

в

if(entity != 0); 

в результате применения ведет себя, как ожидалось. Это решает проблему в этом вопросе. Спасибо Chris и Tiny за их помощь

1

Использование From и объединение дают объекты, которые могут быть переданы в корневой интерфейс, позволяющий привязывать соединения.
Попробуйте что-то вроде:

public List<Enitem> superQ(Integer topicid, Integer sourceid, 
     Integer segmentid) { 
    CriteriaBuilder cb = em.getCriteriaBuilder(); 
    CriteriaQuery cq = cb.createQuery(Enitem.class); 
    Root<Enitem> rt = cq.from(Enitem.class); 

    cq.select(rt); 
    cq.distinct(true); 

    List<Predicate> criteria = new ArrayList<Predicate>(); 
    Predicate whereClause = cb.conjunction(); 
    if ((topicid != null)||(segmentid != null)){ 
     Path<Entopic> topJoin =rt.get("topics"); 
     if(topicid != null) { 
      ParameterExpression<Integer> p = 
        cb.parameter(Integer.class, "topicid"); 
      whereClause = cb.and(whereClause, cb.equal(topJoin.get("topicid"), p)); 
     } 
     if(segmentid != null) { 
     ParameterExpression<Integer> p = 
       cb.parameter(Integer.class, "segmentid"); 
     whereClause = cb.and(whereClause, cb.equal(topJoin.get("segments").get("segmentid"), p)); 
     } 
    } 

    if(sourceid != null) { 
     ParameterExpression<Integer> p = 
       cb.parameter(Integer.class, "sourceid"); 
     whereClause = cb.and(whereClause, cb.equal(rt.get("source").get("sourceid"), p)); 
    } 


    if(whereClause.getExpressions().isEmpty()) { 
     throw new RuntimeException("no criteria"); 
    } 
    cq.where(whereClause); 
} 

это будет производить что-то без каких-либо не присоединиться, если параметры, которые требуют объединения определены, и будут использовать внутренние соединения, как предоставленному JPQL бы.

+0

У вас есть if() {в приведенном выше коде, который создает ошибку времени компиляции.Должен ли быть аргумент там или есть заявление опечатка? –

+0

Я забыл (topicid! = Null) || (segmentid! = Null), как это было сделано быстро в браузере – Chris

+0

Возможно, вы можете удалить эту (topicid! = Null) || (segmentid! = Null) check, так как rt.get («themes») может быть безопасным вызовом, если идентификаторы темы/сегмента не нужны. Я помню, что это может привести только к объединению, если оно будет использовано в запросе. – Chris