![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=1739622204-n0JWXUay6Wr2qdDAEFuaTn2CpXLSSc5s-0-8fcfb2db929caa820c19302c4e669159)
在这个业务场景里,我们让一个学生(Student)只能拥有一张银行卡(Card),具体而言,学生和银行卡之间是一对一关联。
步骤01 创建学生和银行卡这两张数据表。学生表的结构如表2.4所示,其中用cardID来表示该学生所拥有的银行卡号。
表2.4 一对一关联里的Student表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32505.jpg?sign=1739622204-a08vyGd1j5uyq4aeABmaqn1K8BtK6X4r-0-ccfb3561081dffe39d67bee441fb047b)
描述银行卡的Card表结构如表2.5所示。
表2.5 一对一关联里的Card表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32050.jpg?sign=1739622204-cro09C4ThukUlft3YIZwY8HGxLQxs800-0-8b1c7f69b30bfdf3318a1251cfc381b5)
步骤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=1739622204-X3TpHOA4gSNIvvPCRhR7g4bmApFHgiZp-0-9875313141b97dc2f1eb9f7674b1b829)
这里同样要注意缩进,而且这里代码的具体含义在之前的项目介绍里都解释过,所以就不再额外解释了。
步骤04 编写用来映射数据表的学生和银行卡的Model类,其中Student.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P36_32052.jpg?sign=1739622204-rMwHOYRiZE06H9EaigJNszPYeikCshDn-0-3079c41aa53ea959eed6d28e77f2f34c)
在上述代码的第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=1739622204-OqtP80KdEqKwYo839tq3E2Z4xwCShjF6-0-615750f7c3645b3f291868bf63c5b460)
步骤05 编写控制器类StudentController.java,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32055.jpg?sign=1739622204-fmBMkODVRyGnGKPQpaCTFNe91U6mm4yM-0-fca6601f56ec538a4e05cb0318ae1272)
在上述代码的第7行和第8行里,我们能看到,/one2oneDemo格式的请求将触发one2oneDemo方法,在这个方法里,将调用service层的对应方法。
步骤06 编写实现Service层功能的StudentService.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32056.jpg?sign=1739622204-rYxNqlUOLKfCXUCYVUQB7qHJYAQI0sfc-0-0abadc6c1a444f26d6c5f54b954b8abf)
在上述代码里,我们能看到学生和银行卡之间的关联关系。具体而言,当我们在第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=1739622204-j4zT5GpKVopbjZQzNK0EZuW1I1RnOWsx-0-65e7beef8d57bc1e9015ab55bb7f972d)
我们在第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=1739622204-rkxNwDm0s9sHPLX3ihLq8JXx2ph8aVlf-0-87ae45d723c72958f596ac06d3b055b9)
至此,当我们通过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=1739622204-JySca7aCTv45OfmWyl0bKByqhySJY0Lo-0-b862a5542ec838c1cdc82efcbb25a4e4)
这里,我们将实现一个用户(User)拥有多辆汽车(Car)的业务场景。其中,用户表的结构如表2.6所示,描述汽车的Car表结构如表2.7所示。
表2.6 一对多关联里的User表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32061.jpg?sign=1739622204-FEDoLavDOt2E2jvHuUAxbImOJ65zDl4Z-0-97ca19d5b1003bcdf606000752ce7520)
表2.7 一对多关联里的Car表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32062.jpg?sign=1739622204-7mda7HS3ekwHAwl0FBq4Otdrv9sXD2CI-0-5ea989ae239f986329a1befb1e424a3a)
在创建完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=1739622204-hdyVyVTSKbZGDC0gGbVuG3h3sXPmlhol-0-05f2a005358359302bcae82a66149dae)
在第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=1739622204-gEVgKOhBPgB226Bu3BGv2xNTcUGegJqR-0-bfde63f3af1e4f921a8b4d4062190f9b)
在这里的第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=1739622204-RocbWU5c3EH55I4YzMpNVL9pfr9bGPzt-0-5368c6153a88038ed20351e9964e39a7)
在第7行里,我们通过@RequestMapping注解定义了触发该方法的url格式,在第8行的one2manyDemo方法里,调用了service层里的one2manyDemo方法。下面我们来看一下UserService.java这段代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P40_32519.jpg?sign=1739622204-nEZRZ6AdKHx0ANTmvaQsV0uKhThMiUhy-0-a50f6ded31144fd06d891ffb6f3d1d58)
在上述代码的第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=1739622204-VJw52FMn0gSkBiV96R21eNCk6XyCnmRg-0-ca003dbc1ba5b95bfd78f504b18744f8)
也就是说,在Service层里,我们使用了JpaRepository里自带的save和delete方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P41_32069.jpg?sign=1739622204-FP3zOBMn0MnlUmBxyz1xf5oVQ5Fy2Hof-0-8b3593c1d028ee4eff12a66c3749239d)
当我们启动上述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=1739622204-eBRRkTvT4dmpqhhihozKkVpw73tzMhbk-0-169c2c94640d9a5725bc62a4a4dffcbf)
这里,我们将实现多本图书(Book)和多名作者(Author)之间的多对多关系,具体而言,一本书可以有多名作者,同一作者可以写多本书。
在表2.8中,我们定义了描述图书的Book表。
表2.8 多对多关联里的Book表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32071.jpg?sign=1739622204-FSA94ERvOFxbDQzxRjS4gokpwp79BDlq-0-01ebc8afa0baf2fd1a622db764abaa1f)
描述作者的Author表结构如表2.9所示。
表2.9 多对多关联里的Author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32072.jpg?sign=1739622204-CczfcAbdSq6y0X1X3aj77mL3ChEiSwmg-0-a9d3c3d2fa7da17021718619158e4998)
同时,我们还需要创建book_author表来描述书和作者的多对多关联,结构如表2.10所示。
表2.10 多对多关联里的book_author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32073.jpg?sign=1739622204-KHI99OasNbyttQTAKqYeuvoZhxqCIbt8-0-5142af942ee47b7dddf1699144b0733b)
在创建完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=1739622204-qxlz7tE5XpWjtgiNliG5a3Wd4BCG1k2Q-0-2551a0dc0762e4806f5780dd7d7d2826)
在第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=1739622204-bU74JS0ekmYqA0cQi8QMXnw2PXH5S0wI-0-90d76f59afea96d4bf54ab3e6186dde7)
在Controller.java里,我们定义“控制器类”,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32076.jpg?sign=1739622204-fCAfER0Yu1eX4DRcD5flL50pUZu4mEmV-0-3d84d2ef27cb48dcc8e99be9d34b769a)
其中,在第7行中,我们通过@RequestMapping注解定义了触发该方法的url格式;在第8行的many2manyDemo方法中,调用了service层里的对应方法。下面我们来看一下bookService.java代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32077.jpg?sign=1739622204-bFoukTJnUtkCrpdRaP1uXAPK9kQzj2Vy-0-6a8bd73fa71267aa5187bafbe4c44c2a)
在上述代码的第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=1739622204-SLfRdQYodNv9noT322Sy2Pt09XuGfgTl-0-9ad08b294bed85aecb2146b66accd5b1)
AuthorRepository类的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32869.jpg?sign=1739622204-JleXs4VXzxB21CUbVEhMHjHj8CHwxVW2-0-56aee9c73f5f8a63f688d95ad2da1016)
也就是说,在Service层里,我们也是使用了JpaRepository里自带的save方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32531.jpg?sign=1739622204-P7p4SbIpTM4ZUIQMfwRx3Z08Vjg8uNoc-0-14c9be339455b0f176ad15f9b64e9905)
当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/books/many2manyDemo”后,就会触发BookService类里的many2manyDemo方法,从而看到本案例的演示效果。