1.Mybatis的框架设计
Mybatis总共分为四层,接口层、数据处理层、框架支持层、引导层。
- 接口层-和数据库进行交互的方式。用户通过接口层来进行数据库的增删改查操作。与数据库进行交互的方式有两种:a.使用传统的MyBatis提供的API;b. 使用Mapper接口。
- 数据处理层。数据处理层底层是基于JDBC的。包括,解析sql的参数;拿到sql语句,预编译sql语句;sql的执行;处理sql执行的结果,这些过程都是原生JDBC的流程。
- 框架支持层。读取sql配置文件;支持事务管理、连接池管理、缓存机制。
- 引导层。读取全局配置文件启动Mybatis。
2.Mybatis工作原理-源码分析
分析Mybatis的源码,可以从基本的hello world开始,hello world主要包含下面四个步骤,下面将分别结合源码分别进行分析:
- 获取sqlSessionFactory对象
- 获取sqlSession对象
- 获取Mapper的代理对象(MapperProxy)
- 执行增删改查方法
2.1 创建sqlSessionFactory的过程
查看源码可以得到创建sqlSessionFactory的时序图如下:
步骤解析:
- 首先创建SqlSessionFactoryBuilder对象
- 然后调用SqlSessionFactoryBuilder对象的build方法,inputStream指的是全局配置文件的输入流
- 创建解析器parser(类型是XmlConfigBuilder)
- 解析全局配置文件中每一个标签并且将标签对应的相关配置信息保存在一个Configuration对象中
- 解析mapper标签指定的mapper.xml。解析mapper.xml使用的是也是一个解析器(只不过类型是XmlMapperBuilder)。和解析全局配置文件类似,在解析mapper.xml的时候也是解析mapper.xml配置文件中的每一个标签并且将相关配置信息保存在了同一个Configuration对象中。
注意:在解析mapper的增删改查标签的时候,使用的是类型为XmlStatementBuilder的paser。这个paser将增删改查标签的所有详细信息全部提取出来,封装成了一个MappedStatement,一个MappedStatement就代表一个增删改查标签的详细信息。 - 解析完mapper之后,将mapper的信息放入Configuration对象中,返回Configuration对象
- build中传入Configuration对象
- build中传入的Configuration对象被用来创建了一个DefaultSqlSessionFactory(图片上写的DefaultSqlSession是错的)对象。
- 最终返回一个DefaultSqlSessionFactory。DefaultSqlSessionFactory里面包含了Configuration对象,。
一个Configuration对象例子:保存了所有配置文件的详细信息(全局配置文件、mapper.xml).
Configuration对象中包含了比较重要的MappedStatement对象以及knowMappers(保存了每一个接口对应的代理对象的工厂MapperProxyFactory)。
一个MappedStatement对象例子:里面包含了sql id以及sql语句等重要信息。
总结:创建sqlSessionFactory所做的工作就是,将所有配置文件的信息解析并且保存在Configuration对象中,返回包含了Configuration对象的DefaultSqlSessionFactory对象。
2.2 获取sqlSession对象的过程
查看源码可以得到创建sqlSession对象的时序图如下:
步骤解析:
- 首先调用DefaultSqlSessionFactory的openSession()方法
- OpenSession()里面其实是调用的openSessionFromSource()方法
- 获取Configuration里面的一些内容,创建一个事务(Transaction)
- 创建一个Executor对象
- 根据Executor在全局配置中的类型(目前类型有3种,simple,batch,reuse),创建出SimpleExecutor、ReuseExecutor或者BatchExecutor。Executor是一个接口,里面规定了一些增删改查的接口方法。
- 如果全局配置中开启了二级缓存,那么会使用CacheExecutor对Executor进行包装,创建一个CacheExecutor对象。包装的好处是在查询的时候会先查缓存。
- 调用interceptorChain.pluginAll(executor),pluginAll方法会拿到每一个拦截器的plugin方法包装一下executor。(这一步很重要,和插件有关)。
- 创建DefaultSqlSession对象,这个对象包含Configuration对象以及Executor对象
- 返回DefaultSqlSession对象
这一步返回SqlSession的实现类DefaultSqlSession对象,它里面包含了Configuration对象和Executor对象。四大对象的Executor对象会在这一步被创建。
2.3 获取Mapper的代理对象(MapperProxy)的过程
查看源码可以得到获取Mapper的代理对象的时序图如下:
步骤解析:
- 调用DefaultSqlSession对象的getMapper(type)方法。(type是Mapper接口的class,例如XXXMapper.class)
- 这个方法里面又会调用的是Configuration对象的getMapper(type)方法
- Configuration对象的getMapper(type)方法会调用MapperRegistry的getMapper(type)方法。MapperRegistry是Configuration对象的一个属性。
- 根据接口类型(xxxMappee.class)获取MapperProxyFactory对象
- 调用MapperProxyFactory的newInstance方法
- newInstance第一步会创建MapperProxy对象(它是一个InvocationHandler对象)。InvocationHandler是JDK里面用于创建动态代理的接口。(动态代理的相关知识需要理解)
- 创建MapperProxy的代理对象
- 返回MapperProxy代理对象
一个MapperProxy代理对象例子:里面包含了SqlSession对象。
getMapper:使用MapperProxyFactory对象创建一个MapperProxy代理对象,代理对象里面包含了DefaultSqlSession,DefaultSqlSession里面又包含了Executor,这样代理对象就能使用Executor执行增删改查了。
2.4 执行增删改查方法的过程
查看源码可以得到执行查询方法的时序图如下:
步骤解析:
- 调用代理对象的invoke方法,因为MapperProxy对象是一个代理对象(类型是InvocationHandler),执行真正的目标方法之前,会执行InvocationHandler的invoke方法。这里的知识点和Java的动态代理有关。
- invoke方法会调用MapperMethod的execute方法,execute方法会判断当前要执行的sql语句的类型(增删改查,当前是查询类型)。
- 包装参数为一个map或者直接返回。如果参数是单个,直接返回,如果参数是多个,返回一个map
- 例子是返回一个对象,会调用DefaultSqlsession的selectOne方法。查询多个会调用executorForMany。
- selectOne方法会调用DefaultSqlsession的selectList方法
- 获取MappedStatement对象,里面包含了sql id以及sql语句等重要信息。
- 调用CacheingExecutor对象的query(ms, xx, xx)方法,这里会查询二级缓存
- 获取BoundSql对象,里面包含sql语句的详细信息。sql语句是什么、sql语句的参数是什么等详细信息
- 调用真正的simpleExecutor的query方法进行查询
- 查看本地缓存(一级缓存) 是否有数据,没有就调用queryFromDataBase方法,查出以后结果也会保存在本地缓存中
- 调用BaseExecutor的doQuery方法
- 创建四大对象之一的StatementHandler对象,默认的类型是PrepareStatementHandler,可以通过标签的StatementType进行指定,不指定就是Prepare类型。StatementHandler被用于创建Statement对象。
- 创建出来的StatementHandler会使用拦截器链进行进一步的处理
- 创建四大对象之一的ParameterHandler,创建出来的ParameterHandler也会使用拦截器链进行进一步的处理
- 创建四大对象之一的ResultSetHandler,创建出来的ResultSetHandler也会使用拦截器链进行进一步的处理
- 预编译sql,产生PrepareStatement对象
- 调用ParameterHandler设置参数
- ParameterHandler设置参数的时候是调用的TypeHandler给sql预编译设置参数
- 查出数据之后,使用ResultHandler处理结果,里面也是使用TypeHandler获取value值
- 后续的关闭连接等操作
- 返回结果
一个BoundSql对象的例子:
增删改查流程总结:
- 使用代理对象进行增删改查操作
- 代理对象在进行增删改查操作的时候实际上使用的DefaultSqlSession
- DefaultSqlSession执行增删改查又使用的是Executor
- Exexutor对象在执行增删改查的时候,会创建StatementHandler,StatementHandler用于处理sql语句预编译,设置参数等相关工作
- StatementHandler在候会借助ParameterHandler来设置sql语句的预编译参数
- 当增删改查执行完成之后,StatementHandler会借助ResultsetHandler来处理结果集
- ParameterHandler和ResultsetHandler都会借助TypeHandler来进行数据库类型和Java Bean类型的转换
- 所有的底层操作都是借助于原生的JDBC
需要记住Executor、ParameterHandler、ResultsetHandler、TypeHandler四大对象的作用。Executor用于执行增删改查操作;Executor执行增删改查是借助于StatementHandler;StatementHandler用ParameterHandler设置参数;使用ResultsetHandler处理结果。整个过程中,设计到数据库类型和Java Bean类型的转换又使用的是TypeHandler。
3. 总结
整个流程的总结:
- 根据配置文件(全局、sql映射文件(mapper.xml))创建出Configuration对象
- 创建一个DefaultSqlSession对象,它里面包含Configuration对象和Executor对象(根据全局配置文件中的defaultExecutorType进行创建)
- 通过DefaultSqlSession的getMapper()方法拿到Mapper接口的代理对象MapperProxy;MapProxy对象里面有DefaultSqlSession对象
- 使用代理对象执行增删改查方法。
1)首先会调用DefaultSqlSession的增删改查方法,DefaultSqlSession的增删改查实际上是调用的Executor的增删改查;
2)Executor的增删改查创建StatementHandler对象,同时也会创建出ParameterHandler和ResultsetHandler对象;
3)使用StatementHandler设置sql参数值已经预编译sql语句,借助的是ParameterHandler;
4)调用StatementHandler的增删改查方法
5)使用ResultsetHandler处理结果
注意:四大对象在创建的时候,都会调用拦截器链的pluginAll方法。Executor对象在创建SqlSession对象的时候创建;其他的三大对象在执行增删改查方法的时候创建。