• Mybatis底层实现学习


    目录

     传统JDBC和Mybatis相比的弊病

    mybatis整体架构图

     mybatis功能架构分为三层:

    API接口层:

    数据处理层:

    配置解析流程:

    Configuration对象

    SQL解析(sqlsource)

     SQL执行(executor)

    基础支撑层:

    Mybatis核心配置文件解析原理

    解析的目的

    XML解析流程分析 

    解析入口

    Mapper映射文件解析原理

    核心执行器executor详解

    Mybatis核心执行组件介绍 

     Executor执行器分析

    JDBC的执行器

     Mybatis执行器

     Executor接口

    BaseExecutor(基础执行器)

    simpleExecutor(简单执行器)

     ReuseExecutor(可重用执行器)

     batchExecutor(批处理执行器)

    executor执行器实例化过程

    Mybatis缓存原理

    一级缓存

    二级缓存

     Mybatis的SQL执行过程

    ​编辑 查询语句的执行分析


     传统JDBC和Mybatis相比的弊病

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

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

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

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

    5、JDBC没有提供缓存,增加了数据库压力。

    mybatis整体架构图

     mybatis功能架构分为三层:

    API接口层:

    提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层完成具体的数据处理。其核心是SqlSession接口。

    数据处理层:

    负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次工作。

    配置解析流程:

     在Mybatis初始化过程中,会加载mybatis-config.xml配置文件、映射文件以及Mapper接口中的信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。利用该configuration对象创建SqlSessionFactory对象。待Mybatis初始化之后。开发人员可以通过初始化得到SqlSessionFactory创建SqlSession对象并完成数据库操作。

    Configuration对象

    是一个所有配置型的的容器对象。

    SQL解析(sqlsource)

    对应的是scripting模块。Mybatis中的scripting模块,会根据用户传入的实参,解析映射文件中定义的SQL节点,并形成数据库可执行的SQL语句。之后会处理SQL语句中的占位符,绑定用户传入的实参

    负责根据用户传递的parameterObject,动态生成SQL语句,将信息封装到BoundSql对象中并返回

     SQL执行(executor)

    基础支撑层:

    负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取成最基础的组件。为上层的数据处理层提供最基础的支撑。

    Mybatis核心配置文件解析原理

    在mybatis初始化过程中,会加载mybatis-config.xml配置文件、映射配置文件以及Mapper接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。

    解析的目的

    XML解析流程分析 

    解析入口

    (1)入口:

    Mybatis的初始化流程的入口是SqlSessionFactoryBuilder的build()方法:

    1. public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    2. try {
    3. //创建XMLConfigBuilder对象
    4. XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    5. //执行XML解析
    6. //创建DefaultSqlSessionFactory对象
    7. return build(parser.parse());
    8. } catch (Exception e) {
    9. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    10. } finally {
    11. ErrorContext.instance().reset();
    12. try {
    13. reader.close();
    14. } catch (IOException e) {
    15. // Intentionally ignore. Prefer previous error.
    16. }
    17. }
    18. }

    (2)XMLConfigBuilder对象:

    继承BaseBuilder抽象类,XML配置构建起,主要负责解析mybatis-config.xml配置文件

    1. private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    2. //创建Configuration对象
    3. super(new Configuration());
    4. ErrorContext.instance().resource("SQL Mapper Configuration");
    5. //设置Configuration的variables属性,把props绑定到configuration的props属性上
    6. this.configuration.setVariables(props);
    7. this.parsed = false;
    8. this.environment = environment;
    9. this.parser = parser;
    10. }
    11. //parse判断是否解析过
    12. public Configuration parse() {
    13. //若已经解析过了 就抛出异常
    14. if (parsed) {
    15. throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    16. }
    17. //标志已解析
    18. parsed = true;
    19. //解析XML configuration节点
    20. parseConfiguration(parser.evalNode("/configuration"));
    21. return configuration;
    22. }
    23. //方法实现说明:解析我们mybatis-config.xml的 configuration节点
    24. private void parseConfiguration(XNode root) {
    25. try {
    26. /**
    27. * 解析 properties节点
    28. *
    29. */
    30. propertiesElement(root.evalNode("properties"));
    31. /**
    32. * 解析我们的mybatis-config.xml中的settings节点
    33. */
    34. Properties settings = settingsAsProperties(root.evalNode("settings"));
    35. loadCustomVfs(settings);
    36. // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
    37. loadCustomLogImpl(settings);
    38. ///解析我们的别名
    39. typeAliasesElement(root.evalNode("typeAliases"));
    40. //解析我们的插件(比如分页插件)
    41. pluginElement(root.evalNode("plugins"));
    42. objectFactoryElement(root.evalNode("objectFactory"));
    43. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    44. reflectorFactoryElement(root.evalNode("reflectorFactory"));
    45. // 设置settings 和默认值
    46. settingsElement(settings);
    47. // read it after objectFactory and objectWrapperFactory issue #631
    48. // 解析我们的mybatis环境
    49. environmentsElement(root.evalNode("environments"));
    50. // 解析数据库厂商
    51. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    52. // 解析我们的类型处理器节点
    53. typeHandlerElement(root.evalNode("typeHandlers"));
    54. //最最最最最重要的就是解析我们的mapper
    55. mapperElement(root.evalNode("mappers"));
    56. } catch (Exception e) {
    57. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    58. }
    59. }

    xpath会读取dtd文件中定义好的标签规则,从而对xml文件进行解析 .

    Mapper映射文件解析原理

    再mybatis的核心配置文件解析的过程中,解析到mappers节点时,会进一步解析mapper映射文件。

    当扫描到mappers节点后会去执行mapperElement方法,根据不同的mapper配置方式获取不同的输入流(例如XML文件配置的mapper就获取XMLMapperBuilder)。

    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. *
    10. */
    11. if ("package".equals(child.getName())) {
    12. String mapperPackage = child.getStringAttribute("name");
    13. configuration.addMappers(mapperPackage);
    14. } else {
    15. /**
    16. * 判断从classpath下读取我们的mapper
    17. *
    18. */
    19. String resource = child.getStringAttribute("resource");
    20. /**
    21. * 判断是不是从我们的网络资源读取(或者本地磁盘得)
    22. *
    23. */
    24. String url = child.getStringAttribute("url");
    25. /**
    26. * 解析这种类型(要求接口和xml在同一个包下)
    27. *
    28. *
    29. */
    30. String mapperClass = child.getStringAttribute("class");
    31. /**
    32. * 我们得mappers节点只配置了
    33. *
    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. /**
    2. * 方法实现说明:真正的去解析我们的Mapper.xml(EmployeeMapper.xml)
    3. * @author:xsls
    4. * @return:
    5. * @exception:
    6. * @date:2019/8/30 16:43
    7. */
    8. public void parse() {
    9. /**
    10. * 判断当前的Mapper是否被加载过
    11. */
    12. if (!configuration.isResourceLoaded(resource)) {
    13. /**
    14. * 真正的解析我们的
    15. *
    16. */
    17. configurationElement(parser.evalNode("/mapper"));
    18. /**
    19. * 把资源保存到我们Configuration中
    20. */
    21. configuration.addLoadedResource(resource);
    22. bindMapperForNamespace();
    23. }
    24. parsePendingResultMaps();
    25. parsePendingCacheRefs();
    26. parsePendingStatements();
    27. }
    28. private void configurationElement(XNode context) {
    29. try {
    30. /**
    31. * 解析我们的namespace属性
    32. *
    33. */
    34. String namespace = context.getStringAttribute("namespace");
    35. if (namespace == null || namespace.equals("")) {
    36. throw new BuilderException("Mapper's namespace cannot be empty");
    37. }
    38. /**
    39. * 保存我们当前的namespace 并且判断接口完全类名==namespace
    40. */
    41. builderAssistant.setCurrentNamespace(namespace);
    42. /**
    43. * 解析我们的缓存引用
    44. * 说明我当前的缓存引用和DeptMapper的缓存引用一致
    45. *
    46. 解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
    47. 异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
    48. */
    49. cacheRefElement(context.evalNode("cache-ref"));
    50. /**
    51. * 解析我们的cache节点
    52. *
    53. 解析到:org.apache.ibatis.session.Configuration#caches
    54. org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
    55. */
    56. cacheElement(context.evalNode("cache"));
    57. /**
    58. * 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
    59. */
    60. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    61. /**
    62. * 解析我们的resultMap节点
    63. * 解析到:org.apache.ibatis.session.Configuration#resultMaps
    64. * 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
    65. *
    66. */
    67. resultMapElements(context.evalNodes("/mapper/resultMap"));
    68. /**
    69. * 解析我们通过sql节点
    70. * 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
    71. * 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
    72. * 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
    73. */
    74. sqlElement(context.evalNodes("/mapper/sql"));
    75. /**
    76. * 解析我们的select | insert |update |delete节点
    77. * 解析到org.apache.ibatis.session.Configuration#mappedStatements
    78. */
    79. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    80. } catch (Exception e) {
    81. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    82. }
    83. }

    XMLStatementBuilder-解析insert、update、delete等节点生成SQL语句:

    1. //方法实现说明:用于解析我们的的inset|select|update|delte节点的
    2. public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
    3. super(configuration);
    4. this.builderAssistant = builderAssistant;
    5. this.context = context;
    6. this.requiredDatabaseId = databaseId;
    7. }
    8. public void parseStatementNode() {
    9. // 我们的insert|delte|update|select 语句的sqlId
    10. String id = context.getStringAttribute("id");
    11. //判断我们的insert|delte|update|select 节点是否配置了 数据库厂商标注
    12. String databaseId = context.getStringAttribute("databaseId");
    13. // 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
    14. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    15. return;
    16. }
    17. // 获得节点名称:select|insert|update|delete
    18. String nodeName = context.getNode().getNodeName();
    19. // 根据nodeName 获得 SqlCommandType枚举
    20. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    21. //判断是不是select语句节点
    22. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    23. // 获取flushCache属性 默认值为isSelect的反值:查询:默认flushCache=false 增删改:默认flushCache=true
    24. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    25. /**
    26. * 获取useCache属性
    27. * 默认值为isSelect:查询:默认useCache=true 增删改:默认useCache=false
    28. */
    29. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    30. /**
    31. * resultOrdered: 是否需要处理嵌套查询结果 group by (使用极少)
    32. * 可以将比如 30条数据的三组数据 组成一个嵌套的查询结果
    33. */
    34. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    35. /**
    36. * 解析我们的sql公用片段
    37. *
    38. 解析成sql语句 放在