![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=1739409408-F7CXvhjXo6I2hVoq3jM0gcNo7GwhWtLp-0-0d2138d545fce2b5e26061f059fd2605)
在这个业务场景里,我们让一个学生(Student)只能拥有一张银行卡(Card),具体而言,学生和银行卡之间是一对一关联。
步骤01 创建学生和银行卡这两张数据表。学生表的结构如表2.4所示,其中用cardID来表示该学生所拥有的银行卡号。
表2.4 一对一关联里的Student表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32505.jpg?sign=1739409408-P1Lic8BPKtzP0B5CJ5EvdrdbNC0ZwAcF-0-929e50dfe32bc34ec0c2f22d0d72b144)
描述银行卡的Card表结构如表2.5所示。
表2.5 一对一关联里的Card表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32050.jpg?sign=1739409408-yQiGsmptc2FNE7wtCAL6q1UR2ut0mfBI-0-d1f2a4c8ae9b328a816381f23c945bde)
步骤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=1739409408-0iMFxfuLX8t2b2oNvBP1UrCNsRcGnnMJ-0-0d4a1114e4398db2e79d75164a48c900)
这里同样要注意缩进,而且这里代码的具体含义在之前的项目介绍里都解释过,所以就不再额外解释了。
步骤04 编写用来映射数据表的学生和银行卡的Model类,其中Student.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P36_32052.jpg?sign=1739409408-j1u25NPKi6RAHjF6c8eIt7iIFWk7F8ri-0-c3c132954ec81f1fa6da086114f7e79d)
在上述代码的第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=1739409408-UnoDZra9S3nRIf8Tj9FZrezx0X0rQNzq-0-01a64a231af7017e4660015dbe444b99)
步骤05 编写控制器类StudentController.java,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32055.jpg?sign=1739409408-NpqvdEgzOVYYbB7hqG31H0JMHa6pliW0-0-95eb42dd4ed21969e057d3e669b62927)
在上述代码的第7行和第8行里,我们能看到,/one2oneDemo格式的请求将触发one2oneDemo方法,在这个方法里,将调用service层的对应方法。
步骤06 编写实现Service层功能的StudentService.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32056.jpg?sign=1739409408-n1XPwnnvpIUqGiTofaC75o0mzj0iIKZB-0-bc2b08453e361c8ca00984281d78a950)
在上述代码里,我们能看到学生和银行卡之间的关联关系。具体而言,当我们在第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=1739409408-EBHl9e7IXIlnWMLxho8eDmIZb3VnpI7O-0-830f577636ba2e0493dd0bf09392a082)
我们在第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=1739409408-fsdurAoA5oB8JainukdxMStq2xP1T990-0-9d302a1c3e2abac0b30c9d55979a3d16)
至此,当我们通过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=1739409408-ZVl3WHfmS6aeprItxiB8aByXx1PgEuqL-0-151414c852da27ca52fe9e03a153cc7f)
这里,我们将实现一个用户(User)拥有多辆汽车(Car)的业务场景。其中,用户表的结构如表2.6所示,描述汽车的Car表结构如表2.7所示。
表2.6 一对多关联里的User表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32061.jpg?sign=1739409408-L38uiYZqvhr1U2qGbgI7FOcbYrrhFR1x-0-b3b23305488b4bb0683c9f1219aed634)
表2.7 一对多关联里的Car表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32062.jpg?sign=1739409408-RqxDJaWxphtS5AkRGpS3ThdIk5oaKAAY-0-86762bcf94563628d3af8736a634c222)
在创建完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=1739409408-OoNgHlEf6LfkjEelIJaOqJn065YMhpoj-0-bd777a0a4d26e2a225678f02756763c7)
在第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=1739409408-tlXqfFmVCmuI0fvkePqJ8K7HOIvQwCf9-0-849c634bfe64119b02fc6aec98dcb28e)
在这里的第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=1739409408-bD6cZNBnvgVnobhFaiBOb0zZ18Ns8WS3-0-2d69839aa623e60784d304d5aed36fb1)
在第7行里,我们通过@RequestMapping注解定义了触发该方法的url格式,在第8行的one2manyDemo方法里,调用了service层里的one2manyDemo方法。下面我们来看一下UserService.java这段代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P40_32519.jpg?sign=1739409408-3R7WoqKwj5Twylgfa7fbTl5SOGSLs48P-0-f413cfb0cb743935d1983eadfdbdcd4f)
在上述代码的第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=1739409408-Pi7VOAcnapRXSBwowjR4JIxYaLh2JaxC-0-d431a05e0e4895d4e950ec10613501a9)
也就是说,在Service层里,我们使用了JpaRepository里自带的save和delete方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P41_32069.jpg?sign=1739409408-Xw7PRQg045dL6t1x9KoO4tg1wt1L67Xn-0-efff66d701a3b3f8c8ff01bcf9994b7f)
当我们启动上述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=1739409408-gpEU1p0xUkXxOPNtcPVo610onI99HxS5-0-d1de920ae08d2764560d4bba57b5a0b6)
这里,我们将实现多本图书(Book)和多名作者(Author)之间的多对多关系,具体而言,一本书可以有多名作者,同一作者可以写多本书。
在表2.8中,我们定义了描述图书的Book表。
表2.8 多对多关联里的Book表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32071.jpg?sign=1739409408-Ixov580XWjMfKnlFZHbtozWYFXr00NOF-0-c81b590c7cfea97b85de2c140de075ff)
描述作者的Author表结构如表2.9所示。
表2.9 多对多关联里的Author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32072.jpg?sign=1739409408-k6JUYEVFgEKvHlZsmlLBELwCyq2ZkkqB-0-28166142a11c0a7955fa1d7cf06cd8d5)
同时,我们还需要创建book_author表来描述书和作者的多对多关联,结构如表2.10所示。
表2.10 多对多关联里的book_author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32073.jpg?sign=1739409408-5hx5vIQgggs0S1V7VCY0QOXFSQqRMenK-0-13f2e8314d5f73ca721e1bae852cdbe0)
在创建完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=1739409408-s0y3RlpRDqUzDRrQHgX1TnVX7Xa4KBOA-0-b93de6d1a485cfe5d597d60c2423b263)
在第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=1739409408-18j93dqXzhgUVlA0rAmJgRBrmhUH1WqB-0-9498e6a10aeee3c8d50e3c1f1eba3957)
在Controller.java里,我们定义“控制器类”,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32076.jpg?sign=1739409408-fD0lAJquMoZ0YBQI1Kq8B64Fjt4Ng7F6-0-10e1bc845cce1501e7ddf0ad3c86dbe3)
其中,在第7行中,我们通过@RequestMapping注解定义了触发该方法的url格式;在第8行的many2manyDemo方法中,调用了service层里的对应方法。下面我们来看一下bookService.java代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32077.jpg?sign=1739409408-qnFMSRJHvW77G5qjxPe0oEhQv3ab0Mu8-0-8d7e64c655b4a43974e243f78d4dca5a)
在上述代码的第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=1739409408-IXXRgGidTifwVZPNjsiAKkbCwEVfxvxt-0-1830afe8a8c6d39850ab526a43577ea6)
AuthorRepository类的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32869.jpg?sign=1739409408-y5rZfzJuw0F9g2wCkM1J5ibRft8mjuRF-0-f7bce053726f8c1a006b871d492d3f52)
也就是说,在Service层里,我们也是使用了JpaRepository里自带的save方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32531.jpg?sign=1739409408-5DmVWRIOLbzaL08JNSRe45YZGNRGTb53-0-758dffc8c6e0b1ea66046549ff1f6835)
当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/books/many2manyDemo”后,就会触发BookService类里的many2manyDemo方法,从而看到本案例的演示效果。