文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

盘点 Mybatis 使用过程中遇到的坑!

2024-11-29 19:14

关注

下面我来给大家还原一下案发现场,并介绍一下自己的解决思路,希望能对大家有所启发。

02、案发现场

当时的业务逻辑主要通过 Mybatis 框架来修改数据,具体示例如下:

import org.springframework.data.repository.query.Param;

public interface GroupMapper {
 
 int updateGroup(@Param("oldSerial") Set oldSerial, @Param("newSerial") int newSerial);

 int invalidGroup(@Param("set") Set set);
}

    update groupCode_table_use
    set groupCode=#{newSerial}
    where groupCode in
    
        #{item}
    



    update groupCode_table
    set status='无效'
    where groupCode in
    
        #{item}
    

本以为 so easy 的代码,出现意外了!第一个sql语句updateGroup正常运行,第二个sql语句invalidGroup竟然报错了???

报错信息如下:

图片

从日志上可以看出,提示set参数找不到!

明明使用了@Param("set")将参数命名为set为何找不到,完全不符合多年开发的认知。

更加诡异的是updateGroup已使用了同样的方式去遍历,完全没得问题,那么问题出现在了哪了?

小伙伴可以先猜一猜!

03、原因分析

百思不得其解下,我掏出了祖传绝活 debug 源码,最终发现原来是Mybatis对集合Set进行了特殊处理。

案发项目引入的Mybatis版本是 3.5.1。部分源码如下!

org.apache.ibatis.session.defaults.DefaultSqlSession.java

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    //调用了wrapCollection方法对参数进行了处理
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}


private Object wrapCollection(final Object object) {
  if (object instanceof Collection) {
    StrictMap map = new StrictMap<>();
    map.put("collection", object);
    if (object instanceof List) {
      map.put("list", object);
    }
    return map;
  } else if (object != null && object.getClass().isArray()) {
    StrictMap map = new StrictMap<>();
    map.put("array", object);
    return map;
  }
  return object;
}

通过以上的源码分析,发现Mybatis框架对集合参数进行了特殊处理。

这就是为什么报错信息中提示Available parameters are [collection]。

找到了collection错误信息从哪里来问题,接下来我们分析一下set参数到了哪里去。

首先updateGroup可以正常执行是因为源码中对集合的特殊处理只对单参数生效,也就是说@Param("set")注解失效是因为被Mybatis自家的特殊处理给覆盖了?

这不合乎常理啊,那么问题可能出在@Param注解身上,通过排查代码发现GroupMapper.java类引用的@Param注解不对!

// 代码引入的注解类Param
import org.springframework.data.repository.query.Param;
// 期望的注解类,应该由mybatis提供
import org.apache.ibatis.annotations.Param;

实际上,springframework包下的@Param注解执行时机在wrapCollection处理之前,wrapCollection对集合的特殊处理将springframework包下的@Param注解处理覆盖掉了,所以无法解析参数set。而mybatis包下的@Param注解执行时机在wrapCollection处理之后,程序可以正常运行。

最终确定,绕了这么大一圈,原来是包导入错了,谁能想到springframework包下还有一个@Param注解,在多参数的情况下竟然可以正常使用o(╥﹏╥)o。

04、异常栈分析法

整个异常排查过程中,主要通过分析异常栈信息进行快速定位。下面我给大家介绍一下异常栈分析法。

我们在工作中查看异常栈,往往都是业务代码异常导致的,这个时候我们只需要关心是在我们编写的哪段代码中出了问题。

而这时好用的idea也会为温馨的为我们做出提示,会在异常栈中将我们编写的方法位置信息标蓝处理,这样我们就定快速定位出现问题的代码。

图片

但是一旦是底层引用的jar包出现了异常,仅仅是这样查看异常栈是不够的。下面我们就以上面案例中的异常栈来带大家分析。

当前异常:

图片

原始异常:

图片

分析流程:

  • 异常调用栈顺序:从上至下为方法调用顺序的逆序,异常由异常栈的最上面的方法抛出,整个调用的入口在最下方。
  • 寻找原始异常入口技巧:检索异常栈中与原始异常入口(最下面)抛出位置同名的方法。
  • 寻找真正异常出现的位置,要找最后一个 Caused by 的第一行栈帧。


"Caused by" 是 Java 异常处理机制中的一部分,它表示当前异常是由另一个异常引起的。在 Java 中,每个 Throwable 对象都可以通过 getCause() 方法获取到原始异常,这个原始异常就是通过 "Caused by" 打印出来的。

异常栈示例代码:

假设你在catch块中捕获了一个异常,并重新抛出了一个新的异常,同时保留了原始异常的信息:

public class Example {
    public static void main(String[] args) {
        try {
            methodThatThrowsException();
        } catch (Exception e) {
            throw new RuntimeException("Caught an exception", e);
        }
    }

    public static void methodThatThrowsException() throws Exception {
        throw new Exception("Original exception");
    }
}

当你运行上述代码时,控制台输出的异常栈信息通常会包含Caused by信息:

java.lang.RuntimeException: Caught an exception
    at Example.main(Example.java:7)
Caused by: java.lang.Exception: Original exception
    at Example.methodThatThrowsException(Example.java:12)
    at Example.main(Example.java:5)
  • java.lang.Exception: Original exception 是原始异常。
  • java.lang.RuntimeException: Caught an exception 是新抛出的异常,并且包含了原始异常作为其原因。
  • Caused by 信息显示了原始异常的详细信息,这有助于调试和理解异常的来源。

如何保留原始异常信息呢?

  • 方式一:直接传递原始异常:
try {
     methodThatThrowsException();
 } catch (Exception e) {
     throw new RuntimeException("Caught an exception", e);
 }
  • 方式二:使用 addSuppressed 方法:
try {
     methodThatThrowsException();
 } catch (Exception e) {
     RuntimeException newException = new RuntimeException("Caught an exception");
     newException.addSuppressed(e);
     throw newException;
 }

05、结语

学会查看分析异常栈,可以为我们工作大大提高效率,希望这篇文章给大家带来收获,最后再送给大家一个小技巧。异常栈不仅仅可以用来排查异常哦,还可以帮助大家学习源码,debug 源码找不到入口怎么办,那就创造一个异常!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
咦!没有更多了?去看看其它编程学习网 内容吧