Spring-Boot快速集成MyBatis动态数据源(AbstractRoutingDataSource)

Spring-Boot快速集成MyBatis动态数据源(AbstractRoutingDataSource)

在一些应用场景下,我们会使用到多个数据源,可能存在不同类型数据库,mysql和sql server之类的同时使用,还有就是主从数据库,读写分离也可能会使用多数据源来解决问题。使用的场景一般有:1,主从数据库切换;2,多租户间数据逻辑隔离

依赖引入

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

配置动态数据源

/**
 * 动态数据源配置
 */

public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 数据源存储用map
     */
    private  final Map<Object,Object> dataSourceMap = new ConcurrentHashMap<>();


    /**
     * 当前线程数据源key
     */
    private final ThreadLocal<String> contextHolder = new ThreadLocal<String>();


    @Override
    protected Object determineCurrentLookupKey() {
        //弹出当前数据源key
        return contextHolder.get();
    }

    /**
     * 动态数据源构造方法
     * @param defaultDataSource
     */
    public DynamicDataSource(DataSource defaultDataSource) {
        //存入主数据源
        dataSourceMap.put("master",defaultDataSource);
        //设定目标数据源map
        setTargetDataSources(dataSourceMap);
        //设定默认数据源
        setDefaultTargetDataSource(defaultDataSource);
        DynamicDataSourceUtil.dynamicDataSource =this;
    }
}

代码中的dataSourceMap 是用来保存加入动态数据源中数据源,contextHolder 使用ThreadLocal 来定义,保证只为当前线程所持有。定义动态数据源一定要继承AbstractRoutingDataSource 类,并且重写determineCurrentLookupKey方法,determineCurrentLookupKey方法的返回值表示的是当前数据源的key,通过key去找已经加入map中的数据源。

注入数据源

/**
 * 数据源设定
 *
 * @author DavidLei
 */
@Configuration
@MapperScan(basePackages = {"club.dlblog.datasource.mapper"},
        sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {

    /**
     * 数据源设定
     */
    @Bean(name = "dataSource")
    @ConfigurationProperties("spring.datasource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * session工厂
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(
            @Qualifier("dynamicDataSource") DynamicDataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().
                        getResources("classpath:mapper/**/*.xml"));
        return sessionFactoryBean.getObject();

    }


    /**
     * session模板
     * @param sqlSessionFactory
     * @return
     */
    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate ComSqlSessionTemplate(
            @Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /**
     * 动态数据源
     * @param dataSource
     * @return
     */
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("dataSource") DataSource dataSource){
        return  new DynamicDataSource(dataSource);
    }
}

在配置类中初始化动态数据源,并将将动态数据源绑定到session工厂上。

数据源管理工具类

/**
 * 数据源切换工具
 */
public class DynamicDataSourceUtil {

    /**
     * 动态数据源
     */
    public static DynamicDataSource dynamicDataSource = null;

    /**
     * 判断当前数据源是否存在
     *
     * @param tenantId
     * @return
     */
    public static boolean isExistDataSource(String tenantId) {
        if (dynamicDataSource != null) {
            return dynamicDataSource.getDataSourceMap().containsKey(tenantId);
        } else {
            return false;
        }
    }

    /**
     * 切换数据源
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        if (dynamicDataSource != null) {
            dynamicDataSource.getContextHolder().set(key);
            dynamicDataSource.afterPropertiesSet();
        }
    }

    /**
     * 获取当前数据源
     *
     * @return
     */
    public static String getDataSourceKey() {
        if (dynamicDataSource != null) {
            return dynamicDataSource.getContextHolder().get();
        } else {
            return null;
        }
    }

    /**
     * 切换到默认数据源
     */
    public static void clearDataSourceKey() {
        if (dynamicDataSource != null) {
            dynamicDataSource.getContextHolder().remove();
        }
    }

    /**
     * 根据租户ID设置数据源
     *
     * @param datatBase
     * @return
     */
    public static void addDataSource(String datatBase, DataSource dataSource) {
        if (dynamicDataSource == null) {
            return;
        }
        // 没有数据源时添加数据源,有数据源直接使用
        if (!isExistDataSource(datatBase)) {
            // 新增数据源
            dynamicDataSource.getDataSourceMap().put(datatBase, dataSource);
        }
        // 切换数据源
        checkoutDataSource(datatBase);
    }

    /**
     * 切换数据源
     *
     * @param tenantId
     */
    public static void checkoutDataSource(String tenantId) {
        if (dynamicDataSource != null) {
            // 切换数据源
            setDataSourceKey(tenantId);
            dynamicDataSource.afterPropertiesSet();
        }
    }


    /**
     * 创建数据源
     * @param driverClassName
     * @param url
     * @param userName
     * @param password
     * @return
     */
    public static DataSource createDataSource(String driverClassName,String url,
                                              String userName,String password){
        return DataSourceBuilder.create().driverClassName(driverClassName)
                .url(url).username(userName).password(password).build();
    }

}

在工具类中就是对动态数据源中的存储数据源的map进行put和remove操作,指定数据源时只需将contextHolder重新设定。
在进行具体CRUD时,数据源就会根据contextHolder进行切换。createDataSource方法随时可以创建新的数据源,放入动态数据源中进行管理。

拓展用法

定义过滤器,根据请求头来变更数据源

@Order(1)
@WebFilter(urlPatterns = "/*")
public class DynamicDataSourceFilter extends   GenericFilter {


    private  final  static String TARGET_ID = "XXX-TARGET-ID";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String targetId = request.getHeader(TARGET_ID);
        //数据源切换
        DynamicDataSourceUtil.checkoutDataSource(targetId);
        filterChain.doFilter(servletRequest,servletResponse);
    }

}

git地址 https://github.com/DavidLei08/BlogDataSource.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值