当前位置:   article > 正文

Mybatis源码解析

mybatis源码解析

目录

传统JDBC的问题如下

mybatis对传统的JDBC的解决方案

Mybaits整体体系图

MyBatis 源码编译

启动流程分析​编辑

1、解析全局配置文件

简单总结

2、Mapper.xml文件解析

3、二级缓存的解析(二级缓存一直是开启的,只是我们调用二级缓存需要条件)

4、sql语句的解析

MyBatis执行Sql的流程分析

1.创建SqlSession

Executor

2.SqlSession操作数据库

3.getMapper形式的调用

4.Mapper方法的执行流程

重要类

调试主要关注点


传统的JDBC

  1. @Test
  2. public void test() throws SQLException {
  3. Connection conn=null;
  4. PreparedStatement pstmt=null;
  5. try {
  6. // 1.加载驱动(这个可以不写,在DriverManager中利用了SPI机制加载了Driver)
  7. Class.forName("com.mysql.jdbc.Driver");
  8. // 2.创建连接
  9. conn= DriverManager.
  10. getConnection("jdbc:mysql://localhost:3306/mybatis_example", "root", "123456");
  11. // SQL语句
  12. String sql="select id,user_name,create_time from t_user where id=?";
  13. // 获得sql执行者
  14. pstmt=conn.prepareStatement(sql);
  15. pstmt.setInt(1,1);
  16. // 执行查询
  17. //ResultSet rs= pstmt.executeQuery();
  18. pstmt.execute();
  19. ResultSet rs= pstmt.getResultSet();
  20. rs.next();
  21. User user =new User();
  22. user.setId(rs.getLong("id"));
  23. user.setUserName(rs.getString("user_name"));
  24. user.setCreateTime(rs.getDate("create_time"));
  25. System.out.println(user.toString());
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. finally{
  30. // 关闭资源
  31. try {
  32. if(conn!=null){
  33. conn.close();
  34. }
  35. if(pstmt!=null){
  36. pstmt.close();
  37. }
  38. } catch (SQLException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

传统JDBC的问题如下

1、数据库连接创建,释放频繁造成资源的浪费,从而影响系统性能,使用数据库连接池可以解决问题。

2、sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可能较大,sql代码和java代码没有分离开来维护不方便。

3、使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定,同样是修改不方便

4、对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维护不方便。

mybatis对传统的JDBC的解决方案

1、数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。

解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链接。

2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

Mybaits整体体系图

 一个Mybatis最简单的使用列子如下:

  1. public class App {
  2. public static void main(String[] args) {
  3. //classpath下的mybatis的全局配置文件
  4. String resource = "mybatis-config.xml";
  5. Reader reader;
  6. try {
  7. //将XML配置文件构建为Configuration配置类,这里是加载将我们的配置文件加载进来
  8. reader = Resources.getResourceAsReader(resource);
  9. // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
  10. SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
  11. // 数据源 执行器 DefaultSqlSession
  12. SqlSession session = sqlMapper.openSession();
  13. try {
  14. // 执行查询 底层执行jdbc
  15. // User user = (User)session.selectOne("com.tuling.mapper.selectById", 1);
  16. UserMapper mapper = session.getMapper(UserMapper.class);
  17. System.out.println(mapper.getClass());
  18. User user = mapper.selectById(1L);
  19. System.out.println(user.getUserName());
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }finally {
  23. session.close();
  24. }
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }

总结下就是分为下面四个步骤:

  • 从配置文件(通常是XML文件)得到SessionFactory;
  • 从SessionFactory得到SqlSession;
  • 通过SqlSession进行CRUD和事务的操作(底层是使用executor进行sql的操作);
  • 执行完相关操作之后关闭Session。

MyBatis 源码编译

MyBatis的源码编译比较简单, 随便在网上找一篇博客即可,在这里不多说

mybatis 源码导入IDEA - 未亦末 - 博客园

启动流程分析

  1. String resource = "mybatis-config.xml";
  2. //将XML配置文件构建为Configuration配置类
  3. reader = Resources.getResourceAsReader(resource);
  4. // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
  5. SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

 通过上面代码发现,创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中,进去一探究竟:

  1. //整个过程就是将配置文件解析成Configration对象,然后创建SqlSessionFactory的过程
  2. //Configuration是SqlSessionFactory的一个内部属性
  3. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  4. try {
  5. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  6. return build(parser.parse());
  7. } catch (Exception e) {
  8. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  9. } finally {
  10. ErrorContext.instance().reset();
  11. try {
  12. inputStream.close();
  13. } catch (IOException e) {
  14. // Intentionally ignore. Prefer previous error.
  15. }
  16. }
  17. }
  18. //这里是上面方法return return build(parser.parse());之后返回的一个默认的SqlSessionFactroy
  19. public SqlSessionFactory build(Configuration config) {
  20. return new DefaultSqlSessionFactory(config);
  21. }

1、解析全局配置文件

 new XMLConfigBuilder()构造函数(在父类中会创建出 configuration,在初始化configuration的时候会创建出很多默认的typehandler)

 

XPathParser主要是用来解析xml文件的

 

 

 下面是解析配置文件的核心方法:

  1. private void parseConfiguration(XNode root) {
  2. try {
  3. /**
  4. * 解析 properties节点
  5. * <properties resource="mybatis/db.properties" />
  6. * 解析到org.apache.ibatis.parsing.XPathParser#variables
  7. * org.apache.ibatis.session.Configuration#variables
  8. */
  9. propertiesElement(root.evalNode("properties"));
  10. /**
  11. * 解析我们的mybatis-config.xml中的settings节点
  12. * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
  13. * <settings>
  14. <setting name="cacheEnabled" value="true"/>
  15. <setting name="lazyLoadingEnabled" value="true"/>
  16. <setting name="mapUnderscoreToCamelCase" value="false"/>
  17. <setting name="localCacheScope" value="SESSION"/>
  18. <setting name="jdbcTypeForNull" value="OTHER"/>
  19. ..............
  20. </settings>
  21. *
  22. */
  23. Properties settings = settingsAsProperties(root.evalNode("settings"));
  24. /**
  25. * 基本没有用过该属性
  26. * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
  27. Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
  28. 解析到:org.apache.ibatis.session.Configuration#vfsImpl
  29. */
  30. loadCustomVfs(settings);
  31. /**
  32. * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
  33. * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
  34. * 解析到org.apache.ibatis.session.Configuration#logImpl
  35. */
  36. loadCustomLogImpl(settings);
  37. /**
  38. * 解析我们的别名
  39. * <typeAliases>
  40. <typeAlias alias="Author" type="cn.tulingxueyuan.pojo.Author"/>
  41. </typeAliases>
  42. <typeAliases>
  43. <package name="cn.tulingxueyuan.pojo"/>
  44. </typeAliases>
  45. 解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
  46. */
  47. typeAliasesElement(root.evalNode("typeAliases"));
  48. /**
  49. * 解析我们的插件(比如分页插件)
  50. * mybatis自带的
  51. * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  52. ParameterHandler (getParameterObject, setParameters)
  53. ResultSetHandler (handleResultSets, handleOutputParameters)
  54. StatementHandler (prepare, parameterize, batch, update, query)
  55. 解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
  56. */
  57. pluginElement(root.evalNode("plugins"));
  58. /**
  59. * todo
  60. */
  61. objectFactoryElement(root.evalNode("objectFactory"));
  62. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  63. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  64. // 设置settings 和默认值
  65. settingsElement(settings);
  66. // read it after objectFactory and objectWrapperFactory issue #631
  67. /**
  68. * 解析我们的mybatis环境
  69. <environments default="dev">
  70. <environment id="dev">
  71. <transactionManager type="JDBC"/>
  72. <dataSource type="POOLED">
  73. <property name="driver" value="${jdbc.driver}"/>
  74. <property name="url" value="${jdbc.url}"/>
  75. <property name="username" value="root"/>
  76. <property name="password" value="Zw726515"/>
  77. </dataSource>
  78. </environment>
  79. <environment id="test">
  80. <transactionManager type="JDBC"/>
  81. <dataSource type="POOLED">
  82. <property name="driver" value="${jdbc.driver}"/>
  83. <property name="url" value="${jdbc.url}"/>
  84. <property name="username" value="root"/>
  85. <property name="password" value="123456"/>
  86. </dataSource>
  87. </environment>
  88. </environments>
  89. * 解析到:org.apache.ibatis.session.Configuration#environment
  90. * 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
  91. */
  92. environmentsElement(root.evalNode("environments"));
  93. /**
  94. * 解析数据库厂商
  95. * <databaseIdProvider type="DB_VENDOR">
  96. <property name="SQL Server" value="sqlserver"/>
  97. <property name="DB2" value="db2"/>
  98. <property name="Oracle" value="oracle" />
  99. <property name="MySql" value="mysql" />
  100. </databaseIdProvider>
  101. * 解析到:org.apache.ibatis.session.Configuration#databaseId
  102. */
  103. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  104. /**
  105. * 解析我们的类型处理器节点
  106. * <typeHandlers>
  107. <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
  108. </typeHandlers>
  109. 解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
  110. */
  111. typeHandlerElement(root.evalNode("typeHandlers"));
  112. /**
  113. * 最最最最最重要的就是解析我们的mapper
  114. *
  115. resource:来注册我们的class类路径下的
  116. url:来指定我们磁盘下的或者网络资源的
  117. class:
  118. 若注册Mapper不带xml文件的,这里可以直接注册
  119. 若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
  120. -->
  121. <mappers>
  122. <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
  123. <mapper class="com.tuling.mapper.DeptMapper"></mapper>
  124. <package name="com.tuling.mapper"></package>
  125. -->
  126. </mappers>
  127. * package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
  128. 2.
  129. */
  130. mapperElement(root.evalNode("mappers"));
  131. } catch (Exception e) {
  132. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  133. }
  134. }

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高;config.properties配置文件的优先级次之;properties标签中的配置优先级最低 -->
  7. <properties resource="org/mybatis/example/config.properties">
  8. <property name="username" value="dev_user"/>
  9. <property name="password" value="F2Fa3!33TYyg"/>
  10. </properties>
  11. <!--一些重要的全局配置-->
  12. <settings>
  13. <setting name="cacheEnabled" value="true"/>
  14. <!--<setting name="lazyLoadingEnabled" value="true"/>-->
  15. <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
  16. <!--<setting name="useColumnLabel" value="true"/>-->
  17. <!--<setting name="useGeneratedKeys" value="false"/>-->
  18. <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
  19. <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
  20. <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
  21. <!--<setting name="defaultStatementTimeout" value="25"/>-->
  22. <!--<setting name="defaultFetchSize" value="100"/>-->
  23. <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
  24. <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
  25. <!--<setting name="localCacheScope" value="STATEMENT"/>-->
  26. <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
  27. <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
  28. <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
  29. </settings>
  30. <typeAliases>
  31. </typeAliases>
  32. <plugins>
  33. <plugin interceptor="com.github.pagehelper.PageInterceptor">
  34. <!--默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果-->
  35. <!--如果某些查询数据量非常大,不应该允许查出所有数据-->
  36. <property name="pageSizeZero" value="true"/>
  37. </plugin>
  38. </plugins>
  39. <environments default="development">
  40. <environment id="development">
  41. <transactionManager type="JDBC"/>
  42. <dataSource type="POOLED">
  43. <property name="driver" value="com.mysql.jdbc.Driver"/>
  44. <property name="url" value="jdbc:mysql://10.59.97.10:3308/windty"/>
  45. <property name="username" value="windty_opr"/>
  46. <property name="password" value="windty!234"/>
  47. </dataSource>
  48. </environment>
  49. </environments>
  50. <databaseIdProvider type="DB_VENDOR">
  51. <property name="MySQL" value="mysql" />
  52. <property name="Oracle" value="oracle" />
  53. </databaseIdProvider>
  54. <mappers>
  55. <!--可以使用package和resource两种方式加载mapper-->
  56. <!--<package name="包名"/>-->
  57. <!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
  58. <mapper resource="./mappers/CbondissuerMapper.xml"/>
  59. </mappers>
  60. </configuration>

上面解析流程结束后会生成一个Configration对象,包含所有配置信息,然后会创建一个SqlSessionFactory对象,这个对象包含了Configration对象。

简单总结

对于MyBatis启动的流程(获取SqlSession的过程)这边简单总结下:

SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;

2、Mapper.xml文件解析

核心配置文件中对mapper.xml的读取主要有四种方式

  1. private void mapperElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. /**
  4. * 获取我们mappers节点下的一个一个的mapper节点
  5. */
  6. for (XNode child : parent.getChildren()) {
  7. /**
  8. * 判断我们mapper是不是通过批量注册的
  9. * <package name="com.tuling.mapper"></package>
  10. */
  11. if ("package".equals(child.getName())) {
  12. String mapperPackage = child.getStringAttribute("name");
  13. configuration.addMappers(mapperPackage);
  14. } else {
  15. /**
  16. * 判断从classpath下读取我们的mapper
  17. * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
  18. */
  19. String resource = child.getStringAttribute("resource");
  20. /**
  21. * 判断是不是从我们的网络资源读取(或者本地磁盘得)
  22. * <mapper url="D:/mapper/EmployeeMapper.xml"/>
  23. */
  24. String url = child.getStringAttribute("url");
  25. /**
  26. * 解析这种类型(要求接口和xml在同一个包下)
  27. * <mapper class="com.tuling.mapper.DeptMapper"></mapper>
  28. *
  29. */
  30. String mapperClass = child.getStringAttribute("class");
  31. /**
  32. * 我们得mappers节点只配置了
  33. * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
  34. */
  35. if (resource != null && url == null && mapperClass == null) {
  36. ErrorContext.instance().resource(resource);
  37. /**
  38. * 把我们的文件读取出一个流
  39. */
  40. InputStream inputStream = Resources.getResourceAsStream(resource);
  41. /**
  42. * 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
  43. */
  44. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  45. /**
  46. * 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
  47. */
  48. mapperParser.parse();
  49. } else if (resource == null && url != null && mapperClass == null) {
  50. ErrorContext.instance().resource(url);
  51. InputStream inputStream = Resources.getUrlAsStream(url);
  52. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  53. mapperParser.parse();
  54. } else if (resource == null && url == null && mapperClass != null) {
  55. Class<?> mapperInterface = Resources.classForName(mapperClass);
  56. configuration.addMapper(mapperInterface);
  57. } else {
  58. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  59. }
  60. }
  61. }
  62. }
  63. }

 来看下xmlMapperBuilder是怎么解析我们的mapper.xml文件的

  1. private void configurationElement(XNode context) {
  2. try {
  3. /**
  4. * 解析我们的namespace属性
  5. * <mapper namespace="com.tuling.mapper.EmployeeMapper">
  6. */
  7. String namespace = context.getStringAttribute("namespace");
  8. if (namespace == null || namespace.equals("")) {
  9. throw new BuilderException("Mapper's namespace cannot be empty");
  10. }
  11. /**
  12. * 保存我们当前的namespace 并且判断接口完全类名==namespace
  13. */
  14. builderAssistant.setCurrentNamespace(namespace);
  15. /**
  16. * 解析我们的缓存引用
  17. * 说明我当前的缓存引用和DeptMapper的缓存引用一致
  18. * <cache-ref namespace="com.tuling.mapper.DeptMapper"></cache-ref>
  19. 解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
  20. 异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
  21. */
  22. cacheRefElement(context.evalNode("cache-ref"));
  23. /**
  24. * 解析我们的cache节点
  25. * <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
  26. 解析到:org.apache.ibatis.session.Configuration#caches
  27. org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
  28. */
  29. cacheElement(context.evalNode("cache"));
  30. /**
  31. * 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
  32. */
  33. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  34. /**
  35. * 解析我们的resultMap节点
  36. * 解析到:org.apache.ibatis.session.Configuration#resultMaps
  37. * 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
  38. *
  39. */
  40. resultMapElements(context.evalNodes("/mapper/resultMap"));
  41. /**
  42. * 解析我们通过sql节点
  43. * 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
  44. * 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
  45. * 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
  46. */
  47. sqlElement(context.evalNodes("/mapper/sql"));
  48. /**
  49. * 解析我们的select | insert |update |delete节点
  50. * 解析到org.apache.ibatis.session.Configuration#mappedStatements
  51. */
  52. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  53. } catch (Exception e) {
  54. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  55. }
  56. }

都是对mapper.xml里面的标签属性的解析(会将我们的每一个节点封装成一个XNode)

3、二级缓存的解析(二级缓存一直是开启的,只是我们调用二级缓存需要条件)

 

 二级缓存的范围是在同一个namespace下,所有的SqlSession范围内有效(必须等SqlSession提交或者是关闭之后才会刷新到二级缓存中)

 

 

 进入build方法

 

 最后将我们创建出来的二级缓存加入到configuration中

 key就是我们的namespace,cache就是我们的SynchronizedCache

流程图

二级缓存结构

二级缓存在结构设计上采用装饰器+责任链模式

1、SynchronizedCache线程同步缓存区

实现线程同步功能,与序列化缓存区共同保证二级缓存线程安全。若blocking=false关闭则SynchronizedCache位于责任链的最前端,否则就位于BlockingCache后面而BlockingCache位于责任链的最前端,从而保证了整条责任链是线程同步的。

源码分析:只是对于操作缓存的方法进行了线程同步功能

2、LoggingCache统计命中率以及打印日志

统计二级缓存命中率并输出打印,由以下源码可知:日志中出现了“Cache Hit Ratio”便表示命中了二级缓存。

  1. public class LoggingCache implements Cache {
  2. private final Log log;
  3. private final Cache delegate;
  4. protected int requests = 0;
  5. protected int hits = 0;
  6. public LoggingCache(Cache delegate) {
  7. this.delegate = delegate;
  8. this.log = LogFactory.getLog(this.getId());
  9. }
  10. public Object getObject(Object key) {
  11. ++this.requests;//执行一次查询加一次
  12. Object value = this.delegate.getObject(key);//查询缓存中是否已经存在
  13. if (value != null) {
  14. ++this.hits;//命中一次加一次
  15. }
  16. if (this.log.isDebugEnabled()) {//开启debug日志
  17. this.log.debug("Cache Hit Ratio [" + this.getId() + "]: " + this.getHitRatio());
  18. }
  19. return value;
  20. }
  21. private double getHitRatio() {//计算命中率
  22. return (double)this.hits / (double)this.requests;//命中次数:查询次数

3、ScheduledCache过期清理缓存区

@CacheNamespace(flushInterval=100L)设置过期清理时间默认1个小时,

若设置flushInterval为0代表永远不进行清除。

源码分析:操作缓存时都会进行检查缓存是否过期

  1. public class ScheduledCache implements Cache {
  2. private final Cache delegate;
  3. protected long clearInterval;
  4. protected long lastClear;
  5. public ScheduledCache(Cache delegate) {
  6. this.delegate = delegate;
  7. this.clearInterval = 3600000L;
  8. this.lastClear = System.currentTimeMillis();
  9. }
  10. public void clear() {
  11. this.lastClear = System.currentTimeMillis();
  12. this.delegate.clear();
  13. }
  14. private boolean clearWhenStale() {
  15. //判断当前时间与上次清理时间差是否大于设置的过期清理时间
  16. if (System.currentTimeMillis() - this.lastClear > this.clearInterval) {
  17. this.clear();//一旦进行清理便是清理全部缓存
  18. return true;
  19. } else {
  20. return false;
  21. }
  22. }
  23. }

4、LruCache(最近最少使用)防溢出缓存区

内部使用链表(增删比较快)实现最近最少使用防溢出机制

  1. public void setSize(final int size) {
  2. this.keyMap = new LinkedHashMap<Object, Object>(size, 0.75F, true) {
  3. private static final long serialVersionUID = 4267176411845948333L;
  4. protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
  5. boolean tooBig = this.size() > size;
  6. if (tooBig) {
  7. LruCache.this.eldestKey = eldest.getKey();
  8. }
  9. return tooBig;
  10. }
  11. };
  12. }
  13. //每次访问都会遍历一次key进行重新排序,将访问元素放到链表尾部。
  14. public Object getObject(Object key) {
  15. this.keyMap.get(key);
  16. return this.delegate.getObject(key);
  17. }

 5、FifoCache(先进先出)防溢出缓存区

内部使用队列存储key实现先进先出防溢出机制。

  1. public class FifoCache implements Cache {
  2. private final Cache delegate;
  3. private final Deque<Object> keyList;
  4. private int size;
  5. public FifoCache(Cache delegate) {
  6. this.delegate = delegate;
  7. this.keyList = new LinkedList();
  8. this.size = 1024;
  9. }
  10. public void putObject(Object key, Object value) {
  11. this.cycleKeyList(key);
  12. this.delegate.putObject(key, value);
  13. }
  14. public Object getObject(Object key) {
  15. return this.delegate.getObject(key);
  16. }
  17. private void cycleKeyList(Object key) {
  18. this.keyList.addLast(key);
  19. if (this.keyList.size() > this.size) {//比较当前队列元素个数是否大于设定值
  20. Object oldestKey = this.keyList.removeFirst();//移除队列头元素
  21. this.delegate.removeObject(oldestKey);//根据移除元素的key移除缓存区中的对应元素
  22. }
  23. }
  24. }

4、sql语句的解析

 这里是对我们的select,insert,update,delete标签的解析

 循环解析我们的sql的节点

  1. public void parseStatementNode() {
  2. /**
  3. * 我们的insert|delte|update|select 语句的sqlId
  4. */
  5. String id = context.getStringAttribute("id");
  6. /**
  7. * 判断我们的insert|delte|update|select 节点是否配置了
  8. * 数据库厂商标注
  9. */
  10. String databaseId = context.getStringAttribute("databaseId");
  11. /**
  12. * 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
  13. */
  14. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  15. return;
  16. }
  17. /**
  18. * 获得节点名称:select|insert|update|delete
  19. */
  20. String nodeName = context.getNode().getNodeName();
  21. /**
  22. * 根据nodeName 获得 SqlCommandType枚举
  23. */
  24. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  25. /**
  26. * 判断是不是select语句节点
  27. */
  28. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  29. /**
  30. * 获取flushCache属性
  31. * 默认值为isSelect的反值:查询:默认flushCache=false 增删改:默认flushCache=true
  32. */
  33. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  34. /**
  35. * 获取useCache属性
  36. * 默认值为isSelect:查询:默认useCache=true 增删改:默认useCache=false
  37. */
  38. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  39. /**
  40. * resultOrdered: 是否需要处理嵌套查询结果 group by (使用极少)
  41. * 可以将比如 30条数据的三组数据 组成一个嵌套的查询结果
  42. */
  43. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  44. /**
  45. * 解析我们的sql公用片段
  46. * <select id="qryEmployeeById" resultType="Employee" parameterType="int">
  47. <include refid="selectInfo"></include>
  48. employee where id=#{id}
  49. </select>
  50. 将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>Node的子节点中
  51. */
  52. // Include Fragments before parsing
  53. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  54. includeParser.applyIncludes(context.getNode());
  55. /**
  56. * 解析我们sql节点的参数类型
  57. */
  58. String parameterType = context.getStringAttribute("parameterType");
  59. // 把参数类型字符串转化为class
  60. Class<?> parameterTypeClass = resolveClass(parameterType);
  61. /**
  62. * 查看sql是否支撑自定义语言
  63. * <delete id="delEmployeeById" parameterType="int" lang="tulingLang">
  64. <settings>
  65. <setting name="defaultScriptingLanguage" value="tulingLang"/>
  66. </settings>
  67. */
  68. String lang = context.getStringAttribute("lang");
  69. /**
  70. * 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
  71. */
  72. LanguageDriver langDriver = getLanguageDriver(lang);
  73. // Parse selectKey after includes and remove them.
  74. /**
  75. * 解析我们<insert 语句的的selectKey节点, 还记得吧,一般在oracle里面设置自增id
  76. */
  77. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  78. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  79. /**
  80. * 我们insert语句 用于主键生成组件
  81. */
  82. KeyGenerator keyGenerator;
  83. /**
  84. * selectById!selectKey
  85. * id+!selectKey
  86. */
  87. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  88. /**
  89. * 把我们的命名空间拼接到keyStatementId中
  90. * com.tuling.mapper.Employee.saveEmployee!selectKey
  91. */
  92. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  93. /**
  94. *<insert id="saveEmployee" parameterType="com.tuling.entity.Employee" useGeneratedKeys="true" keyProperty="id">
  95. *判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象
  96. */
  97. if (configuration.hasKeyGenerator(keyStatementId)) {
  98. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  99. } else {
  100. /**
  101. * 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,
  102. * 否者就看我们的mybatis-config.xml配置文件中是配置了
  103. * <setting name="useGeneratedKeys" value="true"></setting> 默认是false
  104. * 并且判断sql操作类型是否为insert
  105. * 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
  106. * 否则就是NoKeyGenerator.INSTANCE
  107. */
  108. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  109. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  110. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  111. }
  112. /**
  113. * 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
  114. * sql脚本对象 . 解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
  115. */
  116. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  117. /**
  118. * STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
  119. */
  120. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  121. /**
  122. * 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
  123. */
  124. Integer fetchSize = context.getIntAttribute("fetchSize");
  125. /**
  126. * 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
  127. */
  128. Integer timeout = context.getIntAttribute("timeout");
  129. /**
  130. * 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置
  131. */
  132. String parameterMap = context.getStringAttribute("parameterMap");
  133. /**
  134. * 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
  135. * 可以使用 resultType 或 resultMap,但不能同时使用
  136. */
  137. String resultType = context.getStringAttribute("resultType");
  138. /**解析我们查询结果集返回的类型 */
  139. Class<?> resultTypeClass = resolveClass(resultType);
  140. /**
  141. * 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
  142. * 可以使用 resultMap 或 resultType,但不能同时使用。
  143. */
  144. String resultMap = context.getStringAttribute("resultMap");
  145. String resultSetType = context.getStringAttribute("resultSetType");
  146. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  147. if (resultSetTypeEnum == null) {
  148. resultSetTypeEnum = configuration.getDefaultResultSetType();
  149. }
  150. /**
  151. * 解析 keyProperty keyColumn 仅适用于 insert 和 update
  152. */
  153. String keyProperty = context.getStringAttribute("keyProperty");
  154. String keyColumn = context.getStringAttribute("keyColumn");
  155. String resultSets = context.getStringAttribute("resultSets");
  156. /**
  157. * 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象
  158. */
  159. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  160. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  161. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  162. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  163. }

 我们来看下是怎么解析我们的sql语句的

 

 

 

 将我们的sql语句解析成一个个的sqlNode并放入到sqlSource中

 之后再进行其他的解析

最后将全部东西放入到mappedStatement 中(一个sql语句的就会被解析成一个mappedStatement

再将mappedstatement放入到mappedstatements中,key就是namespace+id,value就是对应的mappedstatement

到这里整个配置文件都已经解析完成

MyBatis执行Sql的流程分析

1.创建SqlSession

(1)创建事务工厂

(2)创建Execetor(二级缓存条件如果符合的话也会创建cachingExecutor)
 

 

 

  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Transaction tx = null;
  3. try {
  4. /**
  5. * 获取环境变量
  6. */
  7. final Environment environment = configuration.getEnvironment();
  8. /**
  9. * 获取事务工厂
  10. */
  11. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  12. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  13. /**
  14. * 创建一个sql执行器对象
  15. * 一般情况下 若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回
  16. * 一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor
  17. */
  18. final Executor executor = configuration.newExecutor(tx, execType);
  19. /**
  20. * 创建返回一个DeaultSqlSessoin对象返回
  21. */
  22. return new DefaultSqlSession(configuration, executor, autoCommit);
  23. } catch (Exception e) {
  24. closeTransaction(tx); // may have fetched a connection so lets call close()
  25. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  26. } finally {
  27. ErrorContext.instance().reset();
  28. }
  29. }

  1. /**
  2. * 方法实现说明:创建一个sql语句执行器对象
  3. * @author:xsls
  4. * @param transaction:事务
  5. * @param executorType:执行器类型
  6. * @return:Executor执行器对象
  7. * @exception:
  8. * @date:2019/9/9 13:59
  9. */
  10. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  11. executorType = executorType == null ? defaultExecutorType : executorType;
  12. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  13. Executor executor;
  14. /**
  15. * 判断执行器的类型
  16. * 批量的执行器
  17. */
  18. if (ExecutorType.BATCH == executorType) {
  19. executor = new BatchExecutor(this, transaction);
  20. } else if (ExecutorType.REUSE == executorType) {
  21. //可重复使用的执行器
  22. executor = new ReuseExecutor(this, transaction);
  23. } else {
  24. //简单的sql执行器对象
  25. executor = new SimpleExecutor(this, transaction);
  26. }
  27. //判断mybatis的全局配置文件是否开启缓存
  28. if (cacheEnabled) {
  29. //把当前的简单的执行器包装成一个CachingExecutor
  30. executor = new CachingExecutor(executor);
  31. }
  32. /**
  33. * TODO:调用所有的拦截器对象plugin方法
  34. */
  35. executor = (Executor) interceptorChain.pluginAll(executor);
  36. return executor;
  37. }

Executor

Executor分成两大类,一类是CacheExecutor(二级缓存),另一类是普通Executor。


普通Executor又分为三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、
BatchExecutor。


SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完
立刻关闭Statement对象。


ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String,Statement>内,供下一次使用。简而言之,就是重复使用Statement对象。


BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()方法批处理。与JDBC批处理相同。


作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
CacheExecutor其实是封装了普通的Executor,和普通的区别是在查询前先会查询缓存中是否存在结果,如果存在就使用缓存中的结果,如果不存在还是使用普通的Executor进行查询,再将查询出来的结果存入缓存。



 这里是将我们的执行器装饰到二级缓存执行器中

 调用执行器的时候首先委托二级缓存去查询(前提条件是我们的二级缓存要开启),三种执行器都是继承BaseExcutor,二级缓存查询不到会通过BaseExcutor去一级缓存中查询,假设一级缓存查询不到在调用执行器去数据库中查询

这里就将我们的SqlSession创建完毕了

2.SqlSession操作数据库

我们的mapperStatement的key是我们的namespace+方法id

 

 

 

 

接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我
们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的

 后面就是我们的通过prepareStatement操作sql语句来操作数据库

3.getMapper形式的调用

 

 

 

 

 

4.Mapper方法的执行流程


下面是动态代理类MapperProxy,调用Mapper接口的所有方法都会先调用到这个代理类的
invoke方法(注意由于Mybatis中的Mapper接口没有实现类,所以MapperProxy这个代理对
象中没有委托类,也就是说MapperProxy干了代理类和委托类的事情)。好了下面重点看下
invoke方法。

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {
  2. private static final long serialVersionUID = -6424540398559729838L;
  3. private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
  4. | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  5. private static Constructor<Lookup> lookupConstructor;
  6. private final SqlSession sqlSession;
  7. private final Class<T> mapperInterface;
  8. /**
  9. * 用于缓存我们的MapperMethod方法
  10. */
  11. private final Map<Method, MapperMethod> methodCache;
  12. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  13. this.sqlSession = sqlSession;
  14. this.mapperInterface = mapperInterface;
  15. this.methodCache = methodCache;
  16. }
  17. /**
  18. * 方法实现说明:我们的Mapper接口调用我们的目标对象
  19. * @author:xsls
  20. * @param proxy 代理对象
  21. * @param method:目标方法
  22. * @param args :目标对象参数
  23. * @return:Object
  24. * @exception:
  25. * @date:2019/8/27 19:15
  26. */
  27. @Override
  28. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  29. try {
  30. /**
  31. * 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
  32. */
  33. if (Object.class.equals(method.getDeclaringClass())) {
  34. return method.invoke(this, args);
  35. } else if (method.isDefault()) { //是否接口的默认方法
  36. /**
  37. * 调用我们的接口中的默认方法
  38. */
  39. return invokeDefaultMethod(proxy, method, args);
  40. }
  41. } catch (Throwable t) {
  42. throw ExceptionUtil.unwrapThrowable(t);
  43. }
  44. /**
  45. * 真正的进行调用,做了二个事情
  46. * 第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
  47. */
  48. final MapperMethod mapperMethod = cachedMapperMethod(method);
  49. /**
  50. *通过sqlSessionTemplate来调用我们的目标方法
  51. * 那么我们就需要去研究下sqlSessionTemplate是什么初始化的
  52. * 我们知道spring 跟mybatis整合的时候,进行了偷天换日
  53. * 把我们mapper接口包下的所有接口类型都变为了MapperFactoryBean
  54. * 然后我们发现实现了SqlSessionDaoSupport,我们还记得在整合的时候,
  55. * 把我们EmployeeMapper(案例class类型属性为MapperFactoryBean)
  56. * 的注入模型给改了,改成了by_type,所以会调用SqlSessionDaoSupport
  57. * 的setXXX方法进行赋值,从而创建了我们的sqlSessionTemplate
  58. * 而在实例化我们的sqlSessionTemplate对象的时候,为我们创建了sqlSessionTemplate的代理对象
  59. * this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
  60. new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  61. */
  62. return mapperMethod.execute(sqlSession, args);
  63. }

 MapperProxy的invoke方法非常简单,主要干的工作就是创建MapperMethod对象或者是从缓存中获取MapperMethod对象。获取到这个对象后执行execute方法。
所以这边需要进入MapperMethod的execute方法:这个方法判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作。(这边以sqlSession.selectOne这种方式进行分析
~)

  1. /**
  2. * 方法实现说明:执行我们的目标方法
  3. * @author:sqlSession:我们的sqlSessionTemplate
  4. * @param args:方法参数
  5. * @return:Object
  6. * @exception:
  7. * @date:2019/9/8 15:43
  8. */
  9. public Object execute(SqlSession sqlSession, Object[] args) {
  10. Object result;
  11. /**
  12. * 判断我们执行sql命令的类型
  13. */
  14. switch (command.getType()) {
  15. //insert操作
  16. case INSERT: {
  17. Object param = method.convertArgsToSqlCommandParam(args);
  18. result = rowCountResult(sqlSession.insert(command.getName(), param));
  19. break;
  20. }
  21. //update操作
  22. case UPDATE: {
  23. Object param = method.convertArgsToSqlCommandParam(args);
  24. result = rowCountResult(sqlSession.update(command.getName(), param));
  25. break;
  26. }
  27. //delete操作
  28. case DELETE: {
  29. Object param = method.convertArgsToSqlCommandParam(args);
  30. result = rowCountResult(sqlSession.delete(command.getName(), param));
  31. break;
  32. }
  33. //select操作
  34. case SELECT:
  35. //返回值为空
  36. if (method.returnsVoid() && method.hasResultHandler()) {
  37. executeWithResultHandler(sqlSession, args);
  38. result = null;
  39. } else if (method.returnsMany()) {
  40. //返回值是一个List
  41. result = executeForMany(sqlSession, args);
  42. } else if (method.returnsMap()) {
  43. //返回值是一个map
  44. result = executeForMap(sqlSession, args);
  45. } else if (method.returnsCursor()) {
  46. //返回游标
  47. result = executeForCursor(sqlSession, args);
  48. } else {
  49. //查询返回单个
  50. /**
  51. * 解析我们的参数
  52. */
  53. Object param = method.convertArgsToSqlCommandParam(args);
  54. /**
  55. * 通过调用sqlSessionTemplate来执行我们的sql
  56. * 第一步:获取我们的statmentName(com.tuling.mapper.EmployeeMapper.findOne)
  57. * 然后我们就需要重点研究下SqlSessionTemplate是怎么来的?
  58. * 在mybatis和spring整合的时候,我们偷天换日了我们mapper接口包下的所有的
  59. * beandefinition改成了MapperFactoryBean类型的
  60. * MapperFactoryBean<T> extends SqlSessionDaoSupport的类实现了SqlSessionDaoSupport
  61. * 那么就会调用他的setXXX方法为我们的sqlSessionTemplate赋值
  62. *
  63. */
  64. result = sqlSession.selectOne(command.getName(), param);
  65. if (method.returnsOptional()
  66. && (result == null || !method.getReturnType().equals(result.getClass()))) {
  67. result = Optional.ofNullable(result);
  68. }
  69. }
  70. break;
  71. case FLUSH:
  72. result = sqlSession.flushStatements();
  73. break;
  74. default:
  75. throw new BindingException("Unknown execution method for: " + command.getName());
  76. }
  77. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  78. throw new BindingException("Mapper method '" + command.getName()
  79. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  80. }
  81. return result;
  82. }

 例如我们这里是一个select查询

 

 SqlSession.selectOne方法会会调到DefaultSqlSession的selectList方法。这个方法获取了
MappedStatement对象,并最终调用了Executor的query方法

然后,通过一层一层的调用(这边省略了缓存操作的环节,会在后面的文章中介绍),最终会
来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了
SimpleExecutor

后面就跟上面使用sqlsession操作sql操作一样

到此,整个调用流程结束

简单总结
这边结合获取SqlSession的流程,做下简单的总结:
1、SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;

2、拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法。
3、获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;
4、获得SqlSession对象后就能执行各种CRUD方法了。
以上是获得SqlSession的流程

Sql的执行流程:

1、调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制)


2、MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;

3、往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。


4、调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。


Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理
 

重要类

1、MapperRegistry:本质上是一个Map,其中的key是Mapper接口的全限定名,value的MapperProxyFactory;
2、MapperProxyFactory:这个类是MapperRegistry中存的value值,在通过sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建Mapper的动态代理类;
3、MapperProxy:实现了InvocationHandler接口,Mapper的动态代理接口方法的调用都会到达这个类的invoke方法;
4、MapperMethod:判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作;
5、SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
6、Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
7、StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设
置参数、将Statement结果集转换成List集合。
8、ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数,
9、ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
10、TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换
11、MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装,
12、SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到
BoundSql对象中,并返回
13、BoundSql:表示动态生成的SQL语句以及相应的参数信息
14、Configuration:MyBatis所有的配置信息都维持在Configuration对象之中
 

调试主要关注点


1、MapperProxy.invoke方法:MyBatis的所有Mapper对象都是通过动态代理生成的,任何方法的调用都会调到invoke方法,这个方法的主要功能就是创建MapperMethod对象,并放进缓存。所以调试时我们可以在这个位置打个断点,看下是否成功拿到了MapperMethod对象,并执行了execute方法。


2、MapperMethod.execute方法:这个方法会判断你当前执行的方式是增删改查哪一
种,并通过SqlSession执行相应的操作。Debug时也建议在此打个断点看下。


3、DefaultSqlSession.selectList方法:这个方法获取了获取了MappedStatement对
象,并最终调用了Executor的query方法;
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/650495
推荐阅读
相关标签
  

闽ICP备14008679号