zoukankan      html  css  js  c++  java
  • mybatis 多数据源动态切换

    笔者主要从事c#开发,近期因为项目需要,搭建了一套spring-cloud微服务框架,集成了eureka服务注册中心、
    gateway网关过滤、admin服务监控、auth授权体系验证,集成了redis、swagger、jwt、mybatis多数据源等各项功能。
    具体搭建过程后续另写播客介绍。具体结构如下:

    在搭建过程集成mybatis的时候,考虑到单一数据源无法满足实际业务需要,故结合c#的开发经验,进行多数据源动态集成。
    mybatis的多数据源可以采用两种方式进行,第一种是分包方式实现,这种方式灵活性不高,而且较为繁琐,故不做过多介绍。
    另一种方式是采用AOP的思想,进行注解动态切换,参考网上教程,核心思想是依靠 继承AbstractRoutingDataSource,
    重写determineCurrentLookupKey()方法,在该方法中使用DatabaseContextHolder获取当前线程的dataSource。
    但是网上方法大都是首先定义好各个datasource,比如有三个数据源,就需要实现定义好三个datasource,笔者感觉这种方法,在我
    目前这套框架中不够灵活,因为笔者采用的是微服务框架,考虑到各个服务都有可能使用不同的数据源,而多数据源动态切换
    是放在公共方法中实现的,如果每有新的数据源就要定义一个,对代码的侵入性太高,在c#中,选择数据源很容易,根据连接名
    称就可以切换过去,
    如下所示:

    <connectionStrings>
    <add name="test1" connectionString="server=127.0.0.1;user id=root;password=123456;database=db1;charset=utf8" providerName="MySql.Data.MySqlClient" />
    <add name="test2" connectionString="server=127.0.0.1;user id=root;password=123456;database=db2;charset=utf8" providerName="MySql.Data.MySqlClient" />
    <add name="test3" connectionString="server=127.0.0.1;user id=root;password=123456;database=db3;charset=utf8" providerName="MySql.Data.MySqlClient" />
    <connectionStrings>
    

    能不能像c#这样根据连接名称就自动选择呢,笔者的连接配置如下所示:

    spring:
      application:
        name: csg-auth
      datasource:
        kbase:
         - driverClassName: com.kbase.jdbc.Driver
           jdbcUrl: jdbc:kbase://127.0.0.1
           username: DBOWN
           password:
        jdbc:
         - driverClassName: com.mysql.cj.jdbc.Driver
           jdbcUrl: jdbc:mysql://localhost:3306/nacos?serverTimezone=GMT%2B8&useUnicode=false&characterEncoding=utf8&useSSL=false
           username: root
           password: 123456
           connName: nacos
         - driverClassName: com.mysql.cj.jdbc.Driver
           jdbcUrl: jdbc:mysql://localhost:3306/tpi?serverTimezone=GMT%2B8&useUnicode=false&characterEncoding=utf8&useSSL=false
           username: root
           password: 123456
           connName: tpi
    
    

    其中kbase不用理会,是我们公司自己的数据库,jdbc是维护的连接集合,其中connName就是我们自定义的连接名称,
    根据connName就可以自动切换到对应数据源。

    笔者实现代码如下:

    第一步

    首先,编写DynamicDataSource类集成AbstractRoutingDataSource,重写determineCurrentLookupKey方法,该方法主要作用是选择数据源的key
    代码如下:

    /**
     * 动态数据源
     * */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return  DataSourceHolder.getDataSource();
        }
    }
    

    第二步

    第二部编写DataSourceHolder类,提供设置、获取、情况数据源的方法,如下所示:

    public class DataSourceHolder {
        /**
         * 线程本地环境
         */
        private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    
        /**
         * 设置数据源
         */
        public static void setDataSources(String connName) {
            dataSources.set(connName);
        }
    
        /**
         * 获取数据源
         */
        public static String getDataSource() {
            return dataSources.get();
        }
    
        /**
         * 清楚数据源
         */
        public static void clearDataSource() {
            dataSources.remove();
        }
    }
    
    

    第三步

    第三步,编写DataSourceConfig类,该类主要作用是读取配置文件中的数据源连接集合,以及维护项目数据源的Bean对象,
    代码如下:

    @Component
    @ConfigurationProperties("spring.datasource")
    public class DataSourceConfig {
        private List<DataSourceModel> jdbc;
    
        public Map<Object, Object> getDataSourceMap(){
            Map<Object, Object>map=new HashMap<>();
            if (jdbc!=null&&jdbc.size()>0){
                for (int i = 0; i < jdbc.size() ; i++) {
                    DataSourceBuilder dataSourceBuilder=DataSourceBuilder.create();
                    dataSourceBuilder.driverClassName(jdbc.get(i).getDriverClassName());
                    dataSourceBuilder.password(jdbc.get(i).getPassword());
                    dataSourceBuilder.username(jdbc.get(i).getUsername());
                    dataSourceBuilder.url(jdbc.get(i).getJdbcUrl());
                    map.put(jdbc.get(i).getConnName(),dataSourceBuilder.build());
                }
            }
            return map;
        }
    
        @Bean
        public DataSource csgDataSource(){
            DynamicDataSource dynamicDataSource=new DynamicDataSource();
            Map<Object,Object>dataSourceMap=getDataSourceMap();
            dynamicDataSource.setTargetDataSources(dataSourceMap);
             Object object= dataSourceMap.values().toArray()[0];
            dynamicDataSource.setDefaultTargetDataSource(object);
            return dynamicDataSource;
        }
    
        public void setJdbc(List<DataSourceModel> jdbc) {
            this.jdbc = jdbc;
        }
    
        public List<DataSourceModel> getJdbc(){
            return this.jdbc;
        }
    }
    

    其中,getDataSourceMap()方法,作用是根据配置的连接集合,生成AbstractRoutingDataSource所需要的resolvedDataSources。
    而csgDataSource()方法,添加了@Bean注解,作用是让mybatis的SqlSessionFactory,能够使用咱们维护的数据源。

    第四部

    编写MyBatisConfig类,该类主要作用是 配置好mybatis的数据源。

    @Configuration
    public class MyBatisConfig {
    
        @Autowired
        private DataSource csgDataSource;
    
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(csgDataSource);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath:mapper/**/*.xml"));
            return sqlSessionFactoryBean.getObject();
        }
    
        @Bean
        public PlatformTransactionManager platformTransactionManager(){
            return new DataSourceTransactionManager(csgDataSource);
        }
    }
    
    

    可以看到,这里选择的是我们定义好的csgDataSource,其作用也是如此。

    第五步

    编写TargetDataSource注解

    /**
     * 注解标签
     * 作用于 方法、接口、类、枚举、注解
     * */
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})
    public @interface TargetDataSource {
        String connName();
    }
    

    其中connName,就是我们需要使用的数据源

    第六步

    编写DataSourceExchange,改类为切面,作用于TargetDataSource注解,故使用TargetDataSource注解的时候,
    会根据connName自动选择数据源。

    @Aspect
    @Component
    public class DataSourceExchange {
    
        @Before("@annotation(TargetDataSource)")
        public void before(JoinPoint joinPoint){
            MethodSignature sign = (MethodSignature) joinPoint.getSignature();
            Method method = sign.getMethod();
            boolean isMethodAop= method.isAnnotationPresent(TargetDataSource.class);
            if (isMethodAop){
                TargetDataSource datasource = method.getAnnotation(TargetDataSource.class);
                DataSourceHolder.setDataSources(datasource.connName());
            }else {
                if (joinPoint.getTarget().getClass().isAnnotationPresent(TargetDataSource.class)){
                    TargetDataSource datasource = joinPoint.getTarget().getClass().getAnnotation(TargetDataSource.class);
                    DataSourceHolder.setDataSources(datasource.connName());
                }
            }
        }
    
        @After("@annotation(TargetDataSource)")
        public void after(){
            DataSourceHolder.clearDataSource();
        }
    }
    

    改切面作用于方法运行前后,负责选择、取消数据源。

    第七部

    开始验证,使用方法如下:

    @Service
    public class UserServiceImpl  implements UserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        @TargetDataSource(connName = "nacos")
        public List<UserEntity> getList() {
            List<UserEntity> list= userMapper.selectUserList();
            return list;
        }
    }
    

    在service中,在需要进行数据库操作的方法上,添加TargetDataSource注解,即可自动切换到所需要的数据源。
    至此,mybatis就可以动态切换数据源了。

    笔者从事java开发工作不多,改方法可能不是太好,也请各位看官勿喷~

  • 相关阅读:
    fedora中使用 mariadb数据库建库和建表-- mariadb数据库服务无法启动?
    我在linux中使用的vundle 和 vimrc配置
    vim的加密和解密?
    gvim写html代码时如何快速地跳转到一个标签的结束位置: 终极插件: matchit.vim
    HTML5+CSS3整体回顾
    HTML5无刷新修改URL
    使用ab对nginx进行压力测试
    nginx php-fpm 输出php错误日志
    如何正确配置Nginx+PHP
    关于Nginx的一些优化(突破十万并发)
  • 原文地址:https://www.cnblogs.com/ZyCoder/p/14015130.html
Copyright © 2011-2022 走看看