MyBatis 还有一个很有意思的点在于异常日志的输出。不知道大家有没有发现,使用 MyBatis 时定位问题非常容易,我们只需要查看一下控制台的异常日志就能一目了然地知道问题出现在了哪里。这归功于一个简单的类 ErrorContext。我们首先来一点一点认识一个这个类。
六个存储异常信息的私有成员及其 “set” 方法
private ErrorContext stored;
private String resource;
private String activity;
private String object;
private String message;
private String sql;
private Throwable cause;
MyBatis 异常涵盖的信息总结为一点就是:异常是由谁在做什么的时候在哪个资源文件中发生的,执行的 SQL 是哪个,以及 java 详细的异常信息。这六个私有变量分别存储这些信息:
1、resource:存储异常存在于哪个资源文件中。
如:### The error may exist in mapper/AuthorMapper.xml
2、activity:存储异常是做什么操作时发生的。
如:### The error occurred while setting parameters
3、object:存储哪个对象操作时发生异常。
如:### The error may involve defaultParameterMap
4、message:存储异常的概览信息。
如:### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘id2’ in ‘field list’
5、sql:存储发生日常的 SQL 语句。
如:### SQL: select id2, name, sex, phone from author where name = ?
6、cause:存储详细的 Java 异常日志。
如:### Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘id2’ in ‘field list’ at
org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) at
org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150) at
org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141) at
org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:139) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:76)
对于这六个成员变量的 “set” 方法,命名同相应成员变量,均是对成员变量做赋值操作并返回存储完相应信息后当前 ErrorContext 的实例。例如 resource 变量对应的 resource() 方法:
public ErrorContext resource(String resource) {
this.resource = resource;
return this;
}
使用 ThreadLocal 管理 ErrorContext
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
我们知道,ThreadLocal 是本地线程存储,它的作用是为变量在每个线程中创建一个副本,每个线程内部都可以使用该副本,线程之间互不影响。
ErrorContext 可以看作是线程内部的单例模式:
1、使用 ThreadLocal 来管理 ErrorContext:
保证了在多线程环境中,每个线程内部可以共用一份 ErrorContext,但多个线程持有的 ErrorContext 互不影响,保证了异常日志的正确输出。
提供私有的构造方法:
private ErrorContext() {
}
提供获取线程内实例的静态公有的接口:
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
在同一线程中,ErrorContext 通过该接口提供给外界一个获取其唯一实例的方式。在调用 instance() 时,首先从 LOCAL 中获取,如果获取到了 ErrorContext 实例,则直接返回该实例;若未获取到,则会调用其构造方法创建一个,并将其存入 LOCAL。
store()、 recall() 、reset()、toString() 方法
stored 变量充当一个中介,在调用 store() 方法时将当前 ErrorContext 保存下来,在调用 recall() 方法时将该 ErrorContext 实例传递给 LOCAL。
public ErrorContext store()