学习如何将上一课中创建的单向多对多关系改为双向关系。
在一个双向的关系中,每一方都有对另一方的引用。在我们上一课的例子中,Category
类没有对Tournament
类的任何引用。现在我们将添加一个对锦标赛类的引用,这样关系就可以从两边进行导航。这不会对基础数据库结构产生任何影响。连接表tournament_catogories
已经有了锦标赛和类别表的外键,可以编写SQL查询来获取与类别相关的锦标赛。
对于多对多的关系,我们可以选择任何一方作为所有者。该关系在所有者一方使用@JoinTable
注解进行配置。在目标方,我们使用mappedBy
属性来指定在拥有方映射关系的字段名。从数据库设计的角度来看,多对多的关系没有所有者。如果我们交换@JoinTable
和mappedBy
,对表的结构不会有任何影响。
Category
类中创建一个tournaments
的List
,以及getter
和setter
方法。package io.datajek.databaserelationships.manytomany;
@Entity
public class Category {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Column(unique = true)
private String name;
private List<Tournament> tournaments = new ArrayList<>();
//...
}
在上面创建的tournaments
字段上,使用@ManyToMany
注解与mappedBy
属性。这显示了用于映射锦标赛类中的关系的值。
@ManyToMany(mappedBy= "playingCategories")
private List<Tournament> tournaments = new ArrayList<>();
我们还将使用级联属性来级联除REMOVE
以外的所有操作,因为如果一个类别被删除,我们不希望删除所有相关的锦标赛。
@ManyToMany(mappedBy= "playingCategories",
cascade= {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH},
fetch=FetchType.LAZY)
private List<Tournament> tournaments = new ArrayList<>();
管理双向关系是应用程序的责任。当我们将一个类别添加到锦标赛中时,我们也必须将锦标赛添加到该类别中以保持双向关系。如果不这样做,可能会导致意外的JPA行为。
我们将更新Tournament
类中的addCategory
方法,通过将锦标赛添加到类别中来建立双向关系。
public void addCategory(Category category) {
playingCategories.add(category);
//建立双向关系
category.getTournaments().add(this);
}
同样地,我们将更新Tournament
类中的removeCategory
方法,以便从两边删除关联。
public void removeCategory(Category category) {
if (playingCategories != null)
playingCategories.remove(category);
//更新双向关系
category.getTournaments().remove(this);
}
@JsonIgnoreProperties
当试图对双向关系进行反序列化时,JSON会陷入无限递归。在 "一对一双向关系 "一课中,我们已经看到了解决这个问题的两种方法。在这里,我们将看到另一种避免无限递归的方法。我们可以用@JsonIgnoreProperties
来忽略我们想忽略的属性。这个注解可以在锦标赛和类别类的字段级使用。
@JsonIgnoreProperties("tournaments")
private List<Category> playingCategories = new ArrayList<>();
@JsonIgnoreProperties("playingCategories")
private List<Tournament> tournaments = new ArrayList<>();
<aside> 📢 在多对多的关系中,当涉及到表的结构时,没有所有者。这与一对多的关系不同,在一对多的关系中,多方总是包含一方的键的拥有方。
</aside>
此时所有代码:
为了测试这个应用程序,我们将重现上一课的情景,增加两个锦标赛和五个类别。
要创建锦标赛条目,请向/tournaments
发送POST请求,如下所示。
{
"name": "Canadian Open",
"location": "Toronto"
}
{
"name": "US Open",
"location": "New York City"
}