• mybatisplus savebatch 多数据源时候,sqlSessionFactory 不正确踩坑记录。


    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接: https://zhangxiaofan.blog.csdn.net/article/details/128085906

    记录一下 mybatis-plus + sharding-JDBC 的时候,因为配置多数据源和多个SqlSessionFactory导致 mybatisPlus 执行 saveBatch 异常的问题。

    具体异常就是 saveBatch 执行的数据源,与期望的不一致。

    项目中有2个数据源,1个是单独的数据库A,1个是sharding配置的分表数据源B;

    本来是想保存数据到A数据库,结果是执行的sharding的分表数据库B,其实是因为 SqlSessionFactory 错误导致的。

    项目中有2个数据源,分别用的不同的 SqlSessionFactory。

    第1个 SqlSessionFactory(普通数据库A)

    1. @Primary
    2. @Bean(name = "myNormalSqlSessionFactory")
    3. public SqlSessionFactory getMybatisSqlSessionFactory(@Qualifier("myNormalDataSource") DataSource myDataSource) throws Exception {
    4. MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
    5. bean.setDataSource(myDataSource);
    6. PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    7. // 定义多个 sqlSessionFactory 的时候注意 mapper 要指定子目录,否则会 MybatisPlus 会出现 sqlSessionFactory 不正确。
    8. // 原因详见: TableInfoHelper.initTableInfo()
    9. bean.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml"));
    10. bean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));
    11. List interceptors = new ArrayList<>();
    12. interceptors.add(mybatisPlusInterceptor);
    13. bean.setPlugins(interceptors.toArray(new Interceptor[0]));
    14. Properties properties = new Properties();
    15. properties.put("dialect", "mysql");
    16. bean.setConfigurationProperties(properties);
    17. return bean.getObject();
    18. }

    第2个 SqlSessionFactory(sharding-sphere数据库B)

    1. @Bean(name = "myShardingSqlSessionFactory")
    2. public SqlSessionFactory getMybatisSqlSessionFactory(@Qualifier("myShardingDataSource") DataSource myDataSource) throws Exception {
    3. MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
    4. sqlSessionFactoryBean.setDataSource(myDataSource);
    5. PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    6. // 定义多个 sqlSessionFactory 的时候注意 mapper 要指定子目录,否则会 MybatisPlus 会出现 sqlSessionFactory 不正确。
    7. // 原因详见: TableInfoHelper.initTableInfo()
    8. sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml"));
    9. sqlSessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));
    10. List interceptors = new ArrayList<>();
    11. interceptors.add(mybatisPlusInterceptor);
    12. sqlSessionFactoryBean.setPlugins(interceptors.toArray(new Interceptor[0]));
    13. Properties properties = new Properties();
    14. properties.put("dialect", "mysql");
    15. sqlSessionFactoryBean.setConfigurationProperties(properties);
    16. return sqlSessionFactoryBean.getObject();
    17. }

    启动项目后,执行了  saveBatch()

    1. @GetMapping(value = "/normal/student/insert/batch")
    2. @Transactional(value = "myNormalTransactionManager", rollbackFor = Exception.class)
    3. public String test1() {
    4. List studentList = StudentUtil.getRandomStudentBaseList(5);
    5. studentNormalService.saveBatch(studentList);
    6. return JSON.toJSONString("ok");
    7. }

    注意这里,我用的事务管理器是:myNormalTransactionManager,期望是用第一个 SqlSessionFactory(普通数据库A) ,即myNormalSqlSessionFactory。

    但是执行的时候,用的却是 myShardingSqlSessionFactory,第二个数据源(sharding-sphere数据库B)

    我们可以去这类看到 com.baomidou.mybatisplus.extension.toolkit.SqlHelper 

    1. public static boolean executeBatch(Class entityClass, Log log, Consumer consumer) {
    2. SqlSessionFactory sqlSessionFactory = sqlSessionFactory(entityClass);
    3. SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory);
    4. boolean transaction = TransactionSynchronizationManager.isSynchronizationActive();
    5. if (sqlSessionHolder != null) {
    6. SqlSession sqlSession = sqlSessionHolder.getSqlSession();
    7. //原生无法支持执行器切换,当存在批量操作时,会嵌套两个session的,优先commit上一个session
    8. //按道理来说,这里的值应该一直为false。
    9. sqlSession.commit(!transaction);
    10. }
    11. SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    12. if (!transaction) {
    13. log.warn("SqlSession [" + sqlSession + "] was not registered for synchronization because DataSource is not transactional");
    14. }
    15. ......

    经过分析,找下原因:

    com.baomidou.mybatisplus.core.metadata.TableInfoHelper 中 initTableInfo方法会将每个 实体类 与对应的 数据库配置保存到 缓存:TABLE_INFO_CACHE 中

    也就是。我们在创建 SqlSessionFactory 时候设置的 setMapperLocations, 设置路径下的所有mapper.xml 对应的实体都会保存对应的数据库配置。

    因此,我们需要将不同的 SqlSessionFactory 配置,用不同的 mapper 目录来扫描。不同数据源的操作,放在各自的 mapper 子目录下,作区分。


    上面我们定义2个SqlSessionFactory中有两句一样的代码:

    sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml"));

    由于 classpath*:mapper/**/*Mapper.xml 路径一样,导致初始化 实体类和数据库配置对应关系,被覆盖的现象。
    即同样的 mapper.xml 文件中被 不同的 SqlSessionFactory 扫描了两次,导致mapper.xml中的实体类信息只有一种SqlSessionFactory信息。

    将第1个的 SqlSessionFactory 中这行代码改成:(新建了 mapper子目录-normal)

    bean.setMapperLocations(resolver.getResources("classpath*:mapper/normal/**/*Mapper.xml"));

    将第2个的 SqlSessionFactory 中这行代码改成:(新建了 mapper子目录-sharding)

    sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/sharding/**/*Mapper.xml"));

    重启项目,执行就正常了。

  • 相关阅读:
    测试开发是什么?为什么现在那么多公司都要招聘测试开发?
    C语言进阶C++知识点补充(二)
    接口调不通,如何去排查?没想到10年测试老鸟栽在这道面试题上
    【移远QuecPython】EC800M物联网开发板的内置GNSS定位的恶性BUG(目前没有完全的解决方案)
    综合能力 ---- 2. 法律法规
    全球无人机灯光秀预计2028年将达到7.19亿美元,年复合增长率(CAGR)为21.46%
    滑动平均窗口的定义,优点,缺点,以及目前的应用!!
    【Spring Boot】单元测试
    刷爆力扣之子数组最大平均数 I
    Linux命令总结
  • 原文地址:https://blog.csdn.net/q258523454/article/details/128085906