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
关系添加到 Album
和 Tag
。
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 包含至少一个标签的相册列表。
我得到了下面提到的三个 类。我正在尝试创建一个规范来过滤 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
关系添加到 Album
和 Tag
。
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 包含至少一个标签的相册列表。