扩展 MybatisPlus 添加强制更新字段的方法

因为 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);
        });
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

会功夫的李白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值