当前位置: 首页 > news >正文

springboot下使用druid-spring-boot-starter

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

1.引入依赖

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

        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <!--<version>1.2.6</version>-->
        </dependency>

2.application.properties配置

推荐的配置:

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,方便开发、排查问题,添加配置:

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

配置详解

# 数据库地址
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。

监控有关配置:

# 配置监控统计拦截的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

https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

完整pom.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。

@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,才会加载当前类。

public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {}

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

    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:

    @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:

    @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()

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

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

// 注入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 {}

相关文章:

  • PHREEQC建模及典型案例解析与高阶拓展应用【反向“编译”、“玩转”后处理技术、GibbsStudio和PhreePlo方法】
  • Springboot集成Quartz
  • React 18的新特新
  • springboot实验课程辅助管理系统毕业设计-附源码191113
  • Java面向对象(封装,继承,多态,接口)
  • 头门港大屏
  • pip更改为国内源
  • DBCO-PEG-carboxyl COOH-PEG-DBCO 二苯并环辛炔-聚乙二醇-羧酸 羧酸修饰PEG二苯并环辛炔
  • 【Java 语言】4、如何接收用户键盘输入
  • 猿创征文|我的 Java 成长之路
  • Docker01:概述与历史
  • 网站交换友情链接是否对SEO优化有帮助?
  • Vue.js核心技术解析与uni-app跨平台实战开发学习笔记 第10章 Vuex状态管理 10.1 Vuex基础应用
  • C++枚举
  • 动手学深度学习笔记-线性回归和softmax回归底层从零实现
  • “大数据应用场景”之隔壁老王(连载四)
  • 78. Subsets
  • CentOS7 安装JDK
  • flutter的key在widget list的作用以及必要性
  • HTML-表单
  • IDEA常用插件整理
  • js对象的深浅拷贝
  • laravel 用artisan创建自己的模板
  • ViewService——一种保证客户端与服务端同步的方法
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 构建二叉树进行数值数组的去重及优化
  • 关于for循环的简单归纳
  • 记录:CentOS7.2配置LNMP环境记录
  • 漂亮刷新控件-iOS
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 世界上最简单的无等待算法(getAndIncrement)
  • 探索 JS 中的模块化
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 优秀架构师必须掌握的架构思维
  • 中文输入法与React文本输入框的问题与解决方案
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • 阿里云API、SDK和CLI应用实践方案
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • ​ssh免密码登录设置及问题总结
  • #Java第九次作业--输入输出流和文件操作
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (145)光线追踪距离场柔和阴影
  • (3)(3.5) 遥测无线电区域条例
  • (9)目标检测_SSD的原理
  • (ibm)Java 语言的 XPath API
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (二)c52学习之旅-简单了解单片机
  • (二)换源+apt-get基础配置+搜狗拼音
  • (三)mysql_MYSQL(三)
  • (转)程序员技术练级攻略
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • ./configure、make、make install 命令
  • .gitignore
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布