关于架构心得-mybatis多个数据源使用start包

前言:

最近新搭建了一个项目,用到了多个数据源,并且需要动态切换,所以写了一个切换数据源的starter包。

spring-boot-starter-multiple-data-source

以读写分离两个数据源举例

一.数据源配置

配置多个数据源

spring:
    datasource:
        master:
          jdbc-url: jdbc:mysql://127.0.0.1:3306/demo1?useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8&useSSL=false
          username: xxxxxxxxx
          password: xxxxxxxxx
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          jdbc-url: jdbc:mysql://127.0.0.1:3306/demo2?useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8&useSSL=false
          username: xxxxxx
          password: xxxxxx
          driver-class-name: com.mysql.cj.jdbc.Driver

基础类:

DataSourceType.java 数据源枚举类

public enum DataSourceType {

    MASTER("master"),
    SLAVE("slave");

    private String dataSource;

    DataSourceType(String dataSource){
        this.dataSource = dataSource;
    }

    public String getDataSource() {
        return dataSource;
    }
}

MybatisConfig类:

加载多个数据源,并默认数据源为master

@Configuration
@MapperScan(basePackages = {"**.*Mapper"}) // 扫描DAO
public class MybatisConfig {


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

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

    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(DataSourceType.MASTER.getDataSource(), master());
        dataSourceMap.put(DataSourceType.SLAVE.getDataSource(), slave());
        // 将 master 数据源作为默认指定的数据源
        dynamicDataSource.setDefaultDataSource(master());
        // 将 master 和 slave 数据源作为指定的数据源
        dynamicDataSource.setDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
        sessionFactory.setDataSource(dynamicDataSource());
        // 扫描Model
        //sessionFactory.setTypeAliasesPackage("com.danke.**.entity");
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // 扫描映射文件
        sessionFactory.setMapperLocations(resolver.getResources("**.*Mapper.xml"));
        return sessionFactory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

上下文类,用于保证线程安全

public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @Override
        protected String initialValue() {
            return DataSourceType.MASTER.getDataSource();
        }
    };


    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * 切换数据源
     * @param key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    /**
     * 获取数据源
     * @return
     */
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    /**
     * 重置数据源
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

    /**
     * 判断是否包含数据源
     * @param key 数据源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 添加数据源keys
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }

}

TargetDataSource.java 注解类

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

    /**
     * 数据源key值
     * @return
     */
    DataSourceType value();

}

切面动态切换数据源

@Slf4j
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {

    /**
     * 切换数据源
     * @param point
     * @param targetDataSource
     */
    @Before("@annotation(targetDataSource))")
    public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value().getDataSource())) {
            log.info("数据源 【{}】 不存在, 使用默认数据源 【{}】 " + targetDataSource.value());
        } else {
            // 切换数据源
            DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value().getDataSource());
            log.info("数据源变为【" + DynamicDataSourceContextHolder.getDataSourceKey()
                    + "】 方法 【" + point.getSignature() + "】");
        }
    }

    /**
     * 重置数据源
     * @param point
     * @param targetDataSource
     */
    @After("@annotation(targetDataSource))")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        // 将数据源置为默认数据源
        DynamicDataSourceContextHolder.clearDataSourceKey();
        log.info("重置数据源为 【" + DynamicDataSourceContextHolder.getDataSourceKey()
                + "】 方法为 【" + point.getSignature() + "】");
    }
}

这里所有的配置已经完成

应用:

需要在springboot启动类中添加 

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

第一种:

在starter包中,添加META-INF/spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxx.xxx.xxx.MybatisConfig

工程引入的jar包会自动加载MybatisConfig文件。

第二种:

在工程中新增 MybatisConfig类 集成starter包中的MybatisConfig 

如下代码:

@Configuration
public class MybatisConfig extends xxx.xxx.xxx.MybatisConfig{
}

数据源切换使用,在方法上标识 @TargetDataSource

    @TargetDataSource(DataSourceType.SLAVE)
    @Override
    public void selectById(Integer id) {
       //操作slave数据源
    }
    
    @TargetDataSource(DataSourceType.MASTER)
    @Override
    public void updateById(String str) {
        //操作master数据源
    }

第三种:

这种方式也是springboot目前使用的,不用任何配置,只有注解

新增注解类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MybatisConfig.class)
public @interface MultipleDataSource {
}

然后在启动类上注解

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@MultipleDataSource//加入这个注解
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值