一、如果需要在SpringBoot中整合Druid连接池的话,那么下面两种方式的依赖可以任选添加
这种方式是SpringBoot启动器启动方式,也就意味着这是一个很齐全的jar。打开内部可以发现POM文件中引入了SpringBoot相关的jar及druid依赖,
而且内部提供slf4j-api依赖来对接其他日志实现包协助druid打印日志
com.alibaba
druid-spring-boot-starter
1.2.9
org.springframework.boot
spring-boot-starter-log4j
1.3.8.RELEASE
######################################################
下面这种方式就比较简单暴力,只引入了两个关键依赖。满足druid使用及相关日志实现即可
com.alibaba
druid
1.2.8
log4j
log4j
关于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文档中的注解篇
从上面的截图我们可以了解到两个信息:
bean注解是标识某个方法生产一个bean交由Spring容器管理
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目录