Mybatis(五)------Mybatis执行Mapper接口的方法流程
前面几篇文章我们介绍了JDBC、Mybatis的工具类等,下面我们开始对于mybatis的各个机制开始解析。
前面我们知道,mybatis对excutor进行封装成sqlsession提供给开发人员进行数据库的增删改查,我们先从Mybatis最顶层的API入手。
SQLSession的创建过程分为3个阶段:
1)、Configuration实例的创建(配置类)
2)、SQLSessionFactory实例的创建(工厂类)
3)、SQLSession实例的创建
1、解析xml文件
mybatis的主配置文件和mapper配置文件常用的都是xml格式,Configuration组件用来描述主配置文件等信息,框架在启动时会解析xml配置文件,然后将其转换为Configuration对象。
JDK API中提供了3中方式去解析xml,分别为DOM、SAX、XPath。其中最易使用的是Xpath方式,Mybatis也是采用XPath方式去解析xml文件。
我们可以看看如何使用XPath方式解析xml文件:
假如我们有xml文件将其映射为实体对象:
使用XPath方式进行解析:
为了简化XPath解析操作,Mybatis通过XPathParser工具类封装了对xml的解析操作,同时使用XNode类增强对xml节点的操作。使用XNode对象,我们可以很方便的获取节点的属性、子节点等信息。
我们可以看看XPathParser如何进行解析:
可以看到XPathParser对xml解析,省去了Dociment对象和XPath对象的创建过程,还封装了执行XPath表达式的方法,简化了xml解析的过程。
2、Configuration实例创建
Configuration组件主要有下面三个作用:
1)、用于描述mybatis的主配置信息,例如<setting>等标签
2)、作为容器注册存储Mybatis的其他组件,例如TypeHandler、MapperStatement等
3)、提供工厂方法,创建ResultSetHandler、StatementHandler、Executor、ParameterHandler等组件实例
SqlSession实例化前,会先解析mybatis的主配置文件+所有Mapper文件,其中部分配置文件由Configuration对象存储。mybatis通过XMLConfigBuilder类完成对Configuration对象的构建。例如下面的代码:
进入parse方法:
进入parseConfiguration方法
我们·先看看主配置xml文件大概是这样的:
<configuration>
<settings>
<setting name="useGeneratedKeys" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="LOG4J"/>
</settings>
<environments default="dev" >
<environment id="dev">
<transactionManager type="JDBC">
<property name="" value="" />
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:mybatis" />
<property name="username" value="sa" />
<property name="password" value="" />
</dataSource>
</environment>
<environment id="qa">
<transactionManager type="JDBC">
<property name="" value="" />
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:mybatis_qa" />
<property name="username" value="admin" />
<property name="password" value="admin" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
<!--
<mapper resource="file:///mybatis/com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
<mapper class="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper.UserMapper"/>
<package name="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper"/>
-->
</mappers>
</configuration>
mybatis中传进来了XNode root(实际就是<configuration>标签及包含内容),然后依次处理多个子标签,例如<setting>、<environments>等。我们列出了各个标签的作用如下:
对于每个标签的解析细节,可以自行阅读源码。
3、SqlSession实例创建过程
mybatis创建Sqlsession使用工厂模式创建,在创建之前需要先创建SqlSessionFactory对象,然后调用工厂对象的openSession方法,例如:
进入build方法:
可以看到先创建一个XMLConfigBuilder对象,然后调用 XMLConfigBuilder对象的parse方法对主配置类进行解析,然后返回Configuration对象作为build方法的参数,我们再看看这个重载的build方法:
我们再看看DefaultSqlSessionFactory类对openSession方法的实现:
进入openSessionFromDataSource方法是如何返回一个SqlSession实例的:
(这也就证实了之前说的SqlSession是对Executor的封装的面向开发人员的实例,用来执行增删改查等功能)
=============================SqlSession 执行Mapper的过程====================
上面讲了如何解析xml配置文件、怎么创SqlSession实例(可以看成sql的执行器),下面我们开始看看SqlSession如何去执行我们定义的Mapper.
一、获取Mapper接口的代理对象
在我们日常开发中,Mapper一般分为两部分,Mapper接口+XML/注解,我们先看看如何去执行Mappper中定义的方法:
如上面所示,创建SqlSession实例后,需要调用getMappper获取Mappper对象(接口的代理对象),然后再去调用相应方法。
而这个Mapper接口的代理在mybatis中是由MapperProxy代理工具类去实现的,MapperProxy工具类的关键代码如下:
在java中常用的动态代理一般有两种方式,jdk动态代理和cglib动态代理。MapperProxy使用了JDK动态代理,实现了InvocationHandler接口,在invoke方法中为统一的拦截逻辑,然后需要调用java.lang.reflect.Proxy类的newProxyInstance方法创建代理对象。创建代理对象的过程mybatis使用MapperProxyFactory进行封装,具体代码:
注意:拦截的代理逻辑是在MapperProxy中的invoke方法定义的,而创建代理对是使用MapperProxyFactory工厂返回代理对象MapperProxy的,MapperProxyFactory中的newInstance方法会去实例化一个MapperProxy进行返回。
可以看到,最后是由MapperProxyFactory进行创建了代理对象,这里需要注意的是newInstance方法并不是静态方法,也就是说我们要调用这个方法需要先实例化MapperProxyFactory对象,那么这个工厂对象是什么时候被创建的?
我们之前在说的Configuration配置对象时,里面就有个mapperRegister属性,具体如下:
而mapperRegister是用来注册Mapper接口和MapperProxyFactory对象之间的关系:
(注意:MapperProxyFactory的类型T指定我Mapper的类型,即一个Mapper对应一个MapperProxyFactory对象)
可以看出用了一个map来存储Mapper接口的Class对象与MapperProxyFactory对象之间的关系,然后在addMapper方法向其中注册关系,然后就能调用getMapper方法根据Mapper接口的来获取对应的代理对象(通过对应的MapperProxyFactory调用newInstance方法创建)。
(mybatis启动时就会解析所以mapper接口,然后调用MapperRegistry对象的addMapper方法将Mapper接口信息和对应的MapperProxyFactory对象注册到MapperRegister对象中,待需要获取mappper的代理对象时调用getMapper执行对应的MapperProxyFactory对象的newInstacne方法返回代理对象)
二、MapperStatement的注册过程(Mapper中<select|update|delete|delete>标签或注解的配置类)
前面已经说过,mybatis通过MappedStatement类描述Mapper的sql配置信息。而在将Configuration对象时,说过Configuration类有一个mappedStatements属性,该属性用来注册Mybatis中所有的MappedStatement对象,具体如下:
在讲Configuration时,我们讲过Mybatis主配置文件的解析通过XMLConfigBuilder对象完成的,XmlConfigBuilder类通过parseConfiguration方法中去调用不同的方法解析对应的标签:
而MappedStatement对象的创建重点需要关注的是对<mappers>标签的解析过程,我们进入mapperElement方法:
如上代码,首先获取<mappers>的所有子标签<mapper>、<package>,然后根据不同标签做不同的处理,mappers标签的配置一般如下: (<mappers>标签表示一个Mapper的配置,例如一个UserMapper接口就对应一个<mappers>)
获取到子标签信息后,就创建XMLMapperBuilder对象并调用parse方法进行解析,我们进入该方法:(我们这里以<mapper resource=“ ”>这个子标签来看看parse方法怎么解析的,即看怎么解析Mapper.xml文件的)
首先会调用XPathParser对象的evalNode方法获取根节点对应的XNode对象,接着调用configurationElement方法对Mapper配置内容进一步做解析,我们进去看看configurationElement方法:
如上面代码所示,configurationElement方法对Mappper.xml文件的所有标签进行解析,这里我们重点关注<select|update|delete|insert>标签的解析,在上面代码中获取到<select|update|delete|insert>标签对应的XNode对象后,调用buildStatementFromContext方法进一步做解析:
可以看到该方法为每个<select|update|delete|insert>标签对应的XNode对象创建一个XMLStatementBuilder对象,然后调用其parseStatementNode方法进行解析。我们进入parseStatementNode方法看看:
可以从XMLStatementBuilder类的parseStatementNode方法看出解析标签对应的XNode对象大概分下面几个步骤:
三、调用Mapper方法
上面讲了获取Mapper接口的动态代理对象,创建MapperStatement配置对象,下面开始看看怎么去调用Mapper接口中定义的方法。
为了执行Mapper接口中定义的方法,我们需要调用SqlSession对象的getMapper方法获取一个动态代理对象,然后通过动态代理对象调用方法即可,具体如下:
我们前面说过调用代理对象方法时,会执行MapperProxy类的invoke方法:
此时对Mapper接口的方法,调用cachedMapperMethod方法获取到一个MapperMethod对象:我们先看 cachedMapperMethod方法:
首先该方法中从缓存中获取MapperMathod对象,如果获取不到则创建一个并放进缓存中,而MapperMethod是对Mapper方法相关信息的封装,通过MapperMethod能够方便的获取sql语句的类型、方法的签名信息等。下面是MapperMethod类的构造方法:
MapperMethod构造方法中创建了一个SqlCommand对象和一个MethodSignature对象,SqlCommand对象用于获取Sql语句的类型、Mapper的Id等信息。MethodSignature对象用于获取方法的签名信息、参数注解等信息。我们先看下SqlCommand类的构造方法:
可以看出现获取方法的类或接口的class对象,然后调用resolveMappedStatement方法根据Mapper接口的全限定名和方法名获取对应的MappedStatement对象(配置对象),然后通过MappedStatement对象获取Sql语句的类型和mapper的id。下面是ResolveMappedStatement方法的实现:
可以看到先将接口的全限定名+方法进行拼接,作为Mapper的id从Configuration对象中查找对应的MappedStatement对象,查找不到先从父接口递归调用,还找不到则返回null。
SqlCommand封装了Sql语句的类型和Mapper的id,下面我们看看MethodSignature对象的创建过程,下面是MethodSignature的构造方法:
ParaNameResolver构造方法中完成了Mapper方法参数的解析过程,代码如下:
到此我在,整个MapperMethod对象已经创建完成,接下来解释Mapper方法的执行,MapperMethod提供了一个execute方法用来 执行sql命令,我们可以回到MapperProxy的invoke方法看看:
进入改execute方法:
如图所述,先通过SqlCommand获取sql语句的类型,然后根据类调用SqlSession对象对应的方法
例如Insert类型,先获取参数信息,然后通过SqlCommand的getName方法获取Mapper的id(全限定类或接口名+方法名),然后调用SqlSession的api。
四、SqlSession执行Mapper过程
上面我讲了先通过动态代理获取Mappper的代理对象,而代理方法invoke方法(代理方法调用时会调用,代理方法逻辑是通过Mapperproxy类,而代理对象的创建时通过MapperProxyFactory工厂实例化MapperProxy对象返回)中创建一个MapperMethod对象(用来描述Mappe接口中方法信息的对象),然后通过调用该对象的execute方法去执行sql语句。
而execute中执行sql语句是通过sqlsession提供的增删改查的方法。大概流程如下例子:
下面我们以select类型为例子,介绍SqlSession怎么执行Mapper的?我们之前的文章讲过SqlSessioon接口只有一个实现类就是DefaultSqlSession,下面是DefaultSqlSession类对SqlSession接口中定义的SelectList方法的实现:
上面代码中先根据Mapper的Id获取MappedStatement对象,然后作为参数调用Executor实例的query方法完成查询操作,我们来看看Excutor的实现类BaseExecutor对query方法的实现:
BaseExecutor类的query反方中,先对从MappedStatement对象中获取BoundSql对象,该对象封装了经过解析后的sql语句以及参数映射信息,然后创建CacheKey对象,该对象用于缓存Key值,接着调用重载query放,代码如下:
先从缓存中获取查询结果,如果没有则调用queryFromDatabase方法从数据库中查询,该方法的关键代码如下:
调用doQuery方法进行查询,然后将查询结果进行缓存,doQuery是一个模板方法,由BaseExecutor子类实现(SimpleExecutor、ReuseExecutor),我们以SimpleExecutor对doQuer方法的实现举例:
SimpleExecutor类的doQuery方法中,首先使用Configuration对象的newStatementHandler方法创建了StatementHandler对象(此时返回了一个RoutingStatementHandler类型的),在RoutingStatementHandler中会根据配置Mapper是StatementType属性指定的StatementHandler类型创建对应的StatementHandler实例进行处理(例如statementType属性为simple时则创建SimpleStatementHandler实例)
StatementHandler对象创建完后,就会调用SimpleExecutor类的prepareStatement方法创建JDBC中的Statement对象(JDBC中的sql执行器),然后对Statement对象设置参数操作,Statement对象初始化后,再去调用其query方法执行操作,我们在来看看prepareStatement放怎么去创建Statement对象的。具体代码如下:
上面代码中,首先会获取JDBC的Connection对象吗,然后调用StatementHandler对象的prepare方法创建Statement对象,接着调用StatementHandler对象的parametersize方法(该方法会使用Parameterhandler为Statement对象设置参数)
(StatementHandler、parameterHandler都是mybatis对JDBC的Statement、Parameter操作的封装)
mybatis的StatementHandler有几个不同的实现类,分别为SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,默认情况下回使用PreparedStatementHandler与数据库交互,接下来我们来看看PreparedStatementhandler中对query方法的实现,代码如下:
首先调用了PrepareStatement对象的execute方法执行sql语句,然后调用ResultSetHandler的handleResultSet方法处理结果集。
ResultSetHandler只有一个默认的实现,即DefaultResultSetHandler类,下面是其handleResultSet方法的关键代码:
如上图所示,该方法的具体实现步骤如下:
小结:
aaa