Update批量更新(高性能、动态化)


前言

MySQL数据库批量更新功能,如果数量量比较大,同时更新的内容是依据DO类不同的字段值对应更新,此情景下市面上的框架MyBatis(Plus) 执行的批量更新仅仅是依据主键id循环遍历单条更新,极大的影响了性能。
在此背景下,结合MySQL的语法和MyBatis-Plus框架的预编译功能,通过代码实现高性能、动态化的批量更新,并可配置分段执行大小。

注意:一定看完本文,并且深知使用条件和存在的弊端,否则不建议轻易采用!!


一、环境

开发环境

JDK8
maven3.6.3
SpringBoot2.3.9.RELEASE
MySQL5.7
MyBatisPlus3.4.2
lombok1.18.18
hutool5.6.0

必备条件:JDK、MySQL、MyBatis-Plus、SpringIOC

测试环境

环境简述
Win10版本2004
内存16G
硬盘512G SSD
编辑器IDEA2020.3

二、灵光乍现

最近项目中连续用到批量更新的功能,通过查看MyBatis-Plus源码以及打印执行SQl发现,MyBatis-Plus封装的批量更新方法不尽人意。
当然这样的逻辑对不同场景都有非常好的灵活性,当然我们知道,在程序中灵活与性能一直是相爱相杀的冤家对头,程序很难在保证高灵活性的同时兼顾高性能,毕竟鱼和熊掌不可兼得。

MyBatis-Plus源码

   // MyBatis-Plus封装的IService接口的UpdateBatch方法
    @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean updateBatchById(Collection<T> entityList) {
        return this.updateBatchById(entityList, 1000);
    }

    boolean updateBatchById(Collection<T> entityList, int batchSize)

	// ServiceImpl对应的UpdateBatch实现
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            ParamMap<T> param = new ParamMap();
            param.put("et", entity);
            sqlSession.update(sqlStatement, param);
        });
    }

	// 遍历执行
    public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
            int size = list.size();
            int i = 1;

			// 便利传入的list,拼接执行SQL,并循环执行
            for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
                E element = var6.next();
                consumer.accept(sqlSession, element);
                if (i % batchSize == 0 || i == size) {
                    sqlSession.flushStatements();
                }
            }

        });
    }

2.初见真正的批量更新语法

御世制人博主分析并描述的SQL影响,促使我萌生用Java并配合MyBaitsPlus预编译实现动态批量UpdateBatchById的代码。
在此,向御世制人表示感谢,同时网上也有很多与之类似的描述,在这里就不一一描述了。只把核心的代码拼出来。

UPDATE sys_user
SET phone= CASE id 
               WHEN 2 THEN '15314421111' 
               WHEN 4 THEN '15314422222' 
               WHEN 6 THEN '15314423333' END,
    email= CASE id
               WHEN 2 THEN '1111@qq.com'
               WHEN 4 THEN '2222@qq.com'
               WHEN 6 THEN '3333@qq.com' END
WHERE id IN ('2', '4', '6');

核心逻辑:利用CASE-WHEN语法,同时主键作为Where条件,使多语句拼成一条SQL成为了可能。
如果少量的更新,可以通过MyBatis的xml实现,但需要对每一个DO类做定制化SQL。故此,便萌生了Java代码动态拼接UpdateBatch SQL的想法。
此语法带来可能的同时,也隐藏了一个炸弹,详情看 目录五。


三、开工

基础类搭建

SysUser(表sys_user实体类)

假设我们是对数据库表sys_user的更新,对应的DO类 SysUser

package priv.utrix.exam.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * 系统用户表(SysUser)表实体类
 *
 * @author utrix
 * @since 2021-02-17 10:56:55
 */
@Data
@Accessors(chain = true)
@TableName("sys_user")
public class SysUser implements Serializable {
    private static final long serialVersionUID = -43087234816700913L;
    /**
     * 主键
     */
    private Long id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 手机号
     */
    private String phone;
}

Stash(拼接SQL服务,内部类)

    /**
     * 为了拼接更新sql了创建的存储内部类,在SQL拼接类MakeSqlUtil中
     */
    @Data
    private static class Stash {
        private Object id;
        private Object value;
    }

TableCacheDTO(数据表信息存储)

package priv.utrix.exam.domain.dto;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Map;

/**
 * 数据库表基本信息缓存类
 *
 * @author utrix
 * @date 2021/6/22
 */
@Data
@Accessors(chain = true)
public class TableCacheDTO implements Serializable {
    private static final long serialVersionUID = 4904690745405851925L;

    /**
     * 表名
     */
    private String tableName;

    /**
     * 主键名
     */
    private String keyColumn;

    /**
     * 表字段集合
     * <pre>
     *     key: 实体类fieldName
     *     value: 对应的表列名
     * </pre>
     */
    private Map<String, String> fieldColumn;
}

TableCache(表信息缓存)

package priv.utrix.exam.expand;

import com.google.common.collect.Maps;
import lombok.Getter;
import priv.utrix.exam.domain.dto.TableCacheDTO;

import java.util.Map;

/**
 * 表信息缓存类
 *
 * @author utrix
 * @date 2021/6/22
 */
public class TableCache {
    
    /**
     * 数据库表信息集合
     */
    @Getter
    private static final Map<String, TableCacheDTO> TABLE_MAPS = Maps.newHashMap();

}

MySQL拼接常量类

package priv.utrix.exam.constant;

/**
 * SQL拼接执行常量类
 *
 * @author utrix
 * @date 2021/6/21
 */
public interface SqlConstant {
    // ============= 符号 =================
	
    /**
     * 符号 之 空格
     */
    String SPACE = " ";

    /**
     * 半角逗号
     */
    String COMMA = ",";

    /**
     * 单引号
     */
    String APOSTROPHE = "'";

    /**
     * 等于
     */
    String EQUAL = "=" + SPACE;

    /**
     * 符号之左括号
     */
    String LEFT_PARENTHESIS = SPACE + "(";
    /**
     * 符号之右括号
     */
    String RIGHT_PARENTHESIS = ")";

    // ================= SQL语法函数 ==================

    /**
     * 数据操纵语言(Data Manipulation Language) 之 更新
     */
    String DML_UPDATE = "UPDATE" + SPACE;

    /**
     * 语法之 SET
     */
    String SQL_SET = SPACE + "SET" + SPACE;

    /**
     * SQL函数之 CASE
     */
    String FUNC_CASE = "CASE" + SPACE;

    /**
     * 等号 + CASE
     */
    String EQUAL_CASE = EQUAL + FUNC_CASE;

    /**
     * SQL CASE-WHEN函数之 WHEN
     */
    String FUNC_WHEN = SPACE + "WHEN" + SPACE;

    /**
     * SQL 函数之 WHERE
     */
    String FUNC_WHERE = SPACE + "WHERE" + SPACE;

    /**
     * SQL CASE-WHEN函数之 THEN
     */
    String FUNC_THEN = SPACE + "THEN" + SPACE;

    /**
     * THEN + 单引号
     */
    String THEN_APOSTROPHE = FUNC_THEN + APOSTROPHE;

    /**
     * SQL CASE-WHEN函数之 END
     */
    String FUNC_END = SPACE + "END" + SPACE;

    /**
     * END + 逗号
     */
    String END_COMMA = FUNC_END + COMMA;

    /**
     * 语法之 IN
     */
    String IN = SPACE + "IN";

    /**
     * IN + 左括号
     */
    String IN_PARENTHESIS = IN + LEFT_PARENTHESIS;
}

缓存数据库表信息

利用MyBatis-Plus在应用启动时,对SQL的预编译功能实现。如果不借用此功能,纯粹的用Java原生的反射功能也可以实现,虽然与MyBatis-Plus框架解耦,却降低了性能,并不太易用、易维护。

1. 继承AbstractMethod

package priv.utrix.exam.expand;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.google.common.collect.Maps;
import org.apache.ibatis.mapping.MappedStatement;
import priv.utrix.exam.domain.dto.TableCacheDTO;

import java.util.Map;

/**
 * 利用MyBatisPlus的特性缓存所有实体类的表信息,包括
 * 表名,表列名信息
 *
 * @author utrix
 * @date 2021/6/20
 */
public class CacheTableInfo extends AbstractMethod {

    private static final long serialVersionUID = -8415259305106624978L;

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
		
		// 在应用启动时会被循环加载,其中TableInfo实体类存储着我们想要的数据库表信息,这些内容MyBatis-Plus框架已经为我们写好,我们只拿来用即可
        TableCacheDTO tableCacheDTO = new TableCacheDTO();
        tableCacheDTO.setTableName(tableInfo.getTableName()).setKeyColumn(tableInfo.getKeyColumn());
        // 将表的列字段名称保存到Map中。
        Map<String, String> fieldColumn = Maps.newHashMapWithExpectedSize(tableInfo.getFieldList().size());
        for (TableFieldInfo fieldInfo : tableInfo.getFieldList()) {
        	/*
        	 例: create_time字段。
        	 fieldInfo.getProperty() == createTime
        	 fieldInfo.getColumn() == create_time
        	 */
            fieldColumn.put(fieldInfo.getProperty(), fieldInfo.getColumn());
        }

        tableCacheDTO.setFieldColumn(fieldColumn);

        Map<String, TableCacheDTO> tableMaps = TableCache.getTABLE_MAPS();
        // 放入缓存
        tableMaps.put(modelClass.getName(), tableCacheDTO);
        return null;
    }
}

2. 自定义sql注入器

package priv.utrix.exam.expand;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.AbstractSqlInjector;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;

import java.util.List;

/**
 * 自定义sql注入器,注入缓存信息类CacheTableInfo
 *
 * @author utrix
 * @date 2021/6/20
 */
public class MySqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = new DefaultSqlInjector().getMethodList(mapperClass);
        // 注入缓存表信息的自定义类
        methodList.add(new CacheTableInfo());
        return methodList;
    }
}

3. 自定义注入器生效

package priv.utrix.exam.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import priv.utrix.exam.expand.MySqlInjector;

/**
 * MybatisPlus的配置
 *
 * @author utrix
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 绑定自定义sql注入器, 在DefaultSqlInjector注入器的基础之上添加自定义的方法
     *
     * @return com.baomidou.mybatisplus.core.injector.ISqlInjector
     */
    @Bean
    public ISqlInjector getSqlInjector() {
    	// 替代MyBatis-Plus默认的DefaultSqlInjector注入器
        return new MySqlInjector();
    }
}

事务工具类

package priv.utrix.exam.utils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.function.Supplier;

/**
 * 事务工具类
 *
 * @author utrix
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class TransactionUtil {

   private final TransactionTemplate template;

    /**
     * 手动执行事务, 简化事务覆盖面
     *
     * @param task 执行任务,并自定义返回值
     * @return U 自定义返回对象
     */
    public <U> U execute(Supplier<U> task) {
        return template.execute(transactionStatus -> {
            try {
                return task.get();
            } catch (Exception e) {
                // 自动捕捉异常,回滚
                log.error("任务异常, 回归事务: ", e);
                transactionStatus.setRollbackOnly();
                throw e;
            }
        });
    }
}

制作SQL工具类

package priv.utrix.exam.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Data;
import priv.utrix.exam.domain.dto.TableCacheDTO;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static priv.utrix.exam.constant.SqlConstant.*;

/**
 * 制作SQL工具类
 *
 * @author utrix
 * @date 2021/6/20
 */
public class MakeSqlUtil {

    /**
     * 拼接更新SQL语句
     *
     * @param list 需要更新的实体类集合
     * @return java.lang.String 如果没有需要更新的则返回null
     */
    public static <T> String spliceUpdateSql(List<T> list, TableCacheDTO tableDTO) {
        Map<String, List<Stash>> updateFields = Maps.newHashMap();
        List<Object> ids = Lists.newArrayList();
        for (T t : list) {
            String keyColumn = tableDTO.getKeyColumn();
            Object idValue = ReflectUtil.getFieldValue(t, keyColumn);
            // 如果主键的值为null, 则忽略此条更新SQL
            if (idValue == null) {
                continue;
            }
            ids.add(idValue);

            composeUpdateFields(t, idValue, tableDTO, updateFields);
        }

        if (MapUtil.isEmpty(updateFields)) {
            return null;
        }

        return makeUpdateSql(tableDTO, ids, updateFields);
    }

    /**
     * 制作执行得更新sql语句
     *
     * @param tableDTO     数据表信息
     * @param ids          主键值集合
     * @param updateFields 需要更新得field集合
     * @return java.lang.String 拼接得update SQL
     */
    private static String makeUpdateSql(TableCacheDTO tableDTO, List<Object> ids, Map<String, List<Stash>> updateFields) {
        StringBuilder spliceSQL = new StringBuilder();
        // 拼接Where之前的语句
        spliceSQL.append(DML_UPDATE).append(tableDTO.getTableName()).append(SQL_SET);
        for (Map.Entry<String, List<Stash>> entry : updateFields.entrySet()) {
            String columnName = entry.getKey();
            spliceSQL.append(columnName).append(EQUAL_CASE).append(tableDTO.getKeyColumn());
            // 拼接 WHEN xx THEN xx
            for (Stash term : entry.getValue()) {
                spliceSQL.append(FUNC_WHEN).append(term.getId()).append(THEN_APOSTROPHE).append(term.getValue())
                        .append(APOSTROPHE);
            }
            spliceSQL.append(END_COMMA);
        }

        // 删除最后一个逗号
        spliceSQL.deleteCharAt(spliceSQL.length() - 1);

        // 拼接where语句
        spliceSQL.append(FUNC_WHERE).append(tableDTO.getKeyColumn()).append(IN_PARENTHESIS);
        String conditionValue = ids.stream().map(idVal -> APOSTROPHE + idVal + APOSTROPHE)
                .collect(Collectors.joining(COMMA));
        spliceSQL.append(conditionValue).append(RIGHT_PARENTHESIS);
        return spliceSQL.toString();
    }

    /**
     * 组合需要更新表的列名得map集合
     *
     * @param t            对象值
     * @param idValue      主键得值
     * @param tableDTO     表信息
     * @param updateFields 组合进得集合
     */
    private static <T> void composeUpdateFields(T t, Object idValue, TableCacheDTO tableDTO, Map<String, List<Stash>> updateFields) {
        // 提取存在表中真正的列名集合。这样可以减少因@TableField(exist = false)注解造成的性能浪费
        Set<String> existKeys = tableDTO.getFieldColumn().keySet();
        // 排除主键,因为不需要更新主键信息
        existKeys.remove(tableDTO.getKeyColumn());
        Map<String, Field> targetFieldMap = ReflectUtil.getFieldMap(t.getClass());
        Collection<Field> realFields = MapUtil.filter(targetFieldMap, existKeys.toArray(new String[0])).values();

        for (Field field : realFields) {
            field.setAccessible(true);
            Object fieldValue = ReflectUtil.getFieldValue(t, field);
            if (fieldValue == null) {
                // 只对非空值的字段进行更新
                continue;
            }

            // 依据列名将不同的值归类到Map中
            String columnName = MapUtil.getStr(tableDTO.getFieldColumn(), field.getName());
            List<Stash> valueObjs = updateFields.get(columnName);
            Stash stash = new Stash();
            stash.setId(idValue);
            stash.setValue(fieldValue);
            if (CollUtil.isEmpty(valueObjs)) {
                valueObjs = Lists.newArrayList(stash);
            } else {
                valueObjs.add(stash);
            }

            // 将需要更新的字段保存到Map集合中
            updateFields.put(columnName, valueObjs);
        }
    }

    /**
     * 为了拼接更新sql了创建的存储内部类
     */
    @Data
    private static class Stash {
        private Object id;
        private Object value;
    }

}

SQL执行类

package priv.utrix.exam.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.sql.SqlExecutor;
import com.google.common.collect.Lists;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import priv.utrix.exam.domain.dto.TableCacheDTO;
import priv.utrix.exam.exception.SpliceSqlException;
import priv.utrix.exam.expand.TableCache;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;


/**
 * 自定义sql执行器
 * <pre>
 *     格外注意: 方法参数的值,list中的每一个对象的属性赋值必须保持一致。否则将无差别更新对应主键的SQL
 *
 *     例:List中有两条数据: user1(id:1, username:伞伞, age: 23), user2(id:2, username: null, age: 33)
 *     此时更新List后会把主键为2的 username更新成 null。所以正确的传输是有两种:
 *
 *     第一种:删除username字段
 *     user1(id:1, age: 23), user2(id:2, age: 33)
 *     第二种:给username字段付值, 即: 主键2数据库存储的数据获取并赋值
 *     user1(id:1, username:伞伞, age: 23), user2(id:2, username: 旺旺, age: 33)
 * </pre>
 * 
 * @author utrix
 * @date 2021/6/20
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SqlRunUtil<T> {

    private final DataSource dataSource;
    private final TransactionUtil transactionUtil;

    /**
     * 依据主键批量组装更新SQL语句,并执行SQL语句
     *
     * @param list 需要批量更新的实体类集合
     * @return boolean 执行成功true,反之false
     * @throws SpliceSqlException 如果没有指定表名称
     */
    public boolean updateByIds(@NonNull List<T> list) {
        TableCacheDTO tableCacheDTO = getTableCacheDTO(list.get(0).getClass());
        return updateBatch(list, tableCacheDTO, 500);
    }

    /**
     * 依据主键批量组装更新SQL语句,并执行SQL语句
     *
     * @param list 需要批量更新的实体类集合
     * @param partSize 分段大小配置
     * @return boolean 执行成功true,反之false
     * @throws SpliceSqlException 如果没有指定表名称
     */
    public boolean updateByIds(@NonNull List<T> list, int partSize) {
        TableCacheDTO tableCacheDTO = getTableCacheDTO(list.get(0).getClass());
        return updateBatch(list, tableCacheDTO, partSize < 1 ? 500 : partSize);
    }

    /**
     * 获取数据库表缓存信息
     *
     * @param entity 实体类
     * @return priv.utrix.exam.domain.dto.TableCacheDTO 根据实体类获取的数据库表缓存信息
     */
    private TableCacheDTO getTableCacheDTO(Class<?> entity) {
        TableCacheDTO tableCacheDTO = TableCache.getTABLE_MAPS().get(entity.getName());
        if (tableCacheDTO == null) {
            throw new SpliceSqlException("根据实体类名称获取不到表缓存信息");
        }

        return tableCacheDTO;
    }

    /**
     * 分段批量事务执行update语句
     *
     * @param list          实体类集合
     * @param tableCacheDTO 数据库表缓存信息
     * @param partSize      分段大小
     * @return boolean 更新成功true, 反之false
     */
    private boolean updateBatch(List<T> list, TableCacheDTO tableCacheDTO, int partSize) {
        List<List<T>> partList = ListUtil.split(list, partSize);
        List<String> updateSql = Lists.newArrayListWithExpectedSize(partList.size());
        long start = System.currentTimeMillis();
        for (List<T> part : partList) {
            // 获取数据库表名
            String sql = MakeSqlUtil.spliceUpdateSql(part, tableCacheDTO);
            if (StrUtil.isEmpty(sql)) {
                continue;
            }

            log.error("拼接好的sql: {}", sql);
            updateSql.add(sql);
        }
        log.info("拼接[{}]条 SQL总耗时: {} 毫秒", list.size(), System.currentTimeMillis()-start);

        if (CollUtil.isEmpty(updateSql)) {
            return false;
        }

        try {
            Connection connection = dataSource.getConnection();
            // 开启事务,执行
            return transactionUtil.execute(() -> {
                // 统计更新成功的条数
                int count = 0;
                for (String sql : updateSql) {
                    try {
                        count += SqlExecutor.execute(connection, sql);
                    } catch (SQLException e) {
                        // 抛出异常后, 回滚执行的SQL
                        throw new SpliceSqlException(e.getMessage());
                    }
                }

                return count == updateSql.size();
            });
        } catch (SQLException e) {
            log.error("获取数据库连接异常, 详情: ", e);
        } catch (Exception e) {
            log.error("执行update sql异常, 详情: ", e);
        }

        return false;
    }
}

四、测试

100条测试数据

2021-06-26 16:16:38.553 INFO 21208 --- [ main] priv.utrix.exam.utils.SqlRunUtil : 拼接[100]条 SQL总耗时: 32 毫秒
SQL太长,就不贴出了,可以参考五、弊端的SQL。

1千条测试数据

2021-06-26 16:36:22.754 INFO 13808 --- [ main] priv.utrix.exam.utils.SqlRunUtil : 拼接[1000]条 SQL总耗时: 45 毫秒

1万条测试数据

2021-06-26 16:37:41.740 INFO 7840 --- [ main] priv.utrix.exam.utils.SqlRunUtil : 拼接[10000]条 SQL总耗时: 134 毫秒

10万条测试数据

2021-06-26 16:38:57.129 INFO 15860 --- [ main] priv.utrix.exam.utils.SqlRunUtil : 拼接[100000]条 SQL总耗时: 466 毫秒

五、弊端

采用MySQL的CASE-WEHN组合批量更新语句,有一个很大的弊端。那就是如果拼接的SQL会对主键指定列的指定字段无差别更新。

  1. 更新前数据
    在这里插入图片描述
  2. List数据
// 例如:更新的List<SysUser>数据

在这里插入图片描述
禁忌举例说明:
一共10条数据,需要更新的实体类中,有的username值为null,有的不为null。拼接出的SQL如下:

  1. 拼接的SQL
UPDATE sys_user
SET phone= CASE id
               WHEN 1 THEN '15314876780'
               WHEN 2 THEN '15314876781'
               WHEN 3 THEN '15314876782'
               WHEN 4 THEN '15314876783'
               WHEN 5 THEN '15314876784'
               WHEN 6 THEN '15314876785'
               WHEN 7 THEN '15314876786'
               WHEN 8 THEN '15314876787'
               WHEN 9 THEN '15314876788'
               WHEN 10 THEN '15314876780' END,
    email= CASE id
               WHEN 1 THEN '012345@qq.com'
               WHEN 2 THEN '112345@qq.com'
               WHEN 3 THEN '212345@qq.com'
               WHEN 4 THEN '312345@qq.com'
               WHEN 5 THEN '412345@qq.com'
               WHEN 6 THEN '512345@qq.com'
               WHEN 7 THEN '612345@qq.com'
               WHEN 8 THEN '712345@qq.com'
               WHEN 9 THEN '812345@qq.com'
               WHEN 10 THEN '012345@qq.com' END,
    username= CASE id 
               WHEN 1 THEN '抗日英雄0' 
               WHEN 4 THEN '抗日英雄3' 
               WHEN 7 THEN '抗日英雄6' 
               WHEN 10 THEN '抗日英雄9' END
WHERE id IN ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
  1. 执行结果
    在这里插入图片描述

虽然将将username为null的值过滤掉了,但受语法的影响,还是会更新id为2、2、5、6、8、9的username,默认为null。

所以我们必须严格控制传入的更新List<SysUser>,或者分类更新,否则会出现误更新现象。
后续如果有好的对策,会及时更新此文档。

总结

传入的更新集合必要条件:
保证更新列的属性值在集合内赋值的统一性。

唉!因为弊端的原因,受限了使用场景,并降低了使用灵活性,同时增大了使用风险。
所以务必保证传入的更新的数据集合符合条件再调用,不然可能会造成悲剧!

(本文完!后续有对策继续更新,原创不易,如有引用必须付原文链接)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_函数_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值