简介
Java 程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Druid 已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Spring Boot 2.0 以上默认使用 Hikari 数据源,Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源。
连接
当我们程序需要访问数据库时,需要创建一个本地到数据库服务的网络连接,此时本地代码就相当于一个数据库的客户端,可以通过这个连接去访问数据、执行sql,如下
Driver driver = new com.mysql.cj.jdbc.Driver();
// 创建连接
Connection con = driver.connect(JDBC_URL, props);
Statement statement = con.createStatement();
ResultSet resultSet = statement.executeQuery("show tables");
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
}
con.close();
池化技术
由于我们的代码需要不断与数据库交互读取数据,如果每次请求数据都创建一个连接的话,网络开销是很大的,也会导致我们的程序比较慢,同时连接如果太多也会给数据库造成压力
为了解决这个问题,就有了池化技术,把创建好的连接放在池里,用时去池里获取,节省了创建连接的时间,也可以通过配置来限定池的最大连接数等
连接池最常用的工具基本就是阿里的Druid了,简单使用如下
// druid 数据源
DruidDataSource druidDataSource = new DruidDataSource();
// 数据源配置
druidDataSource.setUrl(JDBC_URL);
druidDataSource.setUsername(USERNAME);
druidDataSource.setPassword(PASSWORD);
// 初始化
druidDataSource.init();
// 获取表名
Connection con = druidDataSource.getConnection();
Statement statement = con.createStatement();
ResultSet resultSet = statement.executeQuery("show tables");
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
}
con.close();
可以看到使用了Druid,获取连接不再是直接使用驱动创建连接,而是通过DruidDataSource
对象获取连接。
参数配置及说明
配置项名称 | 缺省值 | 说明 | java set方法定义 |
---|---|---|---|
connectProperties | {} | map方式放入自定义的key和value,在Filter等地方可以获取该信息进行相应逻辑控制 | public void com.alibaba.druid.pool.DruidDataSource.setConnectProperties(java.util.Properties) |
connectionProperties | null | 字符串方式放入自定义的key和value,键值对用分号隔开,比如“a=b;c=d”,传入空白字符串表示清空属性,实际拆分字符串后赋值给connectProperties,在Filter等地方可以获取该信息进行相应逻辑控制 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setConnectionProperties(java.lang.String) |
connectTimeout | 0 | 新增的控制创建连接时的socket连接最大等待超时,单位是毫秒,默认0表示永远等待,工作原理是在创建连接时将该值设置到对应数据库驱动的属性信息中由其JDBC驱动进行控制 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setConnectTimeout(int) |
connectionInitSqls | [] | 数组方式定义物理连接初始化的时候执行的1到多条sql语句,比如连接MySQL数据库使用低版本驱动的情况下,想使用utf8mb4,则可以配置sql为: set NAMES 'utf8mb4' | public void com.alibaba.druid.pool.DruidAbstractDataSource.setConnectionInitSqls(java.util.Collection) |
createScheduler | null | 可以使用定时线程池方式异步创建连接,比起默认的单线程创建连接方式,经实际验证这种更可靠 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setCreateScheduler(java.util.concurrent.ScheduledExecutorService) |
dbType | null | 对于不是Druid自动适配支持的db类型,可以强制指定db类型,字符串值来自com.alibaba.druid.DbType的枚举名 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setDbType(java.lang.String) |
destroyScheduler | null | 可以使用定时线程池方式异步创建连接,比起默认的单线程创建连接方式,经实际验证这种更可靠 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setDestroyScheduler(java.util.concurrent.ScheduledExecutorService) |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName | com.alibaba.druid.pool.DruidAbstractDataSource.setDriverClassName(String) |
exceptionSorter | null | 当数据库抛出一些不可恢复的异常时,抛弃连接 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setExceptionSorter(com.alibaba.druid.pool.ExceptionSorter) |
failFast | false | null | public void com.alibaba.druid.pool.DruidAbstractDataSource.setFailFast(boolean) |
filters | 属性类型是逗号隔开的字符串,通过别名的方式配置扩展插件,插件别名列表请参考druid jar包中的 /META-INF/druid-filter.properties,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall 防御sql注入的filter:wall | com.alibaba.druid.pool.DruidAbstractDataSource.setFilters(String) | |
initialSize | 0 | 初始化数据源时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setInitialSize(int) |
keepAlive | false | 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。实际项目中建议配置成true | public void com.alibaba.druid.pool.DruidDataSource.setKeepAlive(boolean) |
keepAliveBetweenTimeMillis | 120000 | null | public void com.alibaba.druid.pool.DruidAbstractDataSource.setKeepAliveBetweenTimeMillis(long) |
logAbandoned | false | 在开启removeAbandoned为true的情况,可以开启该设置,druid在销毁未及时关闭的连接时,则会输出日志信息,便于定位连接泄露问题 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setLogAbandoned(boolean) |
loginTimeout | 单位是秒,底层调用DriverManager全局静态方法 | java.sql.DriverManager.setLoginTimeout(int) | |
maxActive | 8 | 连接池最大活跃连接数量,当连接数量达到该值时,再获取新连接时,将处于等待状态,直到有连接被释放,才能借用成功 | public void com.alibaba.druid.pool.DruidDataSource.setMaxActive(int) |
maxEvictableIdleTimeMillis | 25200000 | null | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMaxEvictableIdleTimeMillis(long) |
maxIdle | 8 | 已经彻底废弃,配置了也没效果,以maxActive为准 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMaxIdle(int) |
maxOpenPreparedStatements | 10 | null | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMaxOpenPreparedStatements(int) |
maxPoolPreparedStatementPerConnectionSize | 10 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMaxPoolPreparedStatementPerConnectionSize(int) |
maxWait | -1 | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMaxWait(long) |
minEvictableIdleTimeMillis | 1800000 | 连接保持空闲而不被驱逐的最小时间 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMinEvictableIdleTimeMillis(long) |
minIdle | 0 | 连接池最小空闲数量 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setMinIdle(int) |
name | DataSource-**** | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。详情-点此处。 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setName(java.lang.String) |
numTestsPerEvictionRun | 3 | 不再使用,已经彻底废弃,一个DruidDataSource只支持一个EvictionRun | public void com.alibaba.druid.pool.DruidAbstractDataSource.setNumTestsPerEvictionRun(int) |
password | null | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用passwordCallback进行配置,或者使用ConfigFilter。详细看这里 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setPassword(java.lang.String) |
passwordCallback | null | 可以自定义实现定制的PasswordCallback,然后实现定制的密码解密效果 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setPasswordCallback(javax.security.auth.callback.PasswordCallback) |
phyTimeoutMillis | -1 | 强制回收物理连接的最大超时时长,大于0的情况下才生效,当物理创建之后存活的时长超过该值时,该连接会强制销毁,便于重新创建新连接,建议可以配置成7小时的毫秒值,比如25200000,这样可以规避MySQL的8小时连接断开问题 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setPhyTimeoutMillis(long) |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 | public void com.alibaba.druid.pool.DruidDataSource.setPoolPreparedStatements(boolean) |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 | com.alibaba.druid.pool.DruidAbstractDataSource.setProxyFilters(List) | |
queryTimeout | 0 | 控制查询结果的最大超时,单位是秒,大于0才生效,最终底层调用是java.sql.Statement.setQueryTimeout(int) | public void com.alibaba.druid.pool.DruidAbstractDataSource.setQueryTimeout(int) |
removeAbandoned | false | 是否回收泄露的连接,默认不开启,建议只在测试环境设置未开启,利用测试环境发现业务代码中未正常关闭连接的情况 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setRemoveAbandoned(boolean) |
removeAbandonedTimeoutMillis | 300000 | 开启回收泄露连接的最大超时,默认300秒表示连接被借出超过5分钟后,且removeAbandoned开启的情况下,强制关闭该泄露连接 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setRemoveAbandonedTimeoutMillis(long) |
socketTimeout | 0 | 新增的控制创建连接时的socket最大读超时,单位是毫秒,默认0表示永远等待,配置成10000则表示db操作如果在10秒内未返回应答,将抛出异常,工作原理是在创建连接时将该值设置到对应数据库驱动的属性信息中由其JDBC驱动进行控制 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setSocketTimeout(int) |
testOnBorrow | false | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,其实一般情况下都可以开启,只有性能要求极其高且连接使用很频繁的情况下才有必要禁用。 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setTestOnBorrow(boolean) |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,这个一般不需要开启。 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setTestOnReturn(boolean) |
testWhileIdle | true | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setTestWhileIdle(boolean) |
timeBetweenEvictionRunsMillis | 60000 | 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setTimeBetweenEvictionRunsMillis(long) |
transactionQueryTimeout | 0 | 控制查询结果的最大超时,单位是秒,大于0才生效,最终是在开启事务的情况下底层调用java.sql.Statement.setQueryTimeout(int) | public void com.alibaba.druid.pool.DruidAbstractDataSource.setTransactionQueryTimeout(int) |
url | 连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | com.alibaba.druid.pool.DruidAbstractDataSource.setUrl(String) | |
username | null | 连接数据库的用户名 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setUsername(java.lang.String) |
validationQuery | null | 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setValidationQuery(java.lang.String) |
validationQueryTimeout | -1 | 单位:秒,检测连接是否有效的超时时间,大于0才生效。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 | public void com.alibaba.druid.pool.DruidAbstractDataSource.setValidationQueryTimeout(int) |
实际配置示例
Spring Boot 方式(推荐)
依赖
<!--引入druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
连接池配置:
spring:
datasource:
#数据源基本配置
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_crud
type: com.alibaba.druid.pool.DruidDataSource
#数据源其他配置
druid:
#配置初始化大小、最小、最大线程数
initialSize: 5
minIdle: 5
#CPU核数+1,也可以大些但不要超过20,数据库加锁时连接过多性能下降
maxActive: 20
# 最大等待时间,内网:800,外网:1200(三次握手1s)
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
#配置一个连接在池中最大空间时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
# 设置从连接池获取连接时是否检查连接有效性,true检查,false不检查
testOnBorrow: true
# 设置从连接池归还连接时是否检查连接有效性,true检查,false不检查
testOnReturn: true
#可以支持PSCache(提升写入、查询效率)
poolPreparedStatements: true
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
实现密码加密
目前配置文件中配置的数据库用户名和密码都是明文的,这在某些情况下是不被允许的,那正好druid可以对其进行加密,按以下操作即可:
- 利用工具类生成加密后密码和公钥
public static void main(String[] args) throws Exception {
String password = "root123";
System.out.println("明文密码: " + password);
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);
}
运行后得到:
利用命令加密
java -cp D:/xxxx/druid-1.2.8/druid-1.2.8.jar com.alibaba.druid.filter.config.ConfigTools root java -cp D:/xxxx/druid-1.2.8/druid-1.2.8.jar com.alibaba.druid.filter.config.ConfigTools 123456
注:123456为你数据库的密码
对我们有用的是publicKey和加密后的password,这个publickey主要是用来解密的秘钥
- 修改配置文件
用上面得到的密码和publicKey替换配置文件中的密文:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKXfJyPsQ1rvSQXO+8m1TrIWS5XSSwzwDBIjPGZNbpZ10+Tai7k1GMzF6eufgMNWlNwOHJvxIYwjrts8b4UbSiECAwEAAQ==
druid:
url: jdbc:mysql://lcoahost:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: ENC(EChlZH6XdPa129Ii3akF/92alTAIifYXdbFJaqKlmcQNF0a/QRoaAook16eO72vw4S4Ut8nXeoGOMdgFkQuW+A==)
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
#validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
#login-username: admin
#login-password: admin
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
config:
enabled: true
connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}
配置文件跟加密相关的属性
spring.datasource.druid.connection-properties
这个属性配置的value是键值对,其中config.decrypt=true表示要进行解密,config.decrypt.key=${spring.datasource.publicKey}注入要解密需要的公钥
spring.datasource.druid.filter.config.enabled=true
开启configFilter, 这个不开启是没办法进行解密操作的
本文由博客一文多发平台 OpenWrite 发布!