多个到多个的JPA Criteria API规范

时间:2018-03-27 14:23:43

标签: java spring spring-boot spring-data-jpa jpa-2.0

我有三个课程,如下所述。我正在尝试创建一个规范来过滤链接表中匹配的数据。

public class Album {
    private Long id;
    private List<AlbumTag> albumTags;
}

public class Tag {
    private Long id;
    private String category;
}

public class AlbumTag{
    private Long id;
    private Album album;
    private Tag tag;
}

在上面给出的模式中,我想要找到的是Album表中所有相册的列表以及AlbumTag中的链接。我想要实现的SQL不必相同,低于

select *
from Album A 
where (A.Id in (select [AT].AlbumId 
from AlbumTag [AT]))

到目前为止,我所尝试的当时无效,

public class AlbumWithTagSpecification implements Specification<Album> {

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

         final Subquery<Long> personQuery = cq.subquery(Long.class); 
         final Root<Album> album = personQuery.from(Album.class); 
         final Join<Album, AlbumTag> albumTags = album.join("albumTags");
         personQuery.select((albumTags.get("album")).get("id"));
         personQuery.where(cb.equal(album.get("id"), (albumTags.get("album")).get("id"))); 
         return cb.in(root.get("id")).value(personQuery);

    }
}

6 个答案:

答案 0 :(得分:2)

使用spring boot和spring数据JPA,您可以更喜欢实体关系来获取数据。

1.使用下面给出的实体关系来注释域类:

@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.使用弹簧数据使用以下内容获取详细信息:

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.

我希望这会帮助你解决它。

答案 1 :(得分:1)

JPA中的子查询只能与CriteriaBuilder.exists()一起使用,所以我会尝试:

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);

}

相当于

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

答案 2 :(得分:1)

creteria query for join tables

CriteriaQuery<Album> query = cb.createQuery(Album.class);
Root<Album> album = query.from(Teacher.class);
Join<Album, AlbumTag> tag = teacher.join("id");
query.select(tag).where(cb.equal(album.get("album")));

List<Album> results = em.createQuery(query).getResultList();
for (Album al : results) {
    System.out.println("album-->+al.get(name));
}

答案 3 :(得分:1)

嗯,在这种情况下我不会进行in操作 - 它只会使查询和规范复杂化。您描述的问题实际上是将Table A中的记录与来自Table B的相关记录相关联,因此您的案例中的查询将如下:

SELECT a from Album a join AlbumTag at on a.id = at.albumId - 根据需要,它会返回所有包含相册标签的相册。 Inner join explained

所以在你的情况下,我会创建这个&#34;工厂&#34;将为您创建此规范的方法。

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();
    }
};

}

另外,我建议您查看来自hibernate的static metamodel库 - link to introduction。它为您生成实体类中的静态模型,帮助您避免使用硬编码字符串创建查询/规范。

答案 4 :(得分:1)

这看起来像一个经典的很多例子。您已将这三个类直接映射到数据库中预期的表。 JPA是一个对象关系映射(ORM)库,这意味着我们可以用更多的OO样式构造类并映射到底层的关系数据库。

AlbumTag类可以省略,@ManyToMany关系添加到AlbumTag

public class Album {
    private Long id;

    @ManyToMany
    @JoinTable(name="AlbumTag",
        joinColumns=
            @JoinColumn(name="album", referencedColumnName="id"),
        inverseJoinColumns=
            @JoinColumn(name="tag", referencedColumnName="id"))
    private List<Tag> tags;
}

public class Tag {
    private Long id;
    private String category;

    @ManyToMany(mappedBy="tags")
    private List<Album> albums;
}

要按Tag查找相册,您首先应使用TagfindById(1l);之类的内容从存储库中检索findByCategory("Rock");,然后只需在getAlbums()上调用Tag $(window).on('load', function (e) { line对象。

注意:这里的一个细微差别是AlbumTag表只有两列(专辑和标签)。 AlbumTag上的额外id列是不必要的,因为专辑和标签的组合将是唯一的ID,无论如何你永远不需要在这个表中找到id。

答案 5 :(得分:1)

由于您使用的是spring-data-jpa,因此您应该充分利用它提供的功能。

我的第一个问题与您的实体类有关。我不明白为什么在专辑类中存储专辑标签列表是必要的。由于您有一个连接表,因此该信息是可还原的。

其次,您应该注释您的实体分词:

@Entity
public class Album {
@Id
@Column
private Long id;
}

@Entity
public class Tag {
  @Id
  @Column
  private Long id;
  @Column
  private String category;
}

@Entity
@Table
public class AlbumTag{
  @Id
  @Column
  private Long id;
  @ManyToOne
  @JoinColumn
  private Album album;
  @ManyToOne
  @JoinColumn
  private Tag tag;
}

接下来,您应该为实体类创建存储库。

interface AlbumRepository extends JpaRepository<Album, Long>{

   @Query
   ("select DISTINCT(a) from AlbumTag at "+
    "join at.album a "
    "where at.tag is not null")
   List<Album> findAlbumWithTag();
}

然后只需调用存储库函数,该函数将返回至少包含一个标记的相册列表。