本文介绍 Spring Boot 2 配置阿里巴巴 Druid 数据库连接池的方法。
目录
- 阿里巴巴 Druid 简介
- 开发环境
- 基础示例
- 总结
阿里巴巴 Druid 简介
Druid 是阿里巴巴开源的数据库连接池解决方案,因为有阿里巴巴背书,可以认为 Druid 是当今国内 Java 开发中最流行的数据库连接池。
除 Druid 外流行的 Java 数据库连接池还有:
-> HikariCP,Spring Boot 2 默认使用的数据库连接池
-> C3P0,据称因其性能优秀,Hibernate 发行包中默认使用此连接池
-> Apache Commons DBCP,Apache 开源的数据库连接池
本文不对 Druid 和其它数据库连接池方案做任何对比,只介绍 Spring Boot 集成 Druid 的基础配置和应用。Druid 官方已对常见问题进行了说明,如果想深入了解 Druid 还需详细阅读 Druid 官方文档,最好看一下 Druid 源码。
Druid 官方也针对 Spring Boot 提供了 druid-spring-boot-starter,本文中示例大多也是参考官方文档实现。
开发环境
- JDK 8
- MySQL 5.6.x
基础示例
- 创建存储用户信息的数据表。
CREATE TABLE `test`.`user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`account` VARCHAR(30) NOT NULL,
`name` VARCHAR(60) NOT NULL,
`birth` DATE NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
UNIQUE INDEX `account_UNIQUE` (`account` ASC));
创建 Spring Boot 工程,参考:IntelliJ IDEA 创建 Spring Boot 工程。
在生成的
pom
文件中添加以下依赖:
-
mysql-connector-java
:MySQL 数据库驱动 -
spring-boot-starter-jdbc
:Spring JDBC 支持 -
druid-spring-boot-starter
:帮助在 Spring Boot 项目中轻松集成 Druid 数据库连接池和监控 -
junit
:单元测试
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<groupId>tutorial.spring.boot</groupId>
<artifactId>spring-boot-druid</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-druid</name>
<description>Spring Boot Alibaba Druid</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 在
/resources
目录下添加数据源配置文件application-datasource-single.yml
。
spring:
datasource:
# JDBC配置
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=true
username: root
password: 123456
# 连接池配置
druid:
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时,默认0
initial-size: 3
# 最大连接池数量,默认8
max-active: 5
# 最小连接池数量
min-idle: 1
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降
# 如果需要可以通过配置useUnfairLock属性为true使用非公平锁
max-wait: 30000
# 是否缓存preparedStatement,也就是PSCache
# PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。默认false
pool-prepared-statements: true
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true
# 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100,默认-1
max-pool-prepared-statement-per-connection-size: 100
# 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'
# 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用
validation-query: SELECT 1
# 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
validation-query-timeout: 30000
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,默认true
test-on-borrow: true
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能,默认false
test-on-return: true
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
# 建议配置为true,不影响性能,并且保证安全性,默认false
test-while-idle: true
# 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,默认false
keep-alive: true
# 有两个含义:
# 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
# 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
# 默认1分钟
time-between-eviction-runs-millis: 60000
# 连接保持空闲而不被驱逐的最小时间
min-evictable-idle-time-millis: 300000
- 修改配置文件
/resources/application.yml
,激活数据源配置。
spring:
profiles:
active: datasource-single
- 新建
DataSourceConfig
配置类,在工程创建时实例化一个Spring JdbcTemplate
实例(Spring JdbcTemplate
是Spring
框架提供的对JDBC
的简单封装,JdbcOperations
是JdbcTemplate
实现的接口)
package tutorial.spring.boot.druid.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
public JdbcOperations jdbcOperations(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
- 编写领域模型类,通常类属性和表中字段一一对应
package tutorial.spring.boot.druid.domain;
import java.time.LocalDateTime;
import java.util.Objects;
public class User {
private Long id;
private String account;
private String name;
private LocalDateTime birth;
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getBirth() {
return birth;
}
public void setBirth(LocalDateTime birth) {
this.birth = birth;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(account, user.account) &&
Objects.equals(name, user.name) &&
Objects.equals(birth, user.birth);
}
@Override
public int hashCode() {
return Objects.hash(account, name, birth);
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", account='" + account + '\'' +
", name='" + name + '\'' +
", birth=" + birth +
'}';
}
}
注意:重写了 equals
和 hashCode
方法便于后续测试比较。
- 数据访问层(DAO)接口代码
package tutorial.spring.boot.druid.dao;
import tutorial.spring.boot.druid.domain.User;
public interface IUserDao {
/**
* 新增
*/
int insert(User user);
/**
* 查询
*/
User get(String account);
/**
* 更新
*/
int update(User user);
/**
* 删除
*/
int delete();
}
- 数据访问层(DAO)接口实现代码
package tutorial.spring.boot.druid.dao.impl;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.stereotype.Repository;
import tutorial.spring.boot.druid.dao.IUserDao;
import tutorial.spring.boot.druid.domain.User;
import java.sql.ResultSet;
import java.sql.SQLException;
@Repository
public class UserDaoImpl implements IUserDao {
private JdbcOperations jdbcOperations;
public UserDaoImpl(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
@Override
public int insert(User user) {
String sql = "INSERT INTO `user` (`account`, `name`, `birth`) VALUES (?,?,?)";
return jdbcOperations.update(sql, user.getAccount(), user.getName(), user.getBirth());
}
@Override
public User get(String account) {
String sql = "SELECT `id`, `account`, `name`, `birth` FROM `user` WHERE `account`=?";
return jdbcOperations.queryForObject(sql, this::mapResult, account);
}
private User mapResult(ResultSet rs, int row)
throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setAccount(rs.getString("account"));
user.setName(rs.getString("name"));
user.setBirth(rs.getTimestamp("birth").toLocalDateTime());
return user;
}
@Override
public int update(User user) {
String sql = "UPDATE `user` SET `name`=?, `birth`=? WHERE `account`=?";
return jdbcOperations.update(sql, user.getName(), user.getBirth(), user.getAccount());
}
@Override
public int delete() {
String sql = "DELETE FROM `user`";
return jdbcOperations.update(sql);
}
}
- 单元测试
package tutorial.spring.boot.druid.dao;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import tutorial.spring.boot.druid.domain.User;
import java.time.LocalDateTime;
@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserDaoTest {
private static User user;
@Autowired
private IUserDao userDao;
@BeforeClass
public static void init() {
user = new User();
user.setAccount("admin1");
user.setName("Administrator");
user.setBirth(LocalDateTime.of(1985, 1, 1, 0, 0, 0));
}
@Test
public void test() {
Assert.assertNotNull(userDao);
}
@Test
public void test1Insert() {
Assert.assertEquals(1, userDao.insert(user));
}
@Test
public void test2Query() {
User result = userDao.get(user.getAccount());
System.out.println(result);
Assert.assertEquals(user, result);
}
@Test
public void test3Update() {
user.setName("Master");
user.setBirth(LocalDateTime.of(1970, 1, 1, 0, 0, 0));
Assert.assertEquals(1, userDao.update(user));
}
@Test
public void test4Delete() {
Assert.assertTrue(userDao.delete() > 0);
}
}
测试结果略。
总结
本文中的单元测试存在缺陷,未添加事务回滚,实际开发中为避免单元测试产生的数据污染数据库,通常要添加事务回滚,请大家务必重视。