一、StatementHandler
执行器Executer与缓存 上篇讲到一级缓存在BaseExecuter的queryFromDatabase()方法,当没有命中缓存时,从数据库中查询 doQuery(ms, parameter, rowBounds, resultHandler, boundSql);这个方法在SimpleExecuter中实现 configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);这里就是创建StatementHandler了,跟进去看 这个方法第一行创建了一个RoutingStatementHandler。
1.1 RoutingStatementHandler
RoutingStatementHandler持有一个StatementHandler,这又是一个装饰器模式,它的作用只是为了判断具体使用哪个StatementHandler,默认是PreparedStatementHandler。个人感觉使用RoutingStatementHandler包装一次没啥意义。
1.2 PreparedStatementHandler
回到doQuery()
- stmt = prepareStatement(handler, ms.getStatementLog());得到的是一个增加了日志功能的代理PrepareStatement。
- closeStatement(stmt);在finally中关闭statement,这块和JDBC的写法一样的,只是每一步都有扩展
进入prepareStatement(handler, ms.getStatementLog());
- Connection connection = getConnection(statementLog);获取一个数据库连接,它最终是从数据源的连接池中产生,关闭也由连接池控制。
二、拦截器
2.1 InterceptorChain
回到doQuery()->configuration.newStatementHandler()方法的第二行 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 这块使用了责任链(所有链都执行,不指定next)+动态代理的模式,把真实的statementHandler 使用拦截器链拦截,然后返回一个statementHandler 的代理类。 这里我添加了一个分页拦截器也叫分页插件,调用configuration的addInterceptor()方法就可以添加。 进入interceptor.plugin(target); PaginationInterceptor是分页插件,只拦截StatementHandler,因为interceptors里面可以有很多插件,拦截不同的扩展点(Mybatis暴露了4个扩展点)。 进入Plugin.wrap(target, this); 将目标对象(StatementHandler)和插件(PaginationInterceptor)包装在Plugin(实现InvocationHandler)里面,再返回一个StatementHandler对象的代理。在Plugin中有invoke方法,当执行StatementHandler的方法就会进入invoke中执行,里面会调用PaginationInterceptor的interceptor()方法。 再看第一次循环完之后 RoutingStatementHandler被代理了,同时和插件绑定在了一起。代理对象target实现了和RoutingStatementHandler一样的接口,如果interceptors还有值,它将再次被代理,和其他插件对象绑定。最后得到的还是一个RoutingStatementHandler的代理对象。
2.2 Intercepts注解
再回到Plugin.wrap(target, this),进入getSignatureMap(interceptor); 这块是要记录拦截器具体拦截哪些方法,这就需要在拦截器中加上Intercepts注解 看注解信息,分页拦截器就是拦截的StatementHandler的prepare方法。 再回到prepareStatement(handler, ms.getStatementLog()); 执行RoutingStatementHandler代理类的prepare就进入到了Plugin中的invoke 先比对是否是被拦截的方法,如果是就执行拦截器的intercept方法
三、PaginationInterceptor
invocation记录的是被代理的方法,等拦截逻辑执行完还要继续执行原来的方法 给查询方法加上page参数,就会执行到DialectModel model = DialectFactory.buildPaginationSql(page, buildSql, dbType, dialectClazz); 跟进去 getDialect(dbType, dialectClazz).buildPaginationSql(buildSql, page.offset(), page.getSize());先得到数据库(MYSQL、ORACLE、DB2…),再进入buildPaginationSql组装分页语句 然后将带分页的语句设置到BoundSql 最后执行invocation.proceed();在里面执行被代理类的方法。
|