MyBatis源码解析

一:运行原理

1.SqlSessionFactory的创建及配置文件的解析

首先通过配置文件的文件流创建SqlSessionFactoryBuilder对象

调用build方法,传入文件流

之后通过解析器解析xml配置文件

通过XPathParse解析configuration节点,获取根节点,之后再parseConfiguration()方法中根据根节点解析根节点中的每一个节点

之后来到settingsElement()方法中

产生一个configuration对象,setting解析完毕后,后面解析mapper文件标签

获取mapper配置节点

解析mapper文件中的节点

之后调用buildStatementFromContext()方法,传入所有的crud配置节点,进行解析

循环接下所有crud节点

解析所有crud节点的属性

最后将所有的属性值加入到一个MappedStatement中,封装成MappedStatement,所以一个MappedStatement表示一个crud的详细信息。

最后还是将MappedStatement添加到configuration中,到此,configuration就填充完毕,其中保存了所有的配置信息(全局配置文件及sql映射文件)。

通过configuration创建DefaultSqlSessionFactory

总结:就是将所有的配置文件信息保存在configuration中,通过configuration对象创建SqlSessionFactory 

2.SqlSession对象的创建

通过SqlSessionFactory的openSession获取sqlSession

首先通过configuration.getDefaultExecutorType()获取执行器类型,默认为SIMPLE

之后又来到openSessionFromDataSource()方法

创建了一个Executor对象,来到newExecutor()方法

Executor是一个接口,用来执行crud的

是否配置了二级缓存,如果配置了使用CachingExecutor对Executor包装,实际执行crud还是Executor

包装后的Executor在查询之前会有缓存操作,Executor创建完毕后

会调用连接器链对executor进行一个拼装

循环所有的拦截器重新包装Executor,并返回

将创建好的Executor对象传入DefaultSqlSession中,DefaultSqlSession是SQLSession的实现类,可以看出DefaultSqlSession中也包含了全局配置文件configuration的数据,最终执行crud是Executor

总结:创建Executor对象及DefaultSqlSession对象,注意如果有二级缓存会包装Executor,其中还会使用拦截器链包装Executor

3.获取Mapper的代理对象

通过getMapper获取mapper

最终调用configuration的getMapper方法

又调用mapperRegistry的getMapper方法

mapperRegistry中保存了每一个mapper对应的MapperProxyFactory

根据接口类型获取mapperProxyFactory,通过mapperProxyFactory创建代理对象

首先创建一个mapperProxy对象,在调用newInstance方法

MapperProxy实现InvocationHandler接口,这个是JDK做动态代理需要传入的对象

之后就是通过JDK代理创建代理对象并返回,代理对象中包含了sqlSession,可以用来执行crud。

总结:mapperProxyFactory在configuration初始化完毕后,一个mapper就绑定一个mapperProxyFactory,通过mapperProxyFactory创建mapperProxy代理对象,代理对象中保存sqlSession对象,sqlSession中包含类Executor对象,用来执行crud

4.代理对象执行增删改查

调用代理的目标方法时,会先执行invoke()方法

先判断执行的方法是否是Object中的方法,否则就将方法包装为一个MapperMethod,最后执行其execute方法,传入sqlSession及方法参数

判断sql语句的类型,我这里是查询

我这里返回对象进入else流程,convertArgsToSqlCommandParam在解析参数,单个参数直接返回,多个参数返回一个map,之后调用SqlSession的selectOne方法

调用selectList查询多个方法

从configuration中通过方法id获取MappedStatement(就是configuration初始化时用来执行crud的),之后调用executor的query方法,wrapCollection(parameter)是根据参数将其包装为一个map,map中存放数据

现获取绑定的sql

BoundSql中封装了所有的sql信息,包括sql语句,参数,参数映射关系,之后就是创建二级缓存中保存的key,之后就调用query方法

如果配置二级缓存先获取缓存,之后调用query方法,就是SimpleExecutor的query方法

先从一级缓存中查询获取,这就印证了mybatis先查询二级缓存,在查询一级缓存,如果一级缓存中也没有就执行queryFromDatabase方法

调用doQuery()查询数据,查询出数据后又加入到本地缓存中

首先声明一个Statement对象,原生JDBC对象,在获取configuration,在通过newStatementHandler创建StatementHandler对象,StatementHandler可以创建出Statement对象

首先创建一个 RoutingStatementHandler

根据配置创建不同的Statement,默认PREPARED,所以创建出一个PreparedStatementHandler,保存在RoutingStatementHandler中,之后又将statementHandler包装在拦截器链中,之后调用prepareStatement(handler, ms.getStatementLog());方法创建Statement

在prepareStatement中有对参数进行预编译

调用parameterHandler进行参数预编译设置参数,创建PreparedStatementHandler对象时创建parameterHandler及resultSetHandler,在创建这两个对象时,又将其包装到拦截器链中,至此mybatis的四大对象(Executor,StatementHandler,parameterHandler,resultSetHandler)就全部创建完毕,调用setParameters设置参数

调用TypeHandler给sql语句预编译设置参数,参数设置完毕就调用handler.<E>query(stmt, resultHandler);执行查询

通过resultSetHandler处理结果

最后通过typeHandler.getResult(rs, column);返回结果

总结:先通过代理mapper对象,实际执行crud是使用sqlSession,sqlSession又是使用executor执行的,executor在执行crud是,会创建StatementHandler,StatementHandler就是用来处理(预编译,设置参数等)sql语句,StatementHandler创建时创建了parameterHandler及resultSetHandler,parameterHandler设置参数等等工作,并且执行sql语句,resultSetHandler是用来处理查询后的结果,parameterHandler及resultSetHandler执行时都会有一个TypeHandler做设置参数及获取结果映射为javaBean,TypeHandler底层是使用JDBC操作的

二:运行流程总结

  • 根据配置文件(全局文件及sql映射文件)初始化出Configuration
  • 创建DefaultSqlSession对象,其中包括Configuration及Executor(根据全局配置文件中的defaultExecutorType进行初始化)
  • DefaultSqlSession或Mapper接口对应的MapperProxy,MapperProxy包含DefaultSqlSession
  • 执行增删改查方法,代理对象调用DefaultSqlSession(Executor)的CRUD,创建一个Statement对象,同时创建StatementHandler,创建StatementHandler同时也会创建出parameterHandler及resultSetHandler,调用StatementHandler(parameterHandler)的预编译参数及设置参数值,在调用StatementHandler的CRUD方法,使用resultSetHandler封装结果
  • 四大对象每个创建时都有一个拦截器链的步骤,这一步在mybatis的插件中有用。

三:MyBatis插件开发

上面已经说过,在创建四大对象时都会有一个这样操作interceptorChain.pluginAll();

获得所有的Interceptor(插件需要实现的接口),调用其plugin()方法,返回target包装后的对象,插件机制,我们可以使用插件为目标对象(四大对象)创建一个代理对象,在执行原始四大对象前后,可以干一些事,类似于Spring AOP。我们的插件可以为四大对象创建代理对象,可以拦截四大对象的方法。

1.插件编写

//插件签名,指定拦截哪个对象的哪个方法及方法参数
@Intercepts({
	@Signature(type=StatementHandler.class,method="parameterize",args=Statement.class)
})
public class MyFirstPlugin implements Interceptor {

	//拦截,拦截目标对象的目标方法执行
	public Object intercept(Invocation invocation) throws Throwable {
		System.out.println("intercept......."+invocation.getMethod());
		//执行目标方法
		Object proceed = invocation.proceed();
		return proceed;
	}

	//插件包装,为目标对象创建代理对象
	public Object plugin(Object target) {
		System.out.println("plugin......."+target);
		//创建包装对象,借助Plugin的wrap方法,使用当前的Interceptor拦截包装目标对象
		Object wrap = Plugin.wrap(target, this);
		return wrap;
	}

	//将插件注册时的properties属性设置进来
	public void setProperties(Properties properties) {
		System.out.println(properties);
	}

}

全局配置文件

<plugins>
	<plugin interceptor="com.peng.mybatis.plugin.MyFirstPlugin">
		<!-- 指定属性 -->
		<property name="name" value="test"/>
		<property name="passwd" value="123456"/>
	</plugin>
</plugins>

测试发现

如果配置多个插件同时拦截同一个对象,就按照配置顺序通过interceptorChain.pluginAll();产生嵌套的代理对象,代理对象代理代理对象,执行时会先执行最后一个代理对象的方法,然后依次调用其他代理对象

2.插件应用

//插件签名,指定拦截哪个对象的哪个方法及方法参数
@Intercepts({
	@Signature(type=StatementHandler.class,method="parameterize",args=Statement.class)
})
public class MyFirstPlugin implements Interceptor {

	//拦截,拦截目标对象的目标方法执行
	public Object intercept(Invocation invocation) throws Throwable {
		System.out.println("intercept......."+invocation.getMethod());
		//动态改变sql运行参数,查询id=28改为id=1
		Object target = invocation.getTarget();//获取四大对象
		System.out.println(target);
		//获取StatementHandler-->ParameterHandler--->parameterObject的值
		//获取target的元数据
		MetaObject forObject = SystemMetaObject.forObject(target);
		Object value = forObject.getValue("parameterHandler.parameterObject");
		System.out.println(value);
		forObject.setValue("parameterHandler.parameterObject",1);
		//执行目标方法
		Object proceed = invocation.proceed();
		return proceed;
	}

	//插件包装,为目标对象创建代理对象
	public Object plugin(Object target) {
		System.out.println("plugin......."+target);
		//创建包装对象,借助Plugin的wrap方法,使用当前的Interceptor拦截包装目标对象
		Object wrap = Plugin.wrap(target, this);
		return wrap;
	}

	//将插件注册时的properties属性设置进来
	public void setProperties(Properties properties) {
		System.out.println(properties);
	}

}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值