因为 MybatisPlus 的 update 方法都是默认不更新值为 null 的字段,所以需要进行扩展,以适应某些强制更新字段的场景
使用示例
OrderItemDO orderItemDOUpdate = BeanUtils.toBean(orderItemDO, OrderItemDO.class);
orderItemDOUpdate.setId(orderItemDO.getId());
// 退回到分发将清空所有分配信息
orderItemDO.setChildrenDeptId(null);
orderItemDO.setDepartmentId(null);
orderItemMapper.updateForceSpecifiedColumnsById(
orderItemDOUpdate,
// 强制更新子部门和部门
OrderItemDO::getChildrenDeptId,
OrderItemDO::getDepartmentId
);
// 强制更新所有备注字段
this.updateForceSpecifiedColumnsByIdBatch(updateOrderItemList, OrderItemDO::getRemark);
扩展 Mapper
创建 BaseMapperX 来继承 MybatisPlus 的 BaseMapper
public interface BaseMapperX<T> extends BaseMapper<T> {
/**
* **注意谨慎使用,因为如果字段没有被赋值,将被更新为空,建议使用updateSpecifiedColumnsById方法**,通过ID更新实体,强制更新所有字段,包括null值
*
* @param entity 实体对象
* @param excludeFields 需要排除的字段(不更新)
* @return 更新条数
*/
default int updateForceAllColumnById(T entity, SFunction<T, ?>... excludeFields) {
if (entity == null) {
return 0;
}
// 获取实体类
Class<?> entityClass = entity.getClass();
// 获取主键字段和值
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
if (tableInfo == null || tableInfo.getKeyProperty() == null) {
return 0;
}
String keyProperty = tableInfo.getKeyProperty();
Object keyValue = null;
try {
// 获取主键值
Field idField = entityClass.getDeclaredField(keyProperty);
idField.setAccessible(true);
keyValue = idField.get(entity);
if (keyValue == null) {
return 0;
}
} catch (Exception e) {
return 0;
}
// 获取所有字段
Field[] fields = entityClass.getDeclaredFields();
if (fields.length == 0) {
return 0;
}
// 创建更新Wrapper
UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
// 转换需要排除的字段名称列表
List<String> excludeFieldNames = new ArrayList<>();
if (excludeFields != null && excludeFields.length > 0) {
for (SFunction<T, ?> func : excludeFields) {
try {
// 使用MyBatisPlus提供的工具获取属性名
String fieldName = PropertyNamer.methodToProperty(LambdaUtils.extract(func).getImplMethodName());
excludeFieldNames.add(fieldName);
} catch (Exception e) {
// 忽略异常,继续执行
}
}
}
// 设置ID条件
updateWrapper.eq(tableInfo.getKeyColumn(), keyValue);
// 设置所有字段(除了排除的字段和主键)
for (Field field : fields) {
String fieldName = field.getName();
// 跳过主键和排除的字段
if (fieldName.equals(keyProperty) || excludeFieldNames.contains(fieldName)) {
continue;
}
// 跳过静态和瞬态字段
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
continue;
}
// 获取字段对应的数据库列名
TableFieldInfo tableFieldInfo = tableInfo.getFieldList().stream()
.filter(info -> info.getProperty().equals(fieldName))
.findFirst()
.orElse(null);
// 如果字段没有映射到数据库列,则跳过
if (tableFieldInfo == null) {
continue;
}
try {
field.setAccessible(true);
Object value = field.get(entity);
// 设置字段值
updateWrapper.set(tableFieldInfo.getColumn(), value);
} catch (Exception ignored) {
// 忽略异常,继续处理下一个字段
}
}
return update(updateWrapper);
}
/**
* **注意谨慎使用,因为如果字段没有被赋值,将被更新为空**,通过ID更新实体,强制更新所有字段,包括null值
*
* @param entity 实体对象
* @return 更新条数
*/
default int updateForceAllColumnById(T entity) {
return updateForceAllColumnById(entity, (SFunction<T, ?>) null);
}
/**
* 通过ID强制更新指定字段,指定字段无论是否为null都会被更新
*
* @param entity 实体对象
* @param forceUpdateFields 需要强制更新的字段(包括null值)
* @return 更新条数
*/
default int updateForceSpecifiedColumnsById(T entity, SFunction<T, ?>... forceUpdateFields) {
if (entity == null) {
return 0;
}
// 获取实体类
Class<?> entityClass = entity.getClass();
// 获取主键字段和值
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
if (tableInfo == null || tableInfo.getKeyProperty() == null) {
return 0;
}
String keyProperty = tableInfo.getKeyProperty();
Object keyValue = null;
try {
// 获取主键值
Field idField = entityClass.getDeclaredField(keyProperty);
idField.setAccessible(true);
keyValue = idField.get(entity);
if (keyValue == null) {
return 0;
}
} catch (Exception e) {
return 0;
}
// 创建更新Wrapper
UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
// 转换需要强制更新的字段名称列表
List<String> forceUpdateFieldNames = new ArrayList<>();
if (forceUpdateFields != null && forceUpdateFields.length > 0) {
for (SFunction<T, ?> func : forceUpdateFields) {
try {
// 使用MyBatisPlus提供的工具获取属性名
String fieldName = PropertyNamer.methodToProperty(LambdaUtils.extract(func).getImplMethodName());
forceUpdateFieldNames.add(fieldName);
} catch (Exception e) {
// 忽略异常,继续执行
}
}
}
// 设置ID条件
updateWrapper.eq(tableInfo.getKeyColumn(), keyValue);
// 获取所有字段
Field[] fields = entityClass.getDeclaredFields();
if (fields.length == 0) {
return 0;
}
// 处理所有字段
for (Field field : fields) {
String fieldName = field.getName();
// 跳过主键
if (fieldName.equals(keyProperty)) {
continue;
}
// 跳过静态和瞬态字段
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
continue;
}
// 获取字段对应的数据库列名
TableFieldInfo tableFieldInfo = tableInfo.getFieldList().stream()
.filter(info -> info.getProperty().equals(fieldName))
.findFirst()
.orElse(null);
// 如果字段没有映射到数据库列,则跳过
if (tableFieldInfo == null) {
continue;
}
try {
field.setAccessible(true);
Object value = field.get(entity);
// 对于强制更新的字段,无论值是否为null都更新
if (forceUpdateFieldNames.contains(fieldName)) {
updateWrapper.set(tableFieldInfo.getColumn(), value);
}
// 对于非强制更新的字段,仅当值不为null时才更新
else if (value != null) {
updateWrapper.set(tableFieldInfo.getColumn(), value);
}
} catch (Exception ignored) {
// 忽略异常,继续处理下一个字段
}
}
return update(updateWrapper);
}
}
扩展 Service
另外增加批量支持,创建 IServiceX 和 ServiceImplX
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Collection;
public interface IServiceX<T> extends IService<T> {
/**
* 批量更新实体,强制更新指定字段,指定字段无论是否为null都会被更新
*
* @param entities 实体对象集合
* @param forceUpdateFields 需要强制更新的字段(包括null值)
* @return 是否更新成功
*/
boolean updateForceSpecifiedColumnsByIdBatch(Collection<T> entities, SFunction<T, ?>... forceUpdateFields);
}
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* 基于 MyBatis Plus ServiceImpl 扩展
**/
public class ServiceImplX<M extends BaseMapperX<T>, T> extends ServiceImpl<M, T> implements IServiceX<T> {
@Transactional(rollbackFor = Exception.class)
public boolean updateForceSpecifiedColumnsByIdBatch(Collection<T> entities, SFunction<T, ?>... forceUpdateFields) {
if (Objects.isNull(entities) || entities.isEmpty()) {
return false;
}
// 获取实体类
Class<?> entityClass = entities.iterator().next().getClass();
// 获取主键字段信息
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
if (tableInfo == null || tableInfo.getKeyProperty() == null) {
return false;
}
String keyProperty = tableInfo.getKeyProperty();
// 转换需要强制更新的字段名称列表
List<String> forceUpdateFieldNames = new ArrayList<>();
if (forceUpdateFields != null && forceUpdateFields.length > 0) {
for (SFunction<T, ?> func : forceUpdateFields) {
try {
// 使用MyBatisPlus提供的工具获取属性名
String fieldName = PropertyNamer.methodToProperty(LambdaUtils.extract(func).getImplMethodName());
forceUpdateFieldNames.add(fieldName);
} catch (Exception e) {
// 忽略异常,继续执行
}
}
}
// 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(entityClass);
// 使用日志对象
Log log = LogFactory.getLog(entityClass);
// 使用SqlHelper.executeBatch执行批量更新
return SqlHelper.executeBatch(sqlSessionFactory, log, entities, 1000, (sqlSession, entity) -> {
// 获取主键值
Object keyValue = null;
try {
Field idField = entityClass.getDeclaredField(keyProperty);
idField.setAccessible(true);
keyValue = idField.get(entity);
if (keyValue == null) {
return; // 跳过主键为空的实体
}
} catch (Exception e) {
return; // 跳过获取主键失败的实体
}
// 创建更新Wrapper
UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
// 设置ID条件
updateWrapper.eq(tableInfo.getKeyColumn(), keyValue);
// 获取所有字段
Field[] fields = entityClass.getDeclaredFields();
if (fields.length == 0) {
return;
}
// 处理所有字段
for (Field field : fields) {
String fieldName = field.getName();
// 跳过主键
if (fieldName.equals(keyProperty)) {
continue;
}
// 跳过静态和瞬态字段
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
continue;
}
// 获取字段对应的数据库列名
TableFieldInfo tableFieldInfo = tableInfo.getFieldList().stream()
.filter(info -> info.getProperty().equals(fieldName))
.findFirst()
.orElse(null);
// 如果字段没有映射到数据库列,则跳过
if (tableFieldInfo == null) {
continue;
}
try {
field.setAccessible(true);
Object value = field.get(entity);
// 对于强制更新的字段,无论值是否为null都更新
if (forceUpdateFieldNames.contains(fieldName)) {
updateWrapper.set(tableFieldInfo.getColumn(), value);
}
// 对于非强制更新的字段,仅当值不为null时才更新
else if (value != null) {
updateWrapper.set(tableFieldInfo.getColumn(), value);
}
} catch (Exception ignored) {
// 忽略异常,继续处理下一个字段
}
}
// 使用当前会话的Mapper进行更新
this.baseMapper.update(updateWrapper);
});
}
}