文章目录
前言
MySQL数据库批量更新功能,如果数量量比较大,同时更新的内容是依据DO类不同的字段值对应更新,此情景下市面上的框架MyBatis(Plus) 执行的批量更新仅仅是依据主键id循环遍历单条更新,极大的影响了性能。
在此背景下,结合MySQL的语法和MyBatis-Plus框架的预编译功能,通过代码实现高性能、动态化的批量更新,并可配置分段执行大小。
注意:一定看完本文,并且深知使用条件和存在的弊端,否则不建议轻易采用!!
一、环境
开发环境
JDK | 8 |
---|---|
maven | 3.6.3 |
SpringBoot | 2.3.9.RELEASE |
MySQL | 5.7 |
MyBatisPlus | 3.4.2 |
lombok | 1.18.18 |
hutool | 5.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会对主键指定列的指定字段无差别更新。
- 更新前数据
- List数据
// 例如:更新的List<SysUser>数据
禁忌举例说明:
一共10条数据,需要更新的实体类中,有的username值为null,有的不为null。拼接出的SQL如下:
- 拼接的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');
- 执行结果
虽然将将username为null的值过滤掉了,但受语法的影响,还是会更新id为2、2、5、6、8、9的username,默认为null。
所以我们必须严格控制传入的更新
List<SysUser>
,或者分类更新,否则会出现误更新现象。
后续如果有好的对策,会及时更新此文档。
总结
传入的更新集合必要条件:
保证更新列的属性值在集合内赋值的统一性。
唉!因为弊端的原因,受限了使用场景,并降低了使用灵活性,同时增大了使用风险。
所以务必保证传入的更新的数据集合符合条件再调用,不然可能会造成悲剧!
(本文完!后续有对策继续更新,原创不易,如有引用必须付原文链接)