前言
前一篇文章咱们相熟了 HikariCP 连接池,也理解到它的性能很高,明天咱们讲一下另一款比拟受欢迎的连接池:Druid,这是阿里开源的一款数据库连接池,它官网上宣称:为监控而生!他能够实现页面监控,看到 SQL 的执行次数、工夫和慢 SQL 信息,也能够对数据库明码信息进行加密,也能够对监控后果进行日志的记录,以及能够实现对敏感操作实现开关,杜绝 SQL 注入,上面咱们具体讲一下它如何与 Spring 集成,并且顺便理解一下它的监控的配置。
文章要点:
- Spring 集成 Druid
- 监控 Filters 配置(stat、wall、config、log)
- HiKariCP 和 Druid 该如何抉择
如何集成 Druid
1、减少相干依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
2、配置 DataSource
@Configuration
public class DataSourceConfiguration {@ConfigurationProperties(prefix = "spring.datasource.druid")
@Bean
public DataSource dataSource(){return new DruidDataSource();
}
}
3、配置项参数 application.properties
# 或 spring.datasource.url
spring.datasource.druid.url=jdbc:mysql://localhost:3306/chenrui
# 或 spring.datasource.username
spring.datasource.druid.username=root
# 或 spring.datasource.password
spring.datasource.druid.password=root
#初始化时建设物理连贯的个数。初始化产生在显示调用 init 办法,或者第一次 getConnection 时
spring.datasource.druid.initial-size=5
#最大连接池数量
spring.datasource.druid.max-active=20
#最小连接池数量
spring.datasource.druid.min-idle=5
#获取连贯时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用偏心锁,并发效率会有所降落,如果须要能够通过配置 useUnfairLock 属性为 true 应用非偏心锁
spring.datasource.druid.max-wait=500
#是否缓存 preparedStatement,也就是 PSCache。PSCache 对反对游标的数据库性能晋升微小,比如说 oracle。在 mysql 下倡议敞开。spring.datasource.druid.pool-prepared-statements=false
#要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 主动触发批改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,能够把这个数值配置大一些,比如说 100
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=-1
#用来检测连贯是否无效的 sql,要求是一个查问语句,罕用 select 'x'。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会起作用。spring.datasource.druid.validation-query=select 'x'
#单位:秒,检测连贯是否无效的超时工夫。底层调用 jdbc Statement 对象的 void setQueryTimeout(int seconds) 办法
spring.datasource.druid.validation-query-timeout=1
#申请连贯时执行 validationQuery 检测连贯是否无效,做了这个配置会升高性能。spring.datasource.druid.test-on-borrow=true
#偿还连贯时执行 validationQuery 检测连贯是否无效,做了这个配置会升高性能。spring.datasource.druid.test-on-return=true
#倡议配置为 true,不影响性能,并且保障安全性。申请连贯的时候检测,如果闲暇工夫大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连贯是否无效
spring.datasource.druid.test-while-idle=true
#有两个含意:默认 1 分钟
#1) Destroy 线程会检测连贯的间隔时间,如果连贯闲暇工夫大于等于 minEvictableIdleTimeMillis 则敞开物理连贯。#2) testWhileIdle 的判断根据,具体看 testWhileIdle 属性的阐明
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 连贯放弃闲暇而不被驱赶的最小工夫
spring.datasource.druid.min-evictable-idle-time-millis=600000
# 连贯放弃闲暇而不被驱赶的最大工夫
spring.datasource.druid.max-evictable-idle-time-millis=900000
#配置多个英文逗号分隔
spring.datasource.druid.filters=stat,wall
# WebStatFilter 配置
# 是否启用 StatFilter 默认值 false
spring.datasource.druid.web-stat-filter.enabled=true
# 匹配的 url
spring.datasource.druid.web-stat-filter.url-pattern=/*
# 排除一些不必要的 url,比方.js,/jslib/ 等等
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
# 你能够敞开 session 统计性能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
# 默认 sessionStatMaxCount 是 1000 个,你也能够按须要进行配置
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
# 使得 druid 可能晓得以后的 session 的用户是谁
spring.datasource.druid.web-stat-filter.principal-session-name=cross
# 如果你的 user 信息保留在 cookie 中,你能够配置 principalCookieName,使得 druid 晓得以后的 user 是谁
spring.datasource.druid.web-stat-filter.principal-cookie-name=aniu
# 配置 profileEnable 可能监控单个 url 调用的 sql 列表
spring.datasource.druid.web-stat-filter.profile-enable=
# 配置_StatViewServlet 配置, 用于展现 Druid 的统计信息
#是否启用 StatViewServlet(监控页面)默认值为 false(思考到平安问题默认并未启动,如需启用倡议设置明码或白名单以保障平安)spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#容许清空统计数据
spring.datasource.druid.stat-view-servlet.reset-enable=true
#监控页面登陆的用户名
spring.datasource.druid.stat-view-servlet.login-username=root
# 登陆监控页面所需的明码
spring.datasource.druid.stat-view-servlet.login-password=1234
# deny 优先于 allow,如果在 deny 列表中,就算在 allow 列表中,也会被回绝。# 如果 allow 没有配置或者为空,则容许所有拜访
#容许的 IP
# spring.datasource.druid.stat-view-servlet.allow=
#回绝的 IP
#spring.datasource.druid.stat-view-servlet.deny=127.0.0.1
#指定 xml 文件所在的地位
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
#开启数据库字段和类属性的映射反对驼峰
mybatis.configuration.map-underscore-to-camel-case=true
4、代码相干
数据库脚本
create table user_info
(
id bigint unsigned auto_increment
primary key,
user_id int not null comment '用户 id',
user_name varchar(64) not null comment '实在姓名',
email varchar(30) not null comment '用户邮箱',
nick_name varchar(45) null comment '昵称',
status tinyint not null comment '用户状态,1- 失常,2- 登记,3- 解冻',
address varchar(128) null
)
comment '用户根本信息';
初始化数据
INSERT INTO chenrui.user_info (id, user_id, user_name, email, nick_name, status, address) VALUES (1, 80001, '张三丰', 'xxx@126.com', '三哥', 1, '武当山');
INSERT INTO chenrui.user_info (id, user_id, user_name, email, nick_name, status, address) VALUES (2, 80002, '张无忌', 'yyy@126.com', '', 1, null);
mapper.xml 文件编写
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springdataSourcedruid.dao.UserInfoDAO">
<select id="findAllUser" resultType="com.example.springdataSourcedruid.entity.UserInfo">
select * from user_info
</select>
<select id="getUserById" resultType="com.example.springdataSourcedruid.entity.UserInfo">
select * from user_info where id = #{id}
</select>
<select id="getUserByIdEqualOne" resultType="com.example.springdataSourcedruid.entity.UserInfo">
select * from user_info where id =1
</select>
<select id="getUserByIdEqualTwo" resultType="com.example.springdataSourcedruid.entity.UserInfo">
select * from user_info where id =2
</select>
</mapper>
编写 DAO 接口
public interface UserInfoDAO {List<UserInfo> findAllUser();
UserInfo getUserById(@Param("id") int id);
UserInfo getUserByIdEqualOne();
UserInfo getUserByIdEqualTwo();}
测试 controller
@RestController
@Slf4j
public class UserInfoController {
@Resource
private UserInfoDAO userInfoDAO;
@GetMapping(path = "/all")
public List<UserInfo> getAllUser(){return userInfoDAO.findAllUser();
}
@GetMapping(path = "/getUser/{id}")
public UserInfo getById(@PathVariable int id){return userInfoDAO.getUserById(id);
}
@GetMapping(path = "/getUser/one")
public UserInfo getById1(){return userInfoDAO.getUserByIdEqualOne();
}
@GetMapping(path = "/getUser/two")
public UserInfo getById2(){return userInfoDAO.getUserByIdEqualTwo();
}
}
启动类
@SpringBootApplication
@MapperScan(basePackages = "com.example.springdataSourcedruid.dao")
public class SpringDataSourceDruidApplication {public static void main(String[] args) {SpringApplication.run(SpringDataSourceDruidApplication.class, args);
}
}
5、启动验证
拜访:http://127.0.0.1:8080/druid/,弹出登陆界面,用户和明码对应咱们的配置文件中设置的用户名和明码
登陆进去能够看到外面有很多监控,这里咱们只看咱们本次所关怀的,数据源,SQL 监控,URL 监控,其余的能够自行钻研。
下面咱们看到数据源外面的信息和咱们在 application.properties 中配置的统一
上面咱们别离执行几次,咱们筹备好的验证接口
http://127.0.0.1:8080/all
http://127.0.0.1:8080/getUser/1
http://127.0.0.1:8080/getUser/2
http://127.0.0.1:8080/getUser…
http://127.0.0.1:8080/getUser…
而后看一下的各项监控信息长什么样子
SQL 监控
下面咱们看到咱们总共四个语句,以及四个语句的运行状况
SQL 监控项上,执行工夫、读取行数、更新行数都有区间散布,将耗时散布成 8 个区间:
- 0 – 1 耗时 0 到 1 毫秒的次数
- 1 – 10 耗时 1 到 10 毫秒的次数
- 10 – 100 耗时 10 到 100 毫秒的次数
- 100 – 1,000 耗时 100 到 1000 毫秒的次数
- 1,000 – 10,000 耗时 1 到 10 秒的次数
- 10,000 – 100,000 耗时 10 到 100 秒的次数
- 100,000 – 1,000,000 耗时 100 到 1000 秒的次数
- 1,000,000 – 耗时 1000 秒以上的次数
这里你可能会有疑难 ,id = 1 和 id= 2 怎么还是离开的,如果我 id 有一亿个,难道要在监控页面上有一亿条记录吗?不是应该都应该是 id=?的模式吗?这里前面会讲到,波及到 sql 合并的监控配置
URL 监控
这里能够很清晰的看到,每个 url 波及到的数据库执行的信息
druid 的内置 filters
在 druid 的 jar 中,META-INF/druid-filter.properties 中有其内置的 filter,内容如下:
druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter
default、stat、wall 等是 filter 的别名,能够在 application.properties 中能够通过 spring.datasource.druid.filters 属性指定别名来开启相应的 filter,也能够在 Spring 中通过属性注入形式来开启,接下来介绍一下比拟罕用的 filter
拦截器 stat(default、counter)
在 spring.datasource.druid.filters 配置中蕴含 stat,代表开启监控统计信息,在下面内容中,咱们曾经看到蕴含执行次数、工夫、最慢 SQL 等信息。也提到因为有的 sql 是非参数话的,这样会导致在监控页面有很多监控的 sql 都是一样的,只是参数不一样,咱们这时候须要将合同 sql 配置关上;
只须要在 application.properties 减少配置:
# 为监控开启 SQL 合并,将慢 SQL 的工夫定为 2 毫秒,记录慢 SQL 日志
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2;druid.stat.logSlowSql=true
看一下运行后果:
1、上面 2 个语句在监控页面被合并了:
select * from user_info where id=1
select * from user_info where id=2
// 合并后的后果是:SELECT * FROM user_info WHERE id = ?
2、超过 2ms 的语句,在监控页面红色展现进去
3、慢 SQL 在日志中会被体现进去
拦截器 mergeStat
继承 stat,根本个性和 stat 是一样的,不做延长
拦截器 encoding
因为历史起因,一些数据库保留数据的时候应用了谬误编码,须要做编码转换。
能够用上面的形式开启:
spring.datasource.druid.filters=stat,encoding
#配置客户端的编码 UTF-8,服务端的编码是 ISO-8859-1,这样存在数据库中的乱码查问进去就不是乱码了。spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2;druid.stat.logSlowSql=true;clientEncoding=UTF-8;serverEncoding=ISO-8859-1
拦截器 log4j(log4j2、slf4j、commonlogging、commonLogging)
Druid 内置提供了四种 LogFilter(Log4jFilter、Log4j2Filter、CommonsLogFilter、Slf4jLogFilter),用于输入 JDBC 执行的日志
# 这里应用 log4j2 为例
spring.datasource.druid.filters=stat,log4j2
#druid.log.conn 记录连贯、druid.log.stmt 记录语句、druid.log.rs 记录后果集、druid.log.stmt.executableSql 记录可执行的 SQL
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2;druid.stat.logSlowSql=true;druid.log.conn=true;druid.log.stmt=true;druid.log.rs=true;druid.log.stmt.executableSql=true
#为不便验证,咱们开启以下 loggerName 为 DEBUG
logging.level.druid.sql.Statement=DEBUG
logging.level.druid.sql.ResultSet=DEBUG
logging.level.druid.sql.Connection=DEBUG
logging.level.druid.sql.DataSource=DEBUG
咱们能够看到执行 SQL 的整个过程,开启连贯 > 从连接池获取一个连贯 > 组装 SQL 语句 > 执行 > 后果集返回 > 连接池回收连贯
这里只用了 log4j2 这一种类型,其余能够自行去验证。
拦截器 wall
WallFilter 的性能是进攻 SQL 注入攻打。它是基于 SQL 语法分析,了解其中的 SQL 语义,而后做解决的,智能,精确,误报率低。缩小危险的产生,wall 拦截器还是很重要的。比如说不容许应用 truncate,不容许物理删除,这时候 wall 就用得上了。配置形式有两种:
第一种:缺省配置
spring.datasource.druid.filters=stat,wall,log4j2
这种配置是默认配置,而且大多数都不会拦挡,可能不合乎特定的场景,默认属性值参照:https://www.bookstack.cn/read…
第二种:属性指定配置
这种形式的益处是:咱们能够针对特定场景进行限定,比如说不能用存储过程,不能物理删除,是否容许语句中有正文等等。
// 在 DruidDataSource 生成前注入 WallFilter
@ConfigurationProperties(prefix = "spring.datasource.druid")
@Bean
public DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();
dataSource.getProxyFilters().add(wallFilter());
return dataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.druid.filter.wall.config")
public WallConfig wallConfig(){return new WallConfig();
}
@Bean
public WallFilter wallFilter(){WallFilter filter = new WallFilter();
filter.setConfig(wallConfig());
filter.setDbType("mysql");
return filter;
}
# 不容许物理删除语句
spring.datasource.druid.filter.wall.config.delete-allow=false
执行一下试试成果:
能够看到日志显示,不容许删除,这样能够防止一些同学不依照公司开发标准来开发代码,缩小危险。其余配置本人能够试验一下。
拦截器 Config
Config 作用:从配置文件中读取配置;从近程 http 文件中读取配置;为数据库明码提供加密性能
实际上前两项作用意义不大,最要害的是第三项作用,因为数据库明码间接写在配置中,对运维平安来说,是一个很大的挑战。Druid 为此提供一种数据库明码加密的伎俩 ConfigFilter
如何应用:
# 在 application.properties 的链接属性配置项中减少 config.file,能够是本地文件,也能够是近程文件,比方 config.file=http://127.0.0.1/druid-pool.properties
spring.datasource.druid.connection-properties=config.file=file:///Users/chenrui/druid-pool.properties
加密咱们的数据库明码
应用上面的命令生成数据库明码的密文和秘钥对
java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password
druid-pool.properties 文件内容
数据库明码配置项的值改为密文
spring.datasource.druid.password=kPYuT1e6i6M929mNvKfvhyBx3eCI+Fs0pqA3n7GQYIoo76OaWVg3KALr7EdloivFVBSeF0zo5IGIfpbGtAKa+Q==
本人启动一下试试,发现一切正常,信息安全问题也解决了。
Druid 和 HikariCP 如何抉择
网络上有这么一个图,能够看到 Druid 是和其申明的统一(为监控而生),然而目前市面上有很多监控相干的中间件和技术,HikariCP 能够通过这些技术补救监控方面的有余
HikariCP 则说本人是性能最好的连接池,然而 Druid 也禁受住了阿里双 11 的大考,实际上性能也是很好的
抉择哪一款就见仁见智了,不过两款都是开源产品,阿里的 Druid 有中文的开源社区,交换起来更加不便,并且通过阿里多个零碎的试验,想必也是十分的稳固,而 Hikari 是 SpringBoot2.0 默认的连接池,全世界应用范畴也十分广,对于大部分业务来说,应用哪一款都是差不多的,毕竟性能瓶颈个别都不在连接池。大家可依据本人的爱好自由选择