赞
踩
版权声明:本文为博主原创文章,遵循 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)
- @Primary
- @Bean(name = "myNormalSqlSessionFactory")
- public SqlSessionFactory getMybatisSqlSessionFactory(@Qualifier("myNormalDataSource") DataSource myDataSource) throws Exception {
- MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
- bean.setDataSource(myDataSource);
- PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
- // 定义多个 sqlSessionFactory 的时候注意 mapper 要指定子目录,否则会 MybatisPlus 会出现 sqlSessionFactory 不正确。
- // 原因详见: TableInfoHelper.initTableInfo()
- bean.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml"));
- bean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));
- List<Interceptor> interceptors = new ArrayList<>();
- interceptors.add(mybatisPlusInterceptor);
- bean.setPlugins(interceptors.toArray(new Interceptor[0]));
- Properties properties = new Properties();
- properties.put("dialect", "mysql");
- bean.setConfigurationProperties(properties);
- return bean.getObject();
- }
第2个 SqlSessionFactory(sharding-sphere数据库B)
- @Bean(name = "myShardingSqlSessionFactory")
- public SqlSessionFactory getMybatisSqlSessionFactory(@Qualifier("myShardingDataSource") DataSource myDataSource) throws Exception {
- MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
- sqlSessionFactoryBean.setDataSource(myDataSource);
- PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
- // 定义多个 sqlSessionFactory 的时候注意 mapper 要指定子目录,否则会 MybatisPlus 会出现 sqlSessionFactory 不正确。
- // 原因详见: TableInfoHelper.initTableInfo()
- sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml"));
- sqlSessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));
- List<Interceptor> interceptors = new ArrayList<>();
- interceptors.add(mybatisPlusInterceptor);
- sqlSessionFactoryBean.setPlugins(interceptors.toArray(new Interceptor[0]));
- Properties properties = new Properties();
- properties.put("dialect", "mysql");
- sqlSessionFactoryBean.setConfigurationProperties(properties);
- return sqlSessionFactoryBean.getObject();
- }
启动项目后,执行了 saveBatch()
- @GetMapping(value = "/normal/student/insert/batch")
- @Transactional(value = "myNormalTransactionManager", rollbackFor = Exception.class)
- public String test1() {
- List<StudentBase> studentList = StudentUtil.getRandomStudentBaseList(5);
- studentNormalService.saveBatch(studentList);
- return JSON.toJSONString("ok");
- }
注意这里,我用的事务管理器是:myNormalTransactionManager,期望是用第一个 SqlSessionFactory(普通数据库A) ,即myNormalSqlSessionFactory。
但是执行的时候,用的却是 myShardingSqlSessionFactory,第二个数据源(sharding-sphere数据库B)
我们可以去这类看到 com.baomidou.mybatisplus.extension.toolkit.SqlHelper
- public static boolean executeBatch(Class<?> entityClass, Log log, Consumer<SqlSession> consumer) {
- SqlSessionFactory sqlSessionFactory = sqlSessionFactory(entityClass);
- SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory);
- boolean transaction = TransactionSynchronizationManager.isSynchronizationActive();
- if (sqlSessionHolder != null) {
- SqlSession sqlSession = sqlSessionHolder.getSqlSession();
- //原生无法支持执行器切换,当存在批量操作时,会嵌套两个session的,优先commit上一个session
- //按道理来说,这里的值应该一直为false。
- sqlSession.commit(!transaction);
- }
- SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
- if (!transaction) {
- log.warn("SqlSession [" + sqlSession + "] was not registered for synchronization because DataSource is not transactional");
- }
- ......
经过分析,找下原因:
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"));
重启项目,执行就正常了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。