0
点赞
收藏
分享

微信扫一扫

springboot+mybatis+druid实现动态切换数据库

勇敢乌龟 2022-01-20 阅读 44

提示:
本文主要流程参考博客:https://www.cnblogs.com/xifengxiaoma/p/9888240.html
集成druid数据库连接池参考博客:https://www.cnblogs.com/dyc940210/p/9334589.html

文章目录


配置步骤

1.配置文件中配置多数据源信息

代码如下:

datasource:
    # 使用druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    master:
      url: jdbc:mysql://ip:3306/数据库?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
      username: 用户名
      password: 密码
      driver-class-name: com.mysql.cj.jdbc.Driver
      filters: stat
      maxActive: 20
      initialSize: 1
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: select 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
    slave:
      url: jdbc:mysql://ip:3306/数据库?useSSL=false&useUnicode=yes&characterEncoding=UTF-8
        username: 用户名
        password: 密码
        driver-class-name: com.mysql.cj.jdbc.Driver
        filters: stat
        maxActive: 20
        initialSize: 1
        maxWait: 60000
        minIdle: 1
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: select 'x'
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxOpenPreparedStatements: 20

2.启动类配置

主要是添加注解,禁用数据源默认自动配置
代码如下:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

3.mybatis配置

代码如下:

/**
 * mybatis配置
 */
@Configuration
@MapperScan(basePackages = {"cn.xxxx.xx.mapper"})
public class MybatisConfig {
    //数据源类型:连接池
    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

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

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

    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("master", master());
        dataSourceMap.put("slave", 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());
        sessionFactory.setTypeAliasesPackage("cn.xxxx.xx.bean.*");
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath*:**/mapping/*.xml"));    // 扫描映射文件
        return sessionFactory;
    }

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

4.动态数据源实现类

代码如下:

/**
 * 动态数据源实现类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
     * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
     */
    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }

    /**
     * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
     * 设置默认数据源
     * @param defaultDataSource
     */
    public void setDefaultDataSource(Object defaultDataSource) {
        super.setDefaultTargetDataSource(defaultDataSource);
    }

    /**
     * 设置数据源
     * @param dataSources
     */
    public void setDataSources(Map<Object, Object> dataSources) {
        super.setTargetDataSources(dataSources);
        // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
    }
}

5.动态数据源上下文

代码如下:

/**
 * 动态数据源上下文
 */
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @Override
        protected String initialValue() {
            return "master";
        }
    };


    /**
     * 数据源的 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);
    }
}

那么到了这一步其实就可以实现数据源的切换,只要调用 DynamicDataSourceContextHolder.setDataSourceKey(key) 就可以完成了,使用完成后再设置回默认数据源DynamicDataSourceContextHolder.clearDataSourceKey()。


6.注解AOP配置

使用注解和AOP的方式简化我们的操作,在添加注解的方法前后分别切换数据库。
代码如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 数据源key值
     * @return
     */
    String value();
}


@Aspect
@Order(-1)  // 该切面应当先于 @Transactional 执行
@Component
public class DynamicDataSourceAspect {
    /**
     * 切换数据源
     * @param point
     * @param dataSource
     */
    @Before("@annotation(dataSource))")
    public void switchDataSource(JoinPoint point, DataSource dataSource) {
        if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value())) {
            System.out.println("DataSource [{}] doesn't exist, use default DataSource [{}] " + dataSource.value());
        } else {
            // 切换数据源
            DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value());
            System.out.println("Switch DataSource to [" + DynamicDataSourceContextHolder.getDataSourceKey()
                    + "] in Method [" + point.getSignature() + "]");
        }
    }

    /**
     * 重置数据源
     * @param point
     * @param dataSource
     */
    @After("@annotation(dataSource))")
    public void restoreDataSource(JoinPoint point, DataSource dataSource) {
        // 将数据源置为默认数据源
        DynamicDataSourceContextHolder.clearDataSourceKey();
        System.out.println("Restore DataSource to [" + DynamicDataSourceContextHolder.getDataSourceKey()
                + "] in Method [" + point.getSignature() + "]");
    }
}

7.注解使用

比如在一个service实现类的方法上加注解@DataSource(“slave”),将数据源切换为slave数据源。那么这样做可能有一个弊端就是不够灵活,注解的这个方法中的所有数据源只能都是slave。
代码如下:

	@DataSource("slave")
    @Override
    public List<Map> getAllTask() {
        return taskMapper.getAllTask();
    }

总结

本文主要是记录一下代码流程,如果想要更详细的流程解析,大家可以参考博客一。

举报

相关推荐

0 条评论