Ruo-Yi 前后端分离如何不使用注解@DataSource的方式而是使用Mybatis插件技术实现多数据源的切换【可以根据配置文件进行开启/关闭】

Ruo-Yi 前后端分离如何不使用注解@DataSource的方式而是使用Mybatis插件技术实现多数据源的切换【可以根据配置文件进行开启/关闭】

1、首先 配置文件:

# 数据源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://localhost:3306/ry-vue-master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 123456
            # 从库数据源
            slave:
                # 从数据源开关/默认关闭
                enabled: true
                url: jdbc:mysql://localhost:3306/ry-vue-slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 123456

配置多数据源,我们需要开启 slave 数据源。

2、定义多个数据源

/**
 * druid 配置多数据源
 *
 * @author ruoyi
 */
@Configuration
public class DruidConfig
{
    // 定义数据源1
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    // 定义数据源2
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    // 定义动态数据源
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }

    /**
     * 设置数据源
     *
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }

}

3、定义动态数据源

/**
 * 动态数据源
 * 应用直接操作的是 AbstractRoutingDataSource 的实现类,告诉 AbstractRoutingDataSource 访问哪个数据库,
 * 然后由 AbstractRoutingDataSource 从事先配置好的数据源中选择一个数据源,来访问对应的数据库。
 *
 * AbstractRoutingDataSource 属性:
 *      Map<Object, Object> targetDataSources;所有数据源【需指定】
 *      Object defaultTargetDataSource;默认数据源【需指定】
 *      Map<Object, DataSource> resolvedDataSources = targetDataSources
 *
 * @author ruoyi
 */
public class DynamicDataSource extends AbstractRoutingDataSource
{
    /**
     * 设置数据源
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources 备选数据源
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        // 设置默认数据源,未匹配则使用默认数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }


    @Override
    protected Object determineCurrentLookupKey()
    {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

4、定义mybatis插件

package com.ruoyi.framework.datasource;

import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.ruoyi.common.enums.DataSourceType;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
), @Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
)})
@Component
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") // 为了能根据配置文件,决定是否在容器中创建插件对象
public class MyDynamicDataSourcePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("============================");
        System.out.println("Intercepted method: " + invocation.getMethod().getName());
        System.out.println("============================");

        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];

        String dataSource = SqlCommandType.SELECT == ms.getSqlCommandType() ? (DataSourceType.SLAVE.name()) : (DataSourceType.MASTER.name());
        com.ruoyi.framework.datasource.DynamicDataSourceContextHolder.setDataSourceType(dataSource);
        try {
            return invocation.proceed();
        } finally {
            com.ruoyi.framework.datasource.DynamicDataSourceContextHolder.clearDataSourceType();
        }

    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }
}


你以为完了????没有一定要将这个插件注册到sqlsessionfactory里面,我在这里遇到了很大的坑,因为若依自己写了一个sqlsessionfactory,所以要找到若依写的 SqlSessionFactory,然后将我们的插件加入进去。

若依在 MyBatisConfig 里面写了一个sqlSessionFactory()方法:

    @Value("${spring.datasource.druid.slave.enabled:false}") // 为了能根据配置文件,决定是否使用插件
    private boolean enableDynamicDataSource; 

    @Autowired(required = false) // 若没有开启slave数据元,容器中没有该对象了
    private MyDynamicDataSourcePlugin myDynamicDataSourcePlugin;

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, MyDynamicDataSourcePlugin plugin) throws Exception
    {
        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        String configLocation = env.getProperty("mybatis.configLocation");
        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        if (enableDynamicDataSource && myDynamicDataSourcePlugin != null) {
            sessionFactory.setPlugins(new Interceptor[]{myDynamicDataSourcePlugin});
        }
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }

根据配置文件,实现动态添加 数据源切换插件:

if (enableDynamicDataSource && myDynamicDataSourcePlugin != null) {
            sessionFactory.setPlugins(new Interceptor[]{myDynamicDataSourcePlugin});
        }

这个时候就可以测试了。

数据库当前状态:
master:
在这里插入图片描述
slave:
在这里插入图片描述
添加一个用户:
在这里插入图片描述

添加用户后数据库状态:
页面:在这里插入图片描述
发现没有 orm,没有就对了,因为查询查询的是 slave,添加用户添加进去的是 master库

master:
在这里插入图片描述

slave:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值