若依框架改造之动态数据源切换和引入mybatis-plus
多数据源配置
之前为了给我的校内资源共享与交流平台开发一个后台管理系统,我们在RuoYi-Vue基础上进行开发,由于若依和前台项目用的不是一个数据库我们需要配置多数据源,要实现Spring boot的动态数据源切换,在若依中已经在DruidConfig和DynamicDataSource继承了AbstractRoutingDataSource配置了多数据源,
/**
* druid 配置多数据源
*
* @author ruoyi
*/
@Configuration
public class DruidConfig
{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@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)
{
}
}
/**
* 动态数据源
*
* @author ruoyi
*/
public class DynamicDataSource extends AbstractRoutingDataSource
{
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey()
{
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
我们只需要在application.yml配置从库的数据源即可
引入mybatis-plus
mybatis-plus有一个好处是一些简单的CRUD可以不用写xml,因为以前项目常用,基于若依框架改造时发现没有,不太习惯,故引入。
依赖版本:父pom的依赖申明dependencyManagement中引入,再在common模块中引入,使用是在业务模块,业务模块再依赖common模块。
引入后调用任意默认的CRUD方法,诸如list()、getById()等方法,会报invalid bind statement(not found)。甚不解,不是说好不用写xml吗,怎么和说的不一样呢? 怀疑MybatisPlus没有自动启动,检查依赖确实使用的mybatis-plus-boot-starter没有问题。
源码跟踪MybatisPlusAutoConfiguration类,断点到sqlSessionFactory(DataSource dataSource) 方法发现此方法未执行。对比以前正常的mybatis-plus项目,启动时是会走这个方法的。 这个方法很重要,因为MybatisPlus是通过这个方法入口实现的自动加载默认CRUD方法,而实现的不用手写xml。
关键方法注入断点:AbstractMethod.inject 可以断点此方法跟踪。
/**
* 注入自定义方法
*/
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
/* 注入自定义方法 */
injectMappedStatement(mapperClass, modelClass, tableInfo);
}
继续寻找MybatisPlusAutoConfiguration.sqlSessionFactory未执行的原因。其带注解ConditionalOnMissingBean,即有此Bean则不执行此方法,此方法的Bean为SqlSessionFactory。 经查原系统配置中存在MyBatisConfig配置,产生过SqlSessionFactory。
@Configuration
public class MyBatisConfig
{
@Autowired
private Environment env;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
.....
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) 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);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
【解决方式】注释掉@Configuration 再次启动发现,可以正常加载MybatisPlusAutoConfiguration.sqlSessionFactory.
另引入mybatis-plus后,原mybatis配置就可以不要了。
# MyBatis配置
#mybatis:
# # 搜索指定包别名
# typeAliasesPackage: com.ruoyi.**.domain
# # 配置mapper的扫描,找到所有的mapper.xml映射文件
# mapperLocations: classpath*:mapper/**/*Mapper.xml
# # 加载全局的配置文件
# configLocation: classpath:mybatis/mybatis-config.xml
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath*:mapper/**/*Mapper.xml
# 搜索指定包别名
type-aliases-package: com.ruoyi.**.domain
# config-location: classpath:mybatis/mybatis-config.xml
注意:要去掉 # config-location: classpath:mybatis/mybatis-config.xml 此配置,因为yml中的Configuration和mybatis-config文件的configruation配置是不能共存的。否则会报以下错误 Property ‘configuration’ and ‘configLocation’ can not specified with together
Mapper扫描
在ApplicationConfig下配置@MapperScan 指定要扫描的Mapper类的包的路径
注意不同数据源的dao最好放在不同的包下,不然可能会报错
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@MapperScan(basePackages = {"com.ruoyi.system.mapper","com.ruoyi.quartz.mapper","com.ruoyi.eshare.mapper","com.ruoyi.generator.mapper"})
public class ApplicationConfig
{
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}