如何解决mybatis-plus调用update方法时,自动填充字段不生效问题

前言

使用过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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • 1 前言 本文介绍基于mybatis-plus( 版本:3.3.1 ) 的逻辑删处理办法,同时考虑到对关系数据库...
    gdzwk阅读 1,761评论 0 0
  • MybatisPlus 特性 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会...
    njitzyd阅读 585评论 0 0
  • MyBatis Plus 本文档基于 MyBatis-Plus 官方文档编写,详情请参见 MyBatis-Plus...
    青丶空阅读 2,080评论 3 17
  • Mybatis-Plus(MP)在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 本篇是根据My...
    h2coder阅读 11,443评论 4 41
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,518评论 16 22