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.