您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

【MyBatis详解】——从Xml配置解析到SQL执行过程

目录

  • 整体架构与使用Demo
  • Mybatis核心流程
    • 1. 创建SqlSessionFactory
      • 配置解析——SQL解析(存储MappedStatement到Configuration中)
      • SqlNode
      • MappedStatement
      • SqlSource
      • BoundSql
    • 2. 创建SqlSession
      • SqlSession
      • Executor
    • 3. 获取Mapper
    • 4. 通过Mapper接口调用CRUD方法
      • MapperMethod
    • 5. Executor执行SQL
      • StatementHandler

整体架构与使用Demo

Mybatis的架构整体可以分为3层:
在这里插入图片描述

  1. 接口层
    也就是和数据库进行交互,核心接口为SqlSession,一个SqlSession对应着一次数据库会话,那么其生命周期不是永久的,理论上每次访问数据库时都需要创建它。
    形式分为两种,一种是使用Mapper接口,一种是基于Mybatis提供的Api;
    • Mybatis提供的Api
      需要我们提供StatementId和查询参数,传递给SqlSession对象,提供SqlSession对象实现与数据库的交互;但是这种创建sqlSession的形式不符合面向接口编程的习惯。
    • 使用Mapper接口:
      Mybatis将配置文件中的每一个Mapper节点都抽象为一个Mapper接口,根据SqlSession.getMapper(XXXMapper.class),Mybatis将通过动态代理,生成一个Mapper实例。但当我们调用Mapper接口中的方法时,Mybatis会根据方法名和参数,确定StatementId,底层还是通过SqlSession来实现对数据库的操作。
  2. 数据处理层
    • 配置解析
    • 参数、结果集映射:java数据类型与jdbc数据类型的转换,包括查询阶段和结果返回阶段
    • Sql解析:动态sql生成
    • Sql执行
  3. 框架支持层
    • 事务管理
    • 连接池管理
    • 缓存机制

使用Demo:

String resource = "mybatis-config.xml"; 
InputStream inputStream = Resources.getResourceAsStream(resource); 
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 
// 创建SqlSession 
SqlSession sqlSession = sqlSessionFactory.openSession(); 
// 执行SQL语句 (通过StatementId)
List list = sqlSession.selectList("com.example.mapper.personMapper.selectPersonByMap");
// 执行SQL语句(通过Mapper接口)
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
personMapper.selectPersonByMap();

Mybatis的初始化有两种形式:

  • 基于XML配置文件:通过XML配置文件,将配置信息解析为Configuration对象。
  • 基于Java API:在Java代码中手动创建Configuration对象,然后将配置参数set 进入Configuration对象中。(不推荐)

Mybatis核心流程

1. 创建SqlSessionFactory

String resource = "mybatis-config.xml"; 
InputStream inputStream = Resources.getResourceAsStream(resource); 
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 

在这里插入图片描述

将xml配置信息解析为Configuration对象,然后构建一个DefaultSqlSessionFactory类型的工厂:

  1. 创建XMLConfigBuilder,解析xml全局文件
  2. 创建XMLMapperBuilder,解析每个xml
  3. 最后将返回Configuration对象,保存着全部的xml配置信息;

配置解析——SQL解析(存储MappedStatement到Configuration中)

我们重点分析XMLMapperBuilder.parse方法中,对sql相关信息的解析过程:
在这里插入图片描述

  • 解析Mapper节点,获得XNode,再解析XNode中的 select|insert|update|delete 节点,保存为XNode类型的List;遍历CRUD的XNode节点:
  • 新建XMLStatementBuilder,调用parseStatementNode,用于解析每一个CRUD的节点
  • 创建LanguageDriver(默认为XMLLanguageDriver,用于处理xml中的sql部分)
  • 调用LanguageDriver.createSqlSource,获取SqlSource
  • 根据SqlSource创建MappedStatement,并且注册到Configuration中

createSqlSource获取SqlSource的方法:
在这里插入图片描述

  1. 新建XMLScriptBuilder,并调用parse解析各个节点的sql部分
  2. 递归解析,最终得到MixedSqlNode(本质为SqlNode的List集合),并且标记此Sql类型为动态还是静态
  3. 如果是动态类型,返回DynamicSqlSource
  4. 如果是静态类型,返回RawSqlSource
  5. 返回的SqlSource将会保存在MappedStatement中,然后等sql执行的时候,通过getBoundSql方法触发sql的解析

总结

  • XMLConfigBuilder
    解析xml整体文件
  • XMLMapperBuilder
    解析每个xml文件
  • XMLStatementBuilder
    解析xml文件中各个select,insert,update,delete节点
  • XMLScriptBuilder
    解析xml中各个节点sql部分的Builder,产生MapperStatement,保存到Configuration中

SqlNode

节点解析时的工具类,简单理解就是xml中的每个标签,如update,trim,if标签
其实现类包括
在这里插入图片描述
实现动态Sql的关键就是 各个SqlNode的 apply方法
以IfSqlNode为例,如果满足条件,则apply,并返回true

 @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

而StaticTextSqlNode类型静态sql,则直接append

@Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

MappedStatement

是Configuration中的属性,表示一个CRUD节点的信息

SqlSource

是MappedStatement的属性,实现类包括:
在这里插入图片描述

  • StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。
  • RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,比动态SQL语句要快,因为不需要运行时解析SQL节点。
    • 如#占位符
  • DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理if等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。
    • 如$占位符或者if等标签
  • ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。

其getBoundSql方法提供BoundSql对象。

BoundSql

是从SqlSource中获取得到的信息,封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数;后续在执行Sql的时候将会使用到BoundSql。
在这里插入图片描述

2. 创建SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession(); 

在这里插入图片描述

  1. 创建Transaction事务:
    1. 根据Configuration配置信息获取Environment环境对象
    2. 根据environment信息获取TransactionFactory,这里获取到的事务工厂,即对应着xml文件中的transactionManager节点
    3. newTransaction创建事务tx
  2. 创建Executor执行器
    1. 调用Configuration对象的newExecutor方法,将事务对象tx传参进去;
    2. newExecutor方法,默认创建的是Simple类型的SimpleExecutor
      (类型一共三种:SIMPLE, REUSE, BATCH)
      如果开启了cacheEnabled(默认开启),那么包装为CachingExecutor:在查询数据之前先查找缓存,没有找到再从数据库查询并加入缓存。
    3. executor = (Executor) interceptorChain.pluginAll(executor);
      使用了执行器链模式,使得Executor对象可以被插件拦截;
  3. 创建DefaultSqlSession:
    1. new DefaultSqlSession(configuration, executor, autoCommit)

SqlSession

Mybatis与数据库交互的核心接口,一个SqlSession对应着一次数据库会话。

Executor

执行器,真正的sql执行并不是SqlSession直接执行的,而是通过Executor去执行。

3. 获取Mapper

PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

在这里插入图片描述

  1. 通过MapperRegister(Map类型的Mapper注册器Map<Class<?>, MapperProxyFactory<?>>)根据接口Class信息,获取到目标MapperProxyFactory
  2. 通过MapperProxyFactory创建MapperProxy(也就是Mapper接口的代理)
    1. Proxy.newProxyInstance动态代理创建
  3. 至此,我们通过SqlSession.getMapper获取到的personMapper对象,实际上是一个代理类

4. 通过Mapper接口调用CRUD方法

personMapper.selectPersonByMap();

在这里插入图片描述

被代理对象的方法的访问,都会落实到代理者的invoke上来,所以调用的mapper.select等方法,将会经过MapperProxy中的invoke:

  1. 创建MapperMethod对象:
    根据CRUD的类型将调用分发到SqlSession的不同方法上
  2. 处理参数:
    convertArgsToSqlCommandParam:当args数量>1时,将Object[]的参数包装为Map<String, Object>
  3. SqlSession方法的执行:
    1. 根据MapperMethod内部类SqlCommand中存储的statementId,从Configuration中找对应的MappedStatement
    2. 将MappedStatement作为参数,调用Executor中对应的方法

MapperMethod

它是一个分发者,根据CRUD的类型将调用分发到SqlSession的不同方法上。
有两个内部类同时被创建:
- SqlCommand
interfaceName + methodName作为statementId,去Configuration中去找对应的MappedStatement
存储statementId、SqlCommandType(CRUD类型)信息
- MethodSignature
记录方法的参数、返回值等信息

5. Executor执行SQL

在这里插入图片描述

  1. 我们默认为调用的是有缓存的Executor,那么先本地查询缓存,如果没找到再去调用(默认简单类型)SimpleExecutor的query方法
  2. 创建StatementHandler
    默认类型为ParparedStatementHandler,Executor将执行的任务交给它,它才是sql的具体执行者。也可以被插件所拦截(常见的物理分页插件)
    1. prepare:预编译sql,得到Statement对象
    2. 调用ParameterHandler:处理参数
    3. 调用ResultSetHandler:处理执行结果

StatementHandler

由此可见,Executor也不是真正的执行者,而是由StatementHandler完成的。在StatementHandler被创建的过程中,有一个getBoundSql的步骤,触发了动态sql的处理,下节详细分析。


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进