关于springboot:如何解决mybatisplus调用update方法时自动填充字段不生效问题

58次阅读

共计 5989 个字符,预计需要花费 15 分钟才能阅读完成。

前言

应用过 mybatis-plus 的敌人可能会晓得,通过实现元对象处理器接口 com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 能够实现字段填充性能。但如果在更新实体,应用 boolean update(Wrapper<T> updateWrapper)这个办法进行更新时,则主动填充会生效。明天就来聊聊这个话题,本文例子应用的 mybatis-plus 版本为 3.1.2 版本

为何应用 boolean update(Wrapper<T> updateWrapper),主动填充会生效?

mybatis-plus 3.1.2 版本跟踪源码,能够得悉,主动填充的调用代码实现逻辑是由上面的外围代码块实现

 /**
     * 自定义元对象填充控制器
     *
     * @param metaObjectHandler 元数据填充处理器
     * @param tableInfo         数据库表反射信息
     * @param ms                MappedStatement
     * @param parameterObject   插入数据库对象
     * @return Object
     */
    protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
                                         MappedStatement ms, Object parameterObject, boolean isInsert) {if (null == tableInfo) {
            /* 不解决 */
            return parameterObject;
        }
        /* 自定义元对象填充控制器 */
        MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
        // 填充主键
        if (isInsert && !StringUtils.isEmpty(tableInfo.getKeyProperty())
            && null != tableInfo.getIdType() && tableInfo.getIdType().getKey() >= 3) {Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
            /* 自定义 ID */
            if (StringUtils.checkValNull(idValue)) {if (tableInfo.getIdType() == IdType.ID_WORKER) {metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
                } else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
                } else if (tableInfo.getIdType() == IdType.UUID) {metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
                }
            }
        }
        if (metaObjectHandler != null) {if (isInsert && metaObjectHandler.openInsertFill()) {
                // 插入填充
                metaObjectHandler.insertFill(metaObject);
            } else if (!isInsert) {
                // 更新填充
                metaObjectHandler.updateFill(metaObject);
            }
        }
        return metaObject.getOriginalObject();}

从源码剖析咱们能够得悉 当 tableInfo 为 null 时,是不走主动填充逻辑。而 tableInfo 又是什么从中央进行取值,持续跟踪源码,咱们得悉 tableInfo 能够由底下代码获取

 if (isFill) {Collection<Object> parameters = getParameters(parameterObject);
            if (null != parameters) {List<Object> objList = new ArrayList<>();
                for (Object parameter : parameters) {TableInfo tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
                    if (null != tableInfo) {objList.add(populateKeys(metaObjectHandler, tableInfo, ms, parameter, isInsert));
                    } else {
                        /*
                         * 非表映射类不解决
                         */
                        objList.add(parameter);
                    }
                }
                return objList;
            } else {
                TableInfo tableInfo = null;
                if (parameterObject instanceof Map) {Map<?, ?> map = (Map<?, ?>) parameterObject;
                    if (map.containsKey(Constants.ENTITY)) {Object et = map.get(Constants.ENTITY);
                        if (et != null) {if (et instanceof Map) {Map<?, ?> realEtMap = (Map<?, ?>) et;
                                if (realEtMap.containsKey(Constants.MP_OPTLOCK_ET_ORIGINAL)) {tableInfo = TableInfoHelper.getTableInfo(realEtMap.get(Constants.MP_OPTLOCK_ET_ORIGINAL).getClass());
                                }
                            } else {tableInfo = TableInfoHelper.getTableInfo(et.getClass());
                            }
                        }
                    }
                } else {tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
                }

从源码能够很分明看出,tableInfo 的获取依赖 parameterObject.getClass(),则这个 parameterObject 就是数据库插入或者更新对象。即咱们的实体对象,当实体对象为 null 时,则 tableInfo 的值也是为 null,这就会导致主动填充生效

咱们再来看下 boolean update(Wrapper<T> updateWrapper)这个代码的底层实现

default boolean update(Wrapper<T> updateWrapper) {return this.update((Object)null, updateWrapper);
    }

通过代码咱们能够晓得,当应用这个办法时,其实体对象是 null,导致调用主动填充办法时,失去的 tableInfo 是 null,因此无奈进入主动填充实现逻辑,因而导致填充主动生效

如何解决 update(Wrapper<T> updateWrapper),主动填充不失效问题

通过源码剖析咱们得悉,只有 tableInfo 不为空,则就会进入主动填充逻辑,而 tableInfo 不为空的前提是更新或者插入的实体不是 null 对象,因而咱们的思路就是在调用 update 办法时,要确保实体不为 null

计划一:实体更新时,间接应用 update(Wrapper<T> updateWrapper)的重载办法 boolean update(T entity, Wrapper<T> updateWrapper)

示例:

msgLogService.update(new MsgLog(),lambdaUpdateWrapper)

计划二:重写 update(Wrapper<T> updateWrapper)办法

重写 update 的办法思路有如下

办法一:重写 ServiceImpl 的 update 办法

其外围思路如下,重写一个业务基类 BaseServiceImpl

public class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T>  {

    /**
     * 
     *
     * @param updateWrapper
     * @return
     */
    @Override
    public boolean update(Wrapper<T> updateWrapper) {T entity = updateWrapper.getEntity();
        if (null == entity) {
            try {entity = this.currentModelClass().newInstance();} catch (InstantiationException e) {e.printStackTrace();
            } catch (IllegalAccessException e) {e.printStackTrace();
            }
        }
        return update(entity, updateWrapper);
    }
}

业务 service 去继承 BaseServiceImpl,形如下

@Service
public class MsgLogServiceImpl extends BaseServiceImpl<MsgLogDao, MsgLog> implements MsgLogService {

}

办法二:通过动静代理去重写 update(Wrapper<T> updateWrapper)

其外围代码如下

@Aspect
@Component
@Slf4j
public class UpdateWapperAspect implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private  Map<String,Object> entityMap = new HashMap<>();

    @Pointcut("execution(* com.baomidou.mybatisplus.extension.service.IService.update(com.baomidou.mybatisplus.core.conditions.Wrapper))")
    public void pointcut(){}

    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint pjp){Object updateEnityResult = this.updateEntity(pjp);
        if(ObjectUtils.isEmpty(updateEnityResult)){
            try {return pjp.proceed();
            } catch (Throwable throwable) {throwable.printStackTrace();
            }
        }
        return updateEnityResult;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}

    /**
     * 重写 update(Wrapper<T> updateWrapper), 更新时主动填充不失效问题
     * @param pjp
     * @return
     */
    private Object updateEntity(ProceedingJoinPoint pjp){Object[] args = pjp.getArgs();
        if(args != null && args.length == 1){Object arg = args[0];
            if(arg instanceof Wrapper){Wrapper updateWrapper = (Wrapper)arg;
                Object entity = updateWrapper.getEntity();
                IService service = (IService) applicationContext.getBean(pjp.getTarget().getClass());
                if(ObjectUtils.isEmpty(entity)){entity = entityMap.get(pjp.getTarget().getClass().getName());
                    if(ObjectUtils.isEmpty(entity)){Class entityClz = ReflectionKit.getSuperClassGenericType(pjp.getTarget().getClass(), 1);
                        try {entity = entityClz.newInstance();
                        } catch (InstantiationException e) {log.warn("Entity instantiating exception!");
                        } catch (IllegalAccessException e) {log.warn("Entity illegal access exception!");
                        }
                        entityMap.put(pjp.getTarget().getClass().getName(),entity);
                    }

                }
                return service.update(entity,updateWrapper);
            }
        }

        return null;

    }
}

总结

文章结尾始终在指明 mybatis-plus 版本,是因为我跟过 mybatis-plus3.1 版本、3.3 版本、3.4 版本的主动填充的调用源码,其源码的实现各有不同,因为我 github 上的 mybatis-plus 援用的版本是 3.1.2 版本,因而就以 3.1.2 版本进行剖析。不过其余版本的剖析思路大同小异,都是去跟踪什么中央调用了主动填充的逻辑。

至于解决方案的几种思路,说下我的集体倡议,如果我的项目初期的话,做好宣导,倡议应用计划一,间接应用 update(new MsgLog(),lambdaUpdateWrapper)这种写法。如果我的项目开发到肯定水平了,发现很多中央都存在更新主动填充生效,则举荐应用间接底层重写 update 的计划

demo 链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-mybatisplus-tenant

正文完
 0