JPA Criteria API Specification for Many to Many JPA Criteria API Specification for Many to Many spring spring

JPA Criteria API Specification for Many to Many


Using spring boot and spring data JPA, you can prefer entity relationship to fetch the data.

1.Annotate the domain class with the entity relationship which given below:

@Entity@Table(name="Album")public class Album {    @Id    @Column(name="id")    private Long id;    @OneToMany(targetEntity = AlbumTag.class, mappedBy = "album")    private List<AlbumTag> albumTags;    //getter and setter}@Entity@Table(name="Tag")public class Tag {    @Id    @Column(name="id")    private Long id;    @Column(name="category")    private String category;    //getter and setter}@Entity@Table(name="AlbumTag")public class AlbumTag{    @Id    @Column(name="id")    private Long id;    @ManyToOne(optional = false, targetEntity = Album.class)    @JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)    private Album album;    @ManyToOne(optional = false, targetEntity = Tag.class)    @JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)    private Tag tag;    //getter and setter}

2.use the spring data to fetch the details using the below:

Album album = ablumRepository.findOne(1); // get the complete details about individual album.List<AlbumTag> albumTags = ablum.getAlbumTags(); // get the all related albumTags details for particular album.

I hope this will help you to solve it.


Subqueries in JPA only really work with CriteriaBuilder.exists() so i would try:

public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {     final Subquery<Long> subQuery = cq.subquery(Long.class);      final Root<AlbumTag> albumTag = subQuery.from(AlbumTag.class);      // it doesn't really matter what we select     subQuery.select(cb.literal(1));     subQuery.where(cb.equal(root.get("id"), (albumTag.get("album")).get("id")));      return cb.exists(subQuery);}

which is equivalent to

select *from Album A where exists(    select 1 from AlbumTag AT     where AT.AlbumId = A.Id)


Well, I wouldn't go for in operation in this case - it just complicates the query and the specification. The problem you described is actually matter of joining records from Table A with related records from Table B so the query in your case would be like:

SELECT a from Album a join AlbumTag at on a.id = at.albumId - as you needed it will return all albums that have album tags. Inner join explained

So in your case I would create this "factory" method that would create for you this specification.

public static Specification<Album> withTags() {    return new Specification<Album>() {        @Override        public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> query, CriteriaBuilder cb) {            return root.join("albumTags").getOn();    }};

}

Also I would suggest you to have a look at static metamodel library from hibernate - link to introduction. It generates for you static model from your entity classes that helps you avoid creating queries/specifications using hardcoded strings.