需要在服务中集成表结构维护的功能,维护表结构就需要使用具有执行DDL脚本权限的账号。
为了保证系统的安全性,考虑在工程中配置多个数据源引入不同权限账号,高权限账号只在特定逻辑中使用,其它默认业务使用低权限账号。
加入新的数据源不能影响已有的功能,保证已有功能继续使用只具有CRUD权限的账号。
看了几个多数据源接入方案,都不太满足需求。
由于系统中调用了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; }
配置多个数据源时,必须明确声明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; } }
使用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 ListsqlSessionFactoryList; @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 ListsqlSessionFactoryList; @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()中实现的。