![Spring Cloud实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/796/26846796/b_26846796.jpg)
2.3 通过JPA实现各种关联关系
在实际项目里,我们会关联查询多张数据表,从中获得必要的业务数据,对应地,我们也可以通过JPA把基于多表的各种关联关系映射到Model类里。
具体而言,表之间的关联关系可以是一对一、一对多或多对多,通过JPA,我们能用比较简单的方式来实现这些关联关系。
2.3.1 一对一关联
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T35_32048.jpg?sign=1738858596-Qvy7kT1DOrskEhkRbtkZ4LDbZwadBQc4-0-30441a2de1a7859d7b8d375178f2ebb8)
在这个业务场景里,我们让一个学生(Student)只能拥有一张银行卡(Card),具体而言,学生和银行卡之间是一对一关联。
步骤01 创建学生和银行卡这两张数据表。学生表的结构如表2.4所示,其中用cardID来表示该学生所拥有的银行卡号。
表2.4 一对一关联里的Student表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32505.jpg?sign=1738858596-ZAmQskMXEwIfH0ApZNBWkgM3n85zQbZr-0-75e37be7db64e2169bb3b8f825f1c804)
描述银行卡的Card表结构如表2.5所示。
表2.5 一对一关联里的Card表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32050.jpg?sign=1738858596-kcRWcXn85Vs1v1hxbKHc3jn7vKrbc3IZ-0-b0b4c15c78b9d212fa3d6478900d396a)
步骤02 在pom.xml里描述本项目的依赖包。在这个项目里,我们将和之前的项目一样,依赖JPA、Spring Boot以及MySQL的jar包,所以就不再给出详细的代码了。
步骤03 在application.yml里配置jpa以及mysql数据库连接的信息,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P36_32051.jpg?sign=1738858596-FF5SNJlqwnR2Yvl2gjJ2YFKiav3Cdw5N-0-dd17e3a45bdc7933342f99f2c779f0c1)
这里同样要注意缩进,而且这里代码的具体含义在之前的项目介绍里都解释过,所以就不再额外解释了。
步骤04 编写用来映射数据表的学生和银行卡的Model类,其中Student.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P36_32052.jpg?sign=1738858596-eHyTHESTXIVx1N6168F8lIv1Al67Vihf-0-7d812fa353df2ea674e5ad54b2e76e6b)
在上述代码的第14~16行中,通过@OneToOne的注解指定了Student和Card的一对一关联,其中通过第15行的@JoinColumn来表示是通过cardID来关联到Card表的。
Card.java代码如下,这个类比较简单,通过第2行和第3行的@Entity和Table注解来指定待关联的数据表名,通过第5行的@Id来指定主键,通过第7行的@Column来指定对应的列名。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32054.jpg?sign=1738858596-IX8SMz6G0IUhQpS34AXwfprdxmLnw9TF-0-da536e561f75ac213601b6c48fdce5a0)
步骤05 编写控制器类StudentController.java,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32055.jpg?sign=1738858596-jwEd0ZTgucT7PxGZPT8yVa2MPj1Fmua2-0-200f71369d4730d2d89c2c19a94ab2a4)
在上述代码的第7行和第8行里,我们能看到,/one2oneDemo格式的请求将触发one2oneDemo方法,在这个方法里,将调用service层的对应方法。
步骤06 编写实现Service层功能的StudentService.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32056.jpg?sign=1738858596-wYTtbnPLk9kdMLt1hWGD8BTbyFE2oVub-0-855836e819e84f4b658f240ef3a5305d)
在上述代码里,我们能看到学生和银行卡之间的关联关系。具体而言,当我们在第19行save学生信息后,能在第21行通过name找到该学生所对应的卡,在第22行和第23行里,能打印出对应的卡信息。
由于之前设置的学生和银行卡之间的级联关系(CascadeType)是ALL,其中也包含“删除”,因此在第25行里,当我们通过delete语句删除学生信息后,就能发现card表里和该学生对应的银行卡记录也会被删除。
步骤07 实现StudentRepository接口,在其中实现针对数据库的操作,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P38_32513.jpg?sign=1738858596-77Z7h6II2EFEcYkQR2JB02gAgtx3ENqT-0-d3a9fb86642e92e23d6aa437312adb65)
我们在第4行和第5行的代码里,实现了根据name查找Student对象的功能,至于在Service层里调用的save和delete方法,则是封装在JpaRepository类里的,我们无须编写。
最后,我们还得在App.java里实现SpringBoot的启动代码,这块我们之前已经提到过,所以就不再解释了。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P38_32514.jpg?sign=1738858596-sUvmFdvI9ZwIEReceCjkjB3sXKBkEMyT-0-9020f275e76016378dd5cb24dcc39c29)
至此,当我们通过App.java启动Spring Boot时,就能通过在浏览器里输入如下url来查看效果了。
1 http://localhost:8080/students/one2oneDemo
根据Controller层的定义,该url请求会触发Service层里的one2oneDemo方法,大家如果查看数据库,就能看到“插入学生后对应的银行卡信息也能自动插入”以及“删除学生后对应的卡也会自动删除”的级联操作效果。
2.3.2 一对多关联
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32060.jpg?sign=1738858596-ziuS6rxorTz4mkhh4KmR47dhxBn7mQjK-0-6bba10765f0bd753d791f0f78c70cb10)
这里,我们将实现一个用户(User)拥有多辆汽车(Car)的业务场景。其中,用户表的结构如表2.6所示,描述汽车的Car表结构如表2.7所示。
表2.6 一对多关联里的User表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32061.jpg?sign=1738858596-CzSGmYzTY6lkwLWOefZCEtFG2R5DTfXk-0-44f249d9acd539a39972d07265df6833)
表2.7 一对多关联里的Car表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32062.jpg?sign=1738858596-KMuPJL3cY6ewB69IwGfR2ZvkrdlmTaHK-0-1b5d80fac0ee42c28d47b19f56b590af)
在创建完Maven类型的SpringBootJPAOne2ManyDemo项目后,在其中的pom.xml里,我们将和之前的项目一样,同样引入JPA、Spring Boot以及MySQL的jar包。
由于这里连接的数据库和之前“2.3.1”小节中的一致,因此application.yml用的是和之前一样的代码。
在User.java和Car.java这两个Model类里,我们将定义一对多关联关系,其中User.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P39_32063.jpg?sign=1738858596-JQJfCSFhps8WJ4Nz91tkA1kqr635re7B-0-33cb4fe13e3a752a585569dbeb6691a2)
在第13行里,我们通过Set类来存放一个用户拥有的多辆汽车。在第12行里,我们通过@OneToMany注解定义了“一个用户拥有多辆车”的关系。这里cascade的级联关系是ALL,也就是说,一旦从数据表里删除这个用户,那么对应的汽车也会从数据表里被删除;mappedBy的取值是user,也就是说,在Car类里使用过这个属性来指定车的主人。
描述汽车类的Car.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P40_32517.jpg?sign=1738858596-FdZdYyNbl2LjdIWFbZgi4hNXiQocdWkm-0-b692d80761f6757dfed8ed93e39f6b69)
在这里的第11~13行里,通过@ManyToOne的注解来定义汽车和用户的关联关系,其中用第12行的@JoinColumn来指定Car类是通过userID这个属性和User类关联的,第13行定义的user类则指定了这个Car的主人。
在userController.java里,我们定义了这个Spring Boot项目的“控制器类”,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P40_32518.jpg?sign=1738858596-RJjDu5UdjeQVl6s28AC3MB4gAuCE5e7t-0-1286078b18241799e019c2b7f55c1f50)
在第7行里,我们通过@RequestMapping注解定义了触发该方法的url格式,在第8行的one2manyDemo方法里,调用了service层里的one2manyDemo方法。下面我们来看一下UserService.java这段代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P40_32519.jpg?sign=1738858596-l3ALU81rkoHmtGoWQRZy7YA1vHXMkr3e-0-ba36aaf26563fcb77345a96e75f07dd4)
在上述代码的第8~23行里,我们定义了一个用户和两辆车,并设置了“Peter”拥有两辆车的一对多关系。当我们在第25行通过save方法存入用户时,不仅能在User表里看到对应的用户信息,还能在Car表里看到关联的两辆车也被插入了。
如果我们打开第27行的注释,就会发现虽然我们只是通过delete方法删除了用户,但由于这里一对多的级联关系是ALL,因此这个用户所对应的两辆车也会被从Car数据表里删除。
在上述UserService.java里,我们事实上是调用了UserRepository这个和JPA有关类里的方法,在这个Repository接口里,我们只是继承了JpaRepository,在其中什么都没做,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P41_32867.jpg?sign=1738858596-MFEjdgDcpICnlZQfQA0YhP2qzcMZJGK2-0-19a53ff42afecaccbca3ecd6e3efea70)
也就是说,在Service层里,我们使用了JpaRepository里自带的save和delete方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P41_32069.jpg?sign=1738858596-EGHOSxefp0HuanpazKGA6xWHx2LT39Lr-0-568da5eee7ea45e676056d45da954e8d)
当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/users/one2manyDemo”后,就会触发UserService类里的one2manyDemo方法,从而看到本案例的演示效果。
2.3.3 多对多关联
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T41_32070.jpg?sign=1738858596-WXZdJqnTycXZAKOX3RMdb81T00WScBGl-0-063bcccc342f9e99ffba9e22ef413d0f)
这里,我们将实现多本图书(Book)和多名作者(Author)之间的多对多关系,具体而言,一本书可以有多名作者,同一作者可以写多本书。
在表2.8中,我们定义了描述图书的Book表。
表2.8 多对多关联里的Book表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32071.jpg?sign=1738858596-UJQit2OEclbanflt2DNUHRTMruiEgw5x-0-a0d2042696c0d01ec5bf3352ef4b14eb)
描述作者的Author表结构如表2.9所示。
表2.9 多对多关联里的Author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32072.jpg?sign=1738858596-lvimOfnk7p7GzRqaMEGCO3VqnusMXpHu-0-97ab124c46b4427a52f713002b0c640d)
同时,我们还需要创建book_author表来描述书和作者的多对多关联,结构如表2.10所示。
表2.10 多对多关联里的book_author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32073.jpg?sign=1738858596-rcKmGJxvnuGhOC15eXQregKCHE04LxjW-0-218df9971f7507f49de2b85450966d1f)
在创建完Maven类型的SpringBootJPAMany2ManyDemo项目后,在其中的pom.xml里,我们将和之前的项目一样,同样引入JPA、Spring Boot以及MySQL的jar包。
在Book.java和Author.java这两个Model类里,我们将定义多对多关联关系。其中,Book.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P42_32074.jpg?sign=1738858596-ODMlGyaRnX4Dh3nY0cjV7zajzbIKPZ7S-0-ef9a16993e2c6952037b14611d12cae1)
在第10行中,我们定义了图书和作者的多对多关联;在第11~13行中,定义了book_author表里分别用bookID和authorID来描述双方的多对多关系;在第14行中,通过Set来描述这本图书里的多名作者信息。
描述作者类的Author.java的代码如下,其中通过第10行的@ManyToMany注解来定义作者和图书的多对多关联,通过第11行定义Set类型的books属性来存放作者所写的多本书。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32075.jpg?sign=1738858596-3qFzkuVNjg7wH0CvaQJr2OXxcUuxqNk2-0-a0f4284d79b5654b3d813d6422a8c00b)
在Controller.java里,我们定义“控制器类”,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32076.jpg?sign=1738858596-4w4OUkooXsH8M7ji7gu3yJsbfIrtir9H-0-3ecef92bb52b4267f4d96eb7e38fc03a)
其中,在第7行中,我们通过@RequestMapping注解定义了触发该方法的url格式;在第8行的many2manyDemo方法中,调用了service层里的对应方法。下面我们来看一下bookService.java代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32077.jpg?sign=1738858596-AadBhlH3u72MkWkADUE6rys9MILNoYqL-0-a94d1bdb544cbca6df5dee4ae467ca49)
在上述代码的第10~36行里,我们完成了如下动作。
第一,定义了3名作者信息。
第二,创建了java和DB两本书的信息。
第三,定义了两个Set,在其中存放了两本书的作者信息。
第四,给两本书设置了对应Set,以此指定两本书的作者。
在第38~39行中,我们通过save方法保存了两本书,此时我们能看到如下效果。
第一,在Book表里能看到Java和DB图书的信息。
第二,在Author表里,能看到3名作者的信息。
第三,在book_author表里,能看到图书和作者的对应关系。
在上述的Service类里,我们事实上是调用了BookRepository和AuthorRepository这两个和JPA有关的类中的方法。同样地,在这两个类里我们只是继承了JpaRepository这个接口,在其中什么都没做。BookRepository类的具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32868.jpg?sign=1738858596-ob1BmSB8gmUx5VjMrQGlhUumyvHU1mA3-0-f6f10fbe7a6018df9e8c43d33496874e)
AuthorRepository类的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32869.jpg?sign=1738858596-Q4aosUqczgYAAPfbq4PAjB1FXjhg0Mlp-0-ed699548dc51259df4d520dd1be078d7)
也就是说,在Service层里,我们也是使用了JpaRepository里自带的save方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32531.jpg?sign=1738858596-HzS8pp2nJuDwg6Mp0QeNB1WN0qtefMyl-0-4ab401c26716daed46b92300ca6466f8)
当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/books/many2manyDemo”后,就会触发BookService类里的many2manyDemo方法,从而看到本案例的演示效果。