![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=1738789940-wdtJRO9IaeRnocddaRzoop0Nn9a0nMKF-0-e4c7ba68603e8c4d212816caf7dd0b2b)
在这个业务场景里,我们让一个学生(Student)只能拥有一张银行卡(Card),具体而言,学生和银行卡之间是一对一关联。
步骤01 创建学生和银行卡这两张数据表。学生表的结构如表2.4所示,其中用cardID来表示该学生所拥有的银行卡号。
表2.4 一对一关联里的Student表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32505.jpg?sign=1738789940-0Jz5pTbN3QCmBqtfaQUtd3Lxgce6gXVm-0-f7bb7465d6f7dfe9083908076087d554)
描述银行卡的Card表结构如表2.5所示。
表2.5 一对一关联里的Card表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T36_32050.jpg?sign=1738789940-LkrbxiTYru2FDraA1Jp8CnVNAZPX5fs3-0-6685a0747737dff74c2a8e2c06c6a6bc)
步骤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=1738789940-t4doXIfdDmNuGTZ3dIGPajoBL8iAmQOF-0-888e5da26f1937db95e85a680f7567cf)
这里同样要注意缩进,而且这里代码的具体含义在之前的项目介绍里都解释过,所以就不再额外解释了。
步骤04 编写用来映射数据表的学生和银行卡的Model类,其中Student.java的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P36_32052.jpg?sign=1738789940-IBKqhG2b8huMm2MMmogn41y771UqnBgZ-0-c018cc12e725745d43f4837d9177a292)
在上述代码的第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=1738789940-3vEWOcWLYNVoeoq3E7sDXHFtyt9WdNza-0-797092ac42f16d09fad952ec9df03590)
步骤05 编写控制器类StudentController.java,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32055.jpg?sign=1738789940-Sgshfct3jNceEZhgJTQzja64kCskIDlN-0-290d780f19df2128aec558fdfef4d959)
在上述代码的第7行和第8行里,我们能看到,/one2oneDemo格式的请求将触发one2oneDemo方法,在这个方法里,将调用service层的对应方法。
步骤06 编写实现Service层功能的StudentService.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P37_32056.jpg?sign=1738789940-qNoFKtZo1nRn2xbZhzdx8rUwQoPsrA5O-0-937ea263ff981bd5cfd5f62e1e7feee3)
在上述代码里,我们能看到学生和银行卡之间的关联关系。具体而言,当我们在第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=1738789940-EG2VfotI0iMEXlxdBm4gkMDLcjtDWuet-0-13e78e8f87aeb9ea63a47eab8d438057)
我们在第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=1738789940-Ldik8wAIOLWoz6kMBH02yLz4mggvIc9d-0-a4e66f762cbb44dfd6f4e761a22e4cc6)
至此,当我们通过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=1738789940-rvFYWV7jxXb35AFto5mwPi4zHDrAYulU-0-80553dc2d9a8beac3c9c478be1a7b3a5)
这里,我们将实现一个用户(User)拥有多辆汽车(Car)的业务场景。其中,用户表的结构如表2.6所示,描述汽车的Car表结构如表2.7所示。
表2.6 一对多关联里的User表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32061.jpg?sign=1738789940-gjqxCWeJoVX5Uut0gqjlXG44a4idIlfK-0-5e0f37edca873a4a3a055e17787e5ff6)
表2.7 一对多关联里的Car表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T39_32062.jpg?sign=1738789940-m2tkwZvysvXawRySoIs2BotYHbh5LAjV-0-851f6d25a9f7f5e6492e9ef1130ee4f6)
在创建完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=1738789940-YLzLzYpQTSNX2Jlk8HUmlsfXjA3cSy8r-0-56611a4d367ab790975eac98de124ceb)
在第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=1738789940-gWEdfdcMg18wP4QPn1keazv3XkaRqXgd-0-0437989ffb1f0b18d33a8db82061f3e6)
在这里的第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=1738789940-2XeWr7Jtjit8w2FQiQrkjF70rtQnzpv0-0-28a3432a216999f8e8ec47c3d3e4acef)
在第7行里,我们通过@RequestMapping注解定义了触发该方法的url格式,在第8行的one2manyDemo方法里,调用了service层里的one2manyDemo方法。下面我们来看一下UserService.java这段代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P40_32519.jpg?sign=1738789940-uHNxy1cgYfya8G0Zf0Uotl9UqZGs8oF7-0-c23b1b08429e86ed662ea5f33e9bb934)
在上述代码的第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=1738789940-pWN8QCDWnXqiFEonyPKyDHisMqqti9vh-0-9887f7af502dac9203b3717b7c2435a4)
也就是说,在Service层里,我们使用了JpaRepository里自带的save和delete方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P41_32069.jpg?sign=1738789940-gv16yhLrkPObOMt2fqFqNZMjFJMhYoGA-0-0bfec90762dde9247dcfa6c0b0838f4d)
当我们启动上述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=1738789940-BBAvQg2alOPVnaUyav659Jr7dsZjVVWh-0-27f6958e7083cb3088f71ee14f7b5aa6)
这里,我们将实现多本图书(Book)和多名作者(Author)之间的多对多关系,具体而言,一本书可以有多名作者,同一作者可以写多本书。
在表2.8中,我们定义了描述图书的Book表。
表2.8 多对多关联里的Book表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32071.jpg?sign=1738789940-2STZYWOJ0X7D3B5vyg5cOh6R9xXRgmJd-0-5dc646031613b87005ac201bdc4a8c4c)
描述作者的Author表结构如表2.9所示。
表2.9 多对多关联里的Author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32072.jpg?sign=1738789940-Wr1gk6woFUpZR1nUDGgnftM6ZSbgJbgt-0-c2e1218c2c2880472f2a9c75bff2b9b6)
同时,我们还需要创建book_author表来描述书和作者的多对多关联,结构如表2.10所示。
表2.10 多对多关联里的book_author表结构
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-T42_32073.jpg?sign=1738789940-LEEPy8buxn7u6TzE9ykMO5zCrHaOa9dk-0-2c4c9154b8a3c980ee2ea650ec3a8f61)
在创建完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=1738789940-GTmbD7v1nuJ89gmTeaBDHZDMFxwhQL0N-0-e0bdaa72104b66a88359ac55c5d801e0)
在第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=1738789940-0S3sMPqlHtTqFQEYuvicTLTPbQn6WUdK-0-3c581e1d638cec9a47c09a2a95f4f7dc)
在Controller.java里,我们定义“控制器类”,具体代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32076.jpg?sign=1738789940-PRzHj3MUrtcrHpUQCbbm5kYSc7kKLbDx-0-54228f396568e7542f4aa36dd4b4cd53)
其中,在第7行中,我们通过@RequestMapping注解定义了触发该方法的url格式;在第8行的many2manyDemo方法中,调用了service层里的对应方法。下面我们来看一下bookService.java代码。
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P43_32077.jpg?sign=1738789940-qjm1VMcMuaLP8kJOkABvqU7LCwiG84Gz-0-42330083fe85cadb510c1842ed417a23)
在上述代码的第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=1738789940-z8Rj800IqpXXabYcXzxP96LjufVefBZ3-0-6d7600f6af61ba99c123932da214807f)
AuthorRepository类的代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32869.jpg?sign=1738789940-G99hXtyj1BL04S7tKKIvB6CMk8cOGl3Q-0-2e34a9e7c5771b710930714e428366c3)
也就是说,在Service层里,我们也是使用了JpaRepository里自带的save方法。
最后,我们还得编写该Spring Boot的启动类App.java,代码如下:
![](https://epubservercos.yuewen.com/C966B5/15289822005524206/epubprivate/OEBPS/Images/Figure-P44_32531.jpg?sign=1738789940-3mbqyeizHj8kS4NA4zRjiOd9hmX1pTJC-0-527df87d539c4e48b6e8b5e97baf57d5)
当我们启动上述App.java,并在浏览器里输入“http://localhost:8080/books/many2manyDemo”后,就会触发BookService类里的many2manyDemo方法,从而看到本案例的演示效果。