SpringBoot整合Druid连接池的经历

一、如果需要在SpringBoot中整合Druid连接池的话,那么下面两种方式的依赖可以任选添加

这种方式是SpringBoot启动器启动方式,也就意味着这是一个很齐全的jar。打开内部可以发现POM文件中引入了SpringBoot相关的jar及druid依赖,
而且内部提供slf4j-api依赖来对接其他日志实现包协助druid打印日志
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.9</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j</artifactId>
    <version>1.3.8.RELEASE</version>
</dependency>
######################################################
下面这种方式就比较简单暴力,只引入了两个关键依赖。满足druid使用及相关日志实现即可
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
</dependency>

关于druid的日志依赖实现可以翻阅com.alibaba.druid.support.logging.LogFactory类来查看。默认调用优先级为:slf4j->log4j->log4j2->commonsLog->java logging 虽然slf4j可以单独使用,但更多是配合其他日志依赖来操作

static {
    String logType = System.getProperty("druid.logType");
    if (logType != null) {
        if (logType.equalsIgnoreCase("slf4j")) {
            tryImplementation("org.slf4j.Logger", "com.alibaba.druid.support.logging.SLF4JImpl");
        } else if (logType.equalsIgnoreCase("log4j")) {
            tryImplementation("org.apache.log4j.Logger", "com.alibaba.druid.support.logging.Log4jImpl");
        } else if (logType.equalsIgnoreCase("log4j2")) {
            tryImplementation("org.apache.logging.log4j.Logger", "com.alibaba.druid.support.logging.Log4j2Impl");
        } else if (logType.equalsIgnoreCase("commonsLog")) {
            tryImplementation("org.apache.commons.logging.LogFactory", "com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl");
        } else if (logType.equalsIgnoreCase("jdkLog")) {
            tryImplementation("java.util.logging.Logger", "com.alibaba.druid.support.logging.Jdk14LoggingImpl");
        }
    }
    tryImplementation("org.slf4j.Logger", "com.alibaba.druid.support.logging.SLF4JImpl");
    tryImplementation("org.apache.log4j.Logger", "com.alibaba.druid.support.logging.Log4jImpl");
    tryImplementation("org.apache.logging.log4j.Logger", "com.alibaba.druid.support.logging.Log4j2Impl");
    tryImplementation("org.apache.commons.logging.LogFactory", "com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl");
    tryImplementation("java.util.logging.Logger", "com.alibaba.druid.support.logging.Jdk14LoggingImpl");
    ...................

二、配置文件中加入数据库信息(直接用样例即可)后,编写druid的Configuration类

application.properties加入如下参数:

#标注使用druid数据源而非默认数据源
spring.datasource.druid.type=com.alibaba.druid.pool.DruidDataSource
#一般可以不配置,druid会根据url自动识别dbType,然后选择相应的driverClassName
spring.datasource.druid.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=
spring.datasource.druid.username=
spring.datasource.druid.password=
#下面为连接池的通用设置,注入到druid数据源中
spring.datasource.druid.initialSize=
spring.datasource.druid.minIdle=
spring.datasource.druid.maxActive=
spring.datasource.druid.maxWait=
spring.datasource.druid.timeBetweenEvictionRunsMillis=
spring.datasource.druid.minEvictableIdleTimeMillis=
#保活机制
spring.datasource.druid.keepAlive=true
# Oracle请使用select 1 from dual 必须要有这个参数,testWhileIdle、testOnBorrow、testOnReturn才会生效
spring.datasource.druid.validationQuery=SELECT 'x'
#应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用 默认true
#spring.datasource.druid.testWhileIdle=true
#应用向连接池申请连接时,连接池会判断这条连接是否是可用的 默认false
#spring.datasource.druid.testOnBorrow=false
#应用使用完连接,连接池回收连接的时候会判断该连接是否还可用 默认false
#spring.datasource.druid.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.poolPreparedStatements=true
#启用PSCache,必须配置大于0,可以配置大一点。遵循maxActive应该是可以的
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=
#druid web页面StatViewServlet配置
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall,log4j
spring.datasource.druid.stat-view-servlet.enabled=true
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=123456
# 管控台访问白名单,默认值:127.0.0.1
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,X.X.X.X
# 管控台访问黑名单,默认未设置
spring.datasource.druid.stat-view-servlet.deny=X.X.X.X
#druid web页面WebStatFilter配置,监控web请求,生产一般不加所以不列了

相关配置解析建议还是看看官方文档,核心还是连接池那一套

druid的Configuration类:

@Log4j
@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        log.info("Druid数据库类型=======>" + druidDataSource.getDbType());
        log.info("Druid数据库驱动=======>" + druidDataSource.getDriverClassName());
        log.info("Druid数据库链接=======>" + druidDataSource.getUrl());
        log.info("Druid数据库登陆名=======>" + druidDataSource.getUsername());
        log.info("Druid数据库登陆密码=======>" + druidDataSource.getPassword());
        log.info("Druid数据库初始化连接数=======>" + druidDataSource.getInitialSize());
        log.info("Druid数据库连接池最小连接数=======>" + druidDataSource.getMinIdle());
        log.info("Druid数据库连接池最大连接数=======>" + druidDataSource.getMaxActive());
        log.info("Druid数据库最大访问等待时间ms=======>" + druidDataSource.getMaxWait());
        log.info("Druid数据库链接最大空闲时间=======>" + druidDataSource.getTimeBetweenEvictionRunsMillis());
        log.info("Druid数据库链接最小空闲时间=======>" + druidDataSource.getMinEvictableIdleTimeMillis());
        log.info("Druid数据库最大PSCache连接数=======>" + druidDataSource.getMaxPoolPreparedStatementPerConnectionSize());
        return druidDataSource;
    }
}

到这一步之后,druid就已经配置结束了。如果需要使用数据源直接注入DataSource就可以了。

但对于这一篇随笔来说,这还不是结束~ 大家应该看到DruidConfig类中的log了吧,当时笔者只是想要打印一下数据源配置信息查看。但刚好这一块出现疑问了,给大家看一下打印日志:

2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:28) Druid数据库类型=======>null
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:29) Druid数据库驱动=======>null
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:30) Druid数据库链接=======>null
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:31) Druid数据库登陆名=======>null
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:32) Druid数据库登陆密码=======>null
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:33) Druid数据库初始化连接数=======>0
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:34) Druid数据库连接池最小连接数=======>0
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:35) Druid数据库连接池最大连接数=======>8
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:36) Druid数据库最大访问等待时间ms=======>-1
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:37) Druid数据库链接最大空闲时间=======>60000
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:38) Druid数据库链接最小空闲时间=======>1800000
2023-01-05 23:50:15-[INFO:]-(com.elonjohnson.springboottest.configuration.DruidConfig:39) Druid数据库最大PSCache连接数=======>10

这tm完全和我配置的不一样嘛!!!细看一下,好家伙这不全全是默认值么,这还了得~

当时笔者第一想法必然是没有读取到配置信息,导致直接使用默认值,所以第一时间观察Spring注解:

@ConfigurationProperties(prefix = "spring.datasource.druid")

于是我改了一种思路,添加一个配置bean再增加一个注解@EnableConfigurationProperties,最终代码如下

@Log4j
@Configuration
@EnableConfigurationProperties({DruidDataSourceProperties.class})
public class DruidConfig {
    @Autowired
    private DruidDataSourceProperties properties;

    @Bean
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getDriverClassName);
        druidDataSource.setUrl(properties.getUrl);
        druidDataSource.setUsername(properties.getUsername);
        druidDataSource.setPassword(properties.getPassword);
        druidDataSource.setInitialSize(properties.getInitialSize);
        druidDataSource.setMinIdle(properties.getMinIdle);
        druidDataSource.setMaxActive(properties.getMaxActive);
        druidDataSource.setMaxWait(properties.getMaxWait);
        druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis);
        druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis);
        druidDataSource.setValidationQuery(properties.getValidationQuery);
        druidDataSource.setTestWhileIdle(properties.getTestWhileIdle);
        druidDataSource.setTestOnBorrow(properties.getTestOnBorrow);
        druidDataSource.setTestOnReturn(properties.getTestOnReturn);
        druidDataSource.setPoolPreparedStatements(properties.getPoolPreparedStatements);
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(properties.getMaxPoolPreparedStatementPerConnectionSize);
        try {
            druidDataSource.setFilters(properties.getFilters);
            druidDataSource.init();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        log.info("Druid数据库类型=======>" + druidDataSource.getDbType());
        log.info("Druid数据库驱动=======>" + druidDataSource.getDriverClassName());
        log.info("Druid数据库链接=======>" + druidDataSource.getUrl());
        log.info("Druid数据库登陆名=======>" + druidDataSource.getUsername());
        log.info("Druid数据库登陆密码=======>" + druidDataSource.getPassword());
        log.info("Druid数据库初始化连接数=======>" + druidDataSource.getInitialSize());
        log.info("Druid数据库连接池最小连接数=======>" + druidDataSource.getMinIdle());
        log.info("Druid数据库连接池最大连接数=======>" + druidDataSource.getMaxActive());
        log.info("Druid数据库最大访问等待时间ms=======>" + druidDataSource.getMaxWait());
        log.info("Druid数据库链接最大空闲时间=======>" + druidDataSource.getTimeBetweenEvictionRunsMillis());
        log.info("Druid数据库链接最小空闲时间=======>" + druidDataSource.getMinEvictableIdleTimeMillis());
        log.info("Druid数据库最大PSCache连接数=======>" + druidDataSource.getMaxPoolPreparedStatementPerConnectionSize());
        return druidDataSource;
    }
}

@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DruidDataSourceProperties {
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private int initialSize;
    private int minIdle;
    private int maxActive;
    private long maxWait;
    private long timeBetweenEvictionRunsMillis;
    private long minEvictableIdleTimeMillis;
    private String validationQuery;
    private boolean testWhileIdle;
    private boolean testOnBorrow;
    private boolean testOnReturn;
    private boolean poolPreparedStatements;
    private int maxPoolPreparedStatementPerConnectionSize;
    private String filters;
}

这样操作一下的确在日志中打印出来配置信息了,但转念一想这是我手动注入的值啊,不符合自动装配的逼格啊。而且网上清一色的大手子都是直接使用@ConfigurationProperties就完成了配置。本着不相信长夜将至因为火把就在我们手中的信念,编写了一个Test来获取connection来试试看

@Log4j
@SpringBootTest
public class SpringbootDataJdbcApplicationTests {
    @Autowired
    DataSource dataSource;
    
    @Test
    public void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        Connection connection = dataSource.getConnection();
        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        log.info("Druid数据库类型=======>" + druidDataSource.getDbType());
        log.info("Druid数据库驱动=======>" + druidDataSource.getDriverClassName());
        log.info("Druid数据库链接=======>" + druidDataSource.getUrl());
        log.info("Druid数据库登陆名=======>" + druidDataSource.getUsername());
        log.info("Druid数据库登陆密码=======>" + druidDataSource.getPassword());
        log.info("Druid数据库初始化连接数=======>" + druidDataSource.getInitialSize());
        log.info("Druid数据库连接池最小连接数=======>" + druidDataSource.getMinIdle());
        log.info("Druid数据库连接池最大连接数=======>" + druidDataSource.getMaxActive());
        log.info("Druid数据库最大访问等待时间ms=======>" + druidDataSource.getMaxWait());
        log.info("Druid数据库链接最大空闲时间=======>" + druidDataSource.getTimeBetweenEvictionRunsMillis());
        log.info("Druid数据库链接最小空闲时间=======>" + druidDataSource.getMinEvictableIdleTimeMillis());
        log.info("Druid数据库最大PSCache连接数=======>" + druidDataSource.getMaxPoolPreparedStatementPerConnectionSize());
        //关闭连接
        connection.close();
    }
}

结果执行到第9行就可以结束了,debug已经看到dataSource是全须全尾的

从当前现象来看在配置类配置druid配置之后再到注入DataSource->getConnection的时候就已经获取到配置信息了,咱们翻一下dataSource.getConnection()函数来看看是不是由于druid发现没有关键属性而重新引入了配置信息

debug出来的信息让我们知道其实注入进来的DataSource已经加载了配置信息,现在我们需要了解的就是DataSource这个bean在初始化后填充的属性是自己配置的还是druid自建的。于是笔者编写了一个自定义的beanpostprocessor,在bean初始化完成之后对DataSource打印属性信息

beanpostprocessor:

@Log4j
@Component
public class SelfBeanPostProcessor implements BeanPostProcessor {
    @Nullable
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("druidDataSource".equals(beanName)) {
            DruidDataSource druidDataSource = (DruidDataSource) bean;
            log.info("Druid数据库类型=======>" + druidDataSource.getDbType());
            log.info("Druid数据库驱动=======>" + druidDataSource.getDriverClassName());
            log.info("Druid数据库初始化连接数=======>" + druidDataSource.getInitialSize());
            log.info("Druid数据库连接池最小连接数=======>" + druidDataSource.getMinIdle());
            log.info("Druid数据库连接池最大连接数=======>" + druidDataSource.getMaxActive());
        }
        return bean;
    }
}

其实通过这个bean后置处理器我们已经可以知道ConfigurationProperties注解其实是生效的,值是注入到属性中去了的。这样的话我们就留下了两个疑问:

  • @bean注入的机制

  • @ConfigurationProperties配置的机制

第一个问题我们找一下官方网站看看api文档中的注解篇

从上面的截图我们可以了解到两个信息:

  1. bean注解是标识某个方法生产一个bean交由Spring容器管理

  1. bean注解默认的作用域为单例

第二个问题我们也找一下SpringBoot的api文档

从中我们可以知道想要使 @ConfigurationProperties生效,要么通过在配置类(DruidDataSourceProperties)上调用 setter 来执行,要么通过绑定到(DruidDataSource)构造函数参数来执行

总结:

  • @Bean注解只是在函数执行结束之后将函数的返回值交由spring容器管理,所以new出来的对象使用默认初始化是很正常的事情

  • @ConfigurationProperties注解可以单独使用,使用方式可以是配置类或者绑定到@Bean注解函数返回对象的构造函数上

  • spring的疑问可以多看看官方文档,查找规则与找类的方式相同。spring docs的目录和Spring包的结构相同

@Bean:org.springframework.context.annotation

doc地址:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/

@ConfigurationProperties:org.springframework.boot.context.properties

doc地址:https://docs.spring.io/spring-boot/docs/2.7.7/api/org/springframework/boot/context/properties/package-frame.html

所以以后咱们想要查询自己使用版本的文档就可以拼接url:https://docs.spring.io/ + 项目名 + /docs/ + 版本号

进去之后再按照包结构去找doc目录

猜你喜欢

转载自blog.csdn.net/weixin_42505381/article/details/128575085