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文件中,引入如下依赖:

 
  1. <!-- 常用collection操作 依赖 -->
  2. <dependency>
  3. <groupId>org.apache.commons</groupId>
  4. <artifactId>commons-collections4</artifactId>
  5. <version>4.1</version>
  6. </dependency>
  7.  
  8. <!-- mybatis-plus依赖 -->
  9. <dependency>
  10. <groupId>com.baomidou</groupId>
  11. <artifactId>mybatis-plus-boot-starter</artifactId>
  12. <version>3.4.1</version>
  13. </dependency>
  14.  
  15. <!-- druid连接池依赖 -->
  16. <!-- 如果使用druid-spring-boot-starter, 那么需要在启动类上使用@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class) -->
  17. <!-- <dependency>-->
  18. <!-- <groupId>com.alibaba</groupId>-->
  19. <!-- <artifactId>druid-spring-boot-starter</artifactId>-->
  20. <!-- <version>1.2.4</version>-->
  21. <!-- </dependency>-->
  22. <dependency>
  23. <groupId>com.alibaba</groupId>
  24. <artifactId>druid</artifactId>
  25. <version>1.2.4</version>
  26. </dependency>
  27.  
  28. <!--多数据源依赖-->
  29. <dependency>
  30. <groupId>com.baomidou</groupId>
  31. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  32. <version>3.3.1</version>
  33. </dependency>
  34.  
  35. <!-- mysql connector 依赖 -->
  36. <dependency>
  37. <groupId>mysql</groupId>
  38. <artifactId>mysql-connector-java</artifactId>
  39. <version>8.0.23</version>
  40. <scope>runtime</scope>
  41. </dependency>
  42.  
  43. <!-- lombok -->
  44. <dependency>
  45. <groupId>org.projectlombok</groupId>
  46. <artifactId>lombok</artifactId>
  47. <optional>true</optional>
  48. </dependency>
  49.  
  50. <!-- 测试类 -->
  51. <dependency>
  52. <groupId>org.springframework.boot</groupId>
  53. <artifactId>spring-boot-starter-test</artifactId>
  54. <scope>test</scope>
  55. <exclusions>
  56. <exclusion>
  57. <groupId>org.junit.vintage</groupId>
  58. <artifactId>junit-vintage-engine</artifactId>
  59. </exclusion>
  60. </exclusions>
  61. </dependency>
  62. </dependencies>
 

 

二、application属性文件增加数据源配置项

使用property格式配置,则属性配置文件application.properties内容为:

 
  1.  
  2.  
  3. mybatis-plus.mapper-locations=classpath:mapper/*.xml
  4. mybatis-plus.type-aliases-package=com.mvp.world.mybatisplusdynamicdruid.model.po
  5.  
  6. ########################## 多数据源配置###############################
  7. ## druid连接池配置
  8. # 默认数据源
  9. spring.datasource.dynamic.primary=master
  10. # 主库配置 master
  11. spring.datasource.dynamic.datasource.master.username=test_user
  12. spring.datasource.dynamic.datasource.master.password=test_pswd
  13. spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
  14. #spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver
  15. spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/mysql-plus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
  16. spring.datasource.dynamic.datasource.master.druid.initial-size=5
  17. spring.datasource.dynamic.datasource.master.druid.max-active=20
  18. spring.datasource.dynamic.datasource.master.druid.min-idle=5
  19. spring.datasource.dynamic.datasource.master.druid.max-wait=60000
  20. spring.datasource.dynamic.datasource.master.druid.min-evictable-idle-time-millis=300000
  21. spring.datasource.dynamic.datasource.master.druid.max-evictable-idle-time-millis=300000
  22. spring.datasource.dynamic.datasource.master.druid.time-between-eviction-runs-millis=60000
  23. # 从库配置 slave
  24. spring.datasource.dynamic.datasource.slave.username=test_user
  25. spring.datasource.dynamic.datasource.slave.password=test_pswd
  26. spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
  27. #spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
  28. spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/mysql-plus-slave?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
  29. spring.datasource.dynamic.datasource.slave.druid.initial-size=5
  30. spring.datasource.dynamic.datasource.slave.druid.max-active=20
  31. spring.datasource.dynamic.datasource.slave.druid.min-idle=5
  32. spring.datasource.dynamic.datasource.slave.druid.max-wait=60000
  33. spring.datasource.dynamic.datasource.slave.druid.min-evictable-idle-time-millis=300000
  34. spring.datasource.dynamic.datasource.slave.druid.max-evictable-idle-time-millis=300000
  35. spring.datasource.dynamic.datasource.slave.druid.time-between-eviction-runs-millis=60000
  36. ## 去除druid配置
  37. #spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  38. ############################# 多数据源配置##############################
 

说明:

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配置文件内容为:

 
  1. server:
  2. port: 8080
  3. spring:
  4. datasource:
  5. dynamic:
  6. primary: master # 配置默认数据库
  7. datasource:
  8. master: # 数据源1配置
  9. url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
  10. username: test_user
  11. password: test_pswd
  12. driver-class-name: com.mysql.cj.jdbc.Driver
  13. slave: # 数据源2配置
  14. url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
  15. username: test_user
  16. password: test_pswd
  17. driver-class-name: com.mysql.cj.jdbc.Driver
  18. durid:
  19. initial-size: 1
  20. max-active: 20
  21. min-idle: 1
  22. max-wait: 60000
  23. autoconfigure:
  24. # 去除druid配置。是否需要,根据druid连接池的类型。如果druid连接池为starter类型,则需要排除。否则,不需要。
  25. 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整合多数据源

 
  1. @DS("slave")
  2. public interface UserMapper extends BaseMapper<User> {
  3. }
 

2.在service层使用JdbcTemplate整合多数据源

 
  1. @Service
  2. public class UserServiceImpl implements UserService {
  3.  
  4. @Autowired
  5. private JdbcTemplate jdbcTemplate;
  6.  
  7. public List selectAll() {
  8. return jdbcTemplate.queryForList("select * from user limit 10");
  9. }
  10.  
  11. @Override
  12. @DS("slave")
  13. public List selectByCondition() {
  14. return jdbcTemplate.queryForList("select * from user where age >10 limit 10");
  15. }
  16. }
 

3.在service层使用mybatis-plus整合多数据源

 
  1. @Service
  2. @DS("db2") // @DS注解可以使用在service实现类上
  3. public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements IModelService {
  4. @Select("SELECT * FROM user order by id asc limit 10")
  5. @DS("slave") // @DS注解可以使用在service实现类的方法上
  6. List<User> selectUser();
  7. }
 
 

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

在这里插入图片描述

posted @ 2024-02-04 17:42  CharyGao  阅读(1007)  评论(0编辑  收藏  举报