Experience of SpringBoot integrating Druid connection pool

1. If you need to integrate the Druid connection pool in SpringBoot, then the following two ways of dependency can be added optionally

这种方式是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>

For druid's log dependency implementation, you can refer to the com.alibaba.druid.support.logging.LogFactory class to view it. The default call priority is: slf4j->log4j->log4j2->commonsLog->java logging Although slf4j can be used alone, it is more operated in conjunction with other log dependencies

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");
    ...................

2. After adding database information to the configuration file (just use the sample directly), write the configuration class of druid

application.properties add the following parameters:

#标注使用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请求,生产一般不加所以不列了

It is recommended to look at the official documents for related configuration analysis , the core is the connection pool

Configuration class of druid:

@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;
    }
}

After this step, druid has been configured. If you need to use the data source to inject DataSource directly.

But for this essay, this is not the end~ You should see the log in the DruidConfig class. At that time, the author just wanted to print the data source configuration information for viewing. But it happened that there was a question about this area. Let me show you the print 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

This tm is completely different from what I configured! ! ! Take a closer look, good guy, isn’t this all the default value, it’s not bad~

At that time, the author's first thought must be that the configuration information was not read, which led to the direct use of the default value, so observe the Spring annotation at the first time:

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

So I changed my mind, added a configuration bean and added an annotation @EnableConfigurationProperties, the final code is as follows

@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;
}

After doing this operation, the configuration information is indeed printed in the log, but after thinking about it, this is the value I injected manually, which does not meet the requirements of automatic assembly. And all the big players on the Internet use @ConfigurationProperties directly to complete the configuration. Based on the belief that the long night is coming , because the torch is in our hands, I wrote a Test to get the connection to try it out

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

As a result, the execution can end at line 9. The debug has seen that the dataSource has all the tails and tails.

Judging from the current phenomenon, after the configuration class configures the druid configuration and then injects DataSource->getConnection, the configuration information has been obtained. Let's look at the dataSource.getConnection() function to see if it is because druid finds that there is no key attribute. Configuration information has been reintroduced

The information from the debug lets us know that the injected DataSource has already loaded configuration information. Now we need to know whether the properties filled by the DataSource bean after initialization are configured by ourselves or built by druid. So the author wrote a custom beanpostprocessor to print attribute information to DataSource after the bean initialization is completed

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;
    }
}

In fact, through this bean post-processor, we can already know that the ConfigurationProperties annotation is actually effective, and the value is injected into the property. This leaves us with two questions:

  • Mechanism of @bean injection

  • The mechanism of @ConfigurationProperties configuration

For the first question, let's find the official website and look at the annotations in the api document

From the screenshot above, we can learn two pieces of information:

  1. The bean annotation is to identify a method to produce a bean to be managed by the Spring container

  1. The default scope of bean annotation is singleton

For the second question, let's also look for SpringBoot's api documentation

从中我们可以知道想要使 @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目录

Guess you like

Origin blog.csdn.net/weixin_42505381/article/details/128575085