From 0b293092a323a3565beccf4c9af07c041b51aca6 Mon Sep 17 00:00:00 2001 From: warden Date: Mon, 19 Jun 2023 14:45:48 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E9=80=9A=E7=94=A8=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E3=80=81=E6=9B=B4=E6=96=B0=E3=80=81=E5=88=A0=E9=99=A4=E9=80=BB?= =?UTF-8?q?=E8=BE=91=202.=20=E5=A2=9E=E5=8A=A0=E5=85=A8=E9=87=8F=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=80=81=E6=96=B0=E5=A2=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/config/MybatisPlusConfig.java | 9 + .../mybatis/core/mapper/BaseMapperPlus.java | 2 +- .../mybatis/core/mapper/CommonMapper.java | 32 ++ .../mybatis/core/mapper/MySqlInjector.java | 33 ++ .../common/mybatis/core/utils/BeanUtil.java | 167 ++++++++++ .../mybatis/core/utils/SaveDiffUtil.java | 286 ++++++++++++++++++ 6 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java index c07a2bb3b..5a5e4781f 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.dromara.common.core.factory.YmlPropertySourceFactory; +import org.dromara.common.mybatis.core.mapper.MySqlInjector; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; import org.mybatis.spring.annotation.MapperScan; @@ -82,6 +83,14 @@ public class MybatisPlusConfig { return new DefaultIdentifierGenerator(NetUtil.getLocalhost()); } + /** + * 全量更新 + * @return + */ + @Bean + public MySqlInjector sqlInjector() { + return new MySqlInjector(); + } /** * PaginationInnerInterceptor 分页插件,自动识别数据库类型 * https://baomidou.com/pages/97710a/ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java index 1f36f0400..d2fdc4fcc 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java @@ -30,7 +30,7 @@ import java.util.stream.Collectors; * @since 2021-05-13 */ @SuppressWarnings("unchecked") -public interface BaseMapperPlus extends BaseMapper { +public interface BaseMapperPlus extends CommonMapper { Log log = LogFactory.getLog(BaseMapperPlus.class); diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java new file mode 100644 index 000000000..f0e560f9c --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java @@ -0,0 +1,32 @@ +package org.dromara.common.mybatis.core.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Author warden + * @Date 2023/1/17 17:37 + */ +public interface CommonMapper extends BaseMapper { + /** + * 全量插入,等价于insert + * {@link com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn} + * + * @param entityList + * @return + */ + int insertBatchSomeColumn(List entityList); + + /** + * 全量更新,不忽略null字段,等价于update + * 解决mybatis-plus会自动忽略null字段不更新 + * {@link com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById} + * + * @param entity + * @return + */ + int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity); +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java new file mode 100644 index 000000000..771747367 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java @@ -0,0 +1,33 @@ +package org.dromara.common.mybatis.core.mapper; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById; +import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn; + +import java.util.List; + +/** + * 自定义Sql注入 + * + * @author nieqiurong 2018/8/11 20:23. + */ +public class MySqlInjector extends DefaultSqlInjector { + + @Override + public List getMethodList(Class mapperClass, TableInfo tableInfo) { + List methodList = super.getMethodList(mapperClass, tableInfo); + + /** + * 以下 3 个为内置选装件 + * 头 2 个支持字段筛选函数 + */ + // 例: 不要指定了 update 填充的字段 + methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); + // 标识了INSERT的字段不更新(createTime 和 createBy) + methodList.add(new AlwaysUpdateSomeColumnById(i -> i.getFieldFill() != FieldFill.INSERT)); + return methodList; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java new file mode 100644 index 000000000..8098a75ab --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java @@ -0,0 +1,167 @@ +package org.dromara.common.mybatis.core.utils; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.model.LoginUser; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.satoken.utils.LoginHelper; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * @Author warden + * @Date 2022/12/8 14:56 + */ +@Slf4j +public class BeanUtil { + public static final String ADD = "ADD"; + public static final String REMOVE = "REMOVE"; + public static final String NORMAL = "NORMAL"; + + /** + * 比对两条记录数据库字段是否相同,相同返回true,不同返回false + * + * @param compare + * @param current + * @return + */ + public static boolean diffObject(Object compare, Object current) { + List fieldList = new ArrayList<>(); + + Field[] allFields = ReflectUtil.getFieldsDirectly(compare.getClass(), true); + + String[] dontCompareFields = {"createBy", "createTime", "updateBy", "updateTime", "params", "searchValue", "serialVersionUID"}; + List dCList = new ArrayList<>(); + dCList.addAll(Arrays.asList(dontCompareFields)); + for (Field field : allFields) { + if (dCList.contains(field.getName())) { + continue; + } + TableField tableField = field.getAnnotation(TableField.class); + if (tableField == null || tableField.exist()) { + fieldList.add(field); + } + } + + for (Field field : fieldList) { + try { + //抑制Java对其的检查 + field.setAccessible(true); + + //获取 object 中 field 所代表的属性值 + Object comp = field.get(compare); + Object curr = field.get(current); + if (comp != null && curr != null) { + if (!comp.toString().equals(curr.toString())) { + return false; + } + } else { + if ((comp == null && curr != null) || (comp != null && curr == null)) { + return false; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 比较原List与现List的差异,返回Map + * Map的三个KEY值:ADD(新增)\REMOVE(删除)\NORMAL(相同) + * + * @param compareList 需要比较的list(页面传过来的) + * @param currList 当前的list(当前数据库中的) + * @return + */ + public static Map> diffList(Set compareList, Set currList) { + + if (CollectionUtil.isEmpty(compareList) && CollectionUtil.isEmpty(currList)) { + return null; + } + + Map> result = new HashMap(); + + result.put(ADD, new HashSet()); + result.put(REMOVE, new HashSet()); + result.put(NORMAL, new HashSet()); + + if (CollectionUtil.isEmpty(compareList)) { + result.put(REMOVE, currList); + } else if (CollectionUtil.isEmpty(currList)) { + result.put(ADD, compareList); + } else { + for (Long comStr : compareList) { + if (currList.contains(comStr)) { + result.get(NORMAL).add(comStr); + } else { + result.get(ADD).add(comStr); + } + } + + for (Long currStr : currList) { + if (compareList.contains(currStr)) { + result.get(NORMAL).add(currStr); + } else { + result.get(REMOVE).add(currStr); + } + } + } + return result; + } + + public static Map diffList(List compareList, List currList) { + Set compareSet = new HashSet<>(); + Set currSet = new HashSet<>(); + + if (CollectionUtil.isNotEmpty(compareList)) { + for (Long val : compareList) { + compareSet.add(val); + } + } + + if (CollectionUtil.isNotEmpty(currList)) { + for (Long val : currList) { + currSet.add(val); + } + } + + return diffList(compareSet, currSet); + + } + + public static void fillValue(Object object) { + if (object instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) object; + Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime()) + ? baseEntity.getCreateTime() : new Date(); + baseEntity.setCreateTime(current); + baseEntity.setUpdateTime(current); + Long userId = baseEntity.getCreateBy() != null + ? baseEntity.getCreateBy() : getLoginUserId(); + // 当前已登录 且 创建人为空 则填充 + baseEntity.setCreateBy(userId); + // 当前已登录 且 更新人为空 则填充 + baseEntity.setUpdateBy(userId); + } + } + + /** + * 获取登录用户名 + */ + private static Long getLoginUserId() { + LoginUser loginUser; + try { + loginUser = LoginHelper.getLoginUser(); + } catch (Exception e) { + log.warn("自动注入警告 => 用户未登录"); + return null; + } + return loginUser.getUserId(); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java new file mode 100644 index 000000000..dd27c86c5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java @@ -0,0 +1,286 @@ +package org.dromara.common.mybatis.core.utils; + +import cn.hutool.core.util.ReflectUtil; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.common.mybatis.core.mapper.CommonMapper; + +import java.util.*; + +/** + * 比对列表数据,用于判断差异并保存到数据库中 + * 全量保存 + */ +public class SaveDiffUtil { + // 获取mybatisplus 的ID生成器 + private final static IdentifierGenerator IDENTIFIER_GENERATOR = SpringUtils.getBean("idGenerator"); + + public static Map> saveFormDiff(List formList, List oriList, BaseMapper mapper) { + return saveFormDiff(formList, oriList, mapper, null, null); + } + + public static Map> saveFormDiff(List formList, List oriList, BaseMapper mapper, Boolean exeUpdate) { + return saveFormDiff(formList, oriList, mapper, exeUpdate, null); + } + + /** + * 需要处理手工修改的记录 + * + * @param formList 表单提交的数据 + * @param oriList 数据库数据 + * @param mapper 处理实体的mapper + * @param exeUpdate 是否执行更新 + * @param updateAll 更新操作是否全字段更新 + * @throws Exception + */ + public static Map> saveFormDiff(List formList, List oriList, BaseMapper mapper, + Boolean exeUpdate, Boolean updateAll) { + if (exeUpdate == null) { + exeUpdate = false; + } + if (updateAll == null) { + updateAll = false; + } + + + if (formList == null) { + formList = new ArrayList(); + } + + Map> result = getFormDiff(formList, oriList); + + if (result != null) { + Set addList = result.get(BeanUtil.ADD); + Set removeList = result.get(BeanUtil.REMOVE); + Set updateList = result.get(BeanUtil.NORMAL); + + // 添加的对象 + if (addList != null && addList.size() > 0) { + List addObjList = new ArrayList(); + for (Long id : addList) { + Iterator it = formList.iterator(); + while (it.hasNext()) { + Object obj = it.next(); + Long objId = (Long) ReflectUtil.getFieldValue(obj, "id"); + if (id.equals(objId)) { + addObjList.add(obj); +// it.remove(); 不可删除,需要返回 + break; + } + } + } + if (addObjList != null && addObjList.size() > 0) { +// List list = cn.hutool.core.bean.BeanUtil.copyToList(addObjList, clz); + ((BaseMapperPlus) mapper).insertBatch(addObjList); + } + } + + if (removeList != null && removeList.size() > 0) { + mapper.deleteBatchIds(removeList); + } + + if (exeUpdate && updateList != null && updateList.size() > 0) { + List updateObjList = new ArrayList(); + + for (Long id : updateList) { + Object compareObj = findObj(formList, id); + Object currObj = findObj(oriList, id); + + // 比对数据库字段是否有修改 + boolean diff = BeanUtil.diffObject(compareObj, currObj); + if (!diff) { + updateObjList.add(compareObj); + } + } + + if (updateObjList.size() > 0) { + for (Object obj : updateObjList) { + if (updateAll) { + // 全量更新 + ((CommonMapper) mapper).alwaysUpdateSomeColumnById(obj); + } else { + // 更新不为空 + mapper.updateById(obj); + } + } + } + } + } + + return result; + } + + public static Map> getFormDiff(List formList, List oriList) { + // 需要比较的 + List compareList = new ArrayList<>(); + // 原始数据 + List currList = new ArrayList<>(); + + updateObject(formList, compareList); + updateObject(oriList, currList); + + Map> result = BeanUtil.diffList(compareList, currList); + + return result; + } + + + private static void updateObject(List objList, List targetList, String idFieldName) { + if (idFieldName == null) { + idFieldName = "id"; + } + + for (Object obj : objList) { + Object val = ReflectUtil.getFieldValue(obj, idFieldName); + if (val != null) { + targetList.add(val); + } else { + Long uuid = IDENTIFIER_GENERATOR.nextId(obj).longValue(); + targetList.add(uuid); + ReflectUtil.setFieldValue(obj, "id", uuid); + } + } + } + + private static void updateObject(List objList, List targetList) { + updateObject(objList, targetList, null); + } + + /** + * 根据查询的属性找对象的key并赋值到目标列表中 + * + * @param oriList + * @param targetList + * @param searchFieldName + * @param idFieldName + */ + public static void updateObject(List oriList, List targetList, String searchFieldName, String idFieldName) { + if (oriList == null) { + return; + } + for (Object oriObj : oriList) { + Object searchStr = ReflectUtil.getFieldValue(oriObj, searchFieldName); + + if (searchStr != null) { + for (Object tarObj : targetList) { + Object tarStr = ReflectUtil.getFieldValue(tarObj, searchFieldName); + if (searchStr.equals(tarStr)) { + Object idStr = ReflectUtil.getFieldValue(oriObj, idFieldName); + if (idStr != null) { + ReflectUtil.setFieldValue(tarObj, idFieldName, idStr); + } + continue; + } + } + } + + } + } + + /** + * 根据属性及属性值,从列表中查找对象 + * + * @param objList + * @param idFieldName + * @param keyValue + * @return + */ + private static Object findObj(List objList, String idFieldName, Object keyValue) { + + if (idFieldName == null) { + idFieldName = "id"; + } + + Object result = null; + for (Object obj : objList) { + Object val = ReflectUtil.getFieldValue(obj, idFieldName); + if (keyValue.equals(val)) { + result = obj; + break; + } + } + + return result; + } + + private static Object findObj(List objList, Object keyValue) { + return findObj(objList, null, keyValue); + } + +// /** +// * 修改关系记录(根据linkID+pkId添加和删除) +// */ +// /** +// * @param currentUser 当前用户 +// * @param formList 表单提交的数据 +// * @param oriList 数据库中的数据 +// * @param clz 要插入表中的对象对应的类 +// * @param findId 根据哪个属性查找传入的formList、oriList +// * @param pkIdName 根据哪个属性删除(添加)关系记录 +// * @param linkIdName 外键字段名称 +// * @param linkId 外键字段值 +// * @param mapper +// * @throws Exception +// */ +// public static void saveRelaDiff( +// List formList, +// List oriList, +// Class clz, +// String findId, +// String pkIdName, +// String linkIdName, +// String linkId, +// BaseMapper mapper) throws Exception { +// // 需要比较的 +// List compareList = new ArrayList<>(); +// // 原始数据 +// List currList = new ArrayList<>(); +// +// updateObject(formList, compareList, findId); +// updateObject(oriList, currList, findId); +// +// Map> result = BeanUtil.diffList(compareList, currList); +// +// if (result != null) { +// Set addList = result.get(BeanUtil.ADD); +// Set removeList = result.get(BeanUtil.REMOVE); +// +// //删除数据 +// if (removeList != null && removeList.size() > 0) { +// for (String removeId : removeList) { +// Object obj = clz.newInstance(); +// ReflectUtil.setFieldValue(obj, "delFlag", DelFlag.DELETE); +// +// Example remExample = new Example(clz); +// Example.Criteria remCriteria = remExample.createCriteria(); +// //获取删除标记为正常的记录 +// remCriteria.andEqualTo(linkIdName, linkId); +// remCriteria.andEqualTo(pkIdName, removeId); +// remCriteria.andNotEqualTo("delFlag", DelFlag.DELETE); +// mapper.updateByExampleSelective(obj, remExample); +// } +// } +// +// //添加资源 +// if (addList != null && addList.size() > 0) { +// List addObjList = new ArrayList(); +// for (String addId : addList) { +// Object obj = clz.newInstance(); +// ReflectUtil.setFieldValue(obj, linkIdName, linkId); +// ReflectUtil.setFieldValue(obj, pkIdName, addId); +// +// BeanUtil.setCreateUser(currentUser, obj); +// BeanUtil.setUpdateUser(currentUser, obj); +// +// addObjList.add(obj); +// } +// +// if (addObjList.size() > 0) { +// mapper.batchInsert(addObjList); +// } +// } +// } +// } +}