Search code examples
hibernatejpaspring-data-jpacriteria

Spring data jpa - query parent class based on child class?


I have three entities with many to many relationship between them.
Packages <--> join table <--> ProtectedItems <--> join table <--> ContentItems.

In ContentItems Table, i have a field called streamId. I want to query all packages that are associated with a particular streamId. Currently the native sql query i am using is -

(SELECT sm.stream_id, p.package_id, p.package_name FROM packages p
    INNER JOIN jt_packages_protected_items jtppi
    ON p.account_id=jtppi.p_account_id AND p.package_id=jtppi.package_id 
        INNER JOIN protected_items pi
        ON jtppi.pi_account_id=pi.account_id AND jtppi.protected_item_id=pi.protected_item_id
            INNER JOIN jt_protected_items_stream_mappings jtpism
            ON pi.account_id=jtpism.pi_account_id AND pi.protected_item_id=jtpism.protected_item_id
                INNER JOIN stream_mappings sm
                ON jtpism.ci_account_id=sm.account_id AND jtpism.content_id_extension=sm.content_id_extension
                WHERE sm.stream_id='4004' AND sm.account_id='0d7af311-fa5b-4fac-b899-075d4e75cc82') LIMIT 10 OFFSET 10 ;

Is it possible to convert the above query in to something more specific to spring data jpa - like specification/criteria query etc which is more dynamic in nature and helps avoid any sql/name mistakes.
If it's possible to use criteria query, what would the code look like - any help is greatly appriciated.


Solution

  • Specification example:

    public class PackagesSpecification implements Specification<Packages> {
        private String streamId;
    
        public PackagesSpecification(String streamId) {
            this.streamId = streamId;
        }
    
        @Override
        public Predicate toPredicate(Root<Packages> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            Join<Packages, ProtectedItems> protectedItemsJoin = root.join("protectedItems", JoinType.INNER);
            Join<ProtectedItems, ContentItems> contentItemsJoin = protectedItemsJoin.join("contentItems", JoinType.INNER);
    
            return criteriaBuilder.equal(contentItemsJoin.get("streamId"), streamId);
        }
    }
    
    @Entity
    public class Packages {
        @Id
        private Long id;
    
        private String packageName;
    
        @ManyToMany
        List<ProtectedItems> protectedItems;
    }
    
    @Entity
    public class ProtectedItems {
        @Id
        private Long id;
    
        @ManyToMany
        private List<ContentItems> contentItems;
    
        @ManyToMany
        private List<Packages> packages;
    }
    
    @Entity
    public class ContentItems {
    
        @Id
        private Long id;
    
        @ManyToMany
        private List<ProtectedItems> protectedItems;
    
        private String streamId;
    
    }
    

    Generated query:

        select
            packages0_.id as id1_29_,
            packages0_.package_name as package_2_29_ 
        from
            packages packages0_ 
        inner join
            packages_protected_items protectedi1_ 
                on packages0_.id=protectedi1_.packages_id 
        inner join
            protected_items protectedi2_ 
                on protectedi1_.protected_items_id=protectedi2_.id 
        inner join
            protected_items_content_items contentite3_ 
                on protectedi2_.id=contentite3_.protected_items_id 
        inner join
            content_items contentite4_ 
                on contentite3_.content_items_id=contentite4_.id 
        where
            contentite4_.stream_id=?
    
    

    Criteria example:

            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<Packages> query = criteriaBuilder.createQuery(Packages.class);
            Root<Packages> root = query.from(Packages.class);
    
            Join<Packages, ProtectedItems> protectedItemsJoin = root.join("protectedItems", JoinType.INNER);
            Join<ProtectedItems, ContentItems> contentItemsJoin = protectedItemsJoin.join("contentItems", JoinType.INNER);
            query.select(root).where(criteriaBuilder.equal(contentItemsJoin.get("streamId"), streamId));
    
            entityManager.createQuery(query).getResultList();
    

    UPDATE
    Specification with multiple filter parameters
    Specification oriented only for one return entity so you do not have ability to return multiple entities via specification.

    public class PackagesSpecification implements Specification<Packages> {
        private String streamId;
        private Long packageId;
    
        public PackagesSpecification(Long packageId, String streamId) {
            this.streamId = streamId;
            this.packageId = packageId;
        }
    
        @Override
        public Predicate toPredicate(Root<Packages> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            Join<Packages, ProtectedItems> protectedItemsJoin = root.join("protectedItems", JoinType.INNER);
            Join<ProtectedItems, ContentItems> contentItemsJoin = protectedItemsJoin.join("contentItems", JoinType.INNER);
    
            List<Predicate> predicates = new ArrayList<>();
    
            if (streamId != null) {
                predicates.add(criteriaBuilder.equal(contentItemsJoin.get("streamId"), streamId));
            }
    
            if (packageId != null) {
                predicates.add(criteriaBuilder.equal(root.get("id"), packageId));
            }
    
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        }
    

    Criteria with multiple filter parameters and multiple return entities

        public List<Tuple> find(Long packageId, String streamId) {
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
    
            Root<Packages> root = query.from(Packages.class);
            Join<Packages, ProtectedItems> protectedItemsJoin = root.join("protectedItems", JoinType.INNER);
            Join<ProtectedItems, ContentItems> contentItemsJoin = protectedItemsJoin.join("contentItems", JoinType.INNER);
    
            List<Predicate> predicates = new ArrayList<>();
    
            if (streamId != null) {
                predicates.add(criteriaBuilder.equal(contentItemsJoin.get("streamId"), streamId));
            }
    
            if (packageId != null) {
                predicates.add(criteriaBuilder.equal(root.get("id"), packageId));
            }
    
            query.multiselect(root, contentItemsJoin).where(criteriaBuilder.and(predicates.toArray(new Predicate[0])));
    
            return entityManager.createQuery(query).getResultList();
        }
    

    Generated query:

        select
            packages0_.id as id1_29_0_,
            contentite4_.id as id1_12_1_,
            packages0_.package_name as package_2_29_0_,
            contentite4_.stream_id as stream_i2_12_1_ 
        from
            packages packages0_ 
        inner join
            packages_protected_items protectedi1_ 
                on packages0_.id=protectedi1_.packages_id 
        inner join
            protected_items protectedi2_ 
                on protectedi1_.protected_items_id=protectedi2_.id 
        inner join
            protected_items_content_items contentite3_ 
                on protectedi2_.id=contentite3_.protected_items_id 
        inner join
            content_items contentite4_ 
                on contentite3_.content_items_id=contentite4_.id 
        where
            contentite4_.stream_id=? 
            and packages0_.id=?