Skip to content

Mybatis Source

理解 Mybatis 的源码

测试示例

@Test
public void testDemo() throws IOException {

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

    // 获取 SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 获取 Mapper 的代理对象
    DemoBeanMapper mapper = sqlSession.getMapper(DemoBeanMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("id", "1");

    // execute sql
    System.out.println(mapper.selectById(map).toString());

    // sqlSession.commit();
    sqlSession.close();
}

理解Mybatis

结构上理解

Configuration

Mybatis 中 Configuration 更好的理解上将其理解为 配置 + 解析

  • 存储配置信息
  • 一系列的boolean变量(safeRowBoundsEnabled,safeResultHandlerEnabled,mapUnderscoreToCamelCase,...),用来控制 CURDinputoutput
  • 存储 环境信息 Environment

  • 存储解析信息

  • 通过 MapperRegistry 缓存了 MapperProxyFactory
  • 存储 InterceptorChain 过滤链
  • 存储 TypeHandlerRegistry
  • 存储 TypeAliasRegistry,
  • 通过 HashMap (Map<String, MappedStatement> mappedStatements) 存储了 MappedStatement
    • 一个 MappedStatement 对应 Mapper.xml 中的一个 INSERT,SELECT,DELETE,UPDATE 的代码片段
  • 通过 HashMap(Map<String, Cache>) 存储了 Mybatis 的缓存
  • 通过 HashMap(Map<String, ResultMap>) 存储了 Mapper.xml 中 ResultSet 块
  • 通过 HashMap(Map<String, ParameterMap>) 存储了 Mapper.xml 中 ParameterMap 块
  • 通过 HashMap(Map<String, KeyGenerator>) 存储了KeyGenerator

Environment

Mybatis定义的环境,用途理解: 这个环境和JDBC有紧密的关联

  • 包含了 TransactionFactory,用来创建事务

  • 包含了 DataSource,用来获取连接

MapperRegistry

从多类框架源码中可以发现,一般Registry结尾的都是在框架的初始化阶段将需要的信息存储进去

  • 引用了 Configuration

  • 一对一映射了 Class 和 MapperProxyFactory

  • Class : Java代码书写的 Mapper 接口
  • MapperProxyFactory : 生成动态代理抽象的工厂,用来生成 MapperProxy

MapperProxy

真正执行Mapper接口方法的关键类

  • 包含了 SqlSession sqlSession : 一次JDBC执行抽象Session
  • 包含了 Class<T> mapperInterface : 对应了 Java编写的Mapper接口
  • 包含了 Map<Method, MapperMethod> methodCache : 接口里的每个方法对应了 MapperMethod

创建Mapper接口代理对象的 核心是 MapperProxy 实现了 InvocationHandler

MapperMethod

从名字就可看出 和 Java的Mapper和Xml的Mapper 有关

  • 包含了 SqlCommand command SqlCommandMappedStatement 的引用映射, SqlCommand 主要记录了 id 和 type
  • id 的 作用是从Configuration缓存中找到内容
  • type是CURD的类型,在真实执行的时候再分发一次
  • 包含了 MethodSignature method , MethodSignature 是对接口方法的抽象
  • 枚举了返回类型,用boolean表示选择,记录下返回的类类型
  • 引用参数处理器

Mapper接口方法的唯一定义 MapperMethod = Mapper.class + Mapper.method()

MapperMethod在整个Mybatis框架中关联了 Java的接口 和 Xml文件的标签

MappedStatement

与书写的Mapper.xml文件相关

对于某个 XXXMapper.xml

  • 其中一定要包含namespace, 指向了Java代码中Mapper的接口
  • 对于 SELCET|INSERT|UPDATE|DELETE,每一块儿解析成 MappedStatement

DefaultSqlSessionFactory

从多类框架源码中可以发现, 一般Factory结尾的都是用来生成对象使用的,所以这个是用来产生SqlSession的

  • 引用了 Configuration
  • 深层理解 : 引用了 Environment

  • implements SqlSessionFactory

SqlSessionManager

关键是自己定义的方法

  • 引用了 Configuration

  • implements SqlSessionFactory, 通过观察成员变量存在 SqlSessionFactory sqlSessionFactory,说明这里是代理

  • implements SqlSession, 表明 SqlSessionManager 本身就是个SqlSession

  • 成员变量声明了 ThreadLocal<SqlSession> localSqlSession, 一般和多线程相关

  • 综上从结构上理解,SqlSessionManager 作为 Factory 可以产生 SqlSession, 作为 SqlSession 可以执行, 因为其又包含了 ThreadLocal,所以这个SqlSessionManager暂时理解为是在多个Java方法有SQL调用的时候控制事务使用的

DefaultSqlSession

SqlSession接口上看, 提供了通用的curd方法, 一般不直接调用。

提供了getMapper()的方法, 这个就是Mybatis提供的ORM

  • 引用了 Configuration, 等价于引用了 Environment, 等价于可以获取到 DataSourceTransaction
  • 定义了执行器: Executor executor,真实的JDBC调用封装在这个里面

行为上理解

创建 SqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

理解 : SqlSessionFactory 是最终产生 SqlSession 的地方,在创建 SqlSessionFactory 的时候,需要抓住的关键点是在创建过程中基于哪些外部配置文件创建了内部的配置项

这些内部的配置项(ConfigurationEnvironment)是内部逻辑读取外部配置并构建,从SqlSessionFactoryopenSession()方法上可以看出内部的配置是不需要暴露出来的

小结: 这种设计是大部分框架的设计思路,屏蔽不必要的配置细节,通用化配置过程

创建 SqlSessionFactory 的内部有个读取xml文件并解析的工具 XMLConfigBuilder, 这个类继承自 BaseBuilderBaseBuilder

  • 声明了 Configuration,TypeAliasRegistry,TypeHandlerRegistry,关键修饰 protected,说明子类可以直接使用
  • TypeAliasRegistryTypeHandlerRegistry 是引用的 Configuration 自身包含的

Mybatis自身规划了很多 Builder(查看 BaseBuilder的继承) 去解析各类配置并存储到 Configuration

对于Mapper接口和Mapper.xml文件的解析是在解析Xml文件中的<mapper>标签再次细化

  • 细化出 MapperProxy MapperMethod MapperStatement

Q & A :

Q: Mybatis是屏蔽不必要的配置细节,通用化配置过程的?

A: Mybatis提供一个XML配置文件,通过读取XML配置文件,初始化了相应的ConfigurationEnvironment

获取 SqlSession

sqlSessionFactory.openSession()

与Datasource以及Transaction相关

SqlSession 本身的抽象是对 JDBC 连接以及执行的抽象聚合

  • 引用了Configuration本质上可以传递了初始化的缓存数据,关键是使用 Configuration 中的 Environment 数据
  • 从上面的结构可以了解,Environment主要包含了 TransactionFactoryDataSource
  • 在构建 SqlSession 的时候 构建了 Executor 包含了 Transaction
  • 真正的SQL执行是在Executor中

获取接口代理对象

Proxy.newProxyInstance: JDK的动态代理

对于Mapper而言,无论是接口Mapper还是文件XMLMapper,都被抽象成了MapperProxy

执行细节的抽象就是MapperMethod

执行接口里面的方法 MapperProxy MapperMethod

这里是分发执行就是Mapper接口方法的关键

MapperProxy

观察MapperProxy真实的调用思路

  • Map<Method, MapperMethod> methodCache 获取到 Java的Mapper接口映射的MapperMethod方法
  • MapperMethod 中的 Object execute(SqlSession sqlSession, Object[] args) 这个方法去执行JDBC逻辑
  • 方法的入参 SqlSession sqlSession, 与Sql执行和事务有关
  • 方法的入参 Object[] args, 对于接口方法里的参数,Mybatis会将其转换成完整逻辑需要的参数
  • 方法的出参 用 Object 接收,对应 InvocationHandlerinvoke 的返回
    • 对于出参,在JDBC中,返回的结果类型是可枚举的, 所以对每种返回的结果做好映射转换即可
    • INSERT,UPDATE,DELETE 一般返回影响的行数
    • SELECT 返回的基于 ResultSet 可以转换为 单个对象 或者 对象集合
    • 如果本身接口方法是 void 没有返回值, return null 即可

MapperMethod

对外暴露的执行方法 Object execute(SqlSession sqlSession, Object[] args)

方法执行逻辑

  • 枚举 SqlCommand 的类型(INSERT|UPDATE|DELETE|SELECT|FLUSH), 分发执行类型
  • SELECT 的执行又细化了 returnMany returnMap returnsCursor returnOne
  • 对于影响行数的返回单独处理, (int|Integer|long|Long|boolean|Boolean) boolean是业务封装
  • 对于接口方法为void的处理是最简单的,return null即可、
  • 真实的执行还是由SqlSession来执行的,SqlSession在框架中是距离JDBC最近的抽象封装
  • SqlSession 执行的本质是 Executor <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)

Executor

真正执行JDBC操作的入口

理解Java对于JDBC事务的封装

可以扩展思考到Spring的事务设计

Transaction的设计