• Spring Boot + Mybatis多数据源和动态数据源配置


    文章转自 https://blog.csdn.net/neosmith/article/details/61202084

    网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种方式,记录一下配置方法供大家参考。

    应用场景

    项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库。

    多数据源

    首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。在@SpringBootApplication注解中添加exclude属性即可:

    @SpringBootApplication(exclude = {
            DataSourceAutoConfiguration.class
    })
    public class TitanWebApplication {
        public static void main(String[] args) {
            SpringApplication.run(TitanWebApplication.class, args);
        }
    }

    然后在application.properties中配置多数据源连接信息:

    # titan库
    spring.datasource.titan-master.url=jdbc:mysql://X.X.X.X:port/titan?characterEncoding=UTF-8
    spring.datasource.titan-master.username=
    spring.datasource.titan-master.password=
    spring.datasource.titan-master.driver-class-name=com.mysql.jdbc.Driver
    # 连接池配置
    # 省略
    
    # 其它库
    spring.datasource.db2.url=jdbc:mysql://X.X.X.X:port/titan2?characterEncoding=UTF-8
    spring.datasource.db2.username=
    spring.datasource.db2.password=
    spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver

    由于我们禁掉了自动数据源配置,因些下一步就需要手动将这些数据源创建出来:

    @Configuration
    public class DataSourceConfig {
    
        @Bean(name = "titanMasterDS")
        @ConfigurationProperties(prefix = "spring.datasource.titan-master") // application.properteis中对应属性的前缀
        public DataSource dataSource1() {
            return DataSourceBuilder.create().build();
        }
    
    
    
    
        @Bean(name = "ds2")
        @ConfigurationProperties(prefix = "spring.datasource.db2") // application.properteis中对应属性的前缀
        public DataSource dataSource2() {
            return DataSourceBuilder.create().build();
        }
    
    }

    接下来需要配置两个mybatis的SqlSessionFactory分别使用不同的数据源:

    @Configuration
    @MapperScan(basePackages = {"titan.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory1")
    public class MybatisDbAConfig {
    
        @Autowired
        @Qualifier("titanMasterDS")
        private DataSource ds1;
    
    
        @Bean
        public SqlSessionFactory sqlSessionFactory1() throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(ds1); // 使用titan数据源, 连接titan库
    
            return factoryBean.getObject();
    
        }
    
        @Bean
        public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
            SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); // 使用上面配置的Factory
            return template;
        }
    }

    经过上面的配置后,titan.mapper下的Mapper接口,都会使用titan数据源。同理可配第二个SqlSessionFactory:

    @Configuration
    @MapperScan(basePackages = {"other.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory2")
    public class MybatisDbBConfig {
        @Autowired
        @Qualifier("ds2")
        private DataSource ds2;
    
        @Bean
        public SqlSessionFactory sqlSessionFactory2() throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(ds2);
    
    
            return factoryBean.getObject();
    
        }
    
        @Bean
        public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
            SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
            return template;
        }
    }

    完成这些配置后,假设有2个Mapper titan.mapper.UserMapperother.mapper.RoleMapper,使用前者时会自动连接titan库,后者连接ds2库。

    动态数据源

    使用动态数据源的初衷,是能在应用层做到读写分离,即在程序代码中控制不同的查询方法去连接不同的库。除了这种方法以外,数据库中间件也是个不错的选择,它的优点是数据库集群对应用来说只暴露为单库,不需要切换数据源的代码逻辑。

    我们通过自定义注解 + AOP的方式实现数据源动态切换。

    首先定义一个ContextHolder, 用于保存当前线程使用的数据源名:

    public class DataSourceContextHolder {
        public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
    
        /**
         * 默认数据源
         */
        public static final String DEFAULT_DS = "titan-master";
    
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
        // 设置数据源名
        public static void setDB(String dbType) {
            log.debug("切换到{}数据源", dbType);
            contextHolder.set(dbType);
        }
    
        // 获取数据源名
        public static String getDB() {
            return (contextHolder.get());
        }
    
        // 清除数据源名
        public static void clearDB() {
            contextHolder.remove();
        }
    }

    然后自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource即可:

    public class DynamicDataSource extends AbstractRoutingDataSource {
        private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
    
        @Override
        protected Object determineCurrentLookupKey() {
            log.debug("数据源为{}", DataSourceContextHolder.getDB());
    
            return DataSourceContextHolder.getDB();
        }
    
    }

    创建动态数据源:

    /**
         * 动态数据源: 通过AOP在不同数据源之间动态切换
         * @return
         */
        @Bean(name = "dynamicDS1")
        public DataSource dataSource() {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            // 默认数据源
            dynamicDataSource.setDefaultTargetDataSource(dataSource1());
    
            // 配置多数据源
            Map<Object, Object> dsMap = new HashMap(5);
            dsMap.put("titan-master", dataSource1());
            dsMap.put("ds2", dataSource2());
    
            dynamicDataSource.setTargetDataSources(dsMap);
    
            return dynamicDataSource;
        }

    自定义注释@DS用于在编码时指定方法使用哪个数据源:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({
            ElementType.METHOD
    })
    public @interface DS {
        String value() default "titan-master";
    }

    切面,实现切换逻辑:

    @Aspect
    @Component
    public class DynamicDataSourceAspect {
    
        @Before("@annotation(DS)")
        public void beforeSwitchDS(JoinPoint point){
    
            //获得当前访问的class
            Class<?> className = point.getTarget().getClass();
    
            //获得访问的方法名
            String methodName = point.getSignature().getName();
            //得到方法的参数的类型
            Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
            String dataSource = DataSourceContextHolder.DEFAULT_DS;
            try {
                // 得到访问的方法对象
                Method method = className.getMethod(methodName, argClass);
    
                // 判断是否存在@DS注解
                if (method.isAnnotationPresent(DS.class)) {
                    DS annotation = method.getAnnotation(DS.class);
                    // 取出注解中的数据源名
                    dataSource = annotation.value();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // 切换数据源
            DataSourceContextHolder.setDB(dataSource);
    
        }
    
    
        @After("@annotation(DS)")
        public void afterSwitchDS(JoinPoint point){
    
            DataSourceContextHolder.clearDB();
    
        }
    }

    完成上述配置后,在先前SqlSessionFactory配置中指定使用DynamicDataSource就可以在Service中愉快的切换数据源了:

    @Autowired
        private UserAModelMapper userAMapper;
    
        @DS("titan-master")
        public String ds1() {
            return userAMapper.selectByPrimaryKey(1).getName();
        }
    
        @DS("ds2")
        public String ds2() {
            return userAMapper.selectByPrimaryKey(1).getName();
        }
  • 相关阅读:
    Comet学习资料
    vue 组件使用中的细节点
    js基本数据类型 BigInt 和 Number 的区别
    vue v-model 简单使用
    promise总结
    axio post 请求后端接收不到参数的解决办法
    快速配置webpack多入口脚手架
    BETA 版冲刺前准备
    Alpha 事后诸葛亮(团队)
    Alpha 答辩总结
  • 原文地址:https://www.cnblogs.com/shuaiandjun/p/8667814.html
Copyright © 2020-2023  润新知