在之前的博客中我提到了@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,感觉非常糟糕!而且文中说“且无解”!这是完全不对滴!
获取真实类型并没有想象中的那么难,看了很多博客写得异常复杂,其实解决方法相当简单:
- Class>[] classes = AopProxyUtils.proxiedUserInterfaces(mapper);
- if (classes.length > 0) {
- mapperClass = (Class
) 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
extends BaseEntity, R extends BaseMapper> Class getNoProxyMapperClass(R mapper) { - Class
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
) classes[0]; - } else {
- mapperClass = (Class
) AopUtils.getTargetClass(mapper); - }
- } else {
- mapperClass = (Class
) mapper.getClass(); - }
- return mapperClass;
- }
其中AopUtils也是Spring框架自己的方法,用于判断这个实例是否是代理出来的,如果是代理出来的,则使用AopProxyUtils.proxiedUserInterfaces()方法获取其真实类。如果非代理类,则直接getClass()即可。
关联博客:
【原创】辟谣,实测MyBatisPlus批量新增/更新方法确实有效,且可单独使用无需跟随IService_DCTANT的博客-CSDN博客_mybatisplus批量新增
- public static
extends BaseEntity, R extends BaseMapper> void saveBatch(R mapper, List entityList, Long userId, int batchSize, boolean keepInsertId) { - if (entityList.size() == 0) {
- return;
- }
- T t = entityList.get(0);
- Class
entityClass = (Class) t.getClass(); - Class
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
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的问题。