本课展示了如何创建一对多的关系。
在这一课中,我们将创建一个双向的一对多的关系,一个Player可以有很多Registration。
让我们给这个模型添加一些现实生活中的约束。
Registration对象必须与一个Player对象相关联。Registration对象时,相关的Player对象不应该被删除。球员和注册之间的双向关联意味着,如果我们有一个Player对象,我们就可以得到所有的Registration对象,反之亦然,我们可以通过使用注册得到一个球员。在上一节课讨论的单向一对多关系中,我们可以在给定的Tournament中找到Registration对象,但我们无法从Registration对象中找到Tournament。

对/tournaments和/registrations的GET请求
一对多关系的反向是多对一,即许多的注册信息映射到一个玩家。

对于双向关系的例子,在onetomany包内创建一个名为bi的新包。将onetomany.uni包中的Registration和Tournament类以及onetoone包中的Player和PlayerProfile类以及相关的数据接入层、服务层和控制器类复制到onetomany.bi包。
我们将从更新Registration类开始。为了有一个双向的关系,我们将在上一课定义的注册类中添加一个Player字段。为新字段生成getter和setter方法,并更新构造函数和toString方法。
@Entity
public class Registration {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
private int id;
private Date registrationDate;
private Player player;
//getters and setters
//constructor
//toString method
}
在Registration类和Player类之间存在着多对一的关系,许多注册信息可以映射到一个球员。多对一双向关系的多方总是关系的所有方。
为了模拟这种关系,我们将使用@ManyToOne注解,并在@JoinColumn中指定与注册表的外键列相对应的列。球员表有一列id,它将成为注册表中的外键列player_id。这就是注册表如何知道如何找到它的球员。
@ManyToOne
@JoinColumn(name="player_id", referencedColumnName = "id")
private Player player;
@JoinColumn注解也显示了这是关系的所有方。

接下来,我们将为这种关系选择级联类型。如果一个Registration对象被删除,相关的Player不应该被删除。这意味着,删除操作不应该被级联。由于我们对级联类型有细微的控制,我们将列出所有的级联类型,除了REMOVE。
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH}
@JoinColumn(name="player_id", referencedColumnName = "id")
private Player player;
现在我们将更新Player类,以显示锦标赛的注册情况。由于一个球员可以有多个注册信息,我们将添加一个注册列表作为该类的一个新字段。
private List<Registration> registrations = new ArrayList<>();
//generate getter and setter methods
Player类与Registration类有一对多的关系,因为一个球员可以注册很多场比赛。这可以通过@OneToMany注解来模拟。
由于多方(Registration)是双向关系的拥有方,我们将在这里使用mappedBy属性(在Player类中)来指定这是关系的逆向方。
@OneToMany(mappedBy="player", cascade= CascadeType.ALL)
private List<Registration> registrations = new ArrayList<>();
我们在这里使用级联类型的ALL,因为我们希望在删除球员记录时删除球员的注册信息。
mappedBy属性中的球员引用了注册类中的Player字段。Hibernate通过查看Player字段上的@JoinColumn注解来找到外键列。

接下来,我们将为Player类添加一个方法,设置双向关系。在这个方法中,我们将向Player添加一个Registration对象,同时更新Registration以反映它属于这个Player。
//与注册类建立双向关系
public void registerPlayer(Registration reg) {
//将注册添加到列表中
registrations.add(reg);
//在注册中设置Player字段
reg.setPlayer(this);
}
在PlayerController类中,我们将添加一个新的PUT映射,将一个带有registration_id的注册和一个以id为键的Player联系起来,如下所示。
@PutMapping("/{id}/registrations/{registration_id}")
public Player assignRegistration(@PathVariable int id, @PathVariable int registration_id) {
Registration registration = registrationService.getRegistration(registration_id);
return service.assignRegistration(id, registration);
}
控制器类的方法调用服务类的方法assignRegistration,输入Player的id和Registration对象。PlayerService类中的方法显示。
public Player assignRegistration(int id, Registration registration) {
Player player = repo.findById(id).get();
player.registerPlayer(registration);
return repo.save(player);
}
在设置了Player和Registration实体之间的双向关系后,我们数据库的更新ERD如下所示。同样的情况可以从H2数据库的web控制台中得到验证(在http://localhost:8080/h2-console,连接URL为jdbc:h2:mem:testdb)。
