在前面的章节中,我们在`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 |
- 序言
- 第一章:Hello World
- 第一节:Angular准备工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二节:Hello Angular
- 第三节:Spring Boot准备工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四节:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven国内源配置
- 4 package与import
- 第五节:Hello Spring Boot + Angular
- 1 依赖注入【前】
- 2 HttpClient获取数据【前】
- 3 数据绑定【前】
- 4 回调函数【选学】
- 第二章 教师管理
- 第一节 数据库初始化
- 第二节 CRUD之R查数据
- 1 原型初始化【前】
- 2 连接数据库【后】
- 3 使用JDBC读取数据【后】
- 4 前后台对接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三节 CRUD之C增数据
- 1 新建组件并映射路由【前】
- 2 模板驱动表单【前】
- 3 httpClient post请求【前】
- 4 保存数据【后】
- 5 组件间调用【前】
- 第四节 CRUD之U改数据
- 1 路由参数【前】
- 2 请求映射【后】
- 3 前后台对接【前】
- 4 更新数据【前】
- 5 更新某个教师【后】
- 6 路由器链接【前】
- 7 观察者模式【前】
- 第五节 CRUD之D删数据
- 1 绑定到用户输入事件【前】
- 2 删除某个教师【后】
- 第六节 代码重构
- 1 文件夹化【前】
- 2 优化交互体验【前】
- 3 相对与绝对地址【前】
- 第三章 班级管理
- 第一节 JPA初始化数据表
- 第二节 班级列表
- 1 新建模块【前】
- 2 初识单元测试【前】
- 3 初始化原型【前】
- 4 面向对象【前】
- 5 测试HTTP请求【前】
- 6 测试INPUT【前】
- 7 测试BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后台对接【前】
- 第三节 新增班级
- 1 初始化【前】
- 2 响应式表单【前】
- 3 测试POST请求【前】
- 4 JPA插入数据【后】
- 5 单元测试【后】
- 6 惰性加载【前】
- 7 对接【前】
- 第四节 编辑班级
- 1 FormGroup【前】
- 2 x、[x]、{{x}}与(x)【前】
- 3 模拟路由服务【前】
- 4 测试间谍spy【前】
- 5 使用JPA更新数据【后】
- 6 分层开发【后】
- 7 前后台对接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五节 选择教师组件
- 1 初始化【前】
- 2 动态数据绑定【前】
- 3 初识泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再识单元测试【前】
- 7 其它问题
- 第六节 删除班级
- 1 TDD【前】
- 2 TDD【后】
- 3 前后台对接
- 第四章 学生管理
- 第一节 引入Bootstrap【前】
- 第二节 NAV导航组件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三节 footer组件【前】
- 第四节 欢迎界面【前】
- 第五节 新增学生
- 1 初始化【前】
- 2 选择班级组件【前】
- 3 复用选择组件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校验【后】
- 7 唯一性校验【后】
- 8 @PrePersist【后】
- 9 CM层开发【后】
- 10 集成测试
- 第六节 学生列表
- 1 分页【后】
- 2 HashMap与LinkedHashMap
- 3 初识综合查询【后】
- 4 综合查询进阶【后】
- 5 小试综合查询【后】
- 6 初始化【前】
- 7 M层【前】
- 8 单元测试与分页【前】
- 9 单选与多选【前】
- 10 集成测试
- 第七节 编辑学生
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 功能开发【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成测试
- 7 @Input 异步传值【前】
- 8 值传递与引入传递
- 9 @PreUpdate【后】
- 10 表单验证【前】
- 第八节 删除学生
- 1 CSS选择器【前】
- 2 confirm【前】
- 3 功能开发与测试【后】
- 4 集成测试
- 5 定制提示框【前】
- 6 引入图标库【前】
- 第九节 集成测试
- 第五章 登录与注销
- 第一节:普通登录
- 1 原型【前】
- 2 功能设计【前】
- 3 功能设计【后】
- 4 应用登录组件【前】
- 5 注销【前】
- 6 保留登录状态【前】
- 第二节:你是谁
- 1 过滤器【后】
- 2 令牌机制【后】
- 3 装饰器模式【后】
- 4 拦截器【前】
- 5 RxJS操作符【前】
- 6 用户登录与注销【后】
- 7 个人中心【前】
- 8 拦截器【后】
- 9 集成测试
- 10 单例模式
- 第六章 课程管理
- 第一节 新增课程
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 async管道【前】
- 4 优雅的测试【前】
- 5 功能开发【前】
- 6 实体监听器【后】
- 7 @ManyToMany【后】
- 8 集成测试【前】
- 9 异步验证器【前】
- 10 详解CORS【前】
- 第二节 课程列表
- 第三节 果断
- 1 初始化【前】
- 2 分页组件【前】
- 2 分页组件【前】
- 3 综合查询【前】
- 4 综合查询【后】
- 4 综合查询【后】
- 第节 班级列表
- 第节 教师列表
- 第节 编辑课程
- TODO返回机制【前】
- 4 弹出框组件【前】
- 5 多路由出口【前】
- 第节 删除课程
- 第七章 权限管理
- 第一节 AOP
- 总结
- 开发规范
- 备用