在web开发过程中持久化是每个web开发者都需要面对的问题,那么有的公司选择了jpa持久化,有的选择mybatis来完成对象的持久化,而今天我们就讨论一下如果你选择的框架和持久化框架分别是springmvc和mybatis的话,当你保存或者是查询的时候,你的sql源码是怎么完成这个操作的。我们以插入为列,来说明。
关于数据源和mybatis包扫描的配置我就不写了,这是最基本的,网上也到处都是。
我们都知道mybatis的实现需要dao对象,mapper接口对象和对应的mapper.xml文件来完成这波操作,我们今天要说的mybatis版本为3.3.1版本的。
首先我们在调用对象保存方法时,我们会执行mapper.insert(Dao dao)方法或者insertSelective(Dao dao)方法如下图,
这个时候我们的代码接下来去了哪儿呢;
1)因为mybatis是通过动态代理来完成具体接口绑定的,而且面向方法的,所以我们找一个MapperProxy的类,他是个拦截器,在其中的invoke方法中加断点,然后下一步就进去了,如下图:
从图中我们可以看到要执行的method的值就是我们想要执行的真实方法。
而其中:
final MapperMethod mapperMethod = cachedMapperMethod(method);//查看是否已经缓存了方法,如果没有,则将其初始化并缓存 return mapperMethod.execute(sqlSession, args);//执行mapperMethod方法
2)我们去找一个MapperMethod类并给其中的execute方法打断点,然后下一步:
我们可以看到代码进入其中的execute方法,如下图所示:
从图中我们可以看出会根据command的type值来决定是执行插入,更新,删除还是查找等,因为我们是插入,所以在截图最后,可以看出type确实是INSERT.
3)在2)的截图中我们可以看到如果是插入的话,有两行代码会执行,其中第一行明显是param对象的封装(通过MapperMethod类中的方法)最后将得到我们要入库的对象如下图:
而第二句则比较关键根据方法名,明显是返回执行行数,所以关键就是sqlSession.insert方法了;而sqlSession会通过SqlSessionTemplate拦截器来获取;我们在SqlSessionTemplate中的invoke方法中加断点,执行下一步,然后得到如下图:
从截图中可以看到,这个类来自mybatis-spring-1.2.3.jar包,然后对sqlSession做了初始化;
4)从3)图中可以知道,接下来会执行sqlsession的方法,然后它实际上走的是DefaultSqlSession的方法,于是我们在其类中对应的update两个参数的方法中加断点,得到如下图:
5)从4)图中可以看出方法也不复杂,从配置文件中获取mapper映射,然后通过执行器执行,然后执行器类型比较多,首先是CachingExecutor和BaseExecutor,因为我开启了缓存配置,所以会先进入CatchingExecutor类中,清除缓存,所以我们打断点在其update方法中,下一步得到如下图:
从图中可以看出,首先根据得到的ms来flush了缓存,然后执行了delegate的方法,那么这个delegate就是我上文说到的BaseExecutor类;
6)在BaseExecutor类的update方法中打断点,并执行下一步,得到如下图:
从图中可以看出首先清空本地缓存,然后执行了一个doUpdate方法,这个时候你会发现这个类其实是抽象类,而他执行的方法一定是子类的;
7)然后我们看他的实现有,BatchExecutor,closedExecutor(未实现),ReuseExecutor,SimpleExecutor;从configuration的参数中可以看出我们的是SimpleExecutor类,在其中的doUpdate方法中加断点,然后下一步,得到如下图
然后方法中,从ms中得到配置信息,然后根据配置信息和参数得到handler来执行prepareStatement方法将连接赋值给handler并返回对应的statement,然后执行对应的handler的方法;
8)从StatementHandler的两个实现RoutingStatementHandler和BaseStatementHandler的名字来看一定会先去RoutingStatementHandler,所以在其中的prepare和BaseStatementHandler的prepare中加断点,并下一步得到如下图:
可以看出直接调用了delegate的prepare,我们直接下一步:
可以看到还是来到了baseStatementHandler实现类中。
9)从图中可以看出执行了依据instantiateStatement(connection)这句是关键,查看当前类,发现是抽象的,该方法也是,所以一定是调用的子类的方法,所以看其实现类,如下图:
已经很明显了,我们的肯定是PreparedStatementHandler了,所以在其中对应的方法中加断点下一步然后得到如下图:
已经很明显了,到这里就是通过执行connection.prepareStatement来与数据库执行实际的交互了。
10)接下来就是一路返回了,照着倒序的形式最终回到MapperProxy,然后回到你调用方法的地方,如下图;
到这里整个流程就结束了,如果你执行的是其他方法,那么你会在MapperMethod中通过不同的if判断进不同的执行逻辑,但是整体流程是不会变的。如果想看到具体的sql组装之类的,可以具体F8然后逐个方法查看就可以了。