2016-01-05 29 views
0

我想了解如何使用Criteria API而不是JPQL來應用某些條件,特別是如果可以使用Criteria以遞歸方式從聯接中獲取子實體JPQL可以通過Join層次結構來實現。JPA2 - 使用Criteria API以遞歸方式獲取聯合實體的子實體作爲動態查詢

UPDATE

從微小的和克里斯的評論促使我首先要清楚我想要實現:

我的例子有4個實體按照如下圖。 Enitems與Ensources擁有ManyToOne關係。 Entopics與Enitems具有OneToMany關係。 Entopics與SegmentsNew具有OneToMany關係。

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(); 

} 

如果(entityName!= 0)而不是if(entityName!= null)是我很重要的事情,我之前在做這些事情,這導致應用程序需要用戶填充所有參數。這可能是因爲null的整數值實際上是數字零?

生成的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,也會返回相同的結果。所以應用程序只有在重新啓動時纔有效。我需要引用實體嗎?

+1

「*以上是否正確使用'Entopics','Ensource','Segmentsnew'之間的連接來獲取類型爲'Enitems'的列表中的相關項?*」Is這是唯一真正的問題?您可以通過仔細查看生成的SQL語句來確認這一點。對於使用條件或JPQL制定的每個查詢,堅持不懈地執行此操作。 (順便說一下,不需要兩個'Root',你可能已經知道'Join'可以被遞歸鏈接,這取決於你的項目的功能需求。 – Tiny

+0

如果我不清楚,謝謝你,對不起。不,它不是唯一的問題。我的問題是,如果在JPQL中使用相同的'entity.subentity.object'語法,那麼實際上如何遞歸地鏈接到Criteria Api和d?我編輯了這個問題,並進一步澄清了我想要的內容 –

+1

您仍然沒有清楚地顯示出您想要的內容,並且從例外情況中可以看出,enitems沒有定義「主題」屬性。 Criteria可以從幾乎所有的JPQL語句中形成,所以如果你有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的邏輯是正確的,但問題出現在Web框架中,這意味着我必須相應地調整我的JPA。 JSF不接受'null'作爲搜索頁面中整數的定義,只接受數字值或只是「」。從更改if語句:

if(entity != null); 

if(entity != 0); 

導致應用程序行爲預期。這解決了這個問題中的問題。感謝Chris和Tiny的幫助

1

使用從和連接都給你的對象,可以轉換到根接口允許連接鏈接。
試着這麼做:

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) rt.get(「主題」)可能是安全的調用,如果主題/段ID不需要 - 我記得它可能只強制加入,如果它在查詢中使用。 – Chris

相關問題