原创获取MybatisPlus注入的mapper的真实类型

Posted DCTANT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原创获取MybatisPlus注入的mapper的真实类型相关的知识,希望对你有一定的参考价值。

前言

在之前的博客中我提到了@Autowired出来的mapper是AOP注入出来的代理类,如果直接使用其getClass获取到的是代理类型,而不是mapper的真实类型,这就会导致批量编辑报错,报:

Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.sun.proxy.$Proxy129.updateById

如我上篇博客所示:

【报错记录】MybatisPlus报Mapped Statements collection does not contain value for..._DCTANT的博客-CSDN博客

但是这篇博客做的还是不够优雅,不够优雅的地方在于我明明已经Autowired过mapper示例了,传入的值却是一个Class,感觉非常糟糕!而且文中说“且无解”!这是完全不对滴!

获取mapper的真实类型

获取真实类型并没有想象中的那么难,看了很多博客写得异常复杂,其实解决方法相当简单:

Class<?>[] classes = AopProxyUtils.proxiedUserInterfaces(mapper);
if (classes.length > 0) 
    mapperClass = (Class<R>) classes[0];

不好意思,核心代码就4行,真的是4行,其中AopProxyUtils是org.springframework.aop.framework里的,也就是Spring框架自带的类。

我试过了里面的ultimateTargetClass方法,并没有作用,反而是completeProxiedInterfaces获取第0个类就是我们要的结果。

看一下completeProxiedInterfaces的源码:

    public static Class<?>[] proxiedUserInterfaces(Object proxy) 
        Class<?>[] proxyInterfaces = proxy.getClass().getInterfaces();
        int nonUserIfcCount = 0;
        if (proxy instanceof SpringProxy) 
            ++nonUserIfcCount;
        

        if (proxy instanceof Advised) 
            ++nonUserIfcCount;
        

        if (proxy instanceof DecoratingProxy) 
            ++nonUserIfcCount;
        

        Class<?>[] userInterfaces = (Class[])Arrays.copyOf(proxyInterfaces, proxyInterfaces.length - nonUserIfcCount);
        Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces");
        return userInterfaces;
    

其实就是通过这个类获取其实现的接口列表,然后去除几个包装的Proxy接口,最后封装成一个数组返回出来,就这么简单。按照上面的getClass().getInterfaces(),能够解决获取很多Spring AOP注入的类的原本类型。

优化一下获取方法

    public static <T extends BaseEntity, R extends BaseMapper<T>> Class<R> getNoProxyMapperClass(R mapper) 
        Class<R> mapperClass = null;
        boolean cglibProxy = AopUtils.isCglibProxy(mapper);
        boolean aopProxy = AopUtils.isAopProxy(mapper);
        boolean jdkDynamicProxy = AopUtils.isJdkDynamicProxy(mapper);

        if (cglibProxy || aopProxy || jdkDynamicProxy) 
            Class<?>[] classes = AopProxyUtils.proxiedUserInterfaces(mapper);
            if (classes.length > 0) 
                mapperClass = (Class<R>) classes[0];
             else 
                mapperClass = (Class<R>) AopUtils.getTargetClass(mapper);
            
         else 
            mapperClass = (Class<R>) mapper.getClass();
        
        return mapperClass;
    

其中AopUtils也是Spring框架自己的方法,用于判断这个实例是否是代理出来的,如果是代理出来的,则使用AopProxyUtils.proxiedUserInterfaces()方法获取其真实类。如果非代理类,则直接getClass()即可。

最后放出优化后的SqlUtil中的saveBatch方法

关联博客:

【原创】辟谣,实测MyBatisPlus批量新增/更新方法确实有效,且可单独使用无需跟随IService_DCTANT的博客-CSDN博客_mybatisplus批量新增

    public static <T extends BaseEntity, R extends BaseMapper<T>> void saveBatch(R mapper, List<T> entityList, Long userId, int batchSize, boolean keepInsertId) 
        if (entityList.size() == 0) 
            return;
        
        T t = entityList.get(0);
        Class<T> entityClass = (Class<T>) t.getClass();
        Class<R> mapperClass = getNoProxyMapperClass(mapper);
        SqlHelper.saveOrUpdateBatch(entityClass, mapperClass, log, entityList, batchSize, (sqlSession, entity) -> 
            // INFO: DCTANT: 2021/12/27 insert判断,返回true则是走insert代码,返回false则会走后面的update代码
            if (entity == null) 
                return false;
            
            Long id = entity.getId();
            if (id == null) 
                // INFO: DCTANT: 2021/12/27 insert前加一些自己必要的业务逻辑,如setCreateTime、setDel、setVersion等等
                insertNecessaryField(entity, userId, keepInsertId);
                return true;
             else 
                // INFO: DCTANT: 2021/12/27 去执行update的代码
                return false;
            
        , (sqlSession, entity) -> 
            // INFO: DCTANT: 2021/12/27 判断为update,然后执行必要操作 
            if (entity == null) 
                return;
            
            // INFO: DCTANT: 2021/12/27 update前加一些自己的业务逻辑,如setUpdateTime等等
            updateNecessaryField(entity, userId);
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            // INFO: DCTANT: 2022/8/22 参数需要为Constants.ENTITY,也就是里面的et,否则会报et这个参数无法找到
            param.put(Constants.ENTITY, entity);
            String sqlStatement = SqlHelper.getSqlStatement(mapperClass, SqlMethod.UPDATE_BY_ID);
            sqlSession.update(sqlStatement, param);
        );
    

其中:

SqlHelper是MybatisPlus自带的方法。

insertNecessaryField、updateNecessaryField是给entity赋一些默认值的方法,比如设置创建时间、更新时间、删除标志的方法,不加也无所谓。

主要解决了第一个入参为Class的问题。

以上是关于原创获取MybatisPlus注入的mapper的真实类型的主要内容,如果未能解决你的问题,请参考以下文章

定义了Mapper接口,但是没有写任何SQL,MybatisPlus是如何知道该查询哪张表呢?

mybatisplus是啥

MyBatisPlus

MybatisPlus之-----BaseMapper

MybatisPlus的BaseMapper和Wrapper使用

MybatisPlus的BaseMapper和Wrapper使用