preface

Used mybatis – plus friends may know, by implementing the meta object processor interface. Com baomidou. Mybatisplus. Core. Handlers. MetaObjectHandler fields can be filled function. However, if you update an entity using the Boolean update(Wrapper updateWrapper) method, the automatic fill will fail. For today’s talk, mybatis- Plus is version 3.1.2 for this example

Why Boolean update(Wrapper updateWrapper) automatically fills the Wrapper?

From mybatis-plus version 3.1.2 trace source, it can be known that the automatic filling call code implementation logic is realized by the following core code block

 /** * Custom meta-object fills controller **@paramMetaObjectHandler metadata populates the processor *@paramTableInfo Database table reflection information *@param ms                MappedStatement
     * @paramParameterObject Inserts a database object *@return Object
     */
    protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
                                         MappedStatement ms, Object parameterObject, boolean isInsert) {
        if (null == tableInfo) {
            /* does not process */
            return parameterObject;
        }
        /* Custom meta-object to populate the controller */
        MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
        // Populates the primary key
        if(isInsert && ! StringUtils.isEmpty(tableInfo.getKeyProperty()) &&null! = tableInfo.getIdType() && tableInfo.getIdType().getKey() >=3) {
            Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
            /* Custom 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()) {
                // Insert padding
                metaObjectHandler.insertFill(metaObject);
            } else if(! isInsert) {// Update the populationmetaObjectHandler.updateFill(metaObject); }}return metaObject.getOriginalObject();
    }
Copy the code

From the source analysis we can know that when tableInfo is null, there is no automatic fill logic. And where does tableInfo get its value from, and if we continue to trace the source code, we know that tableInfo can be retrieved from the code below

 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 {
                        /* * Non-table mapping classes do not handle */objList.add(parameter); }}return objList;
            } else {
                TableInfo tableInfo = null;
                if (parameterObject instanceofMap) { Map<? ,? > map = (Map<? ,? >) parameterObject;if (map.containsKey(Constants.ENTITY)) {
                        Object et = map.get(Constants.ENTITY);
                        if(et ! =null) {
                            if (et instanceofMap) { 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());
                }
Copy the code

Parameterobject.getclass () is used to retrieve tableInfo. ParameterObject is a database insert or update object. So our entity object, when the entity object is null, then the value of tableInfo is also null, which will cause autofill to fail.

Let’s look at the underlying implementation of Boolean update(Wrapper updateWrapper)

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

Copy the code

As we can see from the code, when this method is used, its entity object is NULL, resulting in the automatic fill method is called, the tableInfo is null, so it cannot enter the automatic fill implementation logic, so the automatic fill is invalid

How to solve the problem that automatic filling of Update (Wrapper updateWrapper) does not take effect

We know that as long as tableInfo is not empty, it will enter the automatic fill logic, and tableInfo is not empty premise is the update or insert entity is not null object, so our idea is to call the update method, to ensure that the entity is not null

Update (T entity, Wrapper updateWrapper) Boolean Update (T entity, Wrapper updateWrapper)

Example:

msgLogService.update(new MsgLog(),lambdaUpdateWrapper)
Copy the code

Scheme 2: Rewrite the Update (Wrapper updateWrapper) method

The idea behind rewriting update is as follows

Method 1: Override the Update method of ServiceImpl

The idea is to rewrite a business base class, 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(); }}returnupdate(entity, updateWrapper); }}Copy the code

The business service inherits BaseServiceImpl as follows

@Service
public class MsgLogServiceImpl extends BaseServiceImpl<MsgLogDao.MsgLog> implements MsgLogService {}Copy the code

Update (Wrapper updateWrapper) with a dynamic proxy

The core code is as follows

@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(a){}@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;
    }

    /** * rewrite 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); }}returnservice.update(entity,updateWrapper); }}return null; }}Copy the code

conclusion

Mybatis -plus version 3.1.2 mybatis- Plus version 3.1.2 mybatis- Plus version 3.1.2 mybatis- Plus version 3.1.2 Therefore, 3.1.2 was used for analysis. The other versions of the analysis, however, are similar in that they track where the auto-fill logic is invoked.

As for several ideas of solutions, I would like to give my personal advice. In the early stage of the project, it is recommended to use solution 1, which is to write update(new MsgLog(),lambdaUpdateWrapper) directly. If you have reached a point in your project where auto-fill fails in many places, it is recommended to override the UPDATE directly

The demo link

Github.com/lyb-geek/sp…