Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程

网上大多流传的springboot系列的切换多数据源都是以上那种写死在配置文件里的方式,这样如果我需要切换的数据源有10个,那么这种方式会不会显得稍微有点繁琐了。


现在这篇介绍的流程是,我们把各个数据源的配置信息写在一张数据库表里,从数据库表去加载这些数据源信息,根据我们给每个数据源命名的id去切换数据源,操作对应的数据库。


OK,接下来我们开始(如果真的想弄懂,最好跟我一步步来)


首先准备多个数据库,test1 ,test2 ,test3  :


image.png


接下来,我们在test1中,创建表 databasesource ,相关的SQL语句:


CREATE TABLE `databasesource`  (
  `datasource_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据源的id',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '连接信息',
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `pass_word` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '暂留字段',
  `databasetype` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据库类型'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

image.png


然后往test1数据库中的表databasesource里填充test2 、test3 这两个数据库的相关配置信息(对应的数据库帐号密码改成自己的),相关的SQL语句:


ps:这里面的datasource_id的值,是我们后面手动切换数据源的是使用的数据源 id


INSERT INTO `test1`.`databasesource`(`datasource_id`, `url`, `user_name`, `pass_word`, `code`, `databasetype`) VALUES ('dbtest2', 'jdbc:mysql://localhost:3306/test2?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull', 'root', 'root', NULL, 'mysql');
INSERT INTO `test1`.`databasesource`(`datasource_id`, `url`, `user_name`, `pass_word`, `code`, `databasetype`) VALUES ('dbtest3', 'jdbc:mysql://localhost:3306/test3?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull', 'root', 'root', NULL, 'mysql');


接下来,我们分别在test2数据库和test3数据库中都创建user表,相关的SQL语句:


CREATE TABLE `user`  (
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(3) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

image.png


然后往test2数据库的user表里面填充两条数据用于测试, 相关的SQL语句:


INSERT INTO `test2`.`user`(`user_name`, `age`) VALUES ('数据库2-小明', 20);
INSERT INTO `test2`.`user`(`user_name`, `age`) VALUES ('数据库2-小方', 17);


然后往test3数据库的user表里面填充两条数据用于测试, 相关的SQL语句:


INSERT INTO `test3`.`user`(`user_name`, `age`) VALUES ('数据库3-啊强', 11);
INSERT INTO `test3`.`user`(`user_name`, `age`) VALUES ('数据库3-啊木', 12);


OK,到这里我们的数据库模拟场景已经准备完毕了,接下来下面就是真正的核心环节了!


首先是pom.xml:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.testDb</groupId>
    <artifactId>dbsource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>dbsource</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- druid数据源驱动 1.1.10解决springboot从1.0——2.0版本问题-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>


紧接着,application.yml(这里面的数据库配置信息将作为默认数据库):


spring:
  aop:
    proxy-target-class: true #true为使用CGLIB代理
  datasource:
    #nullCatalogMeansCurrent=true&
    url: jdbc:mysql://localhost:3306/test1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
    username: root
    password: root
    #新版mysql驱动配置方法
    driverClassName: com.mysql.cj.jdbc.Driver
    ###################以下为druid增加的配置###########################
    type: com.alibaba.druid.pool.DruidDataSource
    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    initialSize: 5
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    # 合并多个DruidDataSource的监控数据
    useGlobalDataSourceStat: true
    ###############以上为配置druid添加的配置########################################
mybatis:
  type-aliases-package: com.testdb.dbsource.pojo  #扫描包路径
  configuration:
    map-underscore-to-camel-case: true #打开驼峰命名
  config-location: classpath:mybatis/mybatis-config.xml
server:
  port: 8097


先创建DataSource.java实体类,数据源信息装配的时候用:


import lombok.Data;
import lombok.ToString;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Data
@ToString
public class DataSource {
    String datasourceId;
    String url;
    String userName;
    String passWord;
    String code;
    String databasetype;
}


接下来,创建DruidDBConfig.java:


这里主要是配置默认的数据源,配置Druid数据库连接池,配置sql工厂加载mybatis的文件,扫描实体类等


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Configuration
@EnableTransactionManagement
public class DruidDBConfig {
    private final Logger log = LoggerFactory.getLogger(getClass());
    // adi数据库连接信息
    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;
    // 连接池连接信息
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;
    @Bean // 声明其为Bean实例
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    @Qualifier("mainDataSource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource datasource = new DruidDataSource();
        // 基础连接信息
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        // 连接池连接信息
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
        //  datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
        datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        String validationQuery = "select 1 from dual";
        datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
        datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
        datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
        datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
        datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
        datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
        datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
        return datasource;
    }
    /**
     * 注册一个StatViewServlet    druid监控页面配置1-帐号密码配置
     *
     * @return servlet registration bean
     */
    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
                new StatViewServlet(), "/druid/*");
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }
    /**
     * 注册一个:filterRegistrationBean   druid监控页面配置2-允许页面正常浏览
     *
     * @return filter registration bean
     */
    @Bean
    public FilterRegistrationBean druidStatFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(
                new WebStatFilter());
        // 添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");
        // 添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    public DynamicDataSource dynamicDataSource() throws SQLException {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDebug(false);
        //配置缺省的数据源
        // 默认数据源配置 DefaultTargetDataSource
        dynamicDataSource.setDefaultTargetDataSource(dataSource());
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //额外数据源配置 TargetDataSources
        targetDataSources.put("mainDataSource", dataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        //解决手动创建数据源后字段到bean属性名驼峰命名转换失效的问题
        sqlSessionFactoryBean.setConfiguration(configuration());
        // 设置mybatis的主配置文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
        //  sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);
        // 设置别名包
        //  sqlSessionFactoryBean.setTypeAliasesPackage("com.testdb.dbsource.pojo");
        //手动配置mybatis的mapper.xml资源路径,如果单纯使用注解方式,不需要配置该行
        // sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mybatis/mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }
    /**
     * 读取驼峰命名设置
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration configuration() {
        return new org.apache.ibatis.session.Configuration();
    }
}


然后是用于手动切换数据源的 DBContextHolder.java:


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
public class DBContextHolder {
    private final static Logger log = LoggerFactory.getLogger(DBContextHolder.class);
    // 对当前线程的操作-线程安全的
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    // 调用此方法,切换数据源
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
        log.info("已切换到数据源:{}",dataSource);
    }
    // 获取数据源
    public static String getDataSource() {
        return contextHolder.get();
    }
    // 删除数据源
    public static void clearDataSource() {
        contextHolder.remove();
        log.info("已切换到主数据源");
    }
}


然后是核心,手动加载默认数据源、创建数据源连接、检查数据源连接、删除数据源连接等 ,DynamicDataSource.java:



ok,然后是我们切换数据源使用的方法, 我们这里采用mybatis注解的方式获取test1数据库里的databasesource 表信息,然后根据我们传入对应的数据源id进行数据源切换:


DataSourceMapper.java :


import com.testdb.dbsource.pojo.DataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/23
 * @Description :
 **/
@Mapper
public interface DataSourceMapper {
    @Select("SELECT * FROM databasesource")
    List<DataSource> get();
}


DBChangeService.java:


import com.testdb.dbsource.pojo.DataSource;
import java.util.List;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
public interface DBChangeService {
    List<DataSource> get();
    boolean changeDb(String datasourceId) throws Exception;
}


DBChangeServiceImpl.java:


import com.testdb.dbsource.dbconfig.DBContextHolder;
import com.testdb.dbsource.dbconfig.DynamicDataSource;
import com.testdb.dbsource.mapper.DataSourceMapper;
import com.testdb.dbsource.pojo.DataSource;
import com.testdb.dbsource.service.DBChangeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Service
public class DBChangeServiceImpl implements DBChangeService {
   @Autowired
   DataSourceMapper dataSourceMapper;
    @Autowired
    private DynamicDataSource dynamicDataSource;
    @Override
    public List<DataSource> get() {
        return dataSourceMapper.get();
    }
    @Override
    public boolean changeDb(String datasourceId) throws Exception {
        //默认切换到主数据源,进行整体资源的查找
        DBContextHolder.clearDataSource();
        List<DataSource> dataSourcesList = dataSourceMapper.get();
        for (DataSource dataSource : dataSourcesList) {
            if (dataSource.getDatasourceId().equals(datasourceId)) {
                System.out.println("需要使用的的数据源已经找到,datasourceId是:" + dataSource.getDatasourceId());
                //创建数据源连接&检查 若存在则不需重新创建
                dynamicDataSource.createDataSourceWithCheck(dataSource);
                //切换到该数据源
                DBContextHolder.setDataSource(dataSource.getDatasourceId());
                return true;
            }
        }
        return false;
    }
}


注意认真看看上面的changeDb这个方法里面的代码,这就是后续手动切换调用的方法。

 

接下来,写相关操作user表的代码,因为user表分别在test2、test3数据库里,这样用于我们切换到test2或者test3数据库操作这些数据。


User.java:


import lombok.Data;
import lombok.ToString;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Data
@ToString
public class User {
    String userName;
    String age;
}


UserMappper.java  (上面简单介绍了下使用注解的方式获取表数据,可能有些人不习惯,那么这里也使用传统的mapper.xml方式编写mysql语句):


import com.testdb.dbsource.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/23
 * @Description :
 **/
@Mapper
public interface UserMapper {
    List<User> queryUserInfo();
}


userMapper.xml(注意namespace命名空间对应的路径以及user实体类对应的路径):


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.testdb.dbsource.mapper.UserMapper">
    <!--查询所有用户信息-->
    <select id="queryUserInfo" resultType="com.testdb.dbsource.pojo.User">
       select *
       from user
    </select>
</mapper>


顺便一提,我的mapper.xml放在了下面的这个目录结果里,这里的目录结构路径非常关键,因为我们是手动切换数据源,采取了手动配置SqlSessionFactory,需要我们自己去配置mapper.xml路径的,一开始的DruidDBConfig里面有相关的代码,可以去回顾下:


ps:


DruidDBConfig.java


image.pngimage.png


顺带,mybatis-config.xml里面,我做了简单的配置:


其实关于驼峰命名方式开启,我们在手动配置的时候也特意做了配置代码的。


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="useGeneratedKeys" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>


到这里,我们已经可以开始测试,


创建UserController.java,写个简单的测试接口:


import com.testdb.dbsource.dbconfig.DBContextHolder;
import com.testdb.dbsource.pojo.User;
import com.testdb.dbsource.service.DBChangeService;
import com.testdb.dbsource.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @Author : JCccc
 * @CreateTime : 2019/10/23
 * @Description :
 **/
@RestController
public class UserController {
    @Autowired
    private DBChangeService dbChangeServiceImpl;
    @Autowired
    UserService userService;
    /**
     * 查询所有
     * @return
     */
    @GetMapping("/test")
    public  String test() throws Exception {
        //切换到数据库dbtest2
        String datasourceId="dbtest2";
        dbChangeServiceImpl.changeDb(datasourceId);
        List<User> userList= userService.queryUserInfo();
        System.out.println(userList.toString());
        //再切换到数据库dbtest3
        dbChangeServiceImpl.changeDb("dbtest3");
        List<User> userList3= userService.queryUserInfo();
        System.out.println(userList3.toString());
        //切回主数据源
        DBContextHolder.clearDataSource();
        return "ok";
    }
}


主要看代码注释,调用整合出来的changeDb方法,通过传送数据源id (dbtest2)


image.png


切换到对应的数据库,然后操作对应数据库。

 

项目运行起来,调用接口 /test :


image.png


然后我们来看看控制台输出内容:


image.png


OK,非常简单顺利,教程就到此。

 

补充该实战教学的事务相关介绍配置和介绍:


实现动态数据源事务,找到该篇项目实例中的DruidDBConfig.java ,


将 我们实现的动态数据源加载类DynamicDataSource 添加到数据事务管理器里:


    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }


事务测试  (单个数据源的事务)


接着,给我们的新增User方法里面,故意搞个异常出来,测试下回滚是否生效:


在不指定任何异常时,使用注解事务,默认只对RuntimeException异常生效,所以咱们简单点,在啥时候想回滚,咱们就手动丢个RuntimeException异常出来。


image.png


写个测试接口,给db2 插入一条user信息,看看事务回滚情况:


    /**
     * 添加
     * @return
     */
    @GetMapping("/addTest")
    public  String addTest() throws Exception {
        //切换到数据库dbtest2
        String datasourceId="dbtest2";
        dbChangeServiceImpl.changeDb(datasourceId);
        User user2=new User();
        user2.setUserName("db2用户");
        user2.setAge("11");
        userService.insertUser(user2);
        //切回主数据源
        DBContextHolder.clearDataSource();
        return "ok";
    }


调用该接口,可以看到在未抛出异常时,可以看到插入根据影响行数,提示是成功了,但后面咱们抛异常触发了事务回滚:


image.png


看下数据库没出现新增的数据,事务回滚成功:


image.png


事务测试  (多个数据源的事务)


 

可以上图这个场景,就是测试切换不同数据源的时候,目前这两个数据源的插入方法都开启了事务,我们把test2的插入User方法加了故意抛出异常触发事务的代码,看看对于不同数据源事务触发情况:


看看调用该接口,控制台的输出:


image.png


再看看数据库情况,


test2数据库,


image.png


test3数据库,


image.png


可以看到在多个数据源的时候,还是只有单独的数据自己的事务起作用了(怎么解决这种情况? 文章末尾有介绍)。


如果只是用于主从数据库两个数据源的业务场景,那么该篇非常适合使用,因为只需保证主的事务,从库会同步数据。


而且如果仅仅是为了满足主从/读写的场景,大可不必从数据库读取数据源,使用AOP方式读取配置文件的数据源即可满足业务场景


那该篇的动态数据源实战适合哪些场景呢?


一.业务场景需要 很多个不同数据源,进行数据获取,不仅仅是两个,这样一来在配置文件配置是基本不可取的。


二.进行多从数据源数据获取,加上逻辑分析筛选后, 插入或更新 某个数据库,只需为该数据库事务进行负责。


例如我的使用场景:


公司各个小项目很多a,b,c,d,e,都用着自己项目的数据库。


而新项目接口需求,收到第三方的调用后,需要到a,b,c,d,e系统的数据库里读取出不同的核心数据,然后进行业务逻辑分析,然后生成新的订单,插入到新项目的主数据库里,并返回结果给第三方,我只需要对一个数据进行事务管理。


那如果就是想多个数据源事务可以一块回滚呢? 难道就没解决方案了吗?

很可以,就是需要有这种精神,不管你目前的业务场景有没有触及。值得敬佩的你,请看:


并不然,使用JTA分布式事务即可解决。

请看我这篇:

Springboot 整合druid+mybatis+jta分布式事务+多数据源aop注解动态切换


PS: 这篇文章我很久前写的,日常搬砖也比较忙,但是从评论里包括我其他的动态切换数据源文章里面得知,大家使用mybatis-plus来开发还是比较多的。


回归这个问题,那么在该篇文章里,如果使用 mybatis-plus 的话,为了保证使用,需要做什么调整呢?


1. 找到配置文件

 image.png

span,[object Object],2.把本文原先的 SqlSessionFactoryBean 调整为使用  MybatisSqlSessionFactoryBean ,其实也就是说换一下sqlSessionFactory()这个方法的代码即可:,span,[object Object]配置文件  代码:


    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
        // 设置mybatis的主配置文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
        sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);
        return sqlSessionFactoryBean.getObject();
    }


ok,我们来简单写个接口测试一下,


image.png


这里新建一个Mapper继承mybatis-plus里面的BaseMapper,service层我就省略了。

然后写个测试接口,从主数据源分别切换到db2和db3 ,分别插入一下数据:


import com.testdb.dbsource.dbconfig.DBContextHolder;
import com.testdb.dbsource.pojo.User;
import com.testdb.dbsource.service.DBChangeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Author : JCccc
 * @CreateTime : 2020/10/10
 * @Description :
 **/
@RestController
public class UserPlusController {
    @Autowired
    private DBChangeService dbChangeServiceImpl;
    @Autowired
    UserPlusMapper userMapper;
    @GetMapping("/testPlus")
    public void testPlus() throws Exception {
        User User2 = new User();
        User2.setUserName("TEST insert db 2");
        User2.setAge("2");
        //切换到数据库dbtest2
        String datasourceId2="dbtest2";
        dbChangeServiceImpl.changeDb(datasourceId2);
        int effectNum2 = userMapper.insert(User2);
        System.out.println("db2添加后的影响行数:"+effectNum2);
        //切换到数据库dbtest3
        String datasourceId3="dbtest3";
        dbChangeServiceImpl.changeDb(datasourceId3);
        User User3 = new User();
        User3.setUserName("TEST insert db 3");
        User3.setAge("3");
        int effectNum3 = userMapper.insert(User3);
        System.out.println("db3添加后的影响行数:"+effectNum3);
        //切回主数据源
        DBContextHolder.clearDataSource();
    }
}


调用接口,控制台打印:


image.png


然后是数据库分别都有了数据:


image.pngok,那这个对于mybatis-plus的使用补充就到这。

相关文章
|
5天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
20 0
|
9天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
20 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
8天前
|
Java 数据库连接
SpringBoot配置多数据源实战
第四届光学与机器视觉国际学术会议(ICOMV 2025) 2025 4th International Conference on Optics and Machine Vision
35 8
|
5天前
|
Java 数据库连接 数据库
springboot启动配置文件-bootstrap.yml常用基本配置
以上是一些常用的基本配置项,在实际应用中可能会根据需求有所变化。通过合理配置 `bootstrap.yml`文件,可以确保应用程序在启动阶段加载正确的配置,并顺利启动运行。
12 2
|
15天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
55 13
|
9天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
25 4
|
17天前
|
Java Spring 容器
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
43 3
|
11天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
10 0
|
9天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
24 4
|
7天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
23 1