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.
通过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操作的
上面已经说过,在创建四大对象时都会有一个这样操作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);
}
}
全局配置文件
测试发现
如果配置多个插件同时拦截同一个对象,就按照配置顺序通过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);
}
}