栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

实现不同数据源动态切换SpringBoot+MyBatis

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

实现不同数据源动态切换SpringBoot+MyBatis

实现不同数据源动态切换SpringBoot+MyBatis

在实际的业务业务场景中,经常有不同的request请求,需要使用不同的DB数据源。比如此时有请求1需要访问数据库DB1,请求2需要访问数据库DB2,而我们一般的项目中都是固定数据源的,这样的场景就满足不了。这篇文章就是给这种场景尝试提供一种解决方案。

闲话少说开肝。

整体架构使用 SpringBoot + Mybatis +Mysql实现。

1、数据源

先给定两个数据源,定义对应的DataSource的Bean。

数据源1:master

   @Bean
   @ConfigurationProperties("spring.datasource.master")
   public DataSource master() {
       return DataSourceBuilder.create().build();
  }

数据源2:slave

   @Bean
   @ConfigurationProperties("spring.datasource.slave")
   public DataSource slave() {
       return DataSourceBuilder.create().build();
  }

此时需要一个路由数据源,来作为中间层,此时将master和slave数据源传入

   @Bean(name = "DBSource")
   public DataSource dataSourceRoutingDb(@Qualifier("master") DataSource master,
                                         @Qualifier("slave") DataSource slave) {
       Map targetDataSource = new HashMap<>();
       targetDataSource.put(DBTypeEnum.MASTER, master());
       targetDataSource.put(DBTypeEnum.SLAVE, slave());
       RoutingDataSource dataSourceRoutingDb = new RoutingDataSource();
       dataSourceRoutingDb.setDefaultTargetDataSource(master);
       dataSourceRoutingDb.setTargetDataSources(targetDataSource);

       return dataSourceRoutingDb;
  }
2、数据源上下文设置
public class DBContextHolder {
   private final static ThreadLocal contextHolder = new ThreadLocal<>();

   
   public static void set(String dbType) {
       contextHolder.set(dbType);
  }

   
   public static String get() {
       return contextHolder.get();
  }

   
   public static void clear() {
       contextHolder.remove();
  }
}

使用ThreadLocal将传入的数据源设置到每个线程的上下文中。

3、数据源路由

这里需要使用到Spring的AbstractRoutingDataSource中的determineCurrentLookupKey方法。

首先看下AbstractRoutingDataSource类结构,继承了AbstractDataSource

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

既然是AbstractDataSource,当然就是javax.sql.DataSource的子类,于是我们自然地回去看它的getConnection方法:

public Connection getConnection() throws SQLException {  
       return determineTargetDataSource().getConnection();  
}  
public Connection getConnection(String username, String password) throws SQLException {  
   return determineTargetDataSource().getConnection(username, password);  
}

关键就在determineTargetDataSource()里:

  
   protected DataSource determineTargetDataSource() {  
       Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
       Object lookupKey = determineCurrentLookupKey();  
       DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
       if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
           dataSource = this.resolvedDefaultDataSource;  
      }  
       if (dataSource == null) {  
           throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
      }  
       return dataSource;  
  }

这里用到了我们需要进行实现的抽象方法determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource。

public void afterPropertiesSet() {  
       if (this.targetDataSources == null) {  
           throw new IllegalArgumentException("Property 'targetDataSources' is required");  
      }  
       this.resolvedDataSources = new HashMap(this.targetDataSources.size());  
       for (Map.Entry entry : this.targetDataSources.entrySet()) {  
           Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());  
           DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());  
           this.resolvedDataSources.put(lookupKey, dataSource);  
      }  
       if (this.defaultTargetDataSource != null) {  
           this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);  
      }  
  }

直接上代码:

取传入的数据源设置

public class RoutingDataSource extends AbstractRoutingDataSource {
   @Override
   protected Object determineCurrentLookupKey() {
       return DBContextHolder.get();
  }
}
4、Mybatis的设置

这里需要给mybatis重新设置SqlSessionFactory数据源,设置事务PlatformTransactionManager数据源

@Configuration
@EnableTransactionManagement
public class MyBatisConfig {

   @Resource(name = "DBSource")
   private DataSource routingDataSource;

   
   @Bean
   public SqlSessionFactory sqlSessionFactory() throws Exception {
       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
       sqlSessionFactoryBean.setDataSource(routingDataSource);
       //可能出错的问题:sqlSessionFactoryBean.setMapperLocations(resolver.getResources(packageSearchPath)),写错成了getResource导致找不到classpath:mapper
   @Bean
   public PlatformTransactionManager transactionManager() {
       return new DataSourceTransactionManager(routingDataSource);
  }

}
5、使用注解用于数据源切换

设置一个默认的数据源

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@documented
public @interface DataSourceSwitcher {

   
   String value() default DBTypeEnum.MASTER;

   
   boolean clear() default true;
}
6、AOP

根据注解传值,将数据源设置到上下文中。

@Aspect
@Component
public class DataSourceContextAop {
   private final static Logger LOGGER = LoggerFactory.getLogger(DataSourceContextAop.class);

   @Around("@annotation(com.lyu.mms.annotation.DataSourceSwitcher)")
   public Object setDataSource(ProceedingJoinPoint point) {
       boolean clear = false;
       try {
           MethodSignature signature = (MethodSignature) point.getSignature();
           Method method = signature.getMethod();
           DataSourceSwitcher dataSourceSwitcher = method.getAnnotation(DataSourceSwitcher.class);
           clear = dataSourceSwitcher.clear();
           DBContextHolder.set(dataSourceSwitcher.value());
           LOGGER.info("切换数据源至->{}", dataSourceSwitcher.value());
           return point.proceed();
      } catch (Throwable throwable) {
           throwable.printStackTrace();
      } finally {
           if (clear) {
               DBContextHolder.clear();
          }
      }
       return null;
  }
}
7、使用

直接在对应的controller上加上相应的注解就愉快的用起来了

   @DataSourceSwitcher(DBTypeEnum.MASTER)
   //@DataSourceSwitcher(DBTypeEnum.SLAVE)
   @Override
   @PutMapping(name = "保存", value = {"/add"})
   public void save(User user) {
       userMapper.insert(user);
       LOGGER.info("新增成功,{}", user);
  }
8、总结

整体借助springboot和mybatis实现数据源切换,关键点使用determineCurrentLookupKey路由数据源,然后为mybatis的SqlSessionFactory和PlatformTransactionManager事务重新设置数据源。

如果需要代码可以联系我获取。

一键三连哦。

我是程序员一二,see you!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/344821.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号