使用tk.mybatis.mapper偶发OGNL属性不存在异常求助
根据你描述的场景——使用tk.mybatis.mapper 3.3.0的updateByExampleSelective偶发抛出NoSuchPropertyException,结合异常堆栈和你的代码,我整理了几个实际生产中遇到过的排查方向:
1. 先核对对象类型与属性名的拼写差异
仔细看异常信息:
org.apache.ibatis.ognl.NoSuchPropertyException: com.xxx.xxx.batch.model.pojo.XXXXXXXInfo.masFlag
你提到实体类是EntiyObject且有magFlag属性,但异常里找的是XXXXXXXInfo类的masFlag(注意mag和mas的拼写差异)!这很可能是偶发场景下,你传入的entiyObject被错误替换成了XXXXXXXInfo对象——比如线程池里的对象复用、请求参数串线,导致Mapper拿到了错误的实体类,自然找不到对应的属性。
建议在调用updateByExampleSelective前加一行日志,打印entiyObject.getClass().getName()和属性值,确认偶发错误时的对象类型是否正确。
2. 升级tk.mybatis版本解决并发bug
tk.mybatis 3.3.0是2017年的老版本,早期的动态SQL生成逻辑存在线程安全问题,尤其是在多线程批量执行场景下(你的日志显示是线程池线程pool-9-thread-5)。OGNL的属性映射缓存在并发时可能出现混乱,导致偶尔找不到属性。
建议直接升级到tk.mybatis的最新稳定版(比如3.5.4+),官方在后续版本中修复了大量并发场景下的动态SQL生成问题,这是最直接的解决方案。
3. 排查类加载器不一致问题
在Spring多线程或模块化环境中,偶尔会出现类加载器不一致的情况:比如EntiyObject被不同的类加载器加载,导致OGNL认为是两个不同的类,从而找不到属性。
可以在异常捕获时添加日志,打印:
System.out.println("实体类加载器:" + entiyObject.getClass().getClassLoader()); System.out.println("异常中类加载器:" + com.xxx.xxx.batch.model.pojo.XXXXXXXInfo.class.getClassLoader());
如果两个类加载器不一致,需要排查Spring的类加载配置,比如是否使用了自定义类加载器,或者线程池的上下文类加载器是否被修改。
4. 临时禁用OGNL缓存验证问题
MyBatis的OGNL缓存可能在并发场景下缓存了错误的属性信息,可以临时禁用OGNL缓存来验证:
在MyBatis的mybatis-config.xml中添加:
<settings> <!-- 禁用OGNL缓存 --> <setting name="ognlCacheEnabled" value="false"/> </settings>
如果禁用后问题消失,就可以确认是OGNL缓存的并发问题,升级tk.mybatis版本即可彻底解决。
5. 处理Spring代理导致的类信息丢失
你的Service被Spring CGLIB代理(日志中有EnhancerBySpringCGLIB),如果实体类在传递过程中被意外代理,可能导致OGNL无法识别属性。可以在调用Mapper前,将代理对象转为原始对象:
// 工具方法:获取Spring代理的原始对象 private static <T> T getOriginalObject(Object proxy) throws Exception { if (!AopUtils.isAopProxy(proxy)) { return (T) proxy; } if (AopUtils.isJdkDynamicProxy(proxy)) { Field hField = proxy.getClass().getSuperclass().getDeclaredField("h"); hField.setAccessible(true); AopProxy aopProxy = (AopProxy) hField.get(proxy); Field advisedField = aopProxy.getClass().getDeclaredField("advised"); advisedField.setAccessible(true); AdvisedSupport advised = (AdvisedSupport) advisedField.get(aopProxy); return (T) advised.getTargetSource().getTarget(); } else { // CGLIB代理 Field field = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); field.setAccessible(true); Object dynamicAdvisedInterceptor = field.get(proxy); Field advisedField = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); advisedField.setAccessible(true); AdvisedSupport advised = (AdvisedSupport) advisedField.get(dynamicAdvisedInterceptor); return (T) advised.getTargetSource().getTarget(); } } // 调用时 EntiyObject realEntity = getOriginalObject(entiyObject); entiyObjectMapper.updateByExampleSelective(realEntity, example);
内容的提问来源于stack exchange,提问作者Devean

