先说第一个问题,多数据源 导致 逻辑删除 等全局配置失效。
先看配置, 很常规的 mybatis-plus 的配置
#mybatis-plus 配置信息
mybatis-plus:
configuration:
#开启驼峰功能
map-underscore-to-camel-case: true
#FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
auto-mapping-behavior: full
#配置的缓存的全局开关
cache-enabled: true
#配置日志输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
#0:数据库ID自增
id-type: 1
# 逻辑删除配置
db-config:
# 删除前
logic-not-delete-value: 1
# 删除后
logic-delete-value: 0
#全局逻辑删除的实体字段名
logic-delete-field: status
在看自动填充我们自己写的拦截器
@Component
public class DataMetaObjectHandler implements MetaObjectHandler {
/**
* 新增字段填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
setFieldValByName("creater", SecurityUtils.getCurrentUserId(), metaObject);
setFieldValByName("createtime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("modifier", SecurityUtils.getCurrentUserId(), metaObject);
setFieldValByName("modifiertime", new Date(), metaObject);
}
}
在看 SqlSessionFactroy的配置
/**
* 创建sqlSessionFactory
* @param dataSource 数据源
* @param mapperXmlPath mapper扫描地址
* @return
* @throws Exception
*/
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, String mapperXmlPath) throws Exception{
//加载数据源
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperXmlPath));
//设置全局配置
return factoryBean.getObject();
}
一般来讲,发现MybatisPlus 配置后失效某些 功能,大多 都是 因为 SqlSessionFactory 配置有问题,那么我们 配置的 逻辑删除 是在全局配置中。所以找 GlobalConfig 准没错。可以看下 MybatisPlusAutoConfiguration,这个配置类是 我们但数据源下,SpringBoot 整合MybatisPlus 自动帮我们做的一些事情。这是 MybatisPlusAutoConfiguration 怎么生成的 SqlSessionFactory
MybatisPlusAutoConfiguration 源码
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
Resource[] mapperLocations = this.properties.resolveMapperLocations();
if (!ObjectUtils.isEmpty(mapperLocations)) {
factory.setMapperLocations(mapperLocations);
}
// TODO 修改源码支持定义 TransactionFactory
this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);
// TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (!ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
}
Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);
// TODO 自定义枚举包
if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
}
// TODO 此处必为非 NULL
GlobalConfig globalConfig = this.properties.getGlobalConfig();
// TODO 注入填充器
this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
// TODO 注入主键生成器
this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));
// TODO 注入sql注入器
this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
// TODO 注入ID生成器
this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
// TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
factory.setGlobalConfig(globalConfig);
return factory.getObject();
}
发现最后两行 有设置 globalConfig ,所以 我们就需要 手动设置 globalConfig 了。查了相关资料,具体那个忘了,大体意思就是 需要我们手动设置 globalConfig 在 MybatisSqlSessionFactoryBean 中,于是有了 如下的 修改和配置。
创建一个配置类 MybatisPlusConfig
然后里面的 GlobalConfig ,根据配置文件 加载 里面的属性,然后再设置 数据处理器,也就是我们自动填充的 Handler
@Configuration
public class MybatisPlusConfig {
@Autowired
private DataMetaObjectHandler dataMetaObjectHandler;
/**
* mybatis 分页插件
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/**
* @Author
* @Description mybatis 全局配置,由于多数据源原因 所以需要 代码配置
* @Date 2021/10/12 16:02
* @Param []
* @return com.baomidou.mybatisplus.core.config.GlobalConfig
**/
@Bean(name = "globalConfig")
@ConfigurationProperties(prefix = "mybatis-plus.global-config")
public GlobalConfig globalConfig(){
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setMetaObjectHandler(dataMetaObjectHandler);
return globalConfig;
}
}
在修改 SqlSessionFactory的配置,我这个是一个 抽象类 用其他类集成使用的,可以少些一点代码
public abstract class AbstractDataSourceConfig {
@Autowired
private GlobalConfig globalConfig;
@Autowired
private MybatisPlusInterceptor mybatisPlusInterceptor;
/**
* 数据源配置
* @return
*/
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
/**
* 创建sqlSessionFactory
* @param dataSource 数据源
* @param mapperXmlPath mapper扫描地址
* @return
* @throws Exception
*/
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, String mapperXmlPath) throws Exception{
//加载数据源
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperXmlPath));
factoryBean.setPlugins(mybatisPlusInterceptor);
//设置全局配置
factoryBean.setGlobalConfig(globalConfig);
return factoryBean.getObject();
}
/**
* 配置事务管理器
* @param dataSource
* @return
*/
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
/**
* 封装的是数据库操作
* @param sqlSessionFactory
* @return
*/
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
注意这里:设置了 全局配置的 globalConfig
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, String mapperXmlPath) throws Exception{
//加载数据源
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperXmlPath));
factoryBean.setPlugins(mybatisPlusInterceptor);
//设置全局配置
factoryBean.setGlobalConfig(globalConfig);
return factoryBean.getObject();
}
然后再 使用的时候发现,逻辑删除 是妥妥的安排明白了
但是新的问题出现,批量新增出错了。。。。
报错:Mapped Statements collection does not contain value for com.dlxx.egs.sys.business.role.mapper.RoleMenuMapper.insert
### Error updating database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.dlxx.egs.sys.business.role.mapper.RoleMenuMapper.insert
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.dlxx.egs.sys.business.role.mapper.RoleMenuMapper.insert
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.lambda$saveBatch$0(ServiceImpl.java:130)
at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl$$Lambda$1279/1360737045.accept(Unknown Source)
at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.lambda$executeBatch$0(SqlHelper.java:215)
at com.baomidou.mybatisplus.extension.toolkit.SqlHelper$$Lambda$1280/2062815923.accept(Unknown Source)
at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.executeBatch(SqlHelper.java:179)
... 137 more
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.dlxx.egs.sys.business.role.mapper.RoleMenuMapper.insert
at com.baomidou.mybatisplus.core.MybatisConfiguration$StrictMap.get(MybatisConfiguration.java:420)
at com.baomidou.mybatisplus.core.MybatisConfiguration.getMappedStatement(MybatisConfiguration.java:305)
at com.baomidou.mybatisplus.core.MybatisConfiguration.getMappedStatement(MybatisConfiguration.java:298)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:196)
... 143 more
结合上面 讲的,问题还是出在 MybatisSqlSessionFactoryBean 里面,于是灵光一闪可以看看 globalConfig 源码么,兴许里面有 我想要的答案。
public class GlobalConfig implements Serializable {
/**
* 是否开启 LOGO
*/
private boolean banner = true;
/**
* 机器 ID 部分
*
* @see #setIdentifierGenerator(IdentifierGenerator)
* @deprecated 3.3.0
*/
@Deprecated
private Long workerId;
/**
* 数据标识 ID 部分
*
* @see #setIdentifierGenerator(IdentifierGenerator)
* @deprecated 3.3.0
*/
@Deprecated
private Long datacenterId;
/**
* 是否初始化 SqlRunner
*/
private boolean enableSqlRunner = false;
/**
* 数据库相关配置
*/
private DbConfig dbConfig;
/**
* SQL注入器
*/
private ISqlInjector sqlInjector = new DefaultSqlInjector();
/**
* Mapper父类
*/
private Class<?> superMapperClass = Mapper.class;
/**
* 仅用于缓存 SqlSessionFactory(外部勿进行set,set了也没用)
*/
private SqlSessionFactory sqlSessionFactory;
/**
* 缓存已注入CRUD的Mapper信息
*/
private Set<String> mapperRegistryCache = new ConcurrentSkipListSet<>();
/**
* 元对象字段填充控制器
*/
private MetaObjectHandler metaObjectHandler;
/**
* 主键生成器
*/
private IdentifierGenerator identifierGenerator;
@Data
public static class DbConfig {
/**
* 主键类型
*/
private IdType idType = IdType.ASSIGN_ID;
/**
* 表名前缀
*/
private String tablePrefix;
/**
* schema
*
* @since 3.1.1
*/
private String schema;
/**
* db字段 format
* <li> 例: `%s` </li>
* <p> 对主键无效 </p>
*
* @since 3.1.1
*/
private String columnFormat;
/**
* entity 的字段(property)的 format,
* 只有在 column as property 这种情况下生效
* <li> 例: `%s` </li>
* <p> 对主键无效 </p>
*
* @since 3.3.0
*/
private String propertyFormat;
/**
* 表名是否使用驼峰转下划线命名,只对表名生效
*/
private boolean tableUnderline = true;
/**
* 大写命名,对表名和字段名均生效
*/
private boolean capitalMode = false;
/**
* 表主键生成器
*/
private IKeyGenerator keyGenerator;
/**
* 逻辑删除全局属性名
*/
private String logicDeleteField;
/**
* 逻辑删除全局值(默认 1、表示已删除)
*/
private String logicDeleteValue = "1";
/**
* 逻辑未删除全局值(默认 0、表示未删除)
*/
private String logicNotDeleteValue = "0";
/**
* 字段验证策略之 insert
*
* @since 3.1.2
*/
private FieldStrategy insertStrategy = FieldStrategy.NOT_NULL;
/**
* 字段验证策略之 update
*
* @since 3.1.2
*/
private FieldStrategy updateStrategy = FieldStrategy.NOT_NULL;
/**
* 字段验证策略之 select
*
* @since 3.1.2
*/
private FieldStrategy selectStrategy = FieldStrategy.NOT_NULL;
}
}
于是乎就想到之前 看 Mybatis-plus 学习视频的时候,记得可以自定义 SQl 注入,于是找到一行代码。
/**
* SQL注入器
*/
private ISqlInjector sqlInjector = new DefaultSqlInjector();
于是猜想可能原因是 两个数据源 需要两个 GlobalConfig
于是修改代码 加了一行 @Scope("prototype"),改为多例。再次复测问题搞定。
/**
* @Author shiJin
* @Description mybatis 全局配置,由于多数据源原因 所以需要 代码配置
* @Date 2021/10/12 16:02
* @Param []
* @return com.baomidou.mybatisplus.core.config.GlobalConfig
**/
@Scope("prototype")
@Bean(name = "globalConfig")
@ConfigurationProperties(prefix = "mybatis-plus.global-config")
public GlobalConfig globalConfig(){
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setMetaObjectHandler(dataMetaObjectHandler);
return globalConfig;
}
由于本人 还未细究过 Mybatis-plus 的源码,所以这次 改好,完全处于瞎猫碰到死耗子,以后如果研究到 源码了,在来慢慢 细捋这个 问题。
如果有哪里说的不对的,欢迎指出,相互学习。