这里要讲的巧妙用法是用来实现在拦截器中执行额外 MyBatis 现有方法的用法。

并且会提供一个解决拦截​​Executor​​​时想要修改​​MappedStatement​​时解决并发的问题。

这里假设一个场景:

实现一个拦截器,记录 MyBatis 所有的 insert,update,delete 操作,将记录的信息存入数据库。

这个用法在这里就是将记录的信息存入数据库

实现过程的关键步骤和代码:

1.首先在某个 Mapper.xml 中定义好了一个往日志表中插入记录的方法,假设方法为​​id="insertSqlLog"​​。

2.日志表相关的实体类为​​SqlLog​​.

3.拦截器签名:

@Intercepts({@org.apache.ibatis.plugin.Signature(
type=Executor.class,
method="update",
args={MappedStatement.class, Object.class})})
public class SqlInterceptor implements Interceptor

4.接口方法简单实现:

public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
SqlLog log = new SqlLog();
Configuration configuration = ms.getConfiguration();
Object target = invocation.getTarget();
StatementHandler handler = configuration.newStatementHandler((Executor) target, ms,
parameter, RowBounds.DEFAULT, null, null);
BoundSql boundSql = handler.getBoundSql();
//记录SQL
log.setSqlclause(boundSql.getSql());
//执行真正的方法
Object result = invocation.proceed();
//记录影响行数
log.setResult(Integer.valueOf(Integer.parseInt(result.toString())));
//记录时间
log.setWhencreated(new Date());
//TODO 还可以记录参数,或者单表id操作时,记录数据操作前的状态
//获取insertSqlLog方法
ms = ms.getConfiguration().getMappedStatement("insertSqlLog");
//替换当前的参数为新的ms
args[0] = ms;
//insertSqlLog 方法的参数为 log
args[1] = log;
//执行insertSqlLog方法
invocation.proceed();
//返回真正方法执行的结果
return

重点
​​​MappedStatement​​​是一个共享的缓存对象,这个对象是存在并发问题的,所以几乎任何情况下都不能去修改这个对象(通用Mapper除外),想要对​​MappedStatement​​做修改该怎么办呢?

并不难,​​Executor​​​中的拦截器方法参数中都有​​MappedStatement ms​​​,这个​​ms​​​就是后续方法执行要真正用到的​​MappedStatement​​​,这样一来,问题就容易解决了,根据自己的需要,深层复制​​MappedStatement​​​对象中自己需要修改的属性,然后修改这部分属性,之后将修改后的​​ms​​​通过上面代码中​​args[0]=ms​​​这种方式替换原有的参数,这样就能实现对​​ms​​的修改而且不会有并发问题了。

这里日志的例子就是一个更简单的应用,并没有创建​​ms​​​,只是获取了一个新的​​ms​​​替换现有的​​ms​​,然后去执行。