SpringBoot整合Mybatisplus实现完全动态获取多数据源

上篇我们分享了SpringBoot整合Mybatisplus 完成基本多数据源的配置,此篇我们从更高的层面去去分享 SpringBoot整合Mybatisplus实现完全动态获取多数据源,此方案适合更多的业务场景,比如每个用户一个数据源、每种类型以一个数据源、每种请求一个数据源等等,即此方案也就是常说的多租户、读写分离的业务场景等!下面开始切入正题:

1、核心pom 相关文件配置

   <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
      <version>3.5.1</version>
    </dependency>
    
      <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>3.5.1</version>
       </dependency>

2、yml配置默认数据源:


spring:
  datasource:
    dynamic:
      primary: master# 配置默认数据库,及没有注解指定时走的数据库
      #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master: # 数据源1配置
          url: jdbc:mysql://localhost:3306/user_db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
      durid:
        initial-size: 1
        max-active: 20
        min-idle: 1
        max-wait: 60000
  autoconfigure:
    exclude:  com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure 
 

此数据源是默认的数据源,服务启动后会有一个 key 为master的主数据源;如果服务启动不设置默认数据源,启动时会报错,健康检查失败,错误提示核心是:

com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException: dynamic-datasource can not find primary datasource
	at com.baomidou.dynamic.datasource.DynamicRoutingDataSource.determinePrimaryDataSource(DynamicRoutingDataSource.java:91) ~[dynamic-datasource-spring-boot-starter-3.5.1.jar:3.5.1]
	at com.baomidou.dynamic.datasource.DynamicRoutingDataSource.getDataSource(DynamicRoutingDataSource.java:120) ~[dynamic-datasource-spring-boot-starter-3.5.1.jar:3.5.1]
	at com.baomidou.dynamic.datasource.DynamicRoutingDataSource.determineDataSource(DynamicRoutingDataSource.java:78) ~[dynamic-datasource-spring-boot-starter-3.5.1.jar:3.5.1]

 

 但是,服务最终是启动成功的,如果健康检查不拦截的话。

3、封装数据源处理类

 import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.ds.ItemDataSource;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.DeleteMapping;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* @Author nandao
* @Date 2021/12/01
**/
@Service
@Slf4j
public class CustomDataSourceService {

    @Resource
    private DataSource dataSource;
    @Resource
    private DefaultDataSourceCreator dataSourceCreator;

    @PostConstruct
    public void  init (){
        //可以初始化数据源在此
    }

    /**
     * 获取当前所有数据源的key
     */
    public Set<String> getAllDataSourceKey() {
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        return ds.getDataSources().keySet();
    }

    /**
     * 获取当前所有数据源url
     */
    public String userCurrentDataSource(String tenantId) {
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        Set<String> urlList = ds.getDataSources().keySet();
        if(urlList.contains(tenantId)){
            DataSource dataSource = ds.getDataSource(tenantId);
            if(Objects.nonNull(dataSource)){
                ItemDataSource druidDataSource = (ItemDataSource) dataSource;
                String name = druidDataSource.getName();
                
                return null;
            }
        }
         return null;
    }

    /**
     * 添加数据源
     */
    public Set<String> add(DataSourcePO dto) {
        DataSourceProperty dataSourceProperty = new DataSourceProperty();
        BeanUtils.copyProperties(dto, dataSourceProperty);
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
        // 测试连接
        try {
            dataSource.getConnection();
        }catch (Exception e){
            throw new IllegalStateException("[" + JSONObject.toJSONString(dto) + "]" + ":数据源连接不上可能是连接参数有误或库未创建!");
        }
        ds.addDataSource(dto.getPoolName(), dataSource);
        return ds.getDataSources().keySet();
    }

    /**
     * 删除容器中数据源
     */
    @DeleteMapping
    public Boolean remove(String name) {
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        ds.removeDataSource(name);
        return Boolean.TRUE;
    }
}

4、业务切面处理:


import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;

/**
 * @Author nandao
 * @Date 2021/11/5
 **/
@Aspect
@Component
@Slf4j
@Order(-1)
public class DynamicDataSourceAspect {

    @Resource
    private CustomDataSourceService customDataSourceService ;

    
    @Resource
    private HttpServletRequest request;

    //@Pointcut("@annotation(com.nandao.test.db.DynamicDataSource)")
    @Pointcut("execution(* com.nandao.test.mapper.*.*(..))")
    public void dbMoreProduct() {
    }

    @Around("dbMoreProduct()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 获取当前租户
        String tenantId = request.getHeader("tenant");
        
        // 获取当前所有的数据源
        Set<String> dateSourceKeyList = customDataSourceService.getAllDataSourceKey();
         
        if (!dateSourceKeyList .contains(tenantId)) {
            // 获取租户对应数据库链接配置
            DataSourceVO dataSourceInfo = getTenantDateSource();
            // 添加数据源
            customDataSourceService.add(dataSourceInfo);
           
        }
        // 手动指定数据源
        String push = DynamicDataSourceContextHolder.push(tenantId);
        log.info("租户:{},当前租户的数据源:{}",tenantId,push);
        try {
            return proceedingJoinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.clear();
        }
    }

    private DataSourceVO getTenantDateSource() {
        String tenantId =  request.getHeader("tenant");
        DatabaseUrl databaseUrl = checkDataSource(tenantId);
 
        DataSourceVO dataSource = new DataSourceVO();
        dataSource.setPoolName(tenantId);
        dataSource.setUsername(databaseUrl.getUserName());
        dataSource.setPassword(databaseUrl.getPassword());
        dataSource.setUrl(databaseUrl.getUrl());
        return dataSource;
    }

    private DatabaseUrl checkDataSource(String tenantId) {
        //去数据库查询 返回databaseUrl,此处省略.....

        DatabaseUrl databaseUrl  ;
 
        return databaseUrl;
    }
}

5,注意事项:

            /**
             * 此处根据业务场景判断是否要加,放在finally中:尽量加,利大于弊!
             * 好处:此线程数据源使用后,清楚,避免内存泄露,同时避免其他线程拿到此数据源(垃圾回收之前),造成数据库串用,后果很严重,因为此处是栈的原理;
             * 弊端:如果扫描mapper文件,可能造成多次重置数据源,但是安全稳定起见,还是要清除;
             */
             DynamicDataSourceContextHolder.clear();

业务场景:是张三、李四分别操作A、B两个数据库,张三一直操作A,然后李四操作B,这期间李四很可能拿到A库的数据源执行!

到此大概业务逻辑分享清楚,用户和自己对应的数据源信息可以通过管理后台提前维护到数据库,用户首次登录获取对应的数据源存到数据源容器中,根据租户和线程调用相关的数据源;当然数据源也可以采用缓存预热的方案,服务启动时把所有用户的数据源加载到容器中,功能接口调用时就不用在去数据库查询了,进一步提高了性能!

注意:AOP切面可以用扫描自定义注解、包名等等,具体选择根据业务来判断哪种合适,所有技术方案脱离了业务就是耍流氓!!!下面我们详细分享多数据源的源码,敬请期待!

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Spring Boot是一种快速开发框架,可以快速、高效地开发和部署应用程序。而Mybatis Plus是一种基于Mybatis的扩展插件,可以简化Mybatis的开发流程,并提供更多的功能。 Spring Boot和Mybatis Plus整合起来,可以实现对两个不同的数据(MySQL和Oracle)进行动态支持。具体来说,需要在Spring Boot配置文件中配置对应的数据,并在Mybatis的配置中设置不同的数据。 首先,需要在配置文件中定义两个数据,如下所示: ``` spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.secondary.url=jdbc:oracle:thin:@localhost:1521:ORCL spring.datasource.secondary.username=system spring.datasource.secondary.password=123456 ``` 这里定义了两个数据,一个是MySQL,一个是Oracle。其中,MySQL的数据配置了url、username和password,而Oracle的数据配置了url、username和password。 然后,在Mybatis的配置中,需要使用动态数据来支持两个数据。具体来说,可以通过继承AbstractRoutingDataSource类,并重写determineCurrentLookupKey()方法,来动态切换数据。如下所示: ``` public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSource = new ThreadLocal<String>(); public static void setDataSource(String dataSourceType) { dataSource.set(dataSourceType); } @Override protected Object determineCurrentLookupKey() { return dataSource.get(); } } ``` 这里定义了一个DynamicDataSource类,继承了AbstractRoutingDataSource类。通过设置ThreadLocal变量来指定要使用的数据。当需要切换数据时,只需调用setDataSource()方法即可。 最后,在Spring Boot启动类中,需要配置Mapper扫描器,并设置数据路由规则。如下所示: ``` @SpringBootApplication @MapperScan(basePackages = "com.example.mapper") public class DemoApplication { @Bean public DynamicDataSource dataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object,Object> targetDataSources = new HashMap<>(); targetDataSources.put("mysql", DataSourceBuilder.create().url(mysqlUrl).username(mysqlUsername).password(mysqlPassword).build()); targetDataSources.put("oracle", DataSourceBuilder.create().url(oracleUrl).username(oracleUsername).password(oraclePassword).build()); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(targetDataSources.get("mysql")); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource); sqlSessionFactoryBean.setTypeAliasesPackage("com.example.entity"); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); return sqlSessionFactoryBean.getObject(); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 这里定义了一个DynamicDataSource类,并设置了mysql和oracle两个数据。在SqlSessionFactory中,使用DynamicDataSource来替换默认的数据。同时,设置Mapper扫描器,以便自动装配Mapper接口。这样就可以在Mybatis中动态切换数据了。 综上所述,Spring Boot和Mybatis Plus整合起来,可以实现对两个不同的数据(MySQL和Oracle)进行动态支持,可以满足大多数项目的需求。 ### 回答2: Spring Boot和MyBatis Plus是两个非常常用的Java框架。它们的结合为开发者提供了强大的开发工具和框架,可以帮助他们快速地完成应用程序的开发。同时,使用Spring Boot和MyBatis Plus进行动态支持MySQL和Oracle数据整合也非常容易。 首先,我们需要在pom.xml文件中引入MySQL和Oracle数据库驱动程序的依赖。接着,我们需要在application.properties或者application.yml文件中配置数据的相关信息。在Spring Boot和MyBatis Plus整合中,我们可以使用一个配置类来装配相关的Bean,如数据,MyBatis的SqlSessionFactory等。需要注意的是,我们要为每一个数据创建一个SqlSessionFactory对象,以便MyBatis Plus区分不同的数据。 当我们完成以上步骤后,我们就可以基于mybatis-plus的动态数据自动切换使用MySQL或Oracle数据库了。具体地,我们可以使用mybatis-plus提供的AbstractRoutingDataSource和DetermineDataSourceInterceptor类来实现动态数据的切换。AbstractRoutingDataSource是一个抽象的数据实现类,它可以根据key值自动切换对应的数据。而DetermineDataSourceInterceptor则是一个实现MyBatis拦截器接口的类,它可以拦截MyBatis执行SQL语句的操作,根据业务逻辑动态切换数据。通过以上两个类的配合,我们可以实现在同一应用中动态切换MySQL和Oracle数据。 总之,Spring Boot和MyBatis Plus整合可以为开发者提供了非常方便的开发工具和框架,同时也可以轻松地实现动态支持MySQL和Oracle数据整合。这对我们实现业务灵活性和数据共享都非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值