简体   繁体   中英

JPA Criteria query for one to many relation with predicate on child not working

I have a parent entity

@Entity
@Table(name = "tblmodule", uniqueConstraints = {
    @UniqueConstraint(columnNames = "name")
})
@SequenceGenerator(name = ACLEntityConstant.SEQ_MODULE, initialValue = 1, allocationSize = 100)
public class Module implements BaseEntity {

    private static final long serialVersionUID = -4924925716441434537L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = ACLEntityConstant.SEQ_MODULE)
    private Long id;

    @Column
    private String name;

    @Column
    private String displayName;

    @OneToMany(mappedBy = "module")
    @JsonManagedReference
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<SubModule> subModule;

    //getters and setters
}

Module has one to many relationship with SubModule and the entity class is as below

    @Entity
@Table(name = "tblsubmodule", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"id", "name"})
})
@SequenceGenerator(name = ACLEntityConstant.SEQ_SUBMODULE, initialValue = 1, allocationSize = 100)
public class SubModule implements BaseEntity {

    private static final long serialVersionUID = 5452251016263080356L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = ACLEntityConstant.SEQ_SUBMODULE)
    private Long id;

    @Column
    private String name;

    @Column
    private String displayName;

    @ManyToOne
    @JsonBackReference
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private Module module;
}

I am trying to find the module with the specific name ('submodule1') of submodule using criteria query. Here is my code:

    CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
    CriteriaQuery cq = cb.createQuery();
    Root root = cq.from(Module.class);
    cq.select(root);
    ListJoin join = root.joinList("subModule");
    cq.where(cb.equal(join.get("name"), "submodule1"));
    cq.distinct(true);
    cq.groupBy(root.get("name"));
    TypedQuery query = getEntityManager().createQuery(cq);
    return query.getResultList();

Using this code, it gives me all the submodules within the module.

Is there any way to select only submodule with name 'submodule1'? Thanks

You can return all modules that contain a submodule with a name = somevalue, but it is not possible in JPA to get 'module' instances with a list of submodules filtered by something from the original query - JPA is required to return managed module instances that reflect the state in the database so that it can manage any changes you might make.

instead of using the module.submodules set in your object, query for the sub modules directly with a query similar to "Select submodule from SubModule submodule where submodule.name = :name" . You can then use the results to create a map of collections of subModules based on the submodule.module.

Table A

A_IdPKey)   name    age

1            X       34

Table B

B_id(PK)    A_id(FrnKey)    salary  
11             1             2000   
22             1             3000   

when using its Criteria builder: select a.*,b.* from A a, B b where a.A_id=b.B_id and a.name='X' and b.salary=2000

By the above query we should get the field: salary=2000

A.A_id  A.name  A.age   B.B_id  B.salary    
1          X    34       11     2000

But in JPA, for a @OneToMany relationship, Query is fired first on the master, with all the Criteria and fetches just master records. Later Individually the query is fired, but the B table criteria will not contain all the conditions, instead will contain only one condition, ie just the primary key VALUE of master records fetched in just with one condition b.A_id=[primarykeyfetched from master]

select B.* from Table B join Table A where B.A_id=(for individual Ids)

This is what is happening in Backend, and hence it fetches all the B records having A_id matching, irrespective of salary mismatching.Hence we're getting

A.A_id  A.name  A.age   B.B_id  B.salary    B.dept
1        X      34       11     2000        IT
1        X      34       22     3000        IT

The solution is simple: create an independent class, in which we require the fields of @OneToMany relationship.

STEP 1

public class AB {

    private A a;
    private B b;  //@OneToMany with A in the Entity class definitions

    public AB(){}

    public AB(A a, B b){ ///Very Important to have this constructor
        this.a=a;
        this.b=b;
    }

    getters...

    setters....

}

STEP 2

CriteriaQuery<AB> q = criteriaBuilder.createQuery(AB.class);
Root<A> fromA = criteriaQuery.from(A.class);      
List<Predicate> predicates = new ArrayList<Predicate>();         
Join<A,B> fromB = fromA.join("b", JoinType.INNER);/*"b",fieldName of B in entity Class A*/
criteriaQuery.select(criteriaBuilder.construct(AB.class,A,B));//looks AB's 2 param constr.
predicates.add(criteriaBuilder.equal(fromA.get("name"), filter.getName()));   
predicates.add(criteriaBuilder.equal(fromB.get("salary"), filter.getSalary()));
.......... 
List<AB> ABDataList = typedQuery.getResultList();  
for(AB eachABData :ABDataList){
    ....put in ur objects
    obj.setXXX(eachABData.getA().getXXX());
    obj.setYYY(eachABData.getB().getYYY());
} 

This will give all the results applying all the criteria to master table A , and comparing the primary key of Table B instead of foreign key.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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