Demo
//main-----------------------------------------------------
public class MybatisDemo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession seesion = sqlSessionFactory.openSession();
TestMapper testMapper = seesion.getMapper(TestMapper.class);
Object object = testMapper.selectOne();
System.out.println(object.toString());
}
}
//myabtis-config.xml-----------------------------------------------------
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.elijah.mybatisdemo.MybatistInterceptor">
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3307/acme"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/testMapper.xml"/>
</mappers>
</configuration>
//TestMapper.java-----------------------------------------------------
public interface TestMapper {
Object selectOne();
}
//TestMapper.xml-----------------------------------------------------
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.elijah.mybatisdemo.TestMapper">
<select id="selectOne" resultType="object">
select * from test limit 1;
</select>
</mapper>
demo是根据官网提供的demo写的,就是加载配置文件初始化,然后获取mapper装载模版sql,然后调用执行。
基础原理

源码分析
获取配置





生成会话
到这里我们已经建立起来了核心配置类的实例,并且装载进了sqlsessionfactory,用它来生成以及建立和数据库的会话。通过sqlSessionFactory的openSession方法,创建了一个持有和数据库建立会话的实体类SqlSession,这里面又通过不同的包装来封装不同功能的实体类。

这里可以看到他其实是分了三个步骤:
- 通过工厂类创建事务实例,事务实例持有数据库的Connection对象,通过connection对象可以获取和数据库的连接。
- 将事务实例设置到Executor对象中,executor其实是实际调用事务执行sql的,executor会将参数和sql模版语句进行拼装,最后使用transaction发送数据库执行。
- 最后将executor和configration等信息封装进DefaultSqlSession里面,使用sqlSession包装一次完整的会话。



先来看第一步,因为我们配置文件中指定的类型是JDBC所以通过xml文件出事化的时候会创建JDBCTransactionFactory。通过这个工厂类可以获取一个事务实例,看JdbcTransaction的成员变量,其实就是封装了数据源信息和连接对象,看方法确实是控制了连接的打开,事务的提交回滚等。



再来看第二步,创建Executor执行器,通过配置类Configration.newExecutor()的方法,将之前创建的事务实例传入并且根据执行器的类型,进行选择创建对应类型的执行器,默认的执行器的类型是SIMPLE,所以创建的是SimpleExecutor,SimpleExecutor其实继承了基类BaseExecutor,里面封装了configration、transaction等用于和数据库交互的属性。最重要的是使用包装者模式进行了层层包装,将插件一一包装,变成了一个过滤连,通过所有的插件的逻辑,才能运行到simpleexecutor,所以执行的前后可以添加增强的逻,其实也就是通过JDK的动态代理,新建了一个代理类,调用了Plugin类中的invoke方法,来先运行代理类中的增强逻辑。
第三步就没什么可说的了,就是把配置的中心类Configration和操作类Executor设置到sqlSession中持有。
代理包装
下一步就开始生成代理类了,根据dao层的interface生成包装类,在包装逻辑中,调用数据库。



这里的mapperRegistry在解析配置文件初始化Configuration的时候就已经创建了,缓存了interface的类型,以及生成interface代理类的代理工厂,会使用这个代理工厂来创建代理类。
其实这个MapperProxyFactory的作用就是持有代理的Class的类型,也就是interface的类型,以及缓存了这个interface对应的xml的方法和方法签名以及对应的sql语句。可以参考原理那边儿的UML图。




解析调用
上面已经说明了代理类的生成,实际上调用的是代理类的实例,因为使用jdk代理的,所以调用方法后,会进入到invoke方法。

首先先判断调用的是不是Object的方法,Object方法不代理,直接调用,其他的方法会获取MapperMetod的实例对象,这个类中封装了原始sql,以及sql类型,比如select update等,还包装了方法的签名,比如入参类型出参类型等。分别用SqlCommond,MethodSignature两个类来封装。(可以参考基础原理的图)
获取到MapperMethod实例之后,会把当前的会话和入参传入execute方法中执行。

根据MapperMethod实例对象的Sqlcommand的类型,来执行不同逻辑,以select为例,会判断MethodSignature的返回值类型,运行不同的逻辑,demo里面用的是select。首先会根据缓存的对象获取传入的参数的值,因为有可能有多个参数,所以他获取的有可能是一个map,实际上就是@Param里面的参数名,和对应的参数值。因为之前判断的返回值的类型没有特书表明是map还是什么,所以直接调用sqlsession里面的selectOne方法。



com.elijah.mybatisdemo.TestMapper.selectOne来获取Configration中早已经加载的这个方法对应的sql语句,然后将这个包装的sql语句的对象,和入参等信息,传入到executor中,也就是之前创建的SimpleExecutor,但是simpleExecutor只实现了BaseExecutor的模版方法,所以会先进入baseExecutor的代码中。


这里就会调用模版方法也就是进入simpleExecutor里面去执行了。


这里的prepare方法会调用connection发送未设置参数的sql语句給mysql,一是为了mysql将语句预编译,还可以为防止sql注入。
在预编译结束之后,会把参数和sql语句拼装起来,得到完整的sql语句设置到statment中返回,然后在返回到SimpleExecutor的doQuery方法中,调用query方法,实际真正的执行sql。

执行结束后再处理返回参数的拼装。
到这里就分析完了整个sql的执行过程以及架构。
插件原理
插件的主要目的就是增强或者修改一些东西,其实上面分析过程中已经可以看到Executor创建过程和statment的创建过程中都已经有interceptorChain.pluginAll的方法了,其实这个就是用动态代理设置的一个责任链。
因为利用的是动态代理的原理,所以首先需要实现一个interceptor的接口用于增强。
@Intercepts({
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class})
})
public class MybatistInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("this is MybatistInterceptor");
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
- intercept方法就是增强方法可以在前面后面进行增强,然后invocation对象的proceed方法,将会继续处理任务,就是一个AOP。
- plugin方法,是为了生成代理对象,target是被拦截的对象,所以一般就是直接返回Plugin.wrap,这个方法会调用动态代理生成代理对象,增强的逻辑就是intercept方法中的内容。
- setProperties方法在mybatis根据配置文件初始化的时候会调用,可以设置配置文件中写的property的属性值。
再来说一下这个方法上面的注解,这个其实就是为了确定要拦截的对象。
- executor是执行SQL的全过程,包括参数组装接口及返回和sql执行的过程。
- statmenthandler是执行sql的过程,是常用的拦截对象。
- parameterhandler是用于拦截sql的参数组装。
- resultsetHandler,用于拦截执行结果的组装。 其他的参数是确定拦截的方法以及来接的参数。
其实说白了就是在装载这几个对象的时候会先调用interceptorChain的pluginall的方法,会先看看那些拦截器是作用在哪些对象上的。拦截器的实现类的Class对象,其实在config文件初始化的时候就已经被加载进来了,只不过是在创建Executor、statmenthandler、parameterhandler、resultsethandler的时候,才是用动态代理生成新的代理对象包装起来形成责任链的。
