zoukankan      html  css  js  c++  java
  • 如何解决mybatis-plus调用update方法时,自动填充字段不生效问题

    前言

    使用过mybatis-plus的朋友可能会知道,通过实现元对象处理器接口com.baomidou.mybatisplus.core.handlers.MetaObjectHandler可以实现字段填充功能。但如果在更新实体,使用boolean update(Wrapper updateWrapper)这个方法进行更新时,则自动填充会失效。今天就来聊聊这个话题,本文例子使用的mybatis-plus版本为3.1.2版本

    为何使用boolean update(Wrapper 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 updateWrapper)这个代码的底层实现

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

    通过代码我们可以知道,当使用这个方法时,其实体对象是null,导致调用自动填充方法时,得到的tableInfo是null,因而无法进入自动填充实现逻辑,因此导致填充自动失效

    如何解决update(Wrapper updateWrapper),自动填充不生效问题

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

    方案一:实体更新时,直接使用update(Wrapper updateWrapper)的重载方法boolean update(T entity, Wrapper updateWrapper)

    示例:

    msgLogService.update(new MsgLog(),lambdaUpdateWrapper)
    

    方案二:重写update(Wrapper 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 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

  • 相关阅读:
    Silverlight 4版本升级到4.0.60531.0
    Silverlight实例教程 理解Navigation导航框架Page类
    微软官方在线培训课程汇总2011版
    分享Silverlight/WPF/Windows Phone一周学习导读(07月11日07月17日)
    Linux内核简介
    Brief Summary of IaaS, PaaS, SaaS
    On Amazon EC2's Underlying Architecture
    Linux进程管理(2)
    一个诡异的时间问题追查[转]
    查看一个进程打开了哪些文件的命令
  • 原文地址:https://www.cnblogs.com/linyb-geek/p/14348702.html
Copyright © 2011-2022 走看看