MybatisPlus扩展,按需求保存null字段,继承AbstractMethod
本文主要是对MybatisPlus的更新方法进行扩展,对set语句的非空校验进行自定义判断,提供了两个方法模板
/** * 根据主键更新字段,null也会更新 * @param entity * @author zhangyong * @date 2025/3/29 * @return int */ int updateIgnoreNullById(@Param("et") T entity); /** * 根据条件更新实体,null也会更新 * @author zhangyong * @date 2025/3/29 * @param entity: * @param updateWrapper: * @return int */ int updateIgnoreNull(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
MybatisPlus扩展接口需要四个步骤,第五步为使用
1、定义方法扩展
这部分必须实现injectMappedStatement接口,接口的内容可以参考com.baomidou.mybatisplus.core.injector.methods包和com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById接口的写法。
主要的拼接setsql的方法有两个,可以参考里面的实现来实现我们自己的逻辑,没必要自己造轮子。
a: AbstractMethod.filterTableFieldInfo
b.AbstractMethod.sqlSet
其中调用的TableFieldInfo.convertIf接口非常重要,是用来生成set字段外的if条件的,可以按需求调整。如:
<if test="accountId != null"> ac.account_id = #{accountId}, </if>
代码中需要注意
return this.addUpdateMappedStatement(mapperClass, modelClass, "updateIgnoreNull", sqlSource);的updateIgnoreNull需要和Mapper里的方法名一样(重要)
a:根据自定义Wrapper条件更新数据,int updateIgnoreNullById(@Param("et") T entity);
package com.hk.abpms.data.api.config.method;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import java.util.function.Predicate;
/**
* 根据自定义Wrapper条件更新数据
*
* @author zhangyong
* @Date 2025/3/28
*/
public class UpdateIgnoreNull extends AbstractMethod {
private Predicate<TableFieldInfo> predicate;
public UpdateIgnoreNull() {
}
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.UPDATE;
if(tableInfo.getTableName().equals("cfg_api")){
System.out.println(tableInfo.getFieldList());
}
String sqlSet = this.filterTableFieldInfo(tableInfo.getFieldList(), this.getPredicate(), (i) -> {
//以下代码来自AbstractMethod.sqlSet
String newPrefix = "et.";
String sqlSet2 = i.getColumn() + "=";
if (StringUtils.isNotBlank(i.getUpdate())) {
sqlSet2 = sqlSet2 + String.format(i.getUpdate(), i.getColumn());
} else {
sqlSet2 = sqlSet2 + SqlScriptUtils.safeParam(newPrefix + i.getEl());
}
sqlSet2 = sqlSet2 + ",";
return i.isWithUpdateFill() ? sqlSet2 : this.convertIf2(i,sqlSet2, this.convertIfProperty2(newPrefix, i.getProperty()), i.getUpdateStrategy());
}, "\n");
sqlSet = SqlScriptUtils.convertSet(sqlSet);
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlSet, this.sqlWhereEntityWrapper(true, tableInfo), this.sqlComment());
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
return this.addUpdateMappedStatement(mapperClass, modelClass, "updateIgnoreNull", sqlSource);
}
private String convertIf2(TableFieldInfo tableFieldInfo,final String sqlScript, final String property, final FieldStrategy fieldStrategy) {
//判断Entity上的注解
if (fieldStrategy == FieldStrategy.NEVER) {
//字段标记为用不更新的 不需要set语句。@TableField(updateStrategy = FieldStrategy.NEVER)
return null;
} else if (!tableFieldInfo.isPrimitive() && fieldStrategy != FieldStrategy.IGNORED) {
//如果字段没有FieldStrategy.IGNORED注解(建议不要加这个注解,加了注解所有涉及这个Entity的保存该字段为null都会更新)
if(tableFieldInfo.isWithInsertFill()){
//判断Entity字段是否配置了 @TableField(fill = FieldFill.INSERT)注解,理论上有这个注解的不需要修改,比如createBy等字段,所以此处set外拼接了if为空判断
return fieldStrategy == FieldStrategy.NOT_EMPTY && tableFieldInfo.isCharSequence() ? SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and %s != ''", property, property), false) : SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", property), false);
}else{
//不添加if为空判断,null也会更新
return sqlScript;
}
} else {
//不添加if为空判断,null也会更新
return sqlScript;
}
}
private String convertIfProperty2(String prefix, String property) {
return StringUtils.isNotBlank(prefix) ? prefix.substring(0, prefix.length() - 1) + "['" + property + "']" : property;
}
private Predicate<TableFieldInfo> getPredicate() {
Predicate<TableFieldInfo> noLogic = (t) -> {
return !t.isLogicDelete();
};
return this.predicate != null ? noLogic.and(this.predicate) : noLogic;
}
}
B:根据ID更新数据int updateIgnoreNull(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
package com.hk.abpms.data.api.config.method;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* 根据ID更新数据
*
* @author zhangyong
* @Date 2025/3/28
*/
public class UpdateIgnoreNullById extends AbstractMethod {
public UpdateIgnoreNullById() {
}
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.UPDATE_BY_ID;
if(tableInfo.getTableName().equals("cfg_api")){
System.out.println(tableInfo.getFieldList());
}
String additional = this.optlockVersion(tableInfo) + tableInfo.getLogicDeleteSql(true, true);
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), this.sqlSet2(tableInfo.isWithLogicDelete(), false, tableInfo, false, "et", "et."), tableInfo.getKeyColumn(), "et." + tableInfo.getKeyProperty(), additional);
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
return this.addUpdateMappedStatement(mapperClass, modelClass, "updateIgnoreNullById", sqlSource);
}
public String getAllSqlSet2( TableInfo table,boolean ignoreLogicDelFiled, final String prefix) {
String newPrefix = prefix == null ? "" : prefix;
return (String)table.getFieldList().stream().filter((i) -> {
if (!ignoreLogicDelFiled) {
return true;
} else {
return !table.isWithLogicDelete() || !i.isLogicDelete();
}
}).map((i) -> {
String sqlSet2 = i.getColumn() + "=";
if (StringUtils.isNotBlank(i.getUpdate())) {
sqlSet2 = sqlSet2 + String.format(i.getUpdate(), i.getColumn());
} else {
sqlSet2 = sqlSet2 + SqlScriptUtils.safeParam(newPrefix + i.getEl());
}
sqlSet2 = sqlSet2 + ",";
return i.isWithUpdateFill() ? sqlSet2 : this.convertIf2(i,sqlSet2, this.convertIfProperty2(newPrefix, i.getProperty()), i.getUpdateStrategy());
}).filter(Objects::nonNull).collect(Collectors.joining("\n"));
}
protected String sqlSet2(boolean logic, boolean ew, TableInfo table, boolean judgeAliasNull, final String alias, final String prefix) {
String sqlScript = this.getAllSqlSet2(table,logic, prefix);
if (judgeAliasNull) {
sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", alias), true);
}
if (ew) {
sqlScript = sqlScript + "\n";
sqlScript = sqlScript + SqlScriptUtils.convertIf(SqlScriptUtils.unSafeParam("ew.sqlSet"), String.format("%s != null and %s != null", "ew", "ew.sqlSet"), false);
}
sqlScript = SqlScriptUtils.convertSet(sqlScript);
return sqlScript;
}
private String convertIf2(TableFieldInfo tableFieldInfo,final String sqlScript, final String property, final FieldStrategy fieldStrategy) {
if (fieldStrategy == FieldStrategy.NEVER) {
return null;
} else if (!tableFieldInfo.isPrimitive() && fieldStrategy != FieldStrategy.IGNORED) {
if(tableFieldInfo.isWithInsertFill()){
return fieldStrategy == FieldStrategy.NOT_EMPTY && tableFieldInfo.isCharSequence() ? SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and %s != ''", property, property), false) : SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", property), false);
}else{
return sqlScript;
}
} else {
return sqlScript;
}
}
private String convertIfProperty2(String prefix, String property) {
return StringUtils.isNotBlank(prefix) ? prefix.substring(0, prefix.length() - 1) + "['" + property + "']" : property;
}
}
2、将方法加入MybatisPlus进行管理
package com.hk.abpms.data.api.config;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById;
import com.hk.abpms.data.api.config.method.UpdateIgnoreNull;
import com.hk.abpms.data.api.config.method.UpdateIgnoreNullById;
import java.util.List;
/**
* sql方法注入
*
* @author zhangyong
* @Date 2025/3/27
*/
public class MybatisSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
// 添加自定义的更新方法,AlwaysUpdateSomeColumnById 会更新所有字段,包括空字段
methodList.add(new UpdateIgnoreNullById());
methodList.add(new UpdateIgnoreNull());
return methodList;
}
}
3、将Injector加入springBean管理
package com.hk.abpms.data.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MybatisPlus的额外配置类
*
* @author zhangyong
* @Date 2025/3/28
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisSqlInjector customSqlInjector() {
return new MybatisSqlInjector();
}
}
4、在BaseMapper中定义与第一步同名的方法
注意参数@Param("et")名字要和第一步的名称对应
package com.hk.abpms.data.api.config;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 基础mapper
*
* @author zhangyong
* @Date 2025/3/28
*/
public interface BaseCommonMapper<T> extends BaseMapper<T> {
/**
* 根据主键更新字段,null也会更新
* @param entity
* @author zhangyong
* @date 2025/3/29
* @return int
*/
int updateIgnoreNullById(@Param("et") T entity);
/**
* 根据条件更新实体,null也会更新
* @author zhangyong
* @date 2025/3/29
* @param entity:
* @param updateWrapper:
* @return int
*/
int updateIgnoreNull(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
}
5、业务Mapper继承公共的BaseCommonMapper后就可正常使用里面的方法
欢迎各位大佬留言。