Druid 是一个 JDBC 组件库,包含数据库连接池、SQL Parser 等组件, 被大量业务和技术产品使用或集成,经历过最严苛线上业务场景考验,是你值得信赖的技术产品。
Druid有以下几点优势或好处:
替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter机制,很方便编写JDBC层的扩展插件。
1、Maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
2、纯代码使用:
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
class TestDruid {
// 数据库驱动名称
final static String driver = "com.mysql.cj.jdbc.Driver";
// 数据库连接地址
final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT";
// 数据库用户名
final static String user = "test";
// 数据库密码
final static String passwd = "1";
// 连接池初始化大小
final static int initialSize = 5;
// 连接池最小空闲
final static int minPoolSize = 10;
// 连接池最大连接数量
final static int maxPoolSize = 50;
// 最小逐出时间,100秒
final static int maxIdleTime = 100000;
// 连接失败重试次数
final static int retryAttempts = 10;
// 当连接池连接耗尽时获取连接数
final static int acquireIncrement = 5;
// 查询次数
final static int count = 10;
// Druid数据源
static DruidDataSource druidDataSource = null;
@BeforeAll
static void init() {
druidDataSource = new DruidDataSource();
druidDataSource.setUsername(user);
druidDataSource.setUrl(jdbcUrl);
druidDataSource.setPassword(passwd);
druidDataSource.setDriverClassName(driver);
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMaxActive(maxPoolSize);
druidDataSource.setMaxWait(maxIdleTime);
druidDataSource.setTestWhileIdle(false);
druidDataSource.setTestOnReturn(false);
druidDataSource.setTestOnBorrow(false);
}
@Test
void test_Insert() throws SQLException {
DruidPooledConnection connection = druidDataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("INSERT INTO sys_user(username,password) VALUES(?,?)");
stmt.setString(1, "admin");
stmt.setString(2, "123456");
stmt.execute();
}
}
3、Spring/SpringMVC中使用
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName"><value>com.mysql.cj.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT</value></property>
<property name="username"><value>test</value></property>
<property name="password"><value>1</value></property>
<!-- 通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall -->
<property name="filters" value="stat,log4j" />
<!-- 最大并发连接数 -->
<property name="maxActive" value="30" />
<!-- 初始化连接数量 -->
<property name="initialSize" value="5" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="5" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<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" />
<property name="poolPreparedStatements" value="false" />
<property name="maxOpenPreparedStatements" value="100" />
<!-- 打开removeAbandoned功能(连接泄漏监测,怀疑存在泄漏之后再打开) -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
</bean>
4、DruidDataSource配置兼容DBCP,但个别配置的语意有所区别,如下表格:
配置 | 缺省值 | 说明 |
name |
| |
url |
| 连接数据库的url,不同数据库不一样。例如: |
username |
| 连接数据库的用户名 |
password |
| |
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 |
| 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: |
proxyFilters |
| 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
5、Druid监控
同Proxool一样Druid提供了监控功能,而且更强大;
配置到web.xml中,即要通过浏览器输入/druid/访问,如果设置了用户用户密码,则开始会进入登录页面,如下图。
<!-- 连接池 启用Web监控统计功能 start -->
<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,*mp3,*.swf,*.xls,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<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>admin</param-value>
</init-param>
<init-param>
<param-name>loginPassword</param-name><!-- 密码 -->
<param-value>123456</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<!-- 连接池 启用Web监控统计功能 end -->
下图是Druid官网给出的几种连接池的对比表格,从数据上来看Druid在各方面都占有很大的优势,实际情况还需要实践去检验。
LRU
LRU是一个性能关键指标,特别Oracle,每个Connection对应数据库端的一个进程,如果数据库连接池遵从LRU,有助于数据库服务器优化,这是重要的指标。在测试中,Druid、DBCP、Proxool、JBoss是遵守LRU的。BoneCP、C3P0则不是。BoneCP在mock环境下性能可能好,但在真实环境中则就不好了。
PSCache
PSCache是数据库连接池的关键指标。在Oracle中,类似SELECT NAME FROM USER WHERE ID = ?这样的SQL,启用PSCache和不启用PSCache的性能可能是相差一个数量级的。Proxool是不支持PSCache的数据库连接池,如果你使用Oracle、SQL Server、DB2、Sybase这样支持游标的数据库,那你就完全不用考虑Proxool。
PSCache-Oracle-Optimized
Oracle 10系列的Driver,如果开启PSCache,会占用大量的内存,必须做特别的处理,启用内部的EnterImplicitCache等方法优化才能够减少内存的占用。这个功能只有DruidDataSource有。如果你使用的是Oracle Jdbc,你应该毫不犹豫采用DruidDataSource。
ExceptionSorter
ExceptionSorter是一个很重要的容错特性,如果一个连接产生了一个不可恢复的错误,必须立刻从连接池中去掉,否则会连续产生大量错误。这个特性,目前只有JBossDataSource和Druid实现。Druid的实现参考自JBossDataSource。
监控
DruidDataSource自身提供有NotEmptyWaitCount、PSCahcheHitCount等有用的监控属性,通过配置StatFilter能够监控SQL的执行情况。
扩展
DruidDataSource提供基于Filter-Chain模式的模式的扩展。