Mybatis-Plus全量更新 null字段默认更新

Mybatis-Plus在默认情况下,使用更新方法update或updateById等方法传入实体类对象时是不会将实体类中的NULL字段更新到数据库的。在SpringBoot项目中可以通过以下配置设置是否更新NULL值:

mybatis-plus:
  global-config:
    db-config:
      update-strategy: NOT_NULL
      # 字段策略 
      # IGNORED:"忽略判断,会更新NULL和空串",
      # NOT_NULL:"非 NULL判断,会更新空串",
      # NOT_EMPTY:"非空判断,不会更新NULL和空串",
      # 默认是NOT_NULL

也可以在实体类的字段上设置注解

@TableField(updateStrategy = FieldStrategy.IGNORED)
private String newLoanBankName;

虽然可以通过配置的方式实现全量更新,但是实际的业务中,配置文件的粒度太粗,给字段设置又过于太细,且有时候的业务又不需要全量更新,所以这时候需要我们自定义全量更新方法

原理

源码:com.baomidou.mybatisplus.core.metadata.TableFieldInfo#getSqlSet(boolean, java.lang.String)

public String getSqlSet(final boolean ignoreIf, final String prefix) {
    final String newPrefix = prefix == null ? EMPTY : prefix;
    // 默认: column=
    String sqlSet = column + EQUALS;
    if (StringUtils.isNotBlank(update)) {
        sqlSet += String.format(update, column);
    } else {
        sqlSet += SqlScriptUtils.safeParam(newPrefix + el);
    }
    sqlSet += COMMA;
    if (ignoreIf) { //1
        return sqlSet;
    }
    if (withUpdateFill) { //2
        // 不进行 if 包裹
        return sqlSet;
    }
    //convertIf方法会根据实体类的属性生成 这样的动态sql 来做到变量更新 
    //我们只需要在1、2位置将sqlset进行返回就可以做到全量更新
    return convertIf(sqlSet, convertIfProperty(newPrefix, property), updateStrategy);
}

给BaseMapper扩展一个方法updateByIdWithNull

public interface CustomBaseMapper<T> extends BaseMapper<T> {

    /**
     * 通过ID更新数据,包括NULL和空串
     */
    int updateByIdWithNull(@Param(Constants.ENTITY) T t);
}

拓展Iservie和ServiceImpl

public interface CustomService<T> extends IService<T> {

    boolean updateByIdWithNull(T t);
}
public class CustomServiceImpl<M extends CustomBaseMapper<T>, T> extends ServiceImpl<M, T> implements CustomService<T> {
    @Override
    public boolean updateByIdWithNull(T t) {
        return SqlHelper.retBool(getBaseMapper().updateByIdWithNull(t));
    }
}

创建一个updateByIdWithNull类

在MyBatis-Plus中BaseMapper的每一个抽象方法都包含一个与方法同名的类,例如updateById()方法就会有一个UpdateById类与它对应,这个类的目的是为了组装相应功能的SQL语句的,这里我们也为扩展方法updateByIdWithNull()创建一个对应的updateByIdWithNull类

public class updateByIdWithNull extends AbstractMethod {
    
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {

        final String additional = optlockVersion(tableInfo) + tableInfo.getLogicDeleteSql(true, true);
        String sql = String.format(SqlUtil.SQL, tableInfo.getTableName(),
                this.newSqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, false, ENTITY, ENTITY_DOT),
                tableInfo.getKeyColumn(), ENTITY_DOT + tableInfo.getKeyProperty(), additional);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addUpdateMappedStatement(mapperClass, modelClass, "updateByIdWithNull", sqlSource);
    }

    private String newSqlSet(boolean logic, boolean ew, TableInfo table, boolean judgeAliasNull, final String alias,
                         final String prefix) {
        String sqlScript = SqlUtil.getAllSqlSet(table, logic, prefix);
        if (judgeAliasNull) {
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", alias), true);
        }
        if (ew) {
            sqlScript += NEWLINE;
            sqlScript += SqlScriptUtils.convertIf(SqlScriptUtils.unSafeParam(U_WRAPPER_SQL_SET),
                    String.format("%s != null and %s != null", WRAPPER, U_WRAPPER_SQL_SET), false);
        }
        sqlScript = SqlScriptUtils.convertSet(sqlScript);
        return sqlScript;
    }
}

重写动态SQL

public class SqlUtil {
    public static final String SQL = "";

    public static String getAllSqlSet(TableInfo tableInfo, boolean ignoreLogicDelFiled, final String prefix) {
        final String newPrefix = prefix == null ? StringPool.EMPTY : prefix;
        return tableInfo.getFieldList().stream()
                .filter(i -> {
                    if (ignoreLogicDelFiled) {
                        return !(tableInfo.isWithLogicDelete() && i.isLogicDelete());
                    }
                    return true;
                }).map(i -> getSqlSet(newPrefix, i)).filter(Objects::nonNull).collect(joining(StringPool.NEWLINE));
    }

    public static String getSqlSet(final String prefix, TableFieldInfo tableFieldInfo) {
        final String newPrefix = prefix == null ? StringPool.EMPTY : prefix;
        // 默认: column=
        String sqlSet = tableFieldInfo.getColumn() + StringPool.EQUALS;
        if (StringUtils.isNotBlank(tableFieldInfo.getUpdate())) {
            sqlSet += String.format(tableFieldInfo.getUpdate(), tableFieldInfo.getColumn());
        } else {
            sqlSet += SqlScriptUtils.safeParam(newPrefix + tableFieldInfo.getEl());
        }
        sqlSet += StringPool.COMMA;
        if (tableFieldInfo.isWithUpdateFill() || true) {
            //如果字段字段在update时自动填充,那么sql就不会被标签包裹 如果字段为null 就会进行全量更新
            return sqlSet;
        }
        return SqlScriptUtils.convertIf(sqlSet, String.format("%s != null", convertIfProperty(newPrefix, tableFieldInfo.getProperty())), false);
    }

    public static String convertIfProperty(String prefix, String property) {
        return StringUtils.isNotBlank(prefix) ? prefix.substring(0, prefix.length() - 1) + "['" + property + "']" : property;
    }
}

创建一个ExDefaultSqlInjector类替换DefaultSqlInjector类

@Component
public class ExDefaultSqlInjector extends AbstractSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
                new Insert(),
                new Delete(),
                new DeleteByMap(),
                new DeleteById(),
                new DeleteBatchByIds(),
                new Update(),
                new UpdateById(),
                new SelectById(),
                new SelectBatchByIds(),
                new SelectByMap(),
                new SelectOne(),
                new SelectCount(),
                new SelectMaps(),
                new SelectMapsPage(),
                new SelectObjs(),
                new SelectList(),
                new SelectPage(),
                new updateByIdWithNull()
        ).collect(toList());
    }
}

目的是将BaseMapper中的抽象方法和与之对应的同名类生的SQL语句绑定并注入到MyBatis中。就好比我们在Mybatis中的Mapper.java接口中创建一个方法,然后在Mapper.xml在创建一个与方法对应的sql语句(这里相当与UpdateById这些类)与之绑定,然后就可以通过调用Mapper.java中的接口方法实现对数据库的操作。

测试

@Service
public class BookServiceImpl extends CustomServiceImpl<BookMapper, Book>
        implements BookService {

}
@Test
void updateById() {
    Book book1 = Book.builder().id("1692881007231950850")
            .bookName("[email protected]")
            .authors("张三")
            .build();
    Book book2 = Book.builder().id("1692881009077444610")
            .bookName("[email protected]")
            .authors("张三")
            .build();
    bookService.updateById(book2);
}

你可能感兴趣的:(mybatis)