MyBatis

想学习的东西

  1. 从开始到连接到封装的流程,比如配置解析,Mapper 生成,Result 映射等
  2. 多级缓存的坑
  3. Orm框架的核心思想与设计能力
  4. 如何与 Spring 的整合
  5. 注解和 xml 都有,哪个优先级更高
  6. 重点了解下反射工具类的用法,如Reflector#addSetMethods 是如何解决父类子类的set冲突的
  7. 是如何让 Spring 事务管理器接管的
  8. 动态 sql 是如何解析的
  9. 参数是怎么绑定的
    10.返回值是怎么解析的

为什么有,定位在什么,怎么做到的。

如何与其他数据源框架整合

动态sql这一块的体系是需要好好看的,还有 where trim 标签这种

延迟加载是怎么做到的

嵌套映射怎么实现的

高层次的抽象是什么,怎么实现的,单一法则什么的是怎么实现的。

更多的是为什么要这么做,为什么要抽象出这个组件

版本

3.4.x

前提知识

JDBC

原生 JDBC 的主要流程:

  1. 找到相关驱动,url 等配置
  2. 根据配置通过 DriverManager 打开一个 Connection 连接
  3. 根据 Connection 连接生成一个 Statement 实例
  4. 通过 Statement 执行 sql 语句,得到 ResultSet 对象
  5. 解析 ResultSet 对象,封装成 JavaBean
  6. 关闭 ResultSet、Statement、Connection对象,释放资源

JDBC Api 介绍:

  1. DriverManager:管理各种不同的JDBC驱动
  2. Connection:负责连接数据库并担任传送数据的任务
  3. Statement:由 Connection 产生、负责发送执行SQL语句
  4. ResultSet:负责保存Statement执行后所产生的查询结果
public class JDBCTest {

    @Test
    public void testJdbc() {
        String url = "jdbc:mysql://localhost:3306/mybatis-learn?user=root&password=root&useUnicode=true&characterEncoding=UTF8&useSSL=false";

        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(url);

            String sql = "SELECT * FROM subject WHERE id = ?";
            PreparedStatement stmt = conn.prepareStatement(sql);
            stmt.setInt(1, 2);
            ResultSet rs = stmt.executeQuery();
            List<PrimitiveSubject> articles = new ArrayList<>(rs.getRow());
            while (rs.next()) {
                PrimitiveSubject article = new PrimitiveSubject(
                        rs.getInt("id"),
                        rs.getString("name"),
                        rs.getInt("age"),
                        rs.getInt("height"),
                        rs.getInt("weight")
                );
                articles.add(article);
            }
            articles.forEach(System.out::println);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

XML 解析相关

DOM 解析:把整个 XML 文件读取到内存中,构建出一棵 DOM 树,易于编程,但有时 XML 文件过大会消耗大量资源。

XPath:用一种特定的语法来方便获取 DOM 相关节点。

相关的设计模式

构造者模式,策略模式,责任链

模板方法:类型转换

测试代码

test 下的代码就很完整了,AutoConstructorTest,SqlSessionTest 都可以

MyBatis 的意义

MyBatis 官网简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis 要解决的问题:

  1. 处理预编译的 sql,并为其赋值。
  2. 对返回值封装成 JavaBean。
  3. 关联 Java 和 MySQL 的数据类型

MyBatis 就是对 jdbc 的二次开发,可以称的上是广义上的适配器。

总体架构

先把核心走一遍,再看各个组件,再走一遍总流程。
MyBatis_第1张图片接口层: 提供外部接口API,用于开发数据库操作功能。
数据处理层:负责具体的SQL实现,执行数据库SQL操作。
框架支撑层:主要负责将公共功能抽离独立,为数据处理层提供最基础的组件服务,比如:事务管理、缓存管理。

组件一览
SqlSession:顶级接口 API 入口,用于完成所有功能
Executor:MyBatis 执行器,负责 SQL 动态语句生成、查询、维护缓存
StatementHandler:负责 JDBC 与 Statement 交互,包括设置参数、将 JDBC 返回的 ResultSet 结果集转换

  • Parameter:传递参数值,对 Statement 对象设置参数
  • ResultSetHandler:转化 ResultSet 集合转换成 List
  • TypeHandler:负责 JDBCType 与 JavaType 之间的转化
    • 对 statement 对象设置特定的参数
    • 对 statement 返回的结果集 resultset 取出特定的列

如何读取配置文件并解析

XmlConfigBuilderTest#shouldSuccessfullyLoadXMLConfigFile 来测试。

用 xml 的方式加载 mybatis-config。

就是读取到标签,构造成 Configuration,通过反射调用对应字段的 set 方法。

注入 Mapper 的校验:

核心处理层

获取数据源 —— 执行语句 —— 返回值绑定

MyBatis 初始化

初始化入口:SqlSessionFactory#build(InputStream inputStream, String environment, Properties properties) 方法
主要做了:加载并解析 mybatis-config.xml 配置文件、映射配置文件以及相关注解等信息。

XMLConfigBuilder,配置解析类,继承的BaseBuilder里面Configuration(解析后的配置),TypeAliasRegistry,TypeHandlerRegistry的都是全局唯一的。

  • parse() 方法是解析的核心方法,对每个标签都写了对应的方法解析。
    • propertiesElement方法会解析 mybatis-config.xml 配置 文件中的
    • settingsAsProperties方法会解析 标签,并检测每个setting在 Configuration 都有对应的set方法
    • pluginElement,对应的插件放在list里(责任链)
    • environmentsElement,可以配置多个环境,但Mybatis只选择一个环境
    • mapperElement,常用的是 package,直接扫描一个包下的(用注解的builder解析,但是该解析器也会先尝试用 XMLMapperBuilder 解析)。也可以指定 resource,url,class单独导入。
      • XMLMapperBuilder 类,解析 Mapper 文件
        • resultMapElements,将原来需要通过jdbc代码方式来映射result,改为配置方式
      • ResultMap 用来承接
      • ResultMapping,用来承接 里的数据。
        • 对于 association 就是再解析出一个 ResultMap,然后把 id 关联到外层的 ResultMap
    • sqlElement,解析 片段,加到map里存着而已
    • buildStatementFromContext,解析crud的sql语句
      • XMLStatementBuilder:parseStatementNode方法
        • MappedStatement 用来承接解析出的sql
          • XMLIncludeTransformer 处理 会先拿 config 里的 properties 把占位符替换了,再去拿对应的 sql 节点(深拷贝)来递归解析
        • processSelectKeyNodes,解析
        • SqlSource,解析完 include 和 selectKey 之后,解析具体的 sql。里面对动态sql的解析也是调用了 handler 来解决的
        • 优先使用 selectKey,没有才看是否有 useGeneratedKeys
    • parsePending系列方法,如parsePendingResultMaps,会把没法解析的再次解析试试

SqlSource 体系
MyBatis_第2张图片
最终会解析成 StaticSqlSource。

DynamicContext:主要用于记录解析动态 SQL 语句之后产生的 SQL 语句片段

  • appendSql(String sql)
  • getSql()

基础支持层

一些非核心功能,但又是必要的模块。

解析器模块

MyBatis 对 XML 的解析是用 DOM + XPath 的方式。

org.apache.ibatis.parsing 包下

XPathParser:就是一个解析 XML 的工具类。其实也没啥,就是做了:

  1. 在构造方法里,设置了 entityResolver,并创建了 Document 对象
  2. 在 evalString 方法里,基于 Properties 的值做占位符的替换

GenericTokenParser:就是找到指定占位符,具体解析交给其持有的 Handler

XNode:Node 类和 XPathParser 类的包装类

反射工具箱

类型转换

org.apache.ibatis.type

这里可以说是 ORM 的精髓了,实现了 javaType 与 jdbcType 之间的相互映射关系。JdbcType 是把 java.sql.Types 弄成枚举类,再增加一些其他数据库的定义。

操作了底层的 PreparedStatement CallableStatement ResultSet,是对其包装。
MyBatis_第3张图片
TypeHandler:顶级接口,定义了javaType与jdbcType互相转换的方法。setParameter方法负责将Java Type => JDBC Type,getResult系列方法负责将JDBC Type => Java Type。

设计模式:模板方法
顶级设计
TypeReference:提供了成员变量 rawType,代表泛型 T 真正的类
BaseTypeHandler:对几个方法定义了默认的try catch,具体由子类实现

MyBatis_第4张图片
BaseTypeHandler 的子类一大堆,基本都是直接调用底层的方法set/get,如setInt/getInt。
MyBatis_第5张图片
TypeHandlerRegistry
用于管理 TypeHandler。

TypeHandler 注册:

  • 在构造函数里会把已知的 TypeHandler 注册到 TypeHandlerRegistry 里,由其管理。
  • TypeHandlerRegistry 提供了各种注册方法,基本最终会走到register(Type javaType, JdbcType jdbcType, TypeHandler handler),其中有个包下所有类的注册方法register(String packageName),可以看一眼(ResolverUtil也很有趣)。

TypeHandler查找

  • getTypeHandler(Type type, JdbcType jdbcType) ,会获得 Java Type 对应的 TypeHandler 集合getJdbcHandlerMap(Type type)(这个方法会一直找到父类、枚举类) ,有自己的一套get规则。

用户是怎么做到对 TypeHandler 扩展?
我们也可以添加自定义的 TypeHandler接口实现,添加方式是在 mybatis-config 配置文件中的 typeHandlers 节点下, 加相应的

TypeAliasRegistry
用于加上各种类的别名,方便我们使用。

日志模块

资源加载

数据源连接池

一般用其他连接池中间件。
MyBatis_第6张图片连接数据库,需要有驱动(JDBC已经注册到 DriverManager 里面,直接用即可),还需要用户名、密码、URL。

UnpooledDataSourceFactory 为例,把解析好的 properties 设置到metaDataSource里了。
UnpooledDataSource,重点是getConnection方法的实现,实质上就是拿到 JDBC 注册的驱动,如果没有就自己实例化一个(他的 DriverProxy 更像是装饰者)。

PooledDataSourceFactory 继承自 UnpooledDataSourceFactory,除了构造 PooledDataSource 以外其他都一样。
PooledDataSource,核心就是PoolState里的List代表连接池,PooledConnection代表一个连接(是JDK动态代理InvocationHandler,把真正的close方法代理为push回list里)

事务

一般用 spring 的事务了。

MyBatis_第7张图片

binding 模块

用于将 Mapper 接口与 xml 定义的 SQL 语句关联,便于在编译期就可以发现错误。

MyBatis_第8张图片

MapperRegistry,就是Mapper的注册中心,在启动时,会将Mapper通过addMapper(Class type)注册到该对象的Map, MapperProxyFactory> knownMappers里。MapperProxyFactory,用于生成Mapper文件动态代理MapperProxy。SqlSession的getMapper最终也是调用MapperRegistry的getMapper。在MapperProxy的invoke里,会通过MapperMethod的execute(SqlSession sqlSession, Object[] args)执行相应的方法。

MapperMethod,包含了 SqlCommand和MethodSignature,核心是execute(SqlSession sqlSession, Object[] args) 方法(负责完成数据库各项操作)

  • SqlCommand:标识了方法类型和限定名,并可以找到对应的MappedStatement(就是XML中的SQL)
  • MethodSignature:
    • 构造方法对返回值等各种属性做了解析
    • ParamNameResolver :处理Mapper中定义的方法参数列表,有@Param就用@Param指定,没有@Param就用参数索引(有RowBounds和ResultHandler是不会被记录,会导致参数索引和名称不一致)
      • aMethod(@Param(“M”) int a, @Param(“N”) int b) -> {{0, “M”}, {1, “N”}}
        aMethod(int a, int b) -> {{0, “0”}, {1, “1”}}
        aMethod(int a, RowBounds rb, int b) -> {{0, “0”}, {2, “1”}}

      • 构造方法ParamNameResolver(Configuration config, Method method)搞定上面的逻辑
      • getNamedParams(Object[] args)用于构造参数名和实际用户传入的值

缓存模块

你可能感兴趣的:(mybatis,mybatis,java,spring)