JPA 标准 API 多对多规范

JPA Criteria API Specification for Many to Many

我得到了下面提到的三个 类。我正在尝试创建一个规范来过滤 linked table.

中匹配的数据
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;
}

在上面给出的模式中,我试图找到的是专辑 table 中所有专辑的列表,专辑标签中带有 link。我要实现的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);

    }
}

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

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

SELECT a from Album a join AlbumTag at on a.id = at.albumId - 根据您的需要,它将 return 所有具有专辑标签的专辑。 Inner join explained

所以在你的情况下,我会创建这个 "factory" 方法来为你创建这个规范。

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

}

此外,我建议您查看来自休眠的 static metamodel 库 - link to introduction。它从您的实体 类 为您生成静态模型,帮助您避免使用硬编码字符串创建 queries/specifications。

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

1.Annotate 域 class 实体关系如下:

@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 spring 数据使用以下获取详细信息:

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.

希望本文能帮助您解决问题。

这看起来像一个 classic 多对多示例。您拥有的三个 classes 直接映射到您在数据库中期望的 tables。 JPA 是一个对象关系映射 (ORM) 库,这意味着我们可以以更面向对象的风格构造 classes 并映射到底层关系数据库。

AlbumTag class 可以省略, @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 的相册,您首先需要使用 findById(1l);findByCategory("Rock"); 从存储库中检索 Tag,然后只需调用 getAlbums()Tag 对象上。

注意:这里的一个细微差别是 AlbumTag table 只有两列(专辑和标签)。 AlbumTag 上的额外 id 列是不必要的,因为专辑和标签的组合将是一个唯一的 id,并且您永远不需要在此 table 中通过 id 查找。

既然您正在使用 spring-data-jpa,您应该真正利用它提供的功能。

我的第一个问题与您的实体 classes 有关。我不明白为什么需要在专辑 class 中存储专辑标签列表。由于您有一个连接 table,此信息是多余的。

其次,你应该注释你的实体类:

@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;
}

接下来您应该为您的实体创建存储库 classes.

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

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