概述🐶

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

所以配置成IGNORED是可以保证更新实体对象类中NULL值到数据库。
但是这个配置是全局性的,大多数情况我们只想更新几个非NULL字段,设置为NULL的字段数据库保持不变。所以这个更好的做法是做到方法级别的控制,通过调用特定的方法实现更新实体类中的所有字段到数据库。

tip: 我们需要创建一个updateByIdAll方法类似于通用MapperupdateByPrimaryKey方法,而MyBatis-Plus中原来的updateById方法相当与通用Mapper中的updateByPrimaryKeySelective方法。通用Mapper 中的几种update的区别

1.给BaseMapper扩展一个方法updateByIdAll🐶

通过给BaseMapper扩展一个updateByAll方法实现更新所有字段(无论字段是否为空)的功能,创建一个ExBaseMapper接口继承自BaseMapper:
ExBaseMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;

public interface ExBaseMapper<T> extends BaseMapper<T> {
	/**
     * 通过ID更新数据,包括NULL和空串
     * @param entity
     */
    int updateByIdAll(@Param(Constants.ENTITY) T entity);

}
2.创建一个UpdateByIdAll类🐶

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

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.methods.UpdateById;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
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 static java.util.stream.Collectors.joining;

public class UpdateByIdAll extends AbstractMethod {

    /**
     * 参考UpdateById中的injectMappedStatement方法
     * @see UpdateById#injectMappedStatement(Class, Class, TableInfo)
     */
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        boolean logicDelete = tableInfo.isLogicDelete();
        final String additional = optlockVersion() + tableInfo.getLogicDeleteSql(true, false);
        String sql = String.format("<script>\nUPDATE %s %s WHERE %s=#{%s} %s\n</script>", tableInfo.getTableName(),
                sqlSet(logicDelete, false, tableInfo, false, ENTITY, ENTITY_DOT),
                tableInfo.getKeyColumn(), ENTITY_DOT + tableInfo.getKeyProperty(), additional);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addUpdateMappedStatement(mapperClass, modelClass, "updateByIdAll", sqlSource);
    }

    /**
     * 重写AbstractMethod的sqlSet方法
     * @see AbstractMethod#sqlSet(boolean, boolean, TableInfo, boolean, String, String)
     */
    protected String sqlSet(boolean logic, boolean ew, TableInfo table, boolean judgeAliasNull, String alias, String prefix) {
        String sqlScript = 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;
    }

    /**
     * 修改TableInfo中的getAllSqlSet方法
     * 目的是忽略set sql片段中的if包裹
     * @see TableInfo#getAllSqlSet(boolean, String)
     */
    public String getAllSqlSet(TableInfo table, boolean ignoreLogicDelFiled, final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        return table.getFieldList().stream().map(i -> i.getSqlSet(true, newPrefix))
                .filter(Objects::nonNull).collect(joining(NEWLINE));
    }

}
3.创建一个ExDefaultSqlInjector类替换DefaultSqlInjector类🐶

源码DefaultSqlInjector.java类是长这样的:

public class DefaultSqlInjector 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()
        ).collect(toList());
    }
}

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

上面的ExBaseMapper中已经有定义updateByIdAll方法了,现在需要sql与之绑定,将上面UpdateByIdAll加入到DefaultSqllnjector中,源码DefaultSqlInjector不可修改,所以我们创建一个与之相同的类进行扩展:
ExDefaultSqlInjector.java

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.AbstractSqlInjector;
import com.baomidou.mybatisplus.core.injector.methods.*;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;

@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 UpdateByIdAll()  // 注入新增的方法
        ).collect(toList());
    }

}

注意: 上面的@Component是必须的,我们需要将ExDefaultSqlInjector的实例注入到Spring中,Mybatis-Plus会使用ExDefaultSqlInjector替换掉默认的DefaultSqlInjector实现注入updateByIdAll方法的功能。

4. 测试🐶

接下来我们只需要让我们本来继承BaseMapper.java接口的XxxMapper.java接口继承现在的ExBaseMapper.java接口。

这里我们有一张User表:

mybatiesplus 查询 忽略大小写_updateById


使用UserMapper.java继承自ExBaseMapper.java

public interface UserMapper extends ExBaseMapper<User> {

}

创建一个更新用户的接口:

@GetMapping("/user/update")
    public void updateUser(){
        User user = new User();
        user.setUserId(1);
        user.setUsername("jo8tony");
        user.setPassword("123456");
        user.setName(null);
        user.setAddr(null);
//        userMapper.updateById(user); // 仅更新非空的userName和password
        userMapper.updateByIdAll(user); // 更新所有字段
    }

浏览器访问接口后结果:🐶

mybatiesplus 查询 忽略大小写_通用Mapper_02

mybatiesplus 查询 忽略大小写_updateById_03