若依框架改造之动态数据源切换和引入mybatis-plus


若依框架改造之动态数据源切换和引入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配置从库的数据源即可

image-20230321192350379

引入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());
    }
}

  TOC