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: