本课展示了如何创建一对多的关系。
在这一课中,我们将创建一个双向的一对多的关系,一个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
)。