springboot整合mybatis-plus、druid连接池和多数据源配置
在使用springboot开发业务应用程序的过程中,使用多数据源的场景很常见。目前,业界常用的spring boot整合多数据源的方案主要有两个,一个是Mapper分包方式,另一个是AOP切片方式。针对这种多数据源使用场景的痛点,MyBatis-plus团队开源了一个更为方便的解决方案(https://mp.baomidou.com/guide/dynamic-datasource.html),但是对于整合druid数据库连接池,却没有给出具体的使用示例。本篇文章就是使用springboot整合mybatis-plus、druid连接池和多数据源配置,给出一个可用的示例。
一、在SpringBoot项目pom文件中引入依赖
在SpringBoot项目pom文件中,引入如下依赖:
-
<!-- 常用collection操作 依赖 -->
-
<dependency>
-
<groupId>org.apache.commons</groupId>
-
<artifactId>commons-collections4</artifactId>
-
<version>4.1</version>
-
</dependency>
-
-
<!-- mybatis-plus依赖 -->
-
<dependency>
-
<groupId>com.baomidou</groupId>
-
<artifactId>mybatis-plus-boot-starter</artifactId>
-
<version>3.4.1</version>
-
</dependency>
-
-
<!-- druid连接池依赖 -->
-
<!-- 如果使用druid-spring-boot-starter, 那么需要在启动类上使用@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class) -->
-
<!-- <dependency>-->
-
<!-- <groupId>com.alibaba</groupId>-->
-
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
-
<!-- <version>1.2.4</version>-->
-
<!-- </dependency>-->
-
<dependency>
-
<groupId>com.alibaba</groupId>
-
<artifactId>druid</artifactId>
-
<version>1.2.4</version>
-
</dependency>
-
-
<!--多数据源依赖-->
-
<dependency>
-
<groupId>com.baomidou</groupId>
-
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
-
<version>3.3.1</version>
-
</dependency>
-
-
<!-- mysql connector 依赖 -->
-
<dependency>
-
<groupId>mysql</groupId>
-
<artifactId>mysql-connector-java</artifactId>
-
<version>8.0.23</version>
-
<scope>runtime</scope>
-
</dependency>
-
-
<!-- lombok -->
-
<dependency>
-
<groupId>org.projectlombok</groupId>
-
<artifactId>lombok</artifactId>
-
<optional>true</optional>
-
</dependency>
-
-
<!-- 测试类 -->
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-test</artifactId>
-
<scope>test</scope>
-
<exclusions>
-
<exclusion>
-
<groupId>org.junit.vintage</groupId>
-
<artifactId>junit-vintage-engine</artifactId>
-
</exclusion>
-
</exclusions>
-
</dependency>
-
</dependencies>
二、application属性文件增加数据源配置项
使用property格式配置,则属性配置文件application.properties内容为:
-
-
-
mybatis-plus.mapper-locations=classpath:mapper/*.xml
-
mybatis-plus.type-aliases-package=com.mvp.world.mybatisplusdynamicdruid.model.po
-
-
########################## 多数据源配置###############################
-
## druid连接池配置
-
# 默认数据源
-
spring.datasource.dynamic.primary=master
-
# 主库配置 master
-
spring.datasource.dynamic.datasource.master.username=test_user
-
spring.datasource.dynamic.datasource.master.password=test_pswd
-
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
-
#spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver
-
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/mysql-plus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
-
spring.datasource.dynamic.datasource.master.druid.initial-size=5
-
spring.datasource.dynamic.datasource.master.druid.max-active=20
-
spring.datasource.dynamic.datasource.master.druid.min-idle=5
-
spring.datasource.dynamic.datasource.master.druid.max-wait=60000
-
spring.datasource.dynamic.datasource.master.druid.min-evictable-idle-time-millis=300000
-
spring.datasource.dynamic.datasource.master.druid.max-evictable-idle-time-millis=300000
-
spring.datasource.dynamic.datasource.master.druid.time-between-eviction-runs-millis=60000
-
# 从库配置 slave
-
spring.datasource.dynamic.datasource.slave.username=test_user
-
spring.datasource.dynamic.datasource.slave.password=test_pswd
-
spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
-
#spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
-
spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/mysql-plus-slave?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
-
spring.datasource.dynamic.datasource.slave.druid.initial-size=5
-
spring.datasource.dynamic.datasource.slave.druid.max-active=20
-
spring.datasource.dynamic.datasource.slave.druid.min-idle=5
-
spring.datasource.dynamic.datasource.slave.druid.max-wait=60000
-
spring.datasource.dynamic.datasource.slave.druid.min-evictable-idle-time-millis=300000
-
spring.datasource.dynamic.datasource.slave.druid.max-evictable-idle-time-millis=300000
-
spring.datasource.dynamic.datasource.slave.druid.time-between-eviction-runs-millis=60000
-
## 去除druid配置
-
#spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
-
############################# 多数据源配置##############################
说明:
1.spring.datasource.dynamic.primary=master 表示设置master数据源为primary数据源。
2.在application.properties属性文件中,配置的多个数据源以spring.datasource.dynamic.datasource为前缀。
3.spring.datasource.dynamic.strict=false 设置严格模式,默认false不启动.。若设置为true,则在未匹配到指定数据源时会抛出异常。若设置为false,则在未匹配到指定数据源时会使用默认数据源。
如果使用yaml格式配置,则application.yaml配置文件内容为:
-
server:
-
port: 8080
-
spring:
-
datasource:
-
dynamic:
-
primary: master # 配置默认数据库
-
datasource:
-
master: # 数据源1配置
-
url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
-
username: test_user
-
password: test_pswd
-
driver-class-name: com.mysql.cj.jdbc.Driver
-
slave: # 数据源2配置
-
url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
-
username: test_user
-
password: test_pswd
-
driver-class-name: com.mysql.cj.jdbc.Driver
-
durid:
-
initial-size: 1
-
max-active: 20
-
min-idle: 1
-
max-wait: 60000
-
autoconfigure:
-
# 去除druid配置。是否需要,根据druid连接池的类型。如果druid连接池为starter类型,则需要排除。否则,不需要。
-
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
三、@DS 注解
@DS注解可用于方法或类上。若同时存在,则方法上的注解优先于类上的注解。
官网推荐@DS注解在service层使用,且是实现类上使用,在接口层无效。注解用在service实现或mapper接口方法上,不要同时在service和mapper注解。
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库名 |
若没有使用@DS注解,则会使用默认数据源。
@DS(“dsName”) dsName可以为某个具体库名,也可以为组名。若dsName为组名,则会使用负载均衡算法进行切换。
四、启动排除druid连接池默认自动配置
1. 如果在pom文件中引入druid连接池的依赖为druid-spring-boot-starter,那么需要在应用启动时排除druid默认加载的db配置项。原因为:DruidDataSourceAutoConfigure会注入一个DataSourceWrapper,这个数据源包装器会在原生spring.datasource或spring.datasource.druid路径下查找url、username、password等属性。动态数据源依赖默认加载的 url、username、password等配置项是在spring.datasource.dynamic路径下,因此需要排除druid属性默认加载,否则程序会报错。排除druid属性默认加载的方式有两种,一种是在配置文件中排除,另一种是在项目启动类中排除。
在配置文件中排除druid属性默认加载,其方法为在配置文件中添加如下内容:
spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
在项目启动类中排除druid属性默认加载,其方法为在@SpringBootApplication注解中排除DruidDataSourceAutoConfigure.class,如下所示:
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
2. 如果在pom文件中引入druid连接池的依赖为非starter版本的druid依赖,那么应用程序在启动时不会加载druid默认的db配置项,也就不用考虑如何排除DataSourceWrapper数据源包装器。建议使用这种方式。
五、使用多数据源代码示例
1.在mapper层使用mybatis-plus整合多数据源
-
-
public interface UserMapper extends BaseMapper<User> {
-
}
2.在service层使用JdbcTemplate整合多数据源
-
-
public class UserServiceImpl implements UserService {
-
-
-
private JdbcTemplate jdbcTemplate;
-
-
public List selectAll() {
-
return jdbcTemplate.queryForList("select * from user limit 10");
-
}
-
-
-
"slave")(
-
public List selectByCondition() {
-
return jdbcTemplate.queryForList("select * from user where age >10 limit 10");
-
}
-
}
3.在service层使用mybatis-plus整合多数据源
-
@Service
-
@DS("db2") // @DS注解可以使用在service实现类上
-
public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements IModelService {
-
@Select("SELECT * FROM user order by id asc limit 10")
-
@DS("slave") // @DS注解可以使用在service实现类的方法上
-
List<User> selectUser();
-
}
Druid连接池参数配置
通过使用数据库连接池可以极大提升数据库CURD操作的效率,尝试参考Druid的github官网配置了一些参数。
DruidDataSource参数配置
DruidDataSource-德鲁伊数据源可以理解为它就是正在使用的某个数据库,最普遍的操作就是直接用于获取维护在连接池中的Connection连接对象,而不用自己再重新new对象了,为我们省去了创建、释放Connection对象的时间。
以下是基本的Druid数据的一些参数配置,像:JDBC基本连接参数、连接池初始化/最大容量等参数的配置。官网的配置是整合了Spring框架,通过XML文档配置的,然后通过IOC机制便捷的获取DruidDataSource的对象,可以省去自己编写代码创建数据源对象的繁琐步骤,这也是使用框架开发的好处了吧。
另外值得一提的是,官网的配置直接指定好了初始化时调用的方法init()和销毁时调用的方法close(),在start和shutdown-Spring项目时会自动调用。当然如果通过Servlet技术开发的话,也是可以基于ServletContext监听器(或者说Application域对象的监听器)javax.servlet.ServletContextListener的contextInitialized()和contextDestroyed()方法模拟Spring的IOC机制实现数据源的创建和销毁。
driverClassName=com.mysql.cj.jdbc.Driver
#开启预编译机制|开启批处理机制
url=jdbc:mysql://localhost:3306/数据库名称?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true
username=root
password=root
initialSize=5
minIdle=5
maxActive=30
maxWait=60000
poolPreparedStatements=true #是否使用预编译机制
maxPoolPreparedStatementPerConnectionSize=20
filters=log4j,wall,stat
#-------------连接泄漏回收参数--------------------------------
#当未使用的时间超过removeAbandonedTimeout时,是否视该连接为泄露连接并删除
#默认为false
removeAbandoned=false
#泄露的连接可以被删除的超时值, 单位毫秒-默认为300*1000
removeAbandonedTimeoutMillis=300*1000
#标记当Statement或连接被泄露时是否打印程序的stack traces日志。
#默认为false
logAbandoned=true
#连接最大存活时间
#默认-1
#phyTimeoutMillis=-1
Druid内置监控页面配置
Servlet(最常用的用于收发请求和响应的类),Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。其的用途包括:
①提供监控信息展示的html页面
②提供监控信息的JSON API内置监控页面是一个Servlet。
只需要在web.xml中按照Servlet的配置方法配置就行了。官网给出的例子如下,
<!-- 配置 Druid 监控信息显示页面 -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<!-- 允许清空统计数据 -->
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 用户名 -->
<param-name>loginUsername</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<!-- 密码 -->
<param-name>loginPassword</param-name>
<param-value>druid</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
Druid配置采集web-jdbc关联监控的数据
监听器(Listener)WebStatFilter用于采集web-jdbc关联监控的数据,其实就是让自己指定想要统计、不想要统计哪些数据。
关于这些init-param参数名称这样写,肯定是因为Druid内部已经预先做好了规范,只要了解参数的作用,并进行配置就可以生效了。
<!--filter配置-->
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<!--排除一些不必要的url数据统计-就是把不想统计的请求过滤掉-->
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<!-- 用户名 -->
<init-param>
<param-name>loginUsername</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<!-- 密码 -->
<param-name>loginPassword</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<param-name>principalCookieName</param-name>
<param-value>USER_COOKIE</param-value>
</init-param>
<init-param>
<param-name>principalSessionName</param-name>
<param-value>USER_SESSION</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Druid连接池数据源创建
通过手写DruidUtil类实现DruidDataSource创建,并提供获取Connection对象的获取方法,向外部提供数据库连接对象,然后配合DButils.jar提供的QueryRunner实现数据库CURD操作。
package com.xwd.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @ClassName DruidUtils
* @Description: com.xwd.utils
* @Auther: xiwd
* @Date: 2022/2/17 - 02 - 17 - 2:33
* @version: 1.0
*/
public class DruidUtil {
//properties
private static PropertyUtil propertyUtil =null;
private static DruidDataSource dataSource;//数据源对象
// private static Logger logger;//日志打印对象
//setter
//getter
//static block
static {
//实例化Logger对象
// logger = Logger.getLogger(DruidUtil.class);
//加载配置文件
propertyUtil=new PropertyUtil("/druid.properties");
try {
//实例化数据源对象
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(propertyUtil.getProperties());
// logger.fatal("Druid DataSource has been initialized SUCCESSFULLY!");
} catch (Exception e) {
e.printStackTrace();
// logger.fatal("Druid DataSource initialized FAILED!");
}
}
//constructors
//methods
/**
* 获取数据源DataSource对象
* @return
*/
public static DruidDataSource getDruidDataSource(){
return dataSource;
}
/**
* 获取数据库连接Connection对象
* @return Connection对象
*/
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
return connection;
}
return connection;
}
/**
* 将数据库连接对象归还到数据库连接池中
* @param connection Connection-数据库连接对象
*/
public static void returnResources( Connection connection){
returnResources(null,null,connection);
}
/**
* 将数据库连接对象归还到数据库连接池中
* @param statement Statement-SQL语句执行器
* @param connection Connection-数据库连接对象
*/
public static void returnResources( Statement statement,Connection connection){
returnResources(null,statement,connection);
}
/**
* 将数据库连接对象归还到数据库连接池中
* @param resultSet ResultSet-结果集
* @param statement Statement-SQL语句执行器
* @param connection Connection-数据库连接对象
*/
public static void returnResources(ResultSet resultSet, Statement statement,Connection connection){
if (null!=resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null!=statement) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null!=connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
关于注释掉Log4j初始化的代码说明
由于Druid内置提供了四种LogFilter(Log4jFilter、Log4j2Filter、CommonsLogFilter、Slf4jLogFilter),用于输出JDBC执行的日志。这些Filter都是Filter-Chain扩展机制中的Filter,在一开始已经配置了参数,指定使用log4j打印日志。
filters=log4j,wall,stat
但是在使用时,还需要引入log4j.jar包或者pom坐标,并准备好log4j.properties配置文件。
#默认error及其以上级别日志都会被记录下来
log4j.rootLogger=debug,stdout,logfile
#
#打印日志到控制台上
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
#
#通过文件记录日志
log4j.appender.logfile=org.apache.log4j.FileAppender
#日志文件路径
log4j.appender.logfile.File=d:/sys.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n
DruidDataSource销毁
上面提到Druid官网数据源初始化参数的例子显然是整合了spring框架的,很容易配置DruidDataSource数据源的销毁方法close(),但是在使用Servlet开发时,可能很容易忽略此项。然后,就可能导致Tomcat服务shutDown时,爆出如下内容:
如何解决?结合已有配置参数,猜想可能是因为没有做DruidDataSource数据源销毁的操作。既然官方配置文档提示了要使用close()方法,那么就可以在ServletContext监听器实现类中的contextDestroyed()方法中,进行数据源的关闭操作。
@Override
public void contextDestroyed(ServletContextEvent sce) {
//ServletContext销毁方法
//销毁自定义的线程池对象
// destroyThreadPoolExector(sce);
//释放驱动
//releaseDriverResources();
//释放Druid数据库连接池资源
releaseDruidSources();
//提示关闭服务器应用
System.out.println("Application shut-down...");
}
/**
* 释放数据库连接池资源
*/
private void releaseDruidSources() {
System.out.println("try closing the Druid-DataSource...");
AbandonedConnectionCleanupThread.checkedShutdown();
DruidDataSource druidDataSource = DruidUtil.getDruidDataSource();
druidDataSource.close();
System.out.println(druidDataSource);
}