SpringBoot⑨SpringData:JDBC、Druid数据源、MyBatis
10、Spring Data
Spring Data 是 Spring 中与 Spring Framework、Spring Boot 等齐名的项目。
- 官网:https://spring.io/projects/spring-data
- 文档:https://docs.spring.io/spring-data/commons/docs/2.5.5/reference/html/
对于数据访问层(包括 SQL 和 NOSQL),Spring Boot 底层采用 Spring Data 进行处理。
10.1、JDBC
创建一个 Spring Boot 项目,用于测试 JDBC 的使用。
10.1.1、环境搭建
创建项目,勾选 JDBC 所需的依赖(也可以在pom.xml
中导入依赖)
- JDBC API
- MySQL Driver
查看依赖项
- Spring 导入了以下依赖;
- 在需要使用到 JDBC 的 Spring Boot 项目中,直接导入以下两个依赖即可 ;
10.1.2、数据库配置文件
在 resources 目录下,创建数据库配置文件application.yaml
(或application.properties
)
spring:
datasource:
username: root
password: 密码
# MySQL8.0之后需要设置时区,并且driver位于cj包下
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
测试数据源
@Test
void contextLoads() throws SQLException {
// 默认数据源
System.out.println(dataSource.getClass());
// 获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 关闭连接
connection.close();
}
-
默认数据源:HikariDataSource
-
如果要自定义数据源,在配置文件中使用
type
属性spring: datasource: ... type: 数据源
成功得到了数据源和数据库连接,就可以使用原生 JDBC 来操作数据库。
10.1.3、JdbcTemplate
原生的 JDBC 比较麻烦,我们可以使用第三方持久层框架(如 MyBatis)。
Spring 也对 JDBC 做了轻量级的封装: JdbcTemplate,封装了 CRUD 方法,主要的几类方法如下:
- execute
- 可以执行任何查询语句,一般用于执行 DDL 语句
- update 和 batchUpdate
- update:执行增删改语句
- batchUpdate:执行批处理相关语句
- query 和 queryForXxx
- 执行查询语句
- call
- 执行存储过程、函数语句
以上方法都有重载方法,常用的两个方法
method(String sql)
- 执行完整的 SQL 语句;
- 如:
SELECT * FROM user
method(String sql, Object... args)
- 执行带有占位符的 SQL
args
:为占位符赋值,注意顺序。(类似 JDBC 中的 preparedStatement)- 如:
SELECT * FROM user WHERE id = ?
,Object[] agrs = {7}
10.1.4、CRUD
使用 JdbcTemplate 进行 CRUD
-
UserDao
- 由于没有实体类,用 map 来存储对象
public interface UserDao { void insertUser(); void deleteUser(Integer id); void updateUser(Integer id, String name); Map<String, Object> getUser(Integer id); List<Map<String, Object>> listUsers(); }
-
UserDaoImpl
@Repository
:注册到 Spring 容器中;update
:增删改queryXxx
:查询
@Repository public class UserDaoImpl implements UserDao { @Resource JdbcTemplate jdbcTemplate; @Override public void insertUser() { String sql = "INSERT INTO jdbcstudy.user(id, name, password) VALUES(10,'test','123456')"; jdbcTemplate.update(sql); } @Override public void deleteUser(Integer id) { String sql = "DELETE FROM jdbcstudy.user WHERE id = " + id; jdbcTemplate.update(sql); } @Override public void updateUser(Integer id, String name) { String sql = "UPDATE jdbcstudy.user SET name = ? WHERE id = ?"; Object[] params = {name, id}; jdbcTemplate.update(sql, params); } @Override public Map<String, Object> getUser(Integer id) { String sql = "SELECT * FROM jdbcstudy.user WHERE id = ?"; return jdbcTemplate.queryForMap(sql, id); } @Override public List<Map<String, Object>> listUsers() { String sql = "SELECT * FROM jdbcstudy.user"; return jdbcTemplate.queryForList(sql); } }
-
UserDaoTest
@Autowired
:DI 注入;- 如果 UserDao 实现类 没有
@Repository
注册到容器中,此处无法注入成功
@SpringBootTest public class UserDaoTest { @Autowired UserDao userDao; @Test void testInsertUser() { userDao.insertUser(); } @Test void testDeleteUser() { userDao.deleteUser(10); } @Test void testUpdateUser() { userDao.updateUser(8); } @Test void testGetUser() { System.out.println(userDao.getUser(7)); } @Test void testListUsers() { System.out.println(userDao.listUsers()); } }
10.2、JDBC连接池:Druid
10.2.1、环境搭建
-
搭建 JDBC 环境:导入依赖
<!-- JDBC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- MySQL连接 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
Druid 数据源
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency>
10.2.2、数据库配置文件
在 resource 目录下,创建数据库配置文件applicaion.yaml
(实际上是在使用 JDBC 的基础上,增加了一些配置)
spring:
datasource:
username: root
password: 密码
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
# Druid数据源
type: com.alibaba.druid.pool.DruidDataSource
# Druid数据源配置
initialSize: 5
maxActive: 20
minIdle: 5
maxWait: 60000
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
validationQuery: SELECT 1 FROM DUAL
testOnBorrow: false
testOnReturn: false
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
# filters:stat监控统计、log4j日志记录(需要导入log4j依赖)、wall防御sql注入
filters: stat,wall,log4j
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
测试数据源
-
数据源变成了 DruidDataSource
-
由于开启了 filters 中的 log4j,因此需要导入 log4j 的依赖
<!-- log4j:注意是Apache Log4j,而不是Apache Log4j Core--> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
常用配置
配置 | 说明 | 默认值 |
---|---|---|
name | 数据源名称。 如果存在多个数据源,监控时通过名称区分 |
"DataSource-" + System.identityHashCode(this) |
jdbcUrl | 连接数据库的 url | - |
username | 连接数据库的用户名 | - |
password | 连接数据库的密码 | - |
driverClassName | 数据库驱动名 | Druid 根据 url 自动识别dbType |
initialSize | 初始化时建立物理连接的个数。 初始化发生在显式调用 init 方法,或第一次 getConnection 时 |
0 |
maxActive | 最大连接数 | 8 |
maxIdle | 最大空闲数,已弃用 | 8 |
minIdle | 最小空闲数 | - |
maxWait | 获取连接时最大等待时间(毫秒)。 配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。 |
- |
poolPreparedStatements | 是否缓存 preparedStatement(即PSCache) | false |
maxOpenPreparedStatements | 要启用 PSCache,必须配置大于0。当大于0时 poolPreparedStatements 自动为 true | -1 |
validationQuery | 用来检测连接是否有效的 sql,要求是一个查询语句。 如果不配置,testOnBorrow、testOnReturn、testWhileIdle 不起作用 |
- |
testOnBorrow | 申请连接时执行 validationQuery 检测连接是否有效,该配置会降低性能 | true |
testOnReturn | 归还连接时执行 validationQuery 检测连接是否有效,该配置会降低性能 | false |
testWhileIdle | 保证安全性,不影响性能,建议配置为 true。 申请连接时检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效 |
false |
timeBetweenEvictionRunsMillis | 两个含义: 1) Destroy 线程会检测连接的间隔时间 2) testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明 |
1min |
numTestsPerEvictionRun | 已启用,一个 DruidDataSource 只支持一个 EvictionRun | - |
minEvictableIdleTimeMillis | 连接保持空闲而不被驱逐的最长时间 | 30min |
connectionInitSqls | 物理连接初始化的时候执行的 sql | - |
exceptionSorter | 当数据库抛出一些不可恢复的异常时,抛弃连接 | 根据 dbType 自动识别 |
filters | 扩展插件 常用插件: stat :监控统计、log4j :日志记录、wall :防御 sql 注入 |
- |
proxyFilters | 与 filters 是组合关系,并非替换关系。 类型: List<com.alibaba.druid.filter.Filter |
- |
10.2.3、DruidConfig
自定义配置文件相当于在 Spring的 xml配置文件中配置 bean
DruidDataSource
- 不使用 Spring Boot 自动创建的数据源,而是使用自定义 Druid 数据源;
@Configuration
:将配置类注册到容器中- 绑定配置文件
@Bean
:将自定义的数据源注入到容器中,“覆盖”掉 Spring Boot 自动创建的数据源;@ConfigurationProperties(prefix = "")
:将全局配置文件中对应前缀的属性值,注入到自定义 Druid 数据源的同名参数中
@Configuration
public class DruidConfig {
/**
* 绑定配置文件
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
...
}
stat 监控统计
@Bean
:注册到容器中ServletRegistrationBean
:Servlet 注册 Bean- 初始化参数:后台账号密码、允许访问、禁止访问名单
- 设置初始化参数
@Configuration
public class DruidConfig {
...
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
// 访问/druid/下的任意路径,自动跳入监控统计的登录页
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 初始化参数
HashMap<String, String> initParameters = new HashMap<>();
// 用户名、密码:key是固定的
initParameters.put("loginUsername", "admin");
initParameters.put("loginPassword", "123456");
// 允许访问名单:key为allow,value为空表示所有人
initParameters.put("allow", "");
// 禁止访问名单:key为禁止访问的人,value为ip地址
// initParameters.put("someone","192.168.11.123");
// 设置初始化参数
bean.setInitParameters(initParameters);
return bean;
}
}
测试
-
运行 SpringBoot 主程序,访问
localhost:8080/druid
,自动跳入登录页; -
只有正确输入了 loginUsername 和 loginPassword,才能进入后台
10.3、MyBatis
10.3.1、环境搭建
-
搭建 JDBC 环境:导入依赖
<!-- JDBC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- MySQL连接 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
mybatis-spring-boot-starter
<!-- SpringBoot整合MyBatis --> <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency>
10.3.2、数据库配置文件
在 resource 目录下,创建数据库配置文件applicaion.yaml
(实际上是使用 JDBC 的数据库配置,有需要可以使用数据源)
spring:
datasource:
username: root
password: 密码
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
10.3.3、使用MyBatis
-
数据库表
-
实体类:User
public class User { /** * ID */ private Integer id; /** * 用户名 */ private String name; /** * 密码 */ private String password; /** * 邮箱 */ private String email; /** * 生日 */ private Date birthday; ... }
-
UserMapper
@Mapper
:将 Mapper 扫描并注册到 Spring 容器中,等价以下用法:- 注册:在 Mapper 使用
@Repository
; - 扫描:在 Spring Boot 主启动类使用
@MapperScan("Mapper所在包")
@Mapper public interface UserMapper { void insertUser(); void deleteUser(Integer id); void updateUser(Integer id, String name); User getUser(Integer id); List<User> listUsers(); }
- 注册:在 Mapper 使用
-
MyBatis 配置
- 在全局配置文件中配置,整合 MyBatis
- 配置别名、注册 Mapper 等
spring: ... mybatis: type-aliases-package: indi.jaywee.pojo # Mapper.xml放在resources目录下与mapper同名的包下 mapper-locations: classpath:indi/jaywee/mapper/*.xml
-
Mapper.xml
<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 接口绑定 --> <mapper namespace="indi.jaywee.mapper.UserMapper"> <!-- 添加用户 --> <insert id="insertUser"> INSERT INTO jdbcstudy.user(id, name) VALUES ('20', 'somebody') </insert> <!-- 删除用户 --> <delete id="deleteUser"> DELETE FROM jdbcstudy.user WHERE id = #{id} </delete> <!-- 更新用户 --> <update id="updateUser"> UPDATE jdbcstudy.user SET name=#{name} WHERE id = #{id} </update> <!-- 根据ID查询用户 --> <select id="getUser" resultType="user"> SELECT * FROM jdbcstudy.user WHERE id = #{id} </select> <!-- 查询所有用户--> <select id="listUsers" resultType="user"> SELECT * from jdbcstudy.user </select> </mapper>
测试
@SpringBootTest
public class UserMapperTest {
@Autowired
UserMapper userMapper;
@Test
void testInsertUser() {
userMapper.insertUser();
}
@Test
void testDeleteUser() {
userMapper.deleteUser(20);
}
@Test
void testUpdateUser() {
userMapper.updateUser(8, "eight");
}
@Test
void testGetUser(){
User user = userMapper.getUser(7);
System.out.println(user);
}
@Test
void testListUsers(){
List<User> userList = userMapper.listUsers();
for (User user : userList) {
System.out.println(user);
}
}
}