I have an entity service on which I need to filter a collection of a child based on a list of identifiers. My service has an open method that gets the identifier of the parent and a list of identifiers of some of its children.
By default, I know that JPA will retrieve all related objects, and this is its actual behavior. But we need to work on the implementation of the service. Therefore, instead of getting all related entities and filtering them with many cycles (filter by identifier, as well as by other properties, such as date property), I want to get only objects related to my request.
My parent object
@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 use Spring -data CrudRepository to retrieve data from my database, and I also extend the JpaSpecificationExecutor to use Predicate.
public interface MyParentRepository extends CrudRepository<MyParentEntity, Long>, JpaSpecificationExecutor<MyParentEntity> { }
This allows me to use the findOne () CrudRepository method, but with the Specification object instead of the usual Long parameter.
In addition, I am combining the Specification object with multiple values with the following call:
this.myParentRepository.findOne(Specifications .where(firstSpecification(parentId)) .and(secondSpecification(childrenIdsList)));
I created a simple junit test with one parent associated with two children. In my request, I can get the parent with the Id provided. But even if I provide a child identifier, I always get both children in the list inside the parent.
In my method, which returns a new specification object in which the toPredicate method is overridden, I cannot create a Predicate that will filter my collection of children and only get the ones that interest me. I know that in Hibernate Criteria it is possible to add "Constraints", but this is not available in CriteriaBuilder, which is equipped 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 using ListJoin instead of Root directly in Entity. I searched in other questions here, but it seems that this problem was solved with restrictions like Hibernate or with LeftJoin, which I tried to use in ListJoin to set JoinType.LEFT parameter.
Here are links to already tested solutions without success:
JPA CriteriaBuilder - How to use the "IN" comparison operator
JPA2 API Criteria: select ... in (select where)
I want to mention that I'm relatively new to the Criteria API and Predicate. Maybe I'm missing something simple, but it's obvious to experienced JPA developers!
Many thanks for your help!