文章目录
Spring Boot - 04
对于数据访问层,无论是关系型数据库 SQL 还是非关系型数据库 NOSQL,Spring Boot 底层都是采用 Spring Data 的方式统一处理。
Spring Data 官网:点此进入
一、整合 JDBC
1. 配置
新建项目,导入 JDBC API 和 MySQL 驱动的启动器
在 Spring Boot 核心配置文件 application.yaml
中,配置数据库连接
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: 1142553864qq
在测试代码中进行测试:自动装配 dataSource 后,可以查看默认的数据源,获得数据库连接等
测试结果
说明:HikariDataSource 为 Spring Boot 默认的数据源。
2. 源码分析
与数据源相关的自动配置类为:DataSourceAutoConfiguration.java
;其中配置属性的类为:DataSourceProperties.java
- 自动配置类
- 配置属性的类
3. CRUD
xxxTemplate:Spring Boot 已经配置好的模板 bean,可以直接使用。
- 首先:准备一个数据库,并用 IDEA 连接数据库;
- 其次:建立 controller 包,创建
JdbcController
类,用 JDBC 来实现 CRUD; - 然后:用 controller 实现将 CURD 的结果展现在网页上。
// 所有方法都直接返回字符串
@RestController
public class JdbcController {
@Resource
public JdbcTemplate jdbcTemplate;
// 查询数据库中的所有信息
@RequestMapping("/select")
public List<Map<String, Object>> select() {
String sql = "select * from exercise.student_information";
return jdbcTemplate.queryForList(sql);
}
// 增加信息
@RequestMapping("/add")
public String add() {
String sql = "insert into exercise.student_information(id, name, sex, class, score, grade) values" + "(18, '大杜', '男', '600', 98.5, 'A')";
jdbcTemplate.update(sql);
return "add success!";
}
// 修改信息
@RequestMapping("/update/{id}")
public String update(@PathVariable("id") int id) {
String sql = "update exercise.student_information set name = ?, sex = ? where id = " + id;
jdbcTemplate.update(sql, "大飞", "男");
return "update success!";
}
// 删除信息
@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") int id) {
String sql = "delete from exercise.student_information where id = " + id;
jdbcTemplate.update(sql);
return "delete success!";
}
}
说明:
- jdbcTemplate 是 Spring Boot 已经配置好的模板 bean,点开
JdbcTemplate
类,可以看到能够使用的所有方法;- 没有实体类,但想查取数据库的信息,可以使用 Map 集合;
- 注解 @Resource 可以自动装配;
- 一般不会直接使用 JDBC 来实现 CRUD,而是使用 Mybatis 框架。
运行结果
二、Druid 数据源
1. 概述
-
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控;
-
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
准备工作:添加 Druid 数据源和 log4j2 的启动器
<!-- druid 启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.15</version>
</dependency>
<!-- log4j2 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.7.9</version>
</dependency>
在配置文件中通过 spring.datasource.type
可以切换数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/exercise?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: 1142553864qq
type: com.alibaba.druid.pool.DruidDataSource
在测试类中,可以看到数据源变为 DruidDataSourceWrapper
更换为 Druid 数据源,对原来的 CURD 操作并没有改变,但是它结合了 C3P0、DBCP 等 DB 池的优点,并加入了日志监控。
2. 源码分析
Druid 数据源的自动配置类为 DruidDataSourceAutoConfigure.java
:
- 其中必须引入
DruidDataSource.java
:可以找到 Druid 数据源的一些专有配置,如初始化数量、最大连接数量等; - 可以配置属性的类:
DruidStatProperties.java
(可以配置监控统计的属性)、DataSourceProperties.java
; - 还导入了四个类。
数据源的类型为:DruidDataSourceWrapper,打开该类可以看到该类继承了 DruidDataSource 类(所以自动配置类中必须引入 DruidDataSource.java
)
再打开 DruidDataSource
类,可以找到 Druid 数据源的一些专有配置,注意:Spring Boot 默认不注入这些属性值,需要自己配置
接下来,再打开配置属性的类 DruidStatProperties.java
:在这里可以配置监控统计的属性
从导入的和监控统计有关的类 DruidStatViewServletConfiguration.java
可以看到
3. 配置及日志监控
在 Spring Boot 核心配置文件 application.yaml
中,对 Druid 数据源进行配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/exercise?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: 1142553864qq
# 切换数据源
type: com.alibaba.druid.pool.DruidDataSource
# Druid 数据源专有配置,Spring Boot 默认不注入这些属性值,需要自己绑定
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
# 配置监控统计拦截的 filters
# stat:监控统计;log4j2:日志记录;wall:防御 SQL 注入
filters: stat,wall,log4j2
max-pool-prepared-statement-per-connection-size: 20
use-global-data-source-stat: true
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 配置监控统计的属性
stat-view-servlet:
enabled: true
# 登录后台的用户名和密码
login-username: Sun3285
login-password: 123456
运行主程序,可以正常执行 SQL,在地址栏请求 http://localhost:8080/druid/
打开监控统计的后台登录界面
登录成功后,可以在后台看到数据源的相关配置信息,以及监控统计每一条 SQL 的执行情况
三、整合 Mybatis 框架
1. 准备工作
新建项目,除了导入 JDBC API 和 MySQL 驱动的启动器,还要从 Maven 仓库导入 mybatis-spring 的启动器
<!-- mybatis-spring 的启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
在 Spring Boot 核心配置文件 application.yaml
中,配置数据库连接信息,以及整合 Mybatis 所需的相关配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: 1142553864qq
# 整合 Mybatis
mybatis:
type-aliases-package: com.Sun3285.pojo
mapper-locations: classpath*:mybatis/mapper/*.xml
说明:在整合 Mybatis 时,配置:
type-aliases-package
是为 pojo 包下的实体类设置别名;mapper-locations
是 mapper 实现类放置的位置。
准备一个数据库,用 IDEA 连接数据库
根据数据库创建实体类,并实现序列化
2. CURD
Mapper 层:Mapper 接口,实现类
Mapper 接口(StudentMapper.java
)放置的位置:main/java/com/Sun3285/mapper/StudentMapper.java
;
实现类(StudentMapper.xml
)放置的位置:main/resources/mybatis/mapper/StudentMapper.xml
。
因为这里接口和实现类的路径不一致,因此才会在 Spring 核心配置文件中对 mybatis.mapper-locations
进行配置。
@Mapper
public interface StudentMapper {
// 查询所有的学生
List<Student> getAllStudents();
// 根据 ID 查询学生
Student getStudentById(@Param("id") int id);
// 增加学生
boolean addStudent(Student student);
// 修改学生信息
boolean updateStudent(Student student);
// 删除学生
boolean deleteStudent(@Param("id") int id);
}
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.Sun3285.mapper.StudentMapper">
<cache/>
<!-- 结果集映射 -->
<resultMap id="studentMap" type="student">
<result property="classRoom" column="class"/>
</resultMap>
<select id="getAllStudents" resultMap="studentMap">
select * from exercise.student_information
</select>
<select id="getStudentById" parameterType="int" resultMap="studentMap">
select * from exercise.student_information where id = #{id}
</select>
<insert id="addStudent" parameterType="student">
insert into student_information (name, sex, class, score, grade)
values (#{name}, #{sex}, #{classRoom}, #{score}, #{grade});
</insert>
<update id="updateStudent" parameterType="student">
update student_information
set name = #{name}, sex = #{sex}, class = #{classRoom}, score = #{score}, grade = #{grade}
where id = #{id};
</update>
<delete id="deleteStudent" parameterType="int">
delete from student_information where id = #{id}
</delete>
</mapper>
说明:
- Mapper 接口需要由注解 @Mapper 声明:
定义:注解 @Mapper 是由 MyBatis 框架中定义的一个描述数据层接口的注解,即 Dao 层开发;
作用:起到一个描述性的作用,用于告诉 Spring 框架,此接口的实现类由 MyBatis 负责创建,并将实现类对象存储到 Spring 容器中。
实现类中需要在 namespace 中绑定要实现的 mapper 接口;
因为有属性名和字段名不一致的情况,所以需要使用 resultMap 来进行对应,注意使用时要将 resultType 改为 resultMap。
Service 层:Service 接口,实现类
public interface StudentService {
// 查询所有的学生
List<Student> getAllStudents();
// 根据 ID 查询学生
Student getStudentById(int id);
// 增加学生
boolean addStudent(Student student);
// 修改学生信息
boolean updateStudent(Student student);
// 删除学生
boolean deleteStudent(int id);
}
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
public StudentMapper studentMapper;
@Override
public List<Student> getAllStudents() {
return studentMapper.getAllStudents();
}
@Override
public Student getStudentById(int id) {
return studentMapper.getStudentById(id);
}
@Override
public boolean addStudent(Student student) {
return studentMapper.addStudent(student);
}
@Override
public boolean updateStudent(Student student) {
return studentMapper.updateStudent(student);
}
@Override
public boolean deleteStudent(int id) {
return studentMapper.deleteStudent(id);
}
}
说明:
- Service 层调用 Mapper 层;
- 注解 @Service 声明在实现类上;
- 在自动装配时,可以使用注解 @Autowired 声明,也可以使用注解 @Resource 声明。
Controller 层
@RestController
public class StudentController {
@Autowired
public StudentService studentService;
@RequestMapping("/select")
public String select() {
List<Student> students = studentService.getAllStudents();
return students.toString();
}
@RequestMapping("/select/{id}")
public String selectById(@PathVariable("id") int id) {
return studentService.getStudentById(id).toString();
}
@RequestMapping("/add")
public String add() {
Student student = new Student(1, "达达", "男", "602", 99.5, "A");
studentService.addStudent(student);
return "add success!";
}
@RequestMapping("/update/{id}")
public String update(@PathVariable("id") int id) {
Student student = studentService.getStudentById(id);
if (student.getScore() >= 98) {
student.setGrade("A+");
}
studentService.updateStudent(student);
return "update success!";
}
@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") int id) {
studentService.deleteStudent(id);
return "delete success!";
}
}
说明:Controller 层调用 Service 层。
3. 运行结果
通过查看数据库,可以看到执行了相关的 CURD 操作,运行成功
4. 补充总结
- Mapper 接口除了可以使用注解 @Mapper 声明外,还可以使用注解 @Repository 声明。两者都是用在 Dao 上,功能差不多,但使用注解 @Repository 时,还要在 Spring 中配置扫描包的地址,如下图所示。总结:@Mapper 相当于 @Repository + @MapperScan;
- 对第一点的补充:
- @Mapper 是 Mybatis 的注解,@Repository 是 Spring 的注解;
- 两个注解可以同时出现,也可以单一使用;
- 单独使用注解 @Mapper 时,可能会在编辑器出现警告,但不影响程序运行,若要消除警告,可以在编辑器中设置,也可以配合注解 @Repository 使用消除警告;
- 单独使用注解 @Repository 时,需要在 Spring 中配置扫描包的地址;
- 最佳实践:使用注解 @Mapper;
- 如果 Mapper 接口和实现类的路径不一致,要在 Spring 核心配置文件中对
mybatis.mapper-locations
进行配置; - 运行报错:
Parameter Maps collection does not contain value for xxx
:- 原因:在 Mapper 的实现类中,使用了 parameterMap,而 parameterMap 已经废弃了;
- 改正:使用 parameterType。
- 注解 @Mapper 声明在 Mapper 接口上,注解 @Service 声明在 Service 实现类上。
注意:
- 下载源码可以看到更详细的注释;
- 有自动配置类,导入启动器,修改配置文件即可。