在前面的章节中,我们在`spring data JPA`的帮助下,使用`Teacher`类来映射了`teacher`表,使用`Klass`类来映射了`klass`表。本节中我们继续展示`spring data JPA`在数据操作上强大的能力。 由于`Teacher`类定义了相关的属性以及为这些属性定义了相应的类型,所以`spring data JPA`才能够根据`Teacher`类来对应生成相关的`teacher`表。故与其说我们前面讲的`Teacher`类是对应`teacher` 表,不如说`Teacher`类映射的是`teacher`表**结构**。 | 序号 | JAVA类 | 数据表 | | --- | --- | --- | | 1 | 类名Teacher | 表名teacher | | 2 | 属性名id | 字段名id | | 3 | 属性类型Long | 字段类型bigint(long) | 在数据库中,一张数据表主要以下述两种要素组成:①表的定义(字段、外健、索引等);②表中所存储的数据。下面,让我们共同学习`spring data JPA`是如何处理表中的存储数据的。 # Repository `Repository`直译为`仓库`,在`spring data JPA`中将存储了数据的表看做一座数据仓库,我们可以由这个仓库中取数据,也可以在这个仓库中添加数据,同时也可以进行删除、修改的操作。如想建`klass`数据表对应的仓库,则需要进行如下操作: 首先同controller及entity一样,我们建立repository包。 ### 初始化JAVA接口文件 repository/KlassRepository.java ```java package com.mengyunzhi.springBootStudy.repository; import com.mengyunzhi.springBootStudy.entity.Klass; import org.springframework.data.repository.CrudRepository; /** * 班级仓库 */ public interface➊ KlassRepository➋ extends➌ CrudRepository➍<Klass➎, Long➏>{ } ``` * ➊ 类型定义为interface接口 * ➋ 按规范命名为`数据表名`+`Repository`(该命名仅为了方便记忆) * ➌ 该接口extends继承CrudRepository➍接口的同时,继承了CrudRepository对数据表操作的功能。 * ➎ 该数据仓库对应对Klass类对应的klass数据表进行操作 * ➏ 该数据表的主键类型为Long ### 自定义查询方法 接本节定义的前后台对接接口,我们需要将班级名称中包括前台传入的值的所有班级数据查询出来,那么我们可以在KlassRepository中如下定义查询方法: repository/KlassRepository.java ```java import java.util.List; ... public interface KlassRepository extends CrudRepository<Klass, Long>{ List<Klass>➊ findAllByNameContains➋(String name➌); } ``` * ➊ 定义返回值为`List<Klass>`来说明我要查询多条班级数据。 * ➋ 定义方法名为:查询出字段`name`中包含有第一个参数的所有数据。 * ➌ 定义第一个传入参数的类型。 ### 调用 我们来到想对班级表进行查询操作的controller/KlassController中,使用下面的方法进行数据表的查询操作: controller/KlassController.java ``` import org.springframework.beans.factory.annotation.Autowired; @RestController @RequestMapping("Klass") public class KlassController { private static final Logger logger = LoggerFactory.getLogger(KlassController.class); @Autowired ➊ KlassRepository klassRepository; @GetMapping public List<Klass> getAll(@RequestParam String name) { return this.klassRepository.findAllByNameContains(name);➋ } } ``` * ➊ 自动的向当前对象中注入一个实现了KlassRepository接口的对象。 * ➋ 调用该对象的findAllByNameContains()方法来完成数据的查询操作并返回。 ***** 在面向对象的语言中,我们学习过通过`new`关键字来由`类`来实例化一个供我们使用的对象。`Spring boot`是面向接口开发的框架,它为我们管理了大多数的对象,所以`new`操作发生在了`Spring boot`框架的底层,而我们在使用该框架的过程中,只需要通过`@Autowired`来告知它我们需要一个什么功能的对象即可。就也就是所谓的`ioc 控制反转` ---- 以前我们在编写代码的过程中来管理对象,现在这个管理对象的任务却**反转为**框架来完成了。 ***** ### 删除冗余的配置项 由于`spring data jpa`已经包含了`JDBC`,所以我们此时可以在`pom.xml`删除原来对`jdbc`依赖了。 pom.xml ``` <dependencies> <dependency> ✘ <groupId>org.springframework.boot</groupId> ✘ <artifactId>spring-boot-starter-data-jdbc</artifactId> ✘ </dependency> ✘ ``` 原则上,如果你不删除该依赖也不应该出现问题。但实际的情况是,如果你不删除该依赖则在应用启动时会出现以下错误: **以下内容选读** ``` org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'klassRepository' defined in null: Cannot register bean definition [Root bean: class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'klassRepository': There is already [Root bean: class [org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound. ``` 它的意思是说:在进行对象管理的时候进行新的对象注册的时候,发现了重名的对象klassRepository。这个原因我猜测是由于:由`JPA`管理的`JpaRepositoryFactoryBean`以与由`JDBC`管理的`JdbcRepositoryFactoryBean`分别扫描到了`KlassRepository`。他们两个都想把它注册到`spring`统一管理的对象中,但该对象只允许存在一个,所以就报错了。解决该问题也可以在配置文件中增加一行:`spring.main.allow-bean-definition-overriding=true`,意思是说如果发现有重名的了,就用后面的覆盖前面的。此做法并不推荐。 ## 测试 我们重新启动应用,然后在数据表中添加2条测试数据,并重新运行测试: 教师表: ![](https://img.kancloud.cn/91/7e/917e4a003140ac08bdb72a9baf878ee5_685x120.png) 班级表: ![](https://img.kancloud.cn/7e/41/7e41f42453d04f1fadd11d972a2489a3_478x163.png) 当name为hello,返回0条数据: ``` GET http://localhost:8080/Klass?name=hello HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 04 Nov 2019 07:08:15 GMT [] Response code: 200; Time: 260ms; Content length: 2 bytes ``` 当name为空时,返回3条数据: ``` GET http://localhost:8080/Klass?name= HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 04 Nov 2019 07:09:47 GMT [ { "id": 1, "name": "测试1" }, { "id": 2, "name": "测试2" }, { "id": 3, "name": "其它" } ] Response code: 200; Time: 59ms; Content length: 66 bytes ``` 当name为`测试`时,返回2条数据 ``` GET http://localhost:8080/Klass?name=%E6%B5%8B%E8%AF%95 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 04 Nov 2019 07:10:17 GMT [ { "id": 1, "name": "测试1" }, { "id": 2, "name": "测试2" } ] Response code: 200; Time: 23ms; Content length: 45 bytes ``` 当name为`其它`时返回1条数据 ``` GET http://localhost:8080/Klass?name=%E5%85%B6%E5%AE%83 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 04 Nov 2019 07:10:49 GMT [ { "id": 3, "name": "其它" } ] Response code: 200; Time: 25ms; Content length: 22 bytes ``` 当name为 `试`时,返回2条数据: ``` GET http://localhost:8080/Klass?name=%E8%AF%95 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 04 Nov 2019 07:11:20 GMT [ { "id": 1, "name": "测试1" }, { "id": 2, "name": "测试2" } ] Response code: 200; Time: 21ms; Content length: 45 bytes ``` ### SETER/GETER 对照我们自己定义的接口,刚刚的测试貌似完全的满足了我们的要求。但问题是我们在前面想显示的为如下数据: ![](https://img.kancloud.cn/a1/45/a14529f39b75ae4cfd7cf058026f5ada_644x448.png) 而返回的数据当中,我们并没有看到`教师`的信息,这样的数据返回给前台必然是无法满足实际需求的。这一问题的出现,暴露了以下问题: **在接口的定义过程中,返回数据的值定义的过于宽泛,在细节上没有实际的指导意义。这为以后的BUG埋下的伏笔!** 这个问题产生的原因在这: entity/Klass.java ``` package com.mengyunzhi.springBootStudy.entity; import javax.persistence.*; @Entity public class Klass { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne private Teacher teacher; private String name; public Klass() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` 通过观察此文件我们发现,`teacher`属性是没有`setter`及`getter`方法的。为了更好的说明`setter`及`getter`在整个程序执行过程中发挥的作用,我们依次添加`getter`及`setter`函数: #### GETTER与SETTER entity/Klass.java ``` public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } ``` 然后重新运行后台、重新添加测试数据后重新测试: ``` GET http://localhost:8080/Klass?name=%E6%B5%8B%E8%AF%95 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 04 Nov 2019 08:22:42 GMT [ { "id": 1, "teacher": { "id": 1, "name": "张三更新", "sex": false, "username": "newzhangsan", "email": "newzhangsan@yunzhiclub.com", "createTime": null, "updateTime": null }, "name": "测试1" }, { "id": 2, "teacher": { "id": 1, "name": "张三更新", "sex": false, "username": "newzhangsan", "email": "newzhangsan@yunzhiclub.com", "createTime": null, "updateTime": null }, "name": "测试2" } ] Response code: 200; Time: 164ms; Content length: 331 bytes ``` 测试结果中返回了教师数据,依此我们可以得出结论: ** 如果没有setter与getter方法,查询到的数据将无法返回给前台。** ## 请测试 那么到底是getter还是setter在真正的起做用呢?先猜猜,然后自己测试一下来验证自己的猜测结果。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.2.9](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.2.9) | - | | Accessing data with MySQL | [https://spring.io/guides/gs/accessing-data-mysql/](https://spring.io/guides/gs/accessing-data-mysql/)| 15 |