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方法会根据实体类的属性生成<if test='name != null'> 这样的动态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 = "<script>\nUPDATE %s %s WHERE %s=#{%s} %s\n</script>";

    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就不会被<if test>标签包裹 如果字段为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("893489@qq.com")
            .authors("张三")
            .build();
    Book book2 = Book.builder().id("1692881009077444610")
            .bookName("893489@qq.com")
            .authors("张三")
            .build();
    bookService.updateById(book2);
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis-Plus是一个优秀的ORM(对象关系映射)框架,除了提供常见的增删改查操作外,还具备更多强大的功能,如全量更新全量更新是指更新数据库表中的所有字段数据,而不仅仅是更新部分字段。在使用MyBatis-Plus进行全量更新时,我们可以通过以下步骤进行操作: 1. 首先,我们需要定义一个实体类,该实体类对应数据库表的结构,并且具备相应的字段属性和对应的getter和setter方法。 2. 在执行全量更新操作前,我们需要获取待更新的数据,并将其封装到该实体类的实例中。 3. 接下来,我们可以使用MyBatis-Plus提供的全量更新方法进行数据库更新操作。在使用全量更新方法时,我们需要传入待更新的实体对象和更新条件,更新条件可以是数据库表的主键或是其他唯一标识。 4. 当全量更新方法执行完成后,MyBatis-Plus会自动构建并执行相应的SQL更新语句,将实体对象中的所有字段数据同步到数据库表中。 需要注意的是,在使用全量更新方法时,我们需要确保实体对象中的所有非空字段数据都被正确赋值,否则可能会导致数据库表中对应的字段值被错误地更新NULL。 总结来说,MyBatis-Plus全量更新功能能够方便地将实体对象中的所有字段数据同步到数据库表中,使得更新操作更加便捷。使用全量更新可以避免漏更新字段的问题,并且是高效的数据库操作方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值