繁体   English   中英

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

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

我想了解如何使用Criteria API而不是JPQL来应用某些条件,尤其是如果可以使用条件以与JPQL相同的方式通过Join层次结构从Joins中递归地获取子实体。

更新

Tiny和Chris的评论促使我首先明确我要实现的目标:

根据下图,我的示例有4个实体。 Enitem与Ensources建立了ManyToOne关系。 Entopics与Enitems具有OneToMany关系。 Entopics与SegmentsNew具有OneToMany关系。

在此处输入图片说明

我正在建立一个搜索页面,用户可以在其中输入或多或少的搜索条件来找到选定的项目。 在以下示例中,搜索“公司法”应返回公司法部分中的所有项目(即使未输入其他内容)。

在此处输入图片说明

我的实体:

项:

@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;

主题:

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;

资源:

@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;

细分新:

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的整数值实际上是数字零吗?

生成的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,如果再次执行查询,也会返回相同的结果。 因此,该应用程序仅在重新启动后才能运行。 我需要推荐实体吗?

都使用From和join可以为您提供对象,这些对象可以转换为根接口,从而允许链接链接。
尝试类似:

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那样使用内部连接。

因此,事实证明JPA的逻辑是正确的,但是问题出在Web框架中,这意味着我必须相应地调整我的JPA。 JSF在搜索页面中不接受'null'作为整数的定义,而仅接受数字值或仅接受“”。 更改if语句从:

if(entity != null);

if(entity != 0);

导致应用程序的行为符合预期。 这解决了这个问题中的问题。 感谢Chris和Tiny的帮助

暂无
暂无

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

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