我们都使用过连接池,比如C3P0、DBCP、hikari、Druid,虽然 HikariCP 的速度稍快,但 Druid 能够提供强大的监控和扩展功能。Druid DataSource 是阿里巴巴开发的号称为监控而生的数据库连接池,它不仅可以获取数据库连接,还把这些数据库连接管理了起来,也就是所谓的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,可以说是 Java 语言中最好的数据库连接池。
Spring Boot 2.0 以上默认使用 Hikari 数据源,而Druid已经在阿里巴巴部署了超过600个应用,经过好几年生产环境大规模部署的严苛考验!Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
首先看一下怎么使用 DruidDataSource。
// 配置一个 DruidDataSource 实例 bean 交由 Spring 容器管理
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/transaction?useSSL=false");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
//连接池最小空闲的连接数
dataSource.setMinIdle(5);
//连接池最大活跃的连接数
dataSource.setMaxActive(20);
//初始化连接池时创建的连接数
dataSource.setInitialSize(10);
return dataSource;
}
}
//测试类
public class DataSourceMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DataSourceConfig.class);
//获取 dataSource 实例
DataSource dataSource = (DataSource) applicationContext.getBean("dataSource");
try {
//通过数据源获取数据库连接
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
statement.execute("update AccountInfo set balance = balance + 1 where id = 1");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
从上可以看出,通过数据源访问跟 jdbc 方式相比,省略了实例化数据库连接驱动 Driver 驱动这一步,此外调用 getConnection() 获取连接的时候,并没有传用户名和密码,说明 dataSource 把这些配置信息都管理起来了。总结来说,DruidDataSource 管理了数据库连接的一些配置信息,还帮助我们创建了连接驱动 Driver,剩下的逻辑就跟 jdbc 一毛一样了。
DruidDataSource大部分属性都是参考DBCP的,个别配置的语意有所区别。如果你原来就是使用DBCP,迁移是十分方便的。
com.alibaba.druid.pool.DruidDataSource
配置 | 缺省值 | 说明 |
---|---|---|
name | 如果存在多个数据源,监控的时候可以通过名字来区分开来 | |
url | 连接数据库的url,不同数据库不一样 | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。 如果不希望密码直接写在配置文件中,可以使用ConfigFilter |
|
driverClassName | 这一项可配可不配,根据url自动识别 | |
maxActive | 8 | 最大连接池数量 |
initialSize | 0 | 初始化时建立连接的个数 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。 | |
filters | 监控统计拦截的filters | |
timeBetweenEvictionRunsMillis | 间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 | |
minEvictableIdleTimeMillis | 一个连接在池中最小生存的时间,单位是毫秒 | |
maxEvictableIdleTimeMillis | 一个连接在池中最大生存的时间,单位是毫秒 | |
validationQuery | 用来检测连接是否有效的sql。 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用 |
|
validationQueryTimeout | 检测连接是否有效的超时时间,单位:秒 | |
testWhileIdle | 如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,配置后会降低性能 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,配置后会降低性能 |
keepAlive | false | 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 |
poolPreparedStatements | false | 是否缓存preparedStatement |
maxOpenPreparedStatements | -1 | 是否要启用PSCache。当大于0时,启用。 |
asyncInit | false | |
connectionInitSqls | 物理连接初始化的时候执行的sql |
在META-INF/druid-filter.properties文件中配置Filter的别名。
Filter类名 | 别名 |
---|---|
default | com.alibaba.druid.filter.stat.StatFilter |
stat | com.alibaba.druid.filter.stat.StatFilter |
mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
log4j | com.alibaba.druid.filter.logging.Log4jFilter |
log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
wall | com.alibaba.druid.wall.WallFilter |
首先我们需要在应用的 pom.xml 文件中添加上 Druid 数据源依赖,可以从 Maven 仓库官网 中获取。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.1version>
<exclusions>
<exclusion>
<groupId>com.zaxxergroupId>
<artifactId>HikariCPartifactId>
exclusion>
exclusions>
dependency>
从 Spring Boot 2.0 开始,默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但其中有一个十分重要的条件,如下图。
@ConditionalOnMissingBean(DataSource.class)
的含义是:当容器中没有 DataSource 时,Spring Boot 才会使用 HikariCP 作为其默认数据源。 也就是说,若向容器中添加 Druid 数据源类的对象时,Spring Boot 就会使用 Druid 作为其数据源,而不再使用 HikariCP。在配置文件 application.yml 中添加以下数据源配置,它们会与与 Druid 数据源中的属性进行绑定,如下所示:
spring:
datasource:
username: root
password: root
# driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=true
platform: mysql
type: com.alibaba.druid.pool.DruidDataSource
com.alibaba.druid.pool.DruidDataSource 基本配置参数,如下所示:
配置 | 说明 |
---|---|
jdbcUrl | 连接数据库的url,多数据源时使用,不同数据库不一样 |
url | 连接数据库的url,单数据源时使用,不同数据库不一样 |
username | 连接数据库的用户名 |
password | 连接数据库的密码 |
driverClassName | 这一项可配可不配,不配置时druid会根据url自动识别dbType,然后选择相应的driverClassName |
connectionInitSqls | 连接初始化的时候执行的sql |
exceptionSorter | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
注:配置类创建 Druid 数据源对象时,应该尽量避免将数据源信息硬编码到代码中,而应该通过@ConfigurationProperties(“spring.datasource”) 注解,将 Druid 数据源对象的属性与配置文件中的以“spring.datasource”开头的配置进行绑定。
至此,我们就已经将数据源从 HikariCP 切换到了 Druid 了。
当数据源配置之后,我们需要来验证数据源类型是否为 Druid 以及是否能正常获取数据库连接、访问数据库,代码如下。
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.*;
/**
* 把今天最好的表现当作明天最新的起点..~
*
* Today the best performance as tomorrow the newest starter!
*
* @类描述: 数据源测试,测试 spring.datasource.xx 的 druid 属性配置是否正常,数据库是否能连接上等等
* @author: 独泪了无痕
* @创建时间: 2023-02-28 00:48
* @版本: V 1.0.1
* @since: JDK 1.8
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class DataSourceTest {
/**
* Spring Boot 默认已经配置好了数据源,可以直接 DI 注入然后使用即可
*/
@Resource
private DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
System.out.println("数据源>>>>>>" + dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println("连接>>>>>>>>>" + connection);
System.out.println("连接地址>>>>>" + connection.getMetaData().getURL());
if (dataSource instanceof DruidDataSource) {
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
System.out.println("version=" + druidDataSource.getVersion());
System.out.println("name=" + druidDataSource.getName());
}
connection.close();
}
}
如同以前 c3p0、dbcp 数据源可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等一样,Druid 数据源同理可以进行设置。Druid Spring Boot Starter 配置属性的名称完全遵照 Druid,可以通过 Spring Boot 配置文件来配置Druid数据库连接池和监控,如果没有配置则使用默认值,如下在 application.yml 配置相关属性:
spring:
datasource:
# druid 数据源专有配置,对应的是 com.alibaba.druid.pool.DruidDataSource 中的属性
druid:
# 数据源名称:当存在多个数据源时,设置名字可以很方便的来进行区分,默认自动生成名称,格式是:"DataSource-" + System.identityHashCode(this)
name: druid-db1
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时,默认0
initialSize: 10
# 最大连接池数量,默认8
maxActive: 200
# 最小连接池数量
minIdle: 10
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁
maxWait: 60000
# 1)Destroy线程会检测连接的间隔时间 2)testWhileIdle的判断依据(详细看testWhileIdle属性的说明)
timeBetweenEvictionRunsMillis: 60000
# 设置连接最小可收回空闲时间(毫秒),默认为 1000L * 60L * 30L
minEvictableIdleTimeMillis: 300000
# 设置连接最大可收回空闲时间(毫秒),默认为 1000L * 60L * 60L * 7
maxEvictableIdleTimeMillis: 900000
# 用来检测连接是否有效的sql,求是一个查询语句。默认为null,此时testOnBorrow、testOnReturn、testWhileIdle都不会其作用
validationQuery: SELECT 1 FROM DUAL
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认为true
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,默认false
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,默认false
testOnReturn: false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
poolPreparedStatements: true
# 要启用PSCache,必须配置大于0,当大于0时, poolPreparedStatements自动触发修改为true,
# 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
maxOpenPreparedStatements: 20
# 连接池中的 minIdle 数量以内的连接,空闲时间超过 minEvictableIdleTimeMillis,则会执行 keepAlive 操作
keepAlive: true
# 每个连接大小的最大池准备语句数
maxPoolPreparedStatementPerConnectionSize: 20
# 是否使用全局数据源统计,默认false
useGlobalDataSourceStat: true
# 连接属性
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 配置监控统计的内置过滤器:
# stat-监控统计(必须配置,否则监控不到sql)
# wall-防御sql注入
# log4j2-日志记录框架(值与应用中的日志框架保持一致,如 log4j、log4j、logback、slf4j)
filters: stat,wall,log4j2
现在可以重新测试 Druid 数据源,查看配置文件中的参数是否已经生效,也可以直接 Debug 查看 DruidDataSource 的属性。
这个过滤器的作用就是统计 web 应用请求中所有的数据库信息,比如 发出的 sql 语句,sql 执行的时间、请求次数、请求的 url 地址、以及seesion 监控、数据库表的访问次数 等等。可以通过 spring.datasource.druid.filters=stat,wall,log4j ...
的方式来启用相应的内置Filter,不过这些Filter都是默认配置。如果默认配置不能满足需求,可以放弃这种方式,通过配置文件来配置Filter,下面是例子。
########## JDBC基本配置 ##########
spring.datasource.username=developer
# spring.datasource.druid.username
spring.datasource.password=developer
# mysql的连接驱动
# spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# spring.datasource.druid.url
spring.datasource.url=jdbc:mysql://localhost:3306/opensource?useUnicode=true&characterEncoding=UTF-8&useSSL=true
# 数据库类型
spring.datasource.platform=mysql
# 指定数据源类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
########## druid数据源专有配置,对应的是com.alibaba.druid.pool.DruidDataSource中的属性
# 配置监控统计的内置过滤器:
spring.datasource.druid.filters=stat,wall,slf4j
##########自定义过滤器配置:stat、slf4j、log4j、log4j2、commons-log、wal
# 内置Filter都是默认配置,无法满足需求时,则可以自定义Filter,自定义的过滤器默认都是没有开启的
# 开启DruidDataSource的状态监控,必须配置,否则监控不到sql
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.db-type=mysql
# 开启慢sql监控,超过2s就认为是慢sql,记录到日志中
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=2000
########## 日志监控,使用slf4j进行日志输出
spring.datasource.druid.filter.slf4j.enabled=true
spring.datasource.druid.filter.slf4j.statement-log-error-enabled=true
spring.datasource.druid.filter.slf4j.statement-create-after-log-enabled=false
spring.datasource.druid.filter.slf4j.statement-close-after-log-enabled=false
spring.datasource.druid.filter.slf4j.result-set-open-after-log-enabled=false
spring.datasource.druid.filter.slf4j.result-set-close-after-log-enabled=false
# 防火墙过滤器,防御sql注入
spring.datasource.druid.filter.wall.enabled=true
# 不允许删除数据
spring.datasource.druid.filter.wall.config.delete-allow=false
##########配置WebStatFilter,用于采集web关联监控的数据##########
# 是否启用StatFilter默认值false
spring.datasource.druid.web-stat-filter.enabled=true
# 过滤所有url
spring.datasource.druid.web-stat-filter.url-pattern=/*
# 排除一些不必要的url
spring.datasource.druid.web-stat-filter.exclusions="*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
# 开启session统计功能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
# session的最大个数,默认100
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
# 使得druid能够知道当前的session的用户是谁
# spring.datasource.druid.web-stat-filter.principal-session-name=
# 如果你的user信息保存在cookie中,你可以配置principalCookieName,使得druid知道当前的user是谁
# spring.datasource.druid.web-stat-filter.principal-cookie-name=
# 配置profileEnable能够监控单个url调用的sql列表
# spring.datasource.druid.web-stat-filter.profile-enable=
########## 配置StatViewServlet(监控页面),用于展示Druid的统计信息 ##########
# 是否启用StatViewServlet(监控页面)默认值为false
spring.datasource.druid.stat-view-servlet.enabled=true
# 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
# 不允许清空统计数据,重新计算
spring.datasource.druid.stat-view-servlet.reset-enable=false
# 配置监控页面访问用户名
spring.datasource.druid.stat-view-servlet.loginUsername=root
# 配置监控页面访问密码
spring.datasource.druid.stat-view-servlet.loginPassword=123
# 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
# 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝
spring.datasource.druid.stat-view-servlet.deny=
不想使用内置的 Filters,要想使自定义 Filter 配置生效需要将对应 Filter 的 enabled 设置为 true ,Druid Spring Boot Starter 默认禁用 StatFilter,可以将其 enabled 设置为 true 来启用它。
Druid Spring Boot Starter 对以下 Druid 内置 Filter,都提供了默认配置StatFilter、WallFilter、ConfigFilter、EncodingConvertFilter、Slf4jLogFilter、Log4jFilter、Log4j2Filter、CommonsLogFilter。我们可以通过 spring.datasource.druid.filters=stat,wall … 的方式来启用相应的内置 Filter,不过这些 Filter 使用的都是默认配置。如果默认配置不能满足我们的需求,我们还可以在配置文件使用 spring.datasource.druid.filter.* 对这些 Filter 进行配置,示例代码如下。
# ####################################################### Druid 监控配置信息 ##########################################
spring:
datasource:
druid:
# 对配置已开启的 filters 即 stat(sql 监控) wall(防火墙)
filter:
#配置StatFilter (SQL监控配置)
stat:
enabled: true #开启 SQL 监控
slow-sql-millis: 1000 #慢查询
log-slow-sql: true #记录慢查询 SQL
#配置WallFilter (防火墙配置)
wall:
enabled: true #开启防火墙
config:
update-allow: true #允许更新操作
drop-table-allow: false #禁止删表操作
insert-allow: true #允许插入操作
delete-allow: true #删除数据操作
在配置 Druid 内置 Filter 时,需要先将对应 Filter 的 enabled 设置为 true,否则内置 Filter 的配置不会生效。
Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装路由器时,人家也提供了一个默认的 web 页面。首先需要设置 Druid 的后台管理页面的属性,比如登录账号、密码等。
Druid 内置提供了一个名为 StatViewServlet 的 Servlet,这个 Servlet 可以开启 Druid 的内置监控页面功能, 展示 Druid 的统计信息,它的主要用途可以提供监控信息展示的 html 页面、提供监控信息的 JSON API等。StatViewServlet 是一个标准的 javax.servlet.http.HttpServlet,想要开启 Druid 的内置监控页面,我们可以在配置类中,通过 ServletRegistrationBean 将 StatViewServlet 注册到容器中,来开启 Druid 的内置监控页面。
spring:
datasource:
druid:
stat-view-servlet:
enabled: true # 启用StatViewServlet
url-pattern: /druid/* # 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
reset-enable: false # 不允许清空统计数据,重新计算
loginUsername: root # 配置监控页面访问用户
login-password: 123 # 配置监控页面访问密码
allow: 127.0.0.1 # 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
deny: # 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝
启动 Spring Boot 项目后,浏览器访问 http://localhost:8080/druid/login.html
,即可访问 Druid 的内置监控页面的登录页,如下图所示。
在登录页中,分别输入自定义的用户名和密码,用户和密码对应我们的配置文件中设置的用户名(loginUsername)和密码(loginPassword)。输入用户名密码登录进去可以看到里面有很多监控,如下所示:
数据源页面是当前DataSource配置的基本信息,上述配置的Filter可以在里面找到,如果没有配置Filter(一些信息会无法统计,例如“SQL监控”,会无法获取JDBC相关的SQL执行信息)
Druid 内置提供了一个 StatFilter,通过它可以开启 Druid 的 SQL 监控功能,对 SQL 进行监控。SQL监控页面统计了所有SQL语句的执行情况,执行时间、读取行数、更新行数都有区间分布,如下图所示。
StatFilter 的别名是 stat,这个别名的映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中。Druid 官方文档-配置_StatFilter 中给出了在 Spring 中配置该别名(stat)开启 Druid SQL 监控的方式,配置如下。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
... ...
<property name="filters" value="stat" />
bean>
根据以上配置我们可以看出,只要在 dataSource 的 Bean 中添加一个取值为“stat”的“filters”属性,就能开启 Druid SQL 监控。
spring:
datasource:
druid:
filter:
stat:
enabled: true # 开启DruidDataSource状态监控
db-type: mysql # 数据库的类型
log-slow-sql: true # 开启慢SQL记录功能
slow-sql-millis: 2000 # 默认3000毫秒,这里超过2s,就是慢,记录到日志
Druid 还内置提供了一个名为 WebStatFilter 的过滤器,它可以用来监控与采集 web-jdbc 关联监控的数据。根据 Druid 官方文档-配置_配置WebStatFilter,想要开启 Druid 的 Web-JDBC 关联监控,只需要将 WebStatFilter 配置在 Web 应用中的 WEB-INF/web.xml 中即可,web.xml 配置如下。
<filter>
<filter-name>DruidWebStatFilterfilter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilterfilter-class>
<init-param>
<param-name>exclusionsparam-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*param-value>
init-param>
filter>
<filter-mapping>
<filter-name>DruidWebStatFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
Spring Boot 项目中是没有 WEB-INF/web.xml 的,但是我们可以在配置类中,通过 FilterRegistrationBean 将 WebStatFilter 注入到容器中,来开启 Druid 的 Web-JDBC 关联监控。
spring:
datasource:
druid:
web-stat-filter:
enabled: true # 启动 StatFilter
url-pattern: /* # 过滤所有url
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除一些不必要的url
session-stat-enable: true # 开启session统计功能
session-stat-max-count: 1000 # session的最大个数,默认100
浏览器访问系统的任意页面,然后再访问 Druid 的内置监控页面,切换到 Web 应用模块,可以看到 Druid 的 Web 监控已经开启,与此同时,URI 监控和 Session 监控也都被开启。
URL监控页面统计了所有Controller接口的访问以及执行情况。这里可以很清晰的看到,每个url涉及到的数据库执行的信息。
Session监控页面 可以看到当前的session状况,创建时间、最后活跃时间、请求次数、请求时间等详细参数。
Spring 监控页面,利用aop 对指定接口的执行时间,jdbc数进行记录
访问之后 Spring 监控默认是没有数据的,Spring 监控是用于通过 aop 切面对指定类及方法进行监控,这需要导入SprngBoot的AOP的Starter,如下所示:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
然后在 application.yml 配置 Spring 监控 AOP 切入点,如 com.springboot.template.dao.*
,配置多个英文逗号分隔,例如如下所示:
spring:
datasource:
druid:
aop-patterns:"com.springboot.template.dao.*" # Spring监控AOP切入点,多个时用英文逗号分隔
Druid 内置提供了一个 WallFilter,使用它可以开启防火墙功能,防御 SQL 注入攻击。WallFilter 的别名是 wall,这个别名映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中。Druid 官方文档-配置 wallfilter 中给出了在 Spring 中使用该别名(wall)开启防火墙的方式,配置如下。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
... ...
<property name="filters" value="wall" />
bean>
WallFilter 可以结合其他 Filter 一起使用,例如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="wall,stat"/>
bean>
根据以上配置我们可以看出,只要在 dataSource 的 Bean 中添加一个取值为“wall”的“filters”属性,就能开启 Druid 的防火墙功能,因此我们只需要在配置类中为 dataSource 的 filters 属性再添加一个“wall”即可(多个属性值之间使用逗号“,”隔开),代码如下。
spring:
datasource:
druid:
filters: stat,wall,slf4j
访问 Druid 的内置监控页面,切换到 SQL 防火墙,可以看到 Druid 防火墙已经开启。SQL防火墙页面 druid提供了黑白名单的访问,可以清楚的看到sql防护情况。
JSONAPI 页面 通过api的形式访问Druid的监控接口,api接口返回Json形式数据。
访问监控页面的时候,可能会在页面底部(footer)看到内置的广告,如下所示。这是因为引入的druid的 druid-x.y.z.jar 包中的 common.js
(里面有一段js代码是给页面的footer追加广告的)。如果想去掉,有两种方式:
直接手动注释这段代码
如果是使用Maven,直接到本地仓库中,查找这个 druid-x.y.z.jar 包,然后用压缩工具打开,找到 support/http/resources/js/common.js,然后注释掉里面的代码 或者修改为自己的页脚内容。
// this.buildFooter();
使用过滤器过滤
注册一个过滤器,过滤common.js
的请求,使用正则表达式替换相关的广告内容,如下代码所示:
@Configuration
@ConditionalOnWebApplication
@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled",
havingValue = "true", matchIfMissing = true)
public class RemoveDruidAdConfig {
/**
* 除去页面底部的广告
*/
@Bean
public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
//创建filter进行过滤
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("
" , "");
text = text.replaceAll("powered.*?shrek.wang", "");
response.getWriter().write(text);
}
@Override
public void destroy() {}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}
两种方式都可以,建议使用的是第一种,从根源解决。
Druid 的监控数据不仅可以在页面上查看,在 开启 StatFilter 后 ,也可以通过 DruidStatManagerFacade
类进行获取。其中 getDataSourceStatDataList 方法可以获取所有数据源的监控数据,除此之外 DruidStatManagerFacade
还提供了一些其他方法,可以按需选择使用。
@RestController
@RequestMapping(value = "/druid")
public class DruidStatController {
/**
* Spring Boot 默认已经配置好了数据源,程序员可以直接 DI 注入然后使用即可
*/
@Resource
private DataSource dataSource;
/**
* 获取 druid 数据监控信息,其中统计了所有数据源的所有详细信息。
*/
@GetMapping("/stat")
public List<Map<String, Object>> druidStat(){
// 获取数据源的监控数据
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
}
/**
* 使用 DruidDataSource API 获取具体数据源的指定的监控信息。
*/
@GetMapping("/druidDataSource")
public Map<String, Object> druidDataSource() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("class", dataSource.getClass());
if (dataSource instanceof DruidDataSource) {
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
dataMap.put("version", druidDataSource.getVersion());
dataMap.put("name", druidDataSource.getName());
dataMap.put("initialSize", druidDataSource.getInitialSize());
dataMap.put("maxActive", druidDataSource.getMaxActive());
dataMap.put("minIdle", druidDataSource.getMinIdle());
dataMap.put("activeCount", druidDataSource.getActiveCount());
dataMap.put("activePeak", druidDataSource.getActivePeak());
dataMap.put("activePeakTime", druidDataSource.getActivePeakTime());
dataMap.put("poolingCount", druidDataSource.getPoolingCount());
dataMap.put("poolingPeak", druidDataSource.getPoolingPeak());
dataMap.put("poolingPeakTime", druidDataSource.getPoolingPeakTime());
}
return dataMap;
}
}
获取 druid 数据监控信息
参数名 | 描述 |
---|---|
ActiveCount | 当前连接池中活跃连接数 |
ActivePeak | 连接池中活跃连接数峰值 |
ActivePeakTime | 活跃连接池峰值出现的时间 |
BlobOpenCount | Blob 打开数 |
ClobOpenCount | Clob 打开数 |
CommitCount | 提交数 |
DbType | 数据库类型 |
DefaultAutoCommit | 是否默认提交 |
DiscardCount | 放弃/丢弃的个数 |
DriverClassName | 驱动类 |
ErrorCount | 错误数 |
ExceptionSorterClassName | 异常分类器类名 |
ExecuteBatchCount | 执行批次计数 |
ExecuteCount | 执行总数 |
ExecuteQueryCount | 执行查询计数 |
ExecuteUpdateCount | 执行更新计数 |
FailFast | 是否快速失败 |
FilterClassNames | 过滤器类名称 |
Identity | |
InitGlobalVariants | 初始全局变量 |
InitVariants | 初始变量 |
InitialSize | 连接池建立时创建的初始化连接数 |
KeepAlive | 是否保持活跃 |
KeepAliveCheckCount | 保持活动检查计数 |
LogDifferentThread | 记录不同的线程 |
LogicCloseCount | 产生的逻辑连接关闭总数 |
LogicConnectCount | 产生的逻辑连接建立总数 |
LogicConnectErrorCount | 产生的逻辑连接出错总数 |
LoginTimeout | 数据库客户端登录超时时间 |
MaxActive | 连接池中最大的活跃连接数 |
MaxEvictableIdleTimeMillis | 最大可收回空闲时间(毫秒) |
MaxWait | 最大等待时间 |
MaxWaitThreadCount | 最大等待线程计数 |
MinEvictableIdleTimeMillis | 最小可执行时间Mill |
MinIdle | 连接池中最小的活跃连接数 |
Name | 数据源名称 |
NotEmptyWaitCount | 获取连接时最多等待多少次 |
NotEmptyWaitMillis | 获取连接时最多等待多长时间,毫秒为单位 |
PSCacheAccessCount | PSCache访问总数 |
PSCacheHitCount | PSCache命中次数 |
PSCacheMissCount | PSCache未命中次数 |
PhysicalCloseCount | 产生的物理关闭总数 |
PhysicalConnectCount | 产生的物理连接建立总数 |
PhysicalConnectErrorCount | 产生的物理连接失败总数 |
PoolPreparedStatements | 预编译准备语句 |
PoolingCount | 当前连接池中的连接数 |
PoolingPeak | 连接池中连接数的峰值 |
PoolingPeakTime | 连接池数目峰值出现的时间 |
PreparedStatementClosedCount | 预编译准备语句关闭计数 |
PreparedStatementOpenCount | 准备语句打开计数 |
QueryTimeout | 查询超时数 |
RecycleErrorCount | 回收错误计数 |
RemoveAbandoned | 已删除 |
RollbackCount | 回滚数 |
StartTransactionCount | 事务开始的个数 |
TestOnBorrow | |
TestOnReturn | |
TestWhileIdle | |
TransactionQueryTimeout | 事务查询超时数 |
URL | 数据库连接地址 |
UseUnfairLock | 使用不公平锁定 |
UserName | 数据库连接账号 |
WaitThreadCount | 当前等待获取连接的线程数 |
log4j2.xml文件中的日志配置
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
Console>
<RollingFile name="RollingFileDebug" fileName="./logs/debug.log"
filePattern="logs/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="DEBUG"/>
<ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
RollingFile>
<RollingFile name="RollingFileInfo" fileName="./logs/info.log"
filePattern="logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="INFO"/>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
RollingFile>
<RollingFile name="RollingFileWarn" fileName="./logs/warn.log"
filePattern="logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="WARN"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
RollingFile>
<RollingFile name="RollingFileError" fileName="./logs/error.log"
filePattern="logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="ERROR"/>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
RollingFile>
<RollingFile name="druidSqlRollingFile" fileName="./logs/druid-sql.log"
filePattern="logs/$${date:yyyy-MM}/api-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
Policies>
RollingFile>
appenders>
<loggers>
<root level="DEBUG">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
<appender-ref ref="RollingFileDebug"/>
root>
<logger name="druid.sql.Statement" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
logger>
<logger name="druid.sql.Statement" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
logger>
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<Logger name="org.crsh.plugin" level="warn" />
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
<logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
loggers>
configuration>
配置 application.properties
# 配置日志输出
spring.datasource.druid.filter.slf4j.enabled=true
spring.datasource.druid.filter.slf4j.statement-create-after-log-enabled=false
spring.datasource.druid.filter.slf4j.statement-close-after-log-enabled=false
spring.datasource.druid.filter.slf4j.result-set-open-after-log-enabled=false
spring.datasource.druid.filter.slf4j.result-set-close-after-log-enabled=false