六、Druid关联
6.1、Web关联监控配置
WebStatFilter用于采集web-jdbc关联监控的数据。
web.xml配置:
<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>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
exlusions配置:
经常需要排除一些不必要的url,比如.js,/jslib/等等。配置在init-param中。比如:
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
sessionStatMaxCount配置:
缺省sessionStatMaxCount是1000个。你可以按需要进行配置,比如:
<init-param>
<param-name>sessionStatMaxCount</param-name>
<param-value>1000</param-value>
</init-param>
sessionStatEnable配置:
你可以关闭session统计功能,比如:
<init-param>
<param-name>sessionStatEnable</param-name>
<param-value>false</param-value>
</init-param>
principalSessionName配置:
你可以配置principalSessionName,使得druid能够知道当前的session的用户是谁。比如:
<init-param>
<param-name>principalSessionName</param-name>
<param-value>xxx.user</param-value>
</init-param>
根据需要,把其中的xxx.user修改为你user信息保存在session中的sessionName。
注意:如果你session中保存的是非string类型的对象,需要重载toString方法。
principalCookieName:
如果你的user信息保存在cookie中,你可以配置principalCookieName,使得druid知道当前的user是谁
<init-param>
<param-name>principalCookieName</param-name>
<param-value>xxx.user</param-value>
</init-param>
根据需要,把其中的xxx.user修改为你user信息保存在cookie中的cookieName
profileEnable:
druid 0.2.7版本开始支持profile,配置profileEnable能够监控单个url调用的sql列表。
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>


6.2.Spring关联监控配置
Druid提供了Spring和Jdbc的关联监控。
配置spring
com.alibaba.druid.support.spring.stat.DruidStatInterceptor是一个标准的Spring MethodInterceptor。
灵活进行AOP配置。
Spring AOP的配置文档:
http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop-api.html
按类型拦截配置:
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean id="druid-type-proxyCreator" class="com.alibaba.druid.support.spring.stat.BeanTypeAutoProxyCreator">
<!-- 所有ABCInterface的派生类被拦截监控 -->
<property name="targetBeanType" value="xxxx.ABCInterface" />
<property name="interceptorNames">
<list>
<value>druid-stat-interceptor</value>
</list>
</property>
</bean>
方法名正则匹配拦截配置:
<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.mycompany.service.*</value>
<value>com.mycompany.dao.*</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>
有些情况下,可能你需要配置proxy-target-class,例如:
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>
按照BeanId来拦截配置:
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="proxyTargetClass" value="true" />
<property name="beanNames">
<list>
<!-- 这里配置需要拦截的bean id列表 -->
<value>xxx-dao</value>
<value>xxx-service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>druid-stat-interceptor</value>
</list>
</property>
</bean>
七、Druid防御
Druid提供了WallFilter,它是基于SQL语义分析来实现防御SQL注入攻击的。
这个文档提供基于Spring的各种配置方式。
使用缺省配置的WallFilter:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="wall"/>
</bean>
结合其他Filter一起使用:
WallFilter可以结合其他Filter一起使用,例如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="wall,stat"/>
</bean>
这样,拦截检测的时间不在StatFilter统计的SQL执行时间内。
如果希望StatFilter统计的SQL执行时间内,则使用如下配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="stat,wall"/>
</bean>
指定dbType
有时候,一些应用框架做了自己的JDBC Proxy Driver,是的DruidDataSource无法正确识别数据库的类型,则需要特别指定,如下:
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
<property name="dbType" value="mysql" />
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="proxyFilters">
<list>
<ref bean="wall-filter"/>
</list>
</property>
</bean>
指定配置装载的目录
缺省情况下,配置装载的目录如下:
| 数据库类型 | 目录 | 
| mysql | META-INF/druid/wall/mysql | 
| oracle | META-INF/druid/wall/oracle | 
| sqlserver | META-INF/druid/wall/sqlserver | 
| postgres | META-INF/druid/wall/postgres | 
从配置目录中以下文件中读取配置:
deny-variant.txt deny-schema.txt deny-function.txt deny-table.txt deny-object.txt
指定配置装载的目录是可以指定,例如:
<bean id="wall-filter-config" class="com.alibaba.druid.wall.WallConfig" init-method="init">
<!-- 指定配置装载的目录 -->
<property name="dir" value="META-INF/druid/wall/mysql" />
</bean>
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
<property name="dbType" value="mysql" />
<property name="config" ref="wall-filter-config" />
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="proxyFilters">
<list>
<ref bean="wall-filter"/>
</list>
</property>
</bean>
WallConfig详细说明
本身的配置
| 配置项 | 缺省值 | 
| dir | 按照dbType分别配置:  | 
拦截配置-语句
| 配置项 | 缺省值 | 描述 | 
| selelctAllow | true | 是否允许执行SELECT语句 | 
| selectAllColumnAllow | true | 是否允许执行SELECT * FROM T这样的语句。如果设置为false,不允许执行select * from t,但select * from (select id, name from t) a。这个选项是防御程序通过调用select *获得数据表的结构信息。 | 
| selectIntoAllow | true | SELECT查询中是否允许INTO字句 | 
| deleteAllow | true | 是否允许执行DELETE语句 | 
| updateAllow | true | 是否允许执行UPDATE语句 | 
| insertAllow | true | 是否允许执行INSERT语句 | 
| replaceAllow | true | 是否允许执行REPLACE语句 | 
| mergeAllow | true | 是否允许执行MERGE语句,这个只在Oracle中有用 | 
| callAllow | true | 是否允许通过jdbc的call语法调用存储过程 | 
| setAllow | true | 是否允许使用SET语法 | 
| truncateAllow | true | truncate语句是危险,缺省打开,若需要自行关闭 | 
| createTableAllow | true | 是否允许创建表 | 
| alterTableAllow | true | 是否允许执行Alter Table语句 | 
| dropTableAllow | true | 是否允许修改表 | 
| commentAllow | false | 是否允许语句中存在注释,Oracle的用户不用担心,Wall能够识别hints和注释的区别 | 
| noneBaseStatementAllow | false | 是否允许非以上基本语句的其他语句,缺省关闭,通过这个选项就能够屏蔽DDL。 | 
| multiStatementAllow | false | 是否允许一次执行多条语句,缺省关闭 | 
| useAllow | true | 是否允许执行mysql的use语句,缺省打开 | 
| describeAllow | true | 是否允许执行mysql的describe语句,缺省打开 | 
| showAllow | true | 是否允许执行mysql的show语句,缺省打开 | 
| commitAllow | true | 是否允许执行commit操作 | 
| rollbackAllow | true | 是否允许执行roll back操作 | 
如果把selectIntoAllow、deleteAllow、updateAllow、insertAllow、mergeAllow都设置为false,这就是一个只读数据源了。
拦截配置-永真条件
| 配置项 | 缺省值 | 描述 | 
| selectWhereAlwayTrueCheck | true | 检查SELECT语句的WHERE子句是否是一个永真条件 | 
| selectHavingAlwayTrueCheck | true | 检查SELECT语句的HAVING子句是否是一个永真条件 | 
| deleteWhereAlwayTrueCheck | true | 检查DELETE语句的WHERE子句是否是一个永真条件 | 
| deleteWhereNoneCheck | false | 检查DELETE语句是否无where条件,这是有风险的,但不是SQL注入类型的风险 | 
| updateWhereAlayTrueCheck | true | 检查UPDATE语句的WHERE子句是否是一个永真条件 | 
| updateWhereNoneCheck | false | 检查UPDATE语句是否无where条件,这是有风险的,但不是SQL注入类型的风险 | 
| conditionAndAlwayTrueAllow | false | 检查查询条件(WHERE/HAVING子句)中是否包含AND永真条件 | 
| conditionAndAlwayFalseAllow | false | 检查查询条件(WHERE/HAVING子句)中是否包含AND永假条件 | 
| conditionLikeTrueAllow | true | 检查查询条件(WHERE/HAVING子句)中是否包含LIKE永真条件 | 
其他拦截配置
| 配置项 | 缺省值 | 描述 | 
| selectIntoOutfileAllow | false | SELECT ... INTO OUTFILE 是否允许,这个是mysql注入攻击的常见手段,缺省是禁止的 | 
| selectUnionCheck | true | 检测SELECT UNION | 
| selectMinusCheck | true | 检测SELECT MINUS | 
| selectExceptCheck | true | 检测SELECT EXCEPT | 
| selectIntersectCheck | true | 检测SELECT INTERSECT | 
| mustParameterized | false | 是否必须参数化,如果为True,则不允许类似WHERE ID = 1这种不参数化的SQL | 
| strictSyntaxCheck | true | 是否进行严格的语法检测,Druid SQL Parser在某些场景不能覆盖所有的SQL语法,出现解析SQL出错,可以临时把这个选项设置为false,同时把SQL反馈给Druid的开发者。 | 
| conditionOpXorAllow | false | 查询条件中是否允许有XOR条件。XOR不常用,很难判断永真或者永假,缺省不允许。 | 
| conditionOpBitwseAllow | true | 查询条件中是否允许有"&"、"~"、"|"、"^"运算符。 | 
| conditionDoubleConstAllow | false | 查询条件中是否允许连续两个常量运算表达式 | 
| minusAllow | true | 是否允许SELECT * FROM A MINUS SELECT * FROM B这样的语句 | 
| intersectAllow | true | 是否允许SELECT * FROM A INTERSECT SELECT * FROM B这样的语句 | 
| constArithmeticAllow | true | 拦截常量运算的条件,比如说WHERE FID = 3 - 1,其中"3 - 1"是常量运算表达式。 | 
| limitZeroAllow | false | 是否允许limit 0这样的语句 | 
禁用对象检测配置
| 配置项 | 缺省值 | 描述 | 
| tableCheck | true | 检测是否使用了禁用的表 | 
| schemaCheck | true | 检测是否使用了禁用的Schema | 
| functionCheck | true | 检测是否使用了禁用的函数 | 
| objectCheck | true | 检测是否使用了“禁用对对象” | 
| variantCheck | true | 检测是否使用了“禁用的变量” | 
| readOnlyTables | 空 | 指定的表只读,不能够在SELECT INTO、DELETE、UPDATE、INSERT、MERGE中作为"被修改表"出现 | 
Jdbc相关配置
| 配置项 | 缺省值 | 描述 | 
| metadataAllow | true | 是否允许调用Connection.getMetadata方法,这个方法调用会暴露数据库的表信息 | 
| wrapAllow | true | 是否允许调用Connection/Statement/ResultSet的isWrapFor和unwrap方法,这两个方法调用,使得有办法拿到原生驱动的对象,绕过WallFilter的检测直接执行SQL。 | 
WallFiler配置说明
| 配置项 | 缺省值 | 描述 | 
| logViolation | false | 对被认为是攻击的SQL进行LOG.error输出 | 
| throwException | true | 对被认为是攻击的SQL抛出SQLExcepton | 
| config | 
 | 
 | 
| provider | 
 | 
 | 
刚开始引入WallFilter的时候,把logViolation设置为true,而throwException设置为false。就可以观察是否存在违规的情况,同时不影响业务运行。
八、Druid参考
不同的业务场景需求不同,你可以使用我们的参考配置,但建议你仔细阅读相关文档,了解清楚之后做定制配置。
以下是一个参考的连接池配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT ‘x‘" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
通常来说,只需要修改initialSize、minIdle、maxActive。
如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。分库分表较多的数据库,建议配置为false。
十、Druid日志
Druid提供了Log4jFilter、CommonsLogFilter和Slf4jFilter,具体配置看这里:
Druid内置提供了三种LogFilter(Log4jFilter、CommonsLogFilter、Slf4jLogFilter),用于输出JDBC执行的日志。这些Filter都是Filter-Chain扩展机制中的Filter,所以配置方式可以参考这里:Filter配置
10.1、别名映射
在druid-xxx.jar!/META-INF/druid-filter.properties文件中描述了这三种Filter的别名
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
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
他们的别名分别是log4j、slf4j、commonlogging和commonLogging。其中commonlogging和commonLogging只是大小写不同。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
... ...
<property name="filters" value="stat,log4j" />
</bean>
10.2、loggerName映射
   LogFilter都是缺省使用四种不同的Logger执行输出,看实现代码:
public abstract class LogFilter {
protected String dataSourceLoggerName = "druid.sql.DataSource";
protected String connectionLoggerName = "druid.sql.Connection";
protected String statementLoggerName = "druid.sql.Statement";
protected String resultSetLoggerName = "druid.sql.ResultSet";
}
  你可以根据你的需要修改,在log4j.properties文件上做配置时,注意配置使用相关的logger。 
10.3、配置输出日志
缺省输入的日志信息全面,但是内容比较多,有时候我们需要定制化配置日志输出。
<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
<property name="resultSetLogEnabled" value="false" />
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="proxyFilters">
<list>
<ref bean="log-filter"/>
</list>
</property>
</bean>
| 参数 | 说明 | 
| dataSourceLogEnabled | 所有DataSource相关的日志 | 
| connectionLogEnabled | 所有连接相关的日志 | 
| connectionLogErrorEnabled | 所有连接上发生异常的日志 | 
| statementLogEnabled | 所有Statement相关的日志 | 
| statementLogErrorEnabled | 所有Statement发生异常的日志 | 
| resultSetLogEnabled | 
 | 
| resultSetLogErrorEnabled | 
 | 
| connectionConnectBeforeLogEnabled | 
 | 
| connectionConnectAfterLogEnabled | 
 | 
| connectionCommitAfterLogEnabled | 
 | 
| connectionRollbackAfterLogEnabled | 
 | 
| connectionCloseAfterLogEnabled | 
 | 
| statementCreateAfterLogEnabled | 
 | 
| statementPrepareAfterLogEnabled | 
 | 
| statementPrepareCallAfterLogEnabled | 
 | 
| statementExecuteAfterLogEnabled | 
 | 
| statementExecuteQueryAfterLogEnabled | 
 | 
| statementExecuteUpdateAfterLogEnabled | 
 | 
| statementExecuteBatchAfterLogEnabled | 
 | 
| statementCloseAfterLogEnabled | 
 | 
| statementParameterSetLogEnabled | 
 | 
| resultSetNextAfterLogEnabled | 
 | 
| resultSetOpenAfterLogEnabled | 
 | 
| resultSetCloseAfterLogEnabled | 
 | 
10.4、log4j.properties配置
如果你使用log4j,可以通过log4j.properties文件配置日志输出选项,例如:
log4j.logger.druid.sql=warn,stdout
log4j.logger.druid.sql.DataSource=warn,stdout
log4j.logger.druid.sql.Connection=warn,stdout
log4j.logger.druid.sql.Statement=warn,stdout
log4j.logger.druid.sql.ResultSet=warn,stdout
10.5、输出可执行的SQL
Java启动参数配置方式
-Ddruid.log.stmt.executableSql=true
logFilter参数直接配置
<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
<property name="statementExecutableSqlLogEnable" value="true" />
</bean>
十一、Druid泄露
Druid提供了多种监测连接泄漏的手段
连接泄漏监测
当程序存在缺陷时,申请的连接忘记关闭,这时候,就存在连接泄漏了。Druid提供了RemoveAbandanded相关配置,用来关闭长时间不使用的连接。例如:
配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
... ...
<property name="removeAbandoned" value="true" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 1800秒,也就是30分钟 -->
<property name="logAbandoned" value="true" />
<!-- 关闭abanded连接时输出错误日志 -->
... ...
</bean>
配置removeAbandoned对性能会有一些影响,建议怀疑存在泄漏之后再打开。在上面的配置中,如果连接超过30分钟未关闭,就会被强行回收,并且日志记录连接申请时的调用堆栈。
内置监控页面查看未关闭连接堆栈信息
当removeAbandoned=true之后,可以在内置监控界面datasource.html中的查看ActiveConnection StackTrace属性的,可以看到未关闭连接的具体堆栈信息,从而方便查出哪些连接泄漏了。
web应用
     如果你的应用配置了WebStatFilter 
     在内置监控页面weburi-detail.html中,查看JdbcPoolConnectionOpenCount和JdbcPoolConnectionCloseCount属性,如果不相等,就是泄漏了。
 
        