1、目标
本文的主要目标是学习MyBatis拦截器的源码,本文将以插入操作为例debug拦截器相关的源码
2、拦截器源码分析
调用mapper接口的insert插入记录方法,会调用SqlSession对象的insert方法
SqlSession执行insert方法
Spring容器会创建SqlSessionTemplate对象,为了实现插入操作,可以分为三步:
(1)创建Executor执行器对象和SqlSession对象
(2)反射调用update方法完成插入操作
(3)SqlSession执行commit方法提交事务
3、创建Executor执行器对象和SqlSession对象
它会分别创建Executor执行器对象和SqlSession对象
3.1 newExecutor方法
执行器链会调用pluginAll方法将拦截器创建JDK动态代理对象
遍历所有的拦截器,每个拦截器都执行plugin方法
plugin方法会调用Plugin的wrap这个静态方法,此时由于没有拦截Executor执行器的拦截器,因此不会创建JDK动态代理对象
4、反射调用update方法完成插入操作
SqlSession对象会反射调用insert方法,其中参数是mapper接口方法的入参,这个方法会调用update方法
它会调用BaseExecutor执行器的update方法进行插入操作,它会调用doUpdate方法
最终会调用SimpleExecutor执行器的doUpdate方法,它会执行插入操作,具体是:
(1)newStatementHandler方法创建RoutingStatementHandler对象
(2)prepareStatement方法创建Stetement对象
(3)StetementHandler对象的update方法
4.1 newStatementHandler方法创建RoutingStatementHandler对象
先创建RoutingStatementHandler对象,然后调用拦截器链的pluginAll方法生成JDK动态代理对象
由于存在StatementHandler对象的拦截器,因此会调用Proxy.newProxyInstance方法创建RoutingStatementHandler对象的JDK动态代理对象
返回的StatementHandler对象是基于StatementHandler接口的JDK动态代理对象,其中三个拦截器对象是层层嵌套的,形成一个拦截器链
4.2 prepareStatement方法创建Stetement对象
prepareStatement方法会先调用StatementHandler对象的prepare方法创建Statement对象,然后调用StatementHandler对象的parameterize方法给参数赋值
4.2.1 prepare方法
由于这个拦截器是拦截StatementHandler对象的prepare方法,因此先走到这个拦截器,然后调用invocation.proceed方法会执行StatementHandler对象的prepare方法
BaseStatementHandler对象的prepare方法会实例化Statement对象
实例化Statement对象的方法会根据BoundSql对象中的sql属性得到sql语句并实例化Statement对象,因此最好在StatementHandler对象的prepare方法之前修改sql语句
4.2.2 parameterize方法
由于这个拦截器拦截的是StatementHandler对象的parameterize方法,因此会先走到这个拦截器中,然后执行invocation.proceed方法会调用StatementHandler对象的parameterize方法
它会调用ParameterHandler对象的setParameters方法完成参数赋值的功能
DefaultParameterHanlder对象会先根据BoundSql对象的parameterMappings这个List集合的大小n,然后设置PreparedStatement对象的参数值对应的下标是从1开始到n
执行PreparedStatement对象的setString方法会设置下标是i,参数值是parameter,用setString方法可以在字符串参数值加上单引号,防止sql注入
因此,在拦截器中会调用PreparedStatement对象的setString等方法
4.2.3 结果
最终将下标和对应的参数值保存在PreparedStatementLogger对象的columnMap中
4.3 StetementHandler对象的update方法
调用StatementHandler对象的update方法,入参是Statement对象
由于这个拦截器拦截的是StatementHandler对象的update方法,因此会先走这个拦截器,然后执行invocation.proceed方法会调用StatementHandler对象的update方法
4.3.1 update方法
PreparedStatementHandler对象的update方法会调用PreparedStatement对象的execute方法执行JDBC的插入操作,并返回更新数据库的行数rows
5、SqlSession执行commit方法提交事务
SqlSession对象的commit方法会清除一级缓存,因此默认情况下SpringBoot整合MyBatis每次调用sql语句不会保存到一级缓存中