简体   繁体   English

JPA2-使用Criteria API作为动态查询以递归方式获取已连接实体的子实体

[英]JPA2 - Getting child entities of joined entities recursively using Criteria API as Dynamic Query

I want to understand how to apply certain conditions using Criteria API instead of JPQL, particularly if Criteria can be used to recursively get child entities from Joins in the same way that JPQL can, through Join hierarchies. 我想了解如何使用Criteria API而不是JPQL来应用某些条件,尤其是如果可以使用条件以与JPQL相同的方式通过Join层次结构从Joins中递归地获取子实体。

UPDATE 更新

The comments from Tiny and Chris prompted me to first be clear what i'm trying to achieve: Tiny和Chris的评论促使我首先明确我要实现的目标:

My example has 4 entities as per the below diagram. 根据下图,我的示例有4个实体。 Enitemshas a ManyToOne relationship with Ensources. Enitem与Ensources建立了ManyToOne关系。 Entopics has OneToMany relationship with Enitems. Entopics与Enitems具有OneToMany关系。 Entopics has a OneToMany relationship with SegmentsNew. Entopics与SegmentsNew具有OneToMany关系。

在此处输入图片说明

I am building a search page where the user can find a chosen item by entering as many or as little in the search criteria. 我正在建立一个搜索页面,用户可以在其中输入或多或少的搜索条件来找到选定的项目。 In the below example a search for "Corporate Law" should return all the items in the Corporate Law segment (even if nothing else is entered). 在以下示例中,搜索“公司法”应返回公司法部分中的所有项目(即使未输入其他内容)。

在此处输入图片说明

My entities: 我的实体:

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;

A desired JPQL string representation of this would be: 所需的JPQL字符串表示形式是:

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

Search page uses JSf Primefaces: 搜索页面使用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>

This is what I'm trying using CAPI: 这是我正在尝试使用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();

}

It is important to state if(entityName != 0) rather than if(entityName != null), which I was previously doing and this caused the application to require all parameters to be populated by the user. 重要的是要声明if(entityName!= 0)而不是if(entityName!= null),这是我之前所做的,这导致应用程序要求用户填充所有参数。 This is probably because integer value of null is actually the number zero? 这可能是因为null的整数值实际上是数字零吗?

The generated SQL: 生成的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 = ?))

The application is behaving dynamically in that the user only needs to enter any single value in the search page and a list is returned corresponding to that value. 该应用程序动态运行,因为用户只需要在搜索页面中输入任何单个值,并返回与该值相对应的列表。 The problem I'm now having is that the same results are being returned if I do a second query even though I already cleared the EntityManager at the start of the method. 我现在遇到的问题是,即使我在方法开始时已经清除了EntityManager,如果再次执行查询,也会返回相同的结果。 SO the application only works if it's restarted. 因此,该应用程序仅在重新启动后才能运行。 Do I need to refersh the entities? 我需要推荐实体吗?

Using From and join both give you objects that can be cast to the root interface allowing joins to be chained. 都使用From和join可以为您提供对象,这些对象可以转换为根接口,从而允许链接链接。
Try something like: 尝试类似:

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

This will produce something without any joining unless parameters that require a join are specified, and will use inner joins as the provided JPQL would. 除非指定需要连接的参数,否则这将产生没有任何连接的内容,并且将像提供的JPQL那样使用内部连接。

So it turns out the logic of the JPA was correct but the problem is in the web framework which means i have to adapt my JPA accordingly. 因此,事实证明JPA的逻辑是正确的,但是问题出在Web框架中,这意味着我必须相应地调整我的JPA。 JSF doesn't accept 'null' as a definition of an integer in the search page and only accepts a numerical value or just "". JSF在搜索页面中不接受'null'作为整数的定义,而仅接受数字值或仅接受“”。 Changing the if statement from : 更改if语句从:

if(entity != null);

to

if(entity != 0);

resulted in the application behaving as expected. 导致应用程序的行为符合预期。 This solves the problem in this question. 这解决了这个问题中的问题。 Thanks to Chris and Tiny for their help 感谢Chris和Tiny的帮助

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM