如何解決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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM