How to filter child entities collections with predicate?

Mario Brissette picture Mario Brissette · Sep 2, 2014 · Viewed 11.7k times · Source

I have an entity service on which I need to filter a collection of child entity, based on a list of id's. My service have a public method which receive the id of the parent entity and a list of id's of some of his children entities.

By default, I know that JPA will fetch all related entities and this his the actual behavior. But we need to work on the performance of the service. So instead of getting all related entities and filter them with many loop (filter on id's and also on other properties like date property), I want to get only entities concerned by my request.

My Parent entity

@Entity
@Table(name = "MyParent")
public class MyParentEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, 
        generator = "SEQ_MyParent")
    @SequenceGenerator(allocationSize = 1, name = "SEQ_MyParent", 
        sequenceName = "SEQ_MyParent")
    @Column(name = "ID_PARENT")
    private Long id;

    @OneToMany(mappedBy = "myParent", cascade = CascadeType.ALL, 
        fetch = FetchType.EAGER, orphanRemoval = true)
    private final List<MyChildEntity> myChild = new ArrayList<MyChildEntity>();

}

My Child Entity

@Entity
@Table(name = "MyChild")
public class MyChildEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, 
        generator = "SEQ_MyChild")
    @SequenceGenerator(allocationSize = 1, name = "SEQ_MyChild", 
        sequenceName = "SEQ_MyChild")
    @Column(name = "ID_CHILD")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "ID_PARENT")
    private MyParentEntity myParent;
}

I'm using Spring-data CrudRepository to get data from my DB and I also extends JpaSpecificationExecutor to use Predicate.

public interface MyParentRepository extends CrudRepository<MyParentEntity, Long>,
    JpaSpecificationExecutor<MyParentEntity> {
}

This let me use CrudRepository findOne() method but with a Specification object instead of the regular Long parameter.

Also, I combine multiples Specification's object with the following call:

this.myParentRepository.findOne(Specifications
    .where(firstSpecification(parentId))
    .and(secondSpecification(childrenIdsList)));

I created a simple junit test with one Parent linked to two children entities. In my request, I'm able to get the parent entity with the provided Id. But even if I provide the child id, I always get both children entities in the list inside the parent.

In my method which return a new Specification object, in which the toPredicate method is override, I'm unable to create a Predicate that will filter my children collection and only get those one I'm interested. I know that the Hibernate Criteria has the possibility to add "Restrictions" but this is not available in the CriteriaBuilder that is provided with the toPredicate method.

public static Specification<MyParentEntite> firstSpecification(final Long id) {
    return new Specification<MyParentEntite>() {

        @Override
        public Predicate toPredicate(Root<MyParentEntite> root, 
            CriteriaQuery<?> query, CriteriaBuilder cb) {

            Predicate predicate = cb.equal(root.get(MyParentEntity_.id), id);
            return cb.and(predicate);
        }
    };
}

public static Specification<MyParentEntite> secondSpecification(final List<Long> ids) {
    return new Specification<MyParentEntite>() {

        @Override
        public Predicate toPredicate(Root<MyParentEntite> root, 
            CriteriaQuery<?> query, CriteriaBuilder cb) {

            Root<MyChildEntity> child = query.from(MyChildEntity.class);
            Expression<Long> exp = child.get(MyChildEntity_.id);
            Predicate p = exp.in(ids);
            return cb.and(p);
        }
    };
}

In the secondSpecification() method, I also tried to use ListJoin instead of Root directly in the Entity. I searched in other questions here but it seems that this concern is solved with the Hibernate Criteria restrictions or with a LeftJoin, which I tried in my ListJoin in specifing JoinType.LEFT parameter.

Here are links to already tested solutions whitout success :

JPA CriteriaBuilder - How to use "IN" comparison operator

JPA2 Criteria-API: select... in (select from where)

I want to mention that I'm relatively new with Criteria API and Predicate. Maybe I'm missing something that is simple but that is obvious to experienced JPA developpers!

Thanks a lot for your help!

Answer

Mario Brissette picture Mario Brissette · Sep 18, 2014

Finally, I found a way to resolved my issue. Requesting only partial collections of sub-entities is something that we found dangerous in terms of data integrity. If a remote service calls to request my parent entity with a partial collection of children's entities within a get, this parent entity object may be return for a modify operation which will result in many "delete" calls on the removed instances of children entities. The persistence API will consider these missing children as relations that were removed, which is something we don't want.

I created a dummy transfert object which contains the partial collections of children's entities requested so this dummy transfert object can't not be use in a future modify operation call. The full version of the parent entity will be used for the "modify" purpose.