
2.3 通过JPA实现各种关联关系
在实际项目里,我们会关联查询多张数据表,从中获得必要的业务数据,对应地,我们也可以通过JPA把基于多表的各种关联关系映射到Model类里。
具体而言,表之间的关联关系可以是一对一、一对多或多对多,通过JPA,我们能用比较简单的方式来实现这些关联关系。
2.3.1 一对一关联

在这个业务场景里,我们让一个学生(Student)只能拥有一张银行卡(Card),具体而言,学生和银行卡之间是一对一关联。
步骤01 创建学生和银行卡这两张数据表。学生表的结构如表2.4所示,其中用cardID来表示该学生所拥有的银行卡号。
表2.4 一对一关联里的Student表结构

描述银行卡的Card表结构如表2.5所示。
表2.5 一对一关联里的Card表结构

步骤02 在pom.xml里描述本项目的依赖包。在这个项目里,我们将和之前的项目一样,依赖JPA、Spring Boot以及MySQL的jar包,所以就不再给出详细的代码了。
步骤03 在application.yml里配置jpa以及mysql数据库连接的信息,代码如下:

这里同样要注意缩进,而且这里代码的具体含义在之前的项目介绍里都解释过,所以就不再额外解释了。
步骤04 编写用来映射数据表的学生和银行卡的Model类,其中Student.java的代码如下:

在上述代码的第14~16行中,通过@OneToOne的注解指定了Student和Card的一对一关联,其中通过第15行的@JoinColumn来表示是通过cardID来关联到Card表的。
Card.java代码如下,这个类比较简单,通过第2行和第3行的@Entity和Table注解来指定待关联的数据表名,通过第5行的@Id来指定主键,通过第7行的@Column来指定对应的列名。

步骤05 编写控制器类StudentController.java,具体代码如下:

在上述代码的第7行和第8行里,我们能看到,/one2oneDemo格式的请求将触发one2oneDemo方法,在这个方法里,将调用service层的对应方法。
步骤06 编写实现Service层功能的StudentService.java,代码如下:

在上述代码里,我们能看到学生和银行卡之间的关联关系。具体而言,当我们在第19行save学生信息后,能在第21行通过name找到该学生所对应的卡,在第22行和第23行里,能打印出对应的卡信息。
由于之前设置的学生和银行卡之间的级联关系(CascadeType)是ALL,其中也包含“删除”,因此在第25行里,当我们通过delete语句删除学生信息后,就能发现card表里和该学生对应的银行卡记录也会被删除。
步骤07 实现StudentRepository接口,在其中实现针对数据库的操作,具体代码如下:

我们在第4行和第5行的代码里,实现了根据name查找Student对象的功能,至于在Service层里调用的save和delete方法,则是封装在JpaRepository类里的,我们无须编写。
最后,我们还得在App.java里实现SpringBoot的启动代码,这块我们之前已经提到过,所以就不再解释了。

至此,当我们通过App.java启动Spring Boot时,就能通过在浏览器里输入如下url来查看效果了。
1 http://localhost:8080/students/one2oneDemo
根据Controller层的定义,该url请求会触发Service层里的one2oneDemo方法,大家如果查看数据库,就能看到“插入学生后对应的银行卡信息也能自动插入”以及“删除学生后对应的卡也会自动删除”的级联操作效果。
2.3.2 一对多关联

这里,我们将实现一个用户(User)拥有多辆汽车(Car)的业务场景。其中,用户表的结构如表2.6所示,描述汽车的Car表结构如表2.7所示。
表2.6 一对多关联里的User表结构

表2.7 一对多关联里的Car表结构

在创建完Maven类型的SpringBootJPAOne2ManyDemo项目后,在其中的pom.xml里,我们将和之前的项目一样,同样引入JPA、Spring Boot以及MySQL的jar包。
由于这里连接的数据库和之前“2.3.1”小节中的一致,因此application.yml用的是和之前一样的代码。
在User.java和Car.java这两个Model类里,我们将定义一对多关联关系,其中User.java的代码如下:

在第13行里,我们通过Set类来存放一个用户拥有的多辆汽车。在第12行里,我们通过@OneToMany注解定义了“一个用户拥有多辆车”的关系。这里cascade的级联关系是ALL,也就是说,一旦从数据表里删除这个用户,那么对应的汽车也会从数据表里被删除;mappedBy的取值是user,也就是说,在Car类里使用过这个属性来指定车的主人。
描述汽车类的Car.java的代码如下:

在这里的第11~13行里,通过@ManyToOne的注解来定义汽车和用户的关联关系,其中用第12行的@JoinColumn来指定Car类是通过userID这个属性和User类关联的,第13行定义的user类则指定了这个Car的主人。
在userController.java里,我们定义了这个Spring Boot项目的“控制器类”,具体代码如下:

在第7行里,我们通过@RequestMapping注解定义了触发该方法的url格式,在第8行的one2manyDemo方法里,调用了service层里的one2manyDemo方法。下面我们来看一下UserService.java这段代码。

在上述代码的第8~23行里,我们定义了一个用户和两辆车,并设置了“Peter”拥有两辆车的一对多关系。当我们在第25行通过save方法存入用户时,不仅能在User表里看到对应的用户信息,还能在Car表里看到关联的两辆车也被插入了。
如果我们打开第27行的注释,就会发现虽然我们只是通过delete方法删除了用户,但由于这里一对多的级联关系是ALL,因此这个用户所对应的两辆车也会被从Car数据表里删除。
在上述UserService.java里,我们事实上是调用了UserRepository这个和JPA有关类里的方法,在这个Repository接口里,我们只是继承了JpaRepository,在其中什么都没做,具体代码如下:

也就是说,在Service层里,我们使用了JpaRepository里自带的save和delete方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:

当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/users/one2manyDemo”后,就会触发UserService类里的one2manyDemo方法,从而看到本案例的演示效果。
2.3.3 多对多关联

这里,我们将实现多本图书(Book)和多名作者(Author)之间的多对多关系,具体而言,一本书可以有多名作者,同一作者可以写多本书。
在表2.8中,我们定义了描述图书的Book表。
表2.8 多对多关联里的Book表结构

描述作者的Author表结构如表2.9所示。
表2.9 多对多关联里的Author表结构

同时,我们还需要创建book_author表来描述书和作者的多对多关联,结构如表2.10所示。
表2.10 多对多关联里的book_author表结构

在创建完Maven类型的SpringBootJPAMany2ManyDemo项目后,在其中的pom.xml里,我们将和之前的项目一样,同样引入JPA、Spring Boot以及MySQL的jar包。
在Book.java和Author.java这两个Model类里,我们将定义多对多关联关系。其中,Book.java的代码如下:

在第10行中,我们定义了图书和作者的多对多关联;在第11~13行中,定义了book_author表里分别用bookID和authorID来描述双方的多对多关系;在第14行中,通过Set来描述这本图书里的多名作者信息。
描述作者类的Author.java的代码如下,其中通过第10行的@ManyToMany注解来定义作者和图书的多对多关联,通过第11行定义Set类型的books属性来存放作者所写的多本书。

在Controller.java里,我们定义“控制器类”,具体代码如下:

其中,在第7行中,我们通过@RequestMapping注解定义了触发该方法的url格式;在第8行的many2manyDemo方法中,调用了service层里的对应方法。下面我们来看一下bookService.java代码。

在上述代码的第10~36行里,我们完成了如下动作。
第一,定义了3名作者信息。
第二,创建了java和DB两本书的信息。
第三,定义了两个Set,在其中存放了两本书的作者信息。
第四,给两本书设置了对应Set,以此指定两本书的作者。
在第38~39行中,我们通过save方法保存了两本书,此时我们能看到如下效果。
第一,在Book表里能看到Java和DB图书的信息。
第二,在Author表里,能看到3名作者的信息。
第三,在book_author表里,能看到图书和作者的对应关系。
在上述的Service类里,我们事实上是调用了BookRepository和AuthorRepository这两个和JPA有关的类中的方法。同样地,在这两个类里我们只是继承了JpaRepository这个接口,在其中什么都没做。BookRepository类的具体代码如下:

AuthorRepository类的代码如下:

也就是说,在Service层里,我们也是使用了JpaRepository里自带的save方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:

当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/books/many2manyDemo”后,就会触发BookService类里的many2manyDemo方法,从而看到本案例的演示效果。