《Mybatis源码》第20章 ErrorContext 全局异常信息

# ErrorContext 错误日志

mybatis在读取mapper文件的时候,会存在下面这行代码,那么它用到了ErrorContext类,那么该类的作用是啥?

ErrorContext.instance().resource(resource);

MyBatis 还有一个很有意思的点在于异常日志的输出。不知道大家有没有发现,使用 MyBatis 时定位问题非常容易,我们只需要查看一下控制台的异常日志就能一目了然地知道问题出现在了哪里。就像这样:

https://upload-images.jianshu.io/upload_images/14707524-2db2f1284ed9fc46.png?imageMogr2/auto-orient/strip|imageView2/2/w/1195/format/webp

1.存储异常信息的字段

MyBatis 异常涵盖的信息总结为一点就是:异常是由谁在做什么的时候在哪个资源文件中发生的,执行的 SQL 是哪个,以及 java 详细的异常信息。这六个私有变量分别存储这些信息:

public class ErrorContext {  
  // 换行符,因为不同的系统,换行符可能不同,而该类的目的主要就是展示日志,
  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

  private ErrorContext stored;
  // 异常文件
  private String resource;
  // 异常操作
  private String activity;
  // 异常对象
  private String object;
  // 异常信息
  private String message;
  // 异常sql
  private String sql;
  // 异常日志
  private Throwable cause;
  • resource:存储异常存在于哪个资源文件中。
    如:### The error may exist in mapper/AuthorMapper.xml
  • activity:存储异常是做什么操作时发生的。
    如:### The error occurred while setting parameters
  • object:存储哪个对象操作时发生异常。
    如:### The error may involve defaultParameterMap
  • message:存储异常的概览信息。
    如:### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘id2’ in ‘field list’
  • sql:存储发生日常的 SQL 语句。
    如:### SQL: select id2, name, sex, phone from author where name = ?
  • cause:存储详细的 Java 异常日志。
    如:### Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘id2’ in ‘field list’ at

对于这六个成员变量的 “set” 方法,命名同相应成员变量,均是对成员变量做赋值操作并返回存储完相应信息后当前 ErrorContext 的实例。例如:

 public ErrorContext resource(String resource) {
    this.resource = resource;
    return this;
  }

  public ErrorContext activity(String activity) {
    this.activity = activity;
    return this;
  }

  // 其余省略...

2.使用 ThreadLocal 管理 ErrorContext

private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

ThreadLocal 是本地线程存储,它的作用是为变量在每个线程中创建一个副本,每个线程内部都可以使用该副本,线程之间互不影响。使用 ThreadLocal 来管理 ErrorContext,保证了在多线程环境中,每个线程内部可以共用一份 ErrorContext,但多个线程持有的 ErrorContext 互不影响,保证了异常日志的正确输出。

ErrorContext采用了单例模式,保证了每个线程共用一份实例

  // 私有构造方法
  private ErrorContext() {
  }
  // 静态方法获取实例
  public static ErrorContext instance() {
    ErrorContext context = LOCAL.get();
    if (context == null) {
      context = new ErrorContext();
      LOCAL.set(context);
    }
    return context;
  } 

3.其它方法

stored 变量充当一个中介,在调用store()方法时将当前 ErrorContext保存下来,在调用 recall() 方法时将该 ErrorContext 实例传递给 LOCAL。

  public ErrorContext store() {
    stored = this;
    LOCAL.set(new ErrorContext());
    return LOCAL.get();
  }

  public ErrorContext recall() {
    if (stored != null) {
      LOCAL.set(stored);
      stored = null;
    }
    return LOCAL.get();
  }

reset()顾名思义就是重置方法

public ErrorContext reset() {
    resource = null;
    activity = null;
    object = null;
    message = null;
    sql = null;
    cause = null;
    LOCAL.remove();
    return this;
  }

toString()方法就是显示我们的错误信息

@Override
  public String toString() {
    StringBuilder description = new StringBuilder();

    // message
    if (this.message != null) {
      description.append(LINE_SEPARATOR);
      description.append("### ");
      description.append(this.message);
    }

    // resource
    if (resource != null) {
      description.append(LINE_SEPARATOR);
      description.append("### The error may exist in ");
      description.append(resource);
    }

    // object
    if (object != null) {
      description.append(LINE_SEPARATOR);
      description.append("### The error may involve ");
      description.append(object);
    }

    // activity
    if (activity != null) {
      description.append(LINE_SEPARATOR);
      description.append("### The error occurred while ");
      description.append(activity);
    }

    // activity
    if (sql != null) {
      description.append(LINE_SEPARATOR);
      description.append("### SQL: ");
      description.append(sql.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').trim());
    }

    // cause
    if (cause != null) {
      description.append(LINE_SEPARATOR);
      description.append("### Cause: ");
      description.append(cause.toString());
    }

    return description.toString();
  }

4.Mybatis使用

mybatis是如何使用的,其实这个就相当于是一个进度,例如上面的那行代码,就是把资源路径存入,然后在后面执行SQL的时候,会继续存入其它的信息

// 把加载的资源文件路径存入
ErrorContext.instance().resource(resource);

这里还有一个小细节,为啥这些赋值方法,要返回this当前这个对象,因为这样就更方便多个赋值,例如ErrorContext.instance().message(message).cause(e).toString(),这样赋值操作1行就可以搞定

5.ErrorContext

text.instance().resource(resource);


这里还有一个小细节,为啥这些赋值方法,要返回`this`当前这个对象,因为这样就更方便多个赋值,例如`ErrorContext.instance().message(message).cause(e).toString()`,这样赋值操作1行就可以搞定

## 5.ErrorContext

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值