springboot下使用druid-spring-boot-starter

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

Druid 声称是 Java 语言中最好的数据库连接池,Druid 能够提供强大的监控和扩展功能。spring boot starter 自动装配组件,简化组件引入的开发工作量,所以 Druid 推出了 druid-spring-boot-starter。

  1. 引入依赖

pom.xml 引入依赖包 (parent 中声明了 spring-boot-starter-parent,所以可不声明版本号):

xml 复制代码
        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <!--<version>1.2.6</version>-->
        </dependency>

2.application.properties 配置

推荐的配置:

ini 复制代码
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/yzh?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.druid.username=root
spring.datasource.druid.password=root
spring.datasource.druid.max-active=20
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.max-wait=60000
spring.datasource.druid.validation-query=select 1
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.time-between-eviction-runs-millis=60000

开发环境可打印执行的 sql,方便开发、排查问题,添加配置:

c 复制代码
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

配置详解

ini 复制代码
# 数据库地址
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/yzh?useUnicode=true&characterEncoding=utf-8&useSSL=false
# 数据库用户名
spring.datasource.druid.username=root
# 数据库密码
spring.datasource.druid.password=root
# 数据库连接池最大值
spring.datasource.druid.max-active=20
# 数据库连接池初始值
spring.datasource.druid.initial-size=5
# 数据库连接池最小空闲值
spring.datasource.druid.min-idle=5
# 池中空闲连接大于minIdle且连接空闲时间大于该值,则关闭该连接,单位毫秒(5分钟,默认30分钟)
spring.datasource.druid.min-evictable-idle-time-millis=300000
# 获取连接时最大等待时间,单位毫秒(1分钟)
spring.datasource.druid.max-wait=60000
# 检测连接是否有效时执行的sql命令
spring.datasource.druid.validation-query=select 1
# 借用连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.druid.test-on-borrow=false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.druid.test-on-return=false
# 连接空闲时检测,如果连接空闲时间大于timeBetweenEvictionRunsMillis指定的毫秒,执行validationQuery指定的SQL来检测连接是否有效
spring.datasource.druid.test-while-idle=true
# 空闲连接检查、废弃连接清理、空闲连接池大小调整的操作时间间隔,单位是毫秒(1分钟)
spring.datasource.druid.time-between-eviction-runs-millis=60000
  1. testOnBorrow 和 testOnReturn 在生产环境一般是不开启的,主要是性能考虑。失效连接主要通过 testWhileIdle 保证,如果获取到了不可用的数据库连接,一般由应用处理异常。
  2. druid 1.1.10 中的 bug 导致开启 testOnReturn 和 testOnBorrow 各有 5 倍的性能差距 (1.1.24 版本已经解决了该问题),试验发现性能大概损耗不到 10%。
  3. 如果你的应用不需要考虑高并发下的性能差距,且想要每次都获取到有效的连接,那就将 testOnReturn 和 testOnBorrow 都设置为 true。

监控有关配置:

ini 复制代码
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#是否启用StatFilter默认值false,用于采集 web-jdbc 关联监控的数据。
spring.datasource.druid.web-stat-filter.enabled=true
#需要监控的 url
spring.datasource.druid.web-stat-filter.url-pattern=/*
#排除一些静态资源,以提高效率
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#是否启用StatViewServlet(监控页面)默认值为false(考虑到安全问题默认并未启动,如需启用建议设置密码或白名单以保障安全)
spring.datasource.druid.stat-view-servlet.enabled=true
#内置的监控页面地址,例如 /druid/*,则内置监控页面的首页是 /druid/index.html
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#是否允许清空统计数据
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin

添加了监控配置后,启动项目访问 http://localhost:8080/druid/,输入上面配置的用户名 admin、密码 admin,可看到如下监控页面:

官方文档:druid/druid-spring-boot-starter at master · alibaba/druid · GitHub

github.com/alibaba/dru...

完整 pom.xml:

xml 复制代码
<?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>
 
    <!-- 父级支持,主要作用:引入默认配置;spring核心包、logger包、加载默认配置文件名等 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.6</version>
    </parent>
 
    <groupId>org.example</groupId>
    <artifactId>yzh-maven</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <spring-boot.version>2.4.6</spring-boot.version>
    </properties>
 
    <dependencies>
        <!-- springboot 基础包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
 
        <!-- springboot 测试包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <!-- springboot web包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <!-- springboot mybatis支持包 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- springboot mysql支持包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
 
        <!-- 公共包 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
 
        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <!--<version>1.2.6</version>-->
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <!-- 当运行"mvn package"进行打包时,会打包成一个可以直接运行的 JAR 文件,使用"java -jar"命令就可以直接运行-。 -->
                <!-- 打的包里面才会有maven依赖的jar包和spring boot的启动类(独立启动) -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>

HikariCP

spring boot2 默认连接池是 HikariCP,号称是性能最好的连接池,不过国内使用较多的是阿里开源的 druid 连接池,在阿里的诸多项目中经过实践验证。

druid-spring-boot-starter 自动注入原理

先回忆一下 spring boot 自动装配的原理:springboot 是在 SpringApplication.run(...) 容器启动时执行了 selectImports() 方法,找到自动配置类的全限类名去加载对应的 class,然后将自动配置类注入 Spring 容器中,即通过加载注入全限类名对应的自动配置类来完成容器启动时组件的自动装载。

那么,druid-spring-boot-starter 的自动配置类是谁?DruidDataSourceAutoConfigure。

less 复制代码
@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
 
    public DruidDataSourceAutoConfigure() {
    }
 
    @Bean(
        initMethod = "init"
    )
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}

@Configuration

加载 DruidDataSourceAutoConfigure 注入 spring 容器。

@ConditionalOnClass({DruidDataSource.class})

类路径上存在 DruidDataSource 的 class,才会加载当前类。

scala 复制代码
public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {}

看一下 Druid 的默认连接池类 DruidAbstractDataSource,是所有连接池属性默认值声明的地方:

ini 复制代码
    public DruidAbstractDataSource(boolean lockFair) {
        this.validationQuery = DEFAULT_VALIDATION_QUERY;
        this.validationQueryTimeout = -1;
        this.testOnBorrow = false;
        this.testOnReturn = false;
        this.testWhileIdle = true;
        this.poolPreparedStatements = false;
        this.sharePreparedStatements = false;
        this.maxPoolPreparedStatementPerConnectionSize = 10;
        this.inited = false;
        this.initExceptionThrow = true;
        this.logWriter = new PrintWriter(System.out);
        this.filters = new CopyOnWriteArrayList();
        this.clearFiltersEnable = true;
        this.exceptionSorter = null;
        this.maxWaitThreadCount = -1;
        this.accessToUnderlyingConnectionAllowed = true;
        this.timeBetweenEvictionRunsMillis = 60000L;
        this.numTestsPerEvictionRun = 3;
        this.minEvictableIdleTimeMillis = 1800000L;
        this.maxEvictableIdleTimeMillis = 25200000L;
        this.keepAliveBetweenTimeMillis = 120000L;
        this.phyTimeoutMillis = -1L;
        this.phyMaxUseCount = -1L;
        this.removeAbandonedTimeoutMillis = 300000L;
        this.maxOpenPreparedStatements = -1;
        this.timeBetweenConnectErrorMillis = 500L;
        this.validConnectionChecker = null;
        this.activeConnections = new IdentityHashMap();
        this.connectionErrorRetryAttempts = 1;
        this.breakAfterAcquireFailure = false;
        this.transactionThresholdMillis = 0L;
        this.createdTime = new Date();
        this.errorCount = 0L;
        this.dupCloseCount = 0L;
        this.startTransactionCount = 0L;
        this.commitCount = 0L;
        this.rollbackCount = 0L;
        this.cachedPreparedStatementHitCount = 0L;
        this.preparedStatementCount = 0L;
        this.closedPreparedStatementCount = 0L;
        this.cachedPreparedStatementCount = 0L;
        this.cachedPreparedStatementDeleteCount = 0L;
        this.cachedPreparedStatementMissCount = 0L;
        this.transactionHistogram = new Histogram(new long[]{1L, 10L, 100L, 1000L, 10000L, 100000L});
        this.dupCloseLogEnable = false;
        this.executeCount = 0L;
        this.executeQueryCount = 0L;
        this.executeUpdateCount = 0L;
        this.executeBatchCount = 0L;
        this.isOracle = false;
        this.isMySql = false;
        this.useOracleImplicitCache = true;
        this.activeConnectionLock = new ReentrantLock();
        this.createErrorCount = 0;
        this.creatingCount = 0;
        this.directCreateCount = 0;
        this.createCount = 0L;
        this.destroyCount = 0L;
        this.createStartNanos = 0L;
        this.useUnfairLock = null;
        this.useLocalSessionState = true;
        this.statLogger = new DruidDataSourceStatLoggerImpl();
        this.asyncCloseConnectionEnable = false;
        this.maxCreateTaskCount = 3;
        this.failFast = false;
        this.failContinuous = 0;
        this.failContinuousTimeMillis = 0L;
        this.initVariants = false;
        this.initGlobalVariants = false;
        this.onFatalError = false;
        this.onFatalErrorMaxActive = 0;
        this.fatalErrorCount = 0;
        this.fatalErrorCountLastShrink = 0;
        this.lastFatalErrorTimeMillis = 0L;
        this.lastFatalErrorSql = null;
        this.lastFatalError = null;
        this.connectionIdSeed = 10000L;
        this.statementIdSeed = 20000L;
        this.resultSetIdSeed = 50000L;
        this.transactionIdSeed = 60000L;
        this.metaDataIdSeed = 80000L;
        this.lock = new ReentrantLock(lockFair);
        this.notEmpty = this.lock.newCondition();
        this.empty = this.lock.newCondition();
    }

@AutoConfigureBefore({DataSourceAutoConfiguration.class})

加载当前类之后再去加载 DataSourceAutoConfiguration 类。

那为什么呢?看一下 DataSourceAutoConfiguration 类的 PooledDataSourceConfiguration:

less 复制代码
    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }
    }

Hikari.class:

less 复制代码
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({HikariDataSource.class})
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "com.zaxxer.hikari.HikariDataSource",
        matchIfMissing = true
    )
    static class Hikari {
        Hikari() {
        }
 
        @Bean
        @ConfigurationProperties(
            prefix = "spring.datasource.hikari"
        )
        HikariDataSource dataSource(DataSourceProperties properties) {
            HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setPoolName(properties.getName());
            }
 
            return dataSource;
        }
    }

PooledDataSourceConfiguration 的 @ConditionalOnMissingBean({DataSource.class, XADataSource.class}) 表示如果存在数据源就不会被加载,相应 Import 里面默认的 Hikari 连接池就不会被 spring 加载,这样就避免了数据源的冲突,这个时候可能就会有人问了,不是可以配置多数据源吗?对的,可以,这个只是 springboot 将自己的数据源关闭掉,并不会影响你自定义的。

总结:自定义的 DataSource 优先级大于默认的 DataSource(默认的 DataSource 使用的是 HikariDataSource)。

@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})

将 DruidStatProperties、DataSourceProperties 2 个配置类注入 spring 容器。

DruidStatProperties:监控有关配置类。

DataSourceProperties:数据源配置类。

@Import()

@Import 注解就是之前 xml 配置中的 import 标签,可以用于依赖包、三方包中 bean 的配置和加载。这里是 Druid 监控有关配置类的加载。

dataSource()

less 复制代码
    @Bean(
        initMethod = "init"
    )
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }

创建 DruidDataSourceWrapper(DruidDataSource 的包装类),并且在创建 Bean 之后执行 init 方法。init 方法在父类 DruidDataSource 中存在。

kotlin 复制代码
// 注入spring.datasource.druid的配置
@ConfigurationProperties("spring.datasource.druid")
public class DruidDataSourceWrapper extends DruidDataSource implements InitializingBean {
    @Autowired
    private DataSourceProperties basicProperties;
 
    public DruidDataSourceWrapper() {
    }
	// 如果没有配置spring.datasource.druid.username/password/url/driverClassName,
    // 而配置了spring.datasource.username/password/url/driverClassName,那么就使用spring.datasource.username/password/url/driverClassName的属性值
    public void afterPropertiesSet() throws Exception {
        if (super.getUsername() == null) {
            super.setUsername(this.basicProperties.determineUsername());
        }
 
        if (super.getPassword() == null) {
            super.setPassword(this.basicProperties.determinePassword());
        }
 
        if (super.getUrl() == null) {
            super.setUrl(this.basicProperties.determineUrl());
        }
 
        if (super.getDriverClassName() == null) {
            super.setDriverClassName(this.basicProperties.getDriverClassName());
        }
 
    }
    ...
}
 
@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {}
相关推荐
EatFan5 分钟前
JunoYi 框架实践:Spring Boot 项目为什么拆成 framework、module、server 三层?
java·spring boot·后端·framework·module·模块化·junoyi
汉堡大王952710 分钟前
一张图三句需求,我用 Trae Work 做了一块能看日出日落和月相的天文机械表
前端·后端·github
mudtools14 分钟前
在.NET现有系统中快速集成飞书任务分配能力
后端·c#·.net
知守观15 分钟前
ThreadLocal + 异步线程导致用户数据串号:一次跨请求数据泄漏的完整复盘
后端
前端冒菜师17 分钟前
我为什么做了 Iris,又为什么停下了它
后端·ai编程
子一!!18 分钟前
集成Spring家族的Spring论坛实战==一阶段
java·后端·spring
Ticnix19 分钟前
你的 Agent 聊到第 20 轮就"失忆"?你管理的是历史,高手管理的是上下文
后端·python·agent
不合格的程序员19 分钟前
Agent Memory架构设计与实现
后端·ai编程
用户938169125536026 分钟前
分页查询 Out of sort memory 问题
后端
xyLJ32 分钟前
为什么 Spring Boot 自动配置了 Redis,还要自己写 RedisTemplate?
后端