java

java_Springboot_Mybatis-Plus_自定义多数据源MybatisSqlSessionFactoryBean配置方法

目录

  • java_Springboot_Mybatis-Plus_自定义多数据源MybatisSqlSessionFactoryBean配置方法
  • 一、引言
  • 二、环境
  • 三、集成过程中遇到的问题
    • 3.1 Invalid bound statement (not found) 错误
    • 3.2 默认数据源问题
    • 3.3 Mybatis-Puls分页组件失效问题
    • 四、测试数据源是否正确
    • 五、总结

      一、引言

      需要在服务中集成表结构维护的功能,维护表结构就需要使用具有执行DDL脚本权限的账号。

      为了保证系统的安全性,考虑在工程中配置多个数据源引入不同权限账号,高权限账号只在特定逻辑中使用,其它默认业务使用低权限账号。

      加入新的数据源不能影响已有的功能,保证已有功能继续使用只具有CRUD权限的账号。

      看了几个多数据源接入方案,都不太满足需求。

      • Springboot默认支持的多数据源
      • Mybatis-Plus的多数据源既动态数据源dynamic-datasource插件
      • Alibaba Druid动态数据源

        二、环境

        • JDK 1.0
        • SpringBoot 1.5.6
        • Mybaits 3.5.3
        • Mybatis-plus 3.3.1

          三、集成过程中遇到的问题

          3.1 Invalid bound statement (not found) 错误

          由于系统中调用了Mybatis-plus的BaseMapper中的扩展方法selectBatchIds(),

          调用selectBatchIds()方法的位置都报:Invalid bound statement (not found) 错误。

          一般遇到这种问题基本都是接口中定义的方法名在对应的XML文件中没有定义。

          但是现在使用的是com.baomidou.mybatisplus.core.mapper.BaseMapper中的扩展方法,不需要在XML中定义的!

          问题产生原因是没有使用Mybatis-Plust自定义的MybatisSqlSessionFactoryBean构建 SqlSessionFactory实例导致,

          改用后解决了Invalid bound statement (not found)的问题。

           @Bean(name = "defSqlSessionFactory")
              @Primary
              public SqlSessionFactory defSqlSessionFactory(@Qualifier("defDataSource") DataSource dataSource) throws Exception {
                  MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
                  //SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
                  bean.setDataSource(dataSource);
                  //设置mybatis的xml所在位置
                  Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:com/.../mapper/**/*Mapper.xml");
                  bean.setMapperLocations(resources);
                  SqlSessionFactory factory = bean.getObject();
                  return factory;
              }
          

          3.2 默认数据源问题

          配置多个数据源时,必须明确声明DataSource、SqlSessionFactory、PlatformTransactionManager、SqlSessionTemplate关键对象!

          在默认的Bean上加@Primary注解,标记为默认配置。以下为同一数据源的Bean配置,多个数据源需要加入多套配置。

          @Configuration
          public class DbDefaultConfig {
              @Bean(name = "defDataSource")
              @Primary
              @ConfigurationProperties(prefix = "spring.datasource")
              public DataSource defDataSource() {
                  DataSource datasource =  DataSourceBuilder.create().build();
                  return datasource;
              }
              @Bean(name = "defSqlSessionFactory")
              @Primary
              public SqlSessionFactory defSqlSessionFactory(@Qualifier("defDataSource") DataSource dataSource) throws Exception {
                  MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
                  //SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
                  bean.setDataSource(dataSource);
                  //设置mybatis的xml所在位置
                  Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:com/.../mapper/**/*Mapper.xml");
                  bean.setMapperLocations(resources);
                  SqlSessionFactory factory = bean.getObject();
                  return factory;
              }
              /**
               * JDBC事务管理器
               * @param dataSource
               * @return
               */
              @Bean("defTransactionManager")
              @Primary
              public PlatformTransactionManager txManager(DataSource dataSource) {
                  return new DataSourceTransactionManager(dataSource);
              }
              @Bean(name = "defSqlSessionTemplate")
              @Primary
              public SqlSessionTemplate defSqlSessionTemplate(@Qualifier("defSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
                  SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
                  return sqlSessionTemplate;
              }
          }
          

          3.3 Mybatis-Puls分页组件失效问题

          使用com.baomidou.mybatisplus.extension.plugins.pagination.Page插件做分页查询时,发现返回的total、pages两个关键的分页属性值都是0,明显分页插件没有生效。

          {
              "records": [
                  ...
              ],
              "total": 0, --!!!
              "size": 10,
              "current": 1,
              "orders": [],
              "hitCount": false,
              "searchCount": true,
              "pages": 0  --!!!
          }
          

          分页组件是基于拦截器com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor实现的,该拦截器对StatementHandler的实例方法prepare进行拦截,对total、pages属性赋值,实现的分页查询功能。问题产生原因是由于手动实现了多个SqlSessionFactory实例,但是实例中没有手动注入拦截器导致的问题。解决方法是:

          1.声明一个分页拦截器;

          @Configuration
          public class MybatisPlusConfig {
              @Bean
              public PaginationInterceptor paginationInterceptor(){
                  PaginationInterceptor page = new PaginationInterceptor();
                  page.setDbType(DbType.POSTGRE_SQL); //这里指明数据库类型
                  return page;
              }
          }
          

          2.将拦截器添加到SqlSessionFactory实例中;

          @Configuration
          public class MybatisConfig {
              @Autowired
              private List sqlSessionFactoryList;
              @Autowired
              private PaginationInterceptor paginationInterceptor;
              @PostConstruct
              public void addSqlInterceptor() {
                  SchemaParamsterInterceptor interceptor = new SchemaParamsterInterceptor();
                  for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
                      //mybaits分页拦截器
                      sqlSessionFactory.getConfiguration().addInterceptor(paginationInterceptor);
                      ...
                  }
              }
          }
          

          四、测试数据源是否正确

          @RunWith(SpringRunner.class)
          //@Transactional
          @SpringBootTest(classes = StartApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
          @ActiveProfiles("")
          public class MultiDatasourceTest {
              @Autowired
              private List sqlSessionFactoryList;
              @Before
              public void before(){
              }
              @Test
              public void datasourceTest(){
                  System.out.println("sqlSessionFactoryList Size = " + sqlSessionFactoryList.size());
                  boolean success = true;
                  for(SqlSessionFactory f:sqlSessionFactoryList ){
                      System.out.println("【工厂】:"+f.toString());
                      try {
                          datasourceTest(f);
                          System.out.println("成功!!!");
                      }catch (Exception ex){
                          System.out.println("异常:"+ex.toString());
                          if(success)
                              success = false;
                      }
                  }
                  Assert.assertTrue("存在不支持的查询!",success);
              }
              private boolean datasourceTest(SqlSessionFactory factory){
                  MyTestMapper mapper = factory.openSession(true).getMapper(MyTestMapper.class);
                  //xml中的方法
                  Object objPk = mapper.selectByPrimaryKey("111");
                  //mybatis-plus BaseMapper 中的扩展方法(使用前必须在对象上加 @TableName,pk字段上加@TableId注解)
                  Object objIds = mapper.selectBatchIds(Arrays.asList("1","2"));
                  return true;
              }
          }
          

          五、总结

          构建SqlSessionFactory必须使用MybatisPlust实现的MybatisSqlSessionFactoryBean对象。

          项目引入Mybatis-Puls依赖包后,会自动化注入一个SqlSessionFactory实例(详见:com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory()方法),这个实例注入是有条件的,即只在没有SqlSessionFactory实例时注入,使用@ConditionalOnMissingBean注解做的约束。

          梳理了一下Mapper实例的构建过程,发现调用的扩展方法必须继承BaseMapper类,Mapper实例又是通过SqlSessionFactory实例创建的,

          大概率Mapper扩展方法绑定在MybatisSqlSessionFactoryBean的实例方法bean.getObject()中实现的。