官网

https://github.com/alibaba/druid/wiki

介绍 - 《Alibaba Druid v1.0 使用手册》 - 书栈网 · BookStack

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<lombok.version>1.18.8</lombok.version>
<logback.version>1.2.3</logback.version>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>

<!-- lombok 提供了 slf4j 日志门面-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>

<!-- logback 日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>

Gradle

1
compile group: 'com.alibaba', name: 'druid', version: '1.1.16'

DruidDataSource 配置

https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

配置 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:”DataSource-“ + System.identityHashCode (this). 另外配置此属性至少在 1.0.5 版本中是不起作用的,强行设置 name 会出错。详情 - 点此处
url 连接数据库的 url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用 ConfigFilter。详细看这里
driverClassName 根据 url 自动识别 这一项可配可不配,如果不配置 druid 会根据 url 自动识别 dbType,然后选择相应的 driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
poolPreparedStatements false 是否缓存 preparedStatement,也就是 PSCache。PSCache 对支持游标的数据库性能提升巨大,比如说 oracle。在 mysql 下建议关闭。
maxPoolPreparedStatementPerConnectionSize -1 要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,可以把这个数值配置大一些,比如说 100
validationQuery 用来检测连接是否有效的 sql,要求是一个查询语句,常用 select ‘x’。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会起作用。
validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。底层调用 jdbc Statement 对象的 void setQueryTimeout (int seconds) 方法
testOnBorrow true 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testWhileIdle false 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
keepAlive false (1.0.28) 连接池中的 minIdle 数量以内的连接,空闲时间超过 minEvictableIdleTimeMillis,则会执行 keepAlive 操作。
timeBetweenEvictionRunsMillis 1 分钟(1.0.14) 有两个含义: 1) Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。 2) testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明
numTestsPerEvictionRun 30 分钟(1.0.14) 不再使用,一个 DruidDataSource 只支持一个 EvictionRun
minEvictableIdleTimeMillis 连接保持空闲而不被驱逐的最小时间
connectionInitSqls 物理连接初始化的时候执行的 sql
exceptionSorter 根据 dbType 自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的 filter:stat 日志用的 filter:log4j 防御 sql 注入的 filter:wall
proxyFilters 类型是 List<com.alibaba.druid.filter.Filter>,如果同时配置了 filters 和 proxyFilters,是组合关系,并非替换关系

数据库密码加密

https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter

  1. 在命令行中执行如下命令,输出到文件中

    1
    java -cp druid-1.0.14.jar com.alibaba.druid.filter.config.ConfigTools csxcgjgmscs > a.txt
  2. 输出内容

    1
    2
    3
    privateKey:MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA6+4avFnQKP+O7bu5YnxWoOZjv3no4aFV558HTPDoXs6EGD0HP7RzzhGPOKmpLQ1BbA5viSht+aDdaxXp6SvtMQIDAQABAkAeQt4fBo4SlCTrDUcMANLDtIlax/I87oqsONOg5M2JS0jNSbZuAXDv7/YEGEtMKuIESBZh7pvVG8FV531/fyOZAiEA+POkE+QwVbUfGyeugR6IGvnt4yeOwkC3bUoATScsN98CIQDynBXC8YngDNwZ62QPX+ONpqCel6g8NO9VKC+ETaS87wIhAKRouxZL38PqfqV/WlZ5ZGd0YS9gA360IK8zbOmHEkO/AiEAsES3iuvzQNYXFL3x9Tm2GzT1fkSx9wx+12BbJcVD7AECIQCD3Tv9S+AgRhQoNcuaSDNluVrL/B/wOmJRLqaOVJLQGg==
    publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOvuGrxZ0Cj/ju27uWJ8VqDmY7956OGhVeefB0zw6F7OhBg9Bz+0c84RjzipqS0NQWwOb4kobfmg3WsV6ekr7TECAwEAAQ==
    password:PNak4Yui0+2Ft6JSoKBsgNPl+A033rdLhFw+L0np1o+HDRrCo9VkCuiiXviEMYwUgpHZUFxb2FpE0YmSguuRww==
  3. 配置

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">
    <property name="url" value="jdbc:derby:memory:spring-test;create=true" />
    <property name="username" value="sa" />
    <property name="password" value="${password}" />
    <property name="filters" value="config" />
    <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${publickey}" />
    </bean>

监控统计配置

参考:Druid 的统计信息WebStatFilterSpring关联监控配置

spring.xml

Spring 监控: Service、Dao 执行情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Spring 监控: Service、Dao 执行情况-->
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>

<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"
scope="prototype">
<property name="patterns">
<list>
<value>com.theking.service.impl.*</value>
<value>com.theking.dao.*</value>
</list>
</property>
</bean>

<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>

spring-mybatis.xml

SQL 监控、SQL防火墙、慢查询输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<!-- SQL 合并配置-->
<property name="mergeSql" value="true" />

<!-- 慢 SQL 记录日志-->
<property name="slowSqlMillis" value="5000" />
<property name="logSlowSql" value="true" />
</bean>

<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
<property name="statementExecutableSqlLogEnable" value="true" />
</bean>
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
<property name="dbType" value="oracle" />
<property name="logViolation" value="true"/>
<property name="throwException" value="false"/>
</bean>

<!-- Log4jFilter -->
<!-- <bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">-->
<!-- <property name="statementExecutableSqlLogEnable" value="true" />-->
<!-- </bean>-->

<!-- 配置数据源-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 其它配置忽略 -->

<!-- filters 和 proxyFilters 属性是组合关系的,不是替换的 -->
<property name="filters" value="config" />
<!-- 监控数据库: stat、防火墙、日志 -->
<property name="proxyFilters">
<list>
<ref bean="stat-filter" />
<ref bean="log-filter"/>
<ref bean="wall-filter"/>
</list>
</property>
</bean>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!-- 包括:Session 监控、URI 监控-->
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
<init-param>
<param-name>sessionStatMaxCount</param-name>
<param-value>1000</param-value>
</init-param>
<init-param>
<param-name>sessionStatEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>principalSessionName</param-name>
<param-value>sessionusername</param-value>
</init-param>
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 配置 Druid 监控信息显示页面 -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<!-- 允许清空统计数据 -->
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 用户名 -->
<param-name>loginUsername</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<!-- 密码 -->
<param-name>loginPassword</param-name>
<param-value>druid</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>

logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 -->
<property name="LOG_HOME" value="../logs" />

<!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
</encoder>
</appender>

<!-- 每天生成一个html格式的日志开始 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/ssm_demo-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<MaxFileSize>500MB</MaxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
</appender>
<!-- 每天生成一个html格式的日志结束 -->

<!-- 生成 error html格式日志开始 -->
<appender name="ERROR" class="ch.qos.logback.core.FileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!--设置日志级别,过滤掉info日志,只输入error日志-->
<level>ERROR</level>
</filter>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
<file>${LOG_HOME}/error-log.html</file>
</appender>
<!-- 生成 error html格式日志结束 -->

<!--myibatis log configure -->
<logger name="com.apache.ibatis" level="TRACE" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />

<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="ERROR" />
</root>

<!-- druidSqlRollingFile 日志开始 -->
<appender name="druidSqlRollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/ssm_demo_druidSql-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<MaxFileSize>500MB</MaxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
</appender>
<!-- druidSqlRollingFile 日志结束 -->

<!--记录druid-sql的记录-->
<logger name="druid.sql.Connection" level="INFO" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<logger name="druid.sql.DataSource" level="INFO" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<!-- 执行 SQL-->
<logger name="druid.sql.Statement" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<!-- SQL 结果集-->
<logger name="druid.sql.ResultSet" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
<appender-ref ref="druidSqlRollingFile"/>
</logger>
</configuration>

log4j.properties「logback 二选一」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#根针对所有的日志包
log4j.rootLogger=error, appendConsole
#下面两个配置是指定包的特殊处理,针对两个指定的日志级别,如果不指定的话则使用父日志记录器(rootLogger)的,指定了就覆盖掉了父日志记录器的
#设置dao包的日志配置
#log4j.logger.com.xun.log4j.dao = debug, appendDao
#设置service包的日志配置
#log4j.logger.com.xun.log4j.service = info, appendService

#druid日志
log4j.logger.druid.sql=error
log4j.logger.druid.sql.DataSource=error
log4j.logger.druid.sql.Connection=error
log4j.logger.druid.sql.Statement=debug
log4j.logger.druid.sql.ResultSet=error

#控制台的配置
log4j.appender.appendConsole=org.apache.log4j.ConsoleAppender
#Threshold:设置此appender的日志级别,这里会覆盖全局的(rootLogger中)定义的日志级别
log4j.appender.appendConsole.Threshold=debug
#设置日志输出编码方式为UTF-8,如果不指定,会以当前运行操作系统的编码方式记录
log4j.appender.appendConsole.encoding=UTF-8
log4j.appender.appendConsole.layout=org.apache.log4j.PatternLayout
log4j.appender.appendConsole.layout.ConversionPattern=%d %p [%c] - <%m>%n

#appendDao配置
log4j.appender.appendDao=org.apache.log4j.FileAppender
log4j.appender.appendDao.layout=org.apache.log4j.PatternLayout
log4j.appender.appendDao.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}]--[%t] [%p] -%l -%m%n%n
log4j.appender.appendDao.Append=false
log4j.appender.appendDao.File=dao.txt

#appendService
log4j.appender.appendService=org.apache.log4j.FileAppender
log4j.appender.appendService.layout=org.apache.log4j.PatternLayout
log4j.appender.appendService.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}]--[%t] [%p] -%l -%m%n%n
log4j.appender.appendService.Append=false
log4j.appender.appendService.File=service.txt

druid 常见问题

https://blog.csdn.net/maguanghui_2012/article/details/51605330

重复打印日志信息

  • filters 与proxyFilters 重复配置

  • log4j.properties中log4j.rootLogger已经指定输出位置Console,而log4j.logger重复指定输出位置,示例

    1
    2
    3
    log4j.logger.druid.sql.Statement=debug, Console
    应改为:
    log4j.logger.druid.sql.Statement=debug

防御SQL注入攻击

WallFilter的功能是防御SQL注入攻击。它是基于SQL语法分析,理解其中的SQL语义,然后做处理的,智能,准确,误报率低。

Druid 数据库用户密码加密代码实现

明文密码 + 私钥 (privateKey) 加密 = 加密密码

加密密码 + 公钥 (publicKey) 解密 = 明文密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.alibaba.druid.filter.config.ConfigTools;
public class DruidTest {
public static void main(String[] args) throws Exception {
//密码明文
String password = "12345";

System.out.println("密码[ "+password+" ]的加密信息如下:\n");

String [] keyPair = ConfigTools.genKeyPair(512);
//私钥
String privateKey = keyPair[0];
//公钥
String publicKey = keyPair[1];
//用私钥加密后的密文
password = ConfigTools.encrypt(privateKey, password);

System.out.println("privateKey:"+privateKey);
System.out.println("publicKey:"+publicKey);
System.out.println("password:"+password);
String decryptPassword=ConfigTools.decrypt(publicKey, password);
System.out.println("decryptPassword:"+decryptPassword);
}
}