Mybatis 实现原理

通过功能猜测源码,比通过源码猜测功能要简单多了
不要为了看源码而看源码,源码会告诉你如何做,但不会告诉你为什么这样做
了解意识到,是两个概念

相关术语

exploring-mapped-sql-statements
mapper:名词,映射器
mapped:映射(ed 结尾,可做动词和形容词,而这里应该是用作形容词,修饰 sql-statements )
statements 有两种定义:XML、注解,而这里使用了 XML 定义 SQL 语句,如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.IUserService">
    <select id="queryNameById" parameterType="java.lang.Integer" resultType="java.lang.String">
    	SELECT name FROM user WHERE id = #{id}
    </select>
    <select id="queryAgeById" parameterType="java.lang.Integer" resultType="java.lang.String">
    	SELECT age FROM user WHERE id = #{id}
    </select>
</mapper>

映射,在数学中指:两个集合内,元素一一对应,如 y = x + 1 ( x = 1  对应  y = 2 , x = 2  对应  y = 3 ) y = x + 1(x = 1 ~对应~ y = 2,x = 2 ~ 对应 ~ y = 3) y=x+1x=1 对应 y=2x=2 对应 y=3
映射语句:与数据库的 SQL 语句进行映射的语句,比如这里的 <select> 标签
cn.IUserService.queryNameById 对应 SELECT name FROM user WHERE id = #{id}
cn.IUserService.queryAgeById 对应 SELECT age FROM user WHERE id = #{id}

为什么要将 SQL 放到 XML 文件中?
方便对 SQL 语句的统一管理

大多数 ORM 框架,会将 Java 对象数据库表 关联起来
Mybaits 没有这样做,而是是将 Java 方法SQL语句 关联起来
使得可以直接在映射文件中,编写近乎原生的 SQL 语句

ORM (Object-Relational Mapping):一种技术,允许你使用面向对象的方式,从数据库中查询和操作数据

那么
Java 是如何调用映射文件内的 SQL 语句呢?
通过映射文件内的 mapper 标签中的 namespace 属性,可以找到一个java文件,该文件是个接口,也是调用 SQL 语句的入口

package cn
public interface IUserService {
    String queryNameById();
    String queryAgeById();
}

这种接口(称为映射接口文件)不需要具体的实现类,Mybatis 会通过代理的方式,将接口内的方法 XML文件内的语句相互关联

具体使用有两种方式

// 第一种方式:类似于反射中的 Class.form,缺点:1 全限定名,容易拼错 2 无 IDE 自动补全,错误不易发现
String name1 = sqlSession.selectOne("cn.IUserService.queryNameById", 1);
// 第二种方式,有 IDE 的智能提示,推荐
IUserService userService = sqlSession.getMapper(IUserService.class);
String name2 = userService.queryNameById(1);

模拟接口与XML文件进行关联

参考:Mybatis 手撸专栏

这里假设:解析映射文件,将解后的内容放入 HashMap 中,其中:key = namespace + id, value = SQL 语句

HashMap<String, String> mapper = new HashMap<>();
mapper.put("cn.zhang.IUserService.queryNameById", "SELECT name FROM user WHERE id = 1");
mapper.put("cn.zhang.IUserService.queryAgeById", "SELECT age FROM user WHERE id = 1");

现在我们有:(1)接口 (2)Map
那么,现在想办法,如何将映射接口文件和 mapper 联系起来呢?
⭐️代理
创建代理对象的代理类中,必定要包含(1)接口 (2)Map
这个代理类就是MapperProxy,如图
在这里插入图片描述

public class MapperProxy implements InvocationHandler {
    private Class mapperInterface;
    private HashMap<String, String> mapper;
    public MapperProxy(Class mapperInterface, HashMap<String, String> mapper) {
        this.mapperInterface = mapperInterface;
        this.mapper = mapper;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return mapper.get(mapperInterface.getName() + "." + method.getName());
    }
}

通过代理类,将接口和 XML 文件联系起来

测试如下

// 模拟解析 XML 之后的形式
HashMap<String, String> mapper = new HashMap<>();
mapper.put("cn.zhang.IUserService.queryNameById", "SELECT name FROM user WHERE id = 1");
mapper.put("cn.zhang.IUserService.queryAgeById", "SELECT age FROM user WHERE id = 1");
MapperProxy mapperProxy =  new MapperProxy(IUserService.class, mapper);
IUserService o = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, mapperProxy);
System.out.println(o.queryNameById());		// SELECT name FROM user WHERE id = 1

测试的时候,每次都要调用 Proxy.newProxyInstance,很麻烦;于是,封装到 MapperProxy 中

public Object getProxyObject(){
    return Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, this);
}

// 之后就可以
MapperProxy mapperProxy =  new MapperProxy(IUserService.class, mapper);
IUserService o = (IUserService) mapperProxy.getProxyObject();
System.out.println(o.queryNameById());		// SELECT name FROM user WHERE id = 1

MapperProxyFactory

相同的IDAO接口,可有不同的实现方式,甚至,所使用的数据库也可能不同

因此,干脆将IDAO封装到一个类中,之后通过 newInstance(xmlFIle) 的方式生成 MapperProxy 代理对象

另外,Proxy.newProxyInstance 的逻辑也封装到 newInstance 中

这个类就是MapperProxyFactory

在这里插入图片描述

class MapperProxyFactory<T> {
    private Class<T> mapperInterface;
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    public T newInstance(HashMap<String, String> mapper) {
        final MapperProxy mapperProxy = new MapperProxy(mapperInterface, mapper);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
}

使用如下

IUserService o = new MapperProxyFactory<>(IUserService.class).newInstance(mapper);
System.out.println(o.queryNameById());

第2章:创建简单的映射器代理工厂

在这里插入图片描述

MethodCache

其实在源码中,MapperProxy 一共有三个属性

private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

我们已经知道,一个 MapperProxy,对应一个 mapperInterface
但其实, methodCache 是一个Map,该Map下的值: MapperMethod, 对应接口方法的具体实现
一个 MapperMethod,对应一个接口中的方法

在这里插入图片描述

当第一次调用某个方法的时候,比如userService.queryNameById(),会先尝试从methodCache(方法缓存)中获取,如果获取失败,会构造 MapperMethod 并存入methodCache

methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

构造 MapperMethod 的过程中

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  this.command = new SqlCommand(config, mapperInterface, method);
  this.method = new MethodSignature(config, mapperInterface, method);
}

在执行 new SqlCommand的时候,会将当前 接口 + 方法作为参数,向 configuration 中(解析的 xml 信息会存入其中)查询是否有对应 SQL 语句:即最终执行的是 mappedStatements.get(mapperInterface.getName() + "." + method.getName())
如果 get 失败,会报错,因为这说明该接口的方法,没有对应 xml SQL 语句:Invalid bound statement (not found)

在这里插入图片描述

MapperRegistry 及 SqlSession 封装

MapperRegistry

MapperProxyFactory 只是针对一个IDAO接口,并不能添加、删除;那么,就必定需要一个集中式管理的类
这个类就是 MapperRegistry,相当于封装了多个 IDAO 接口,用于集中式管理IDAO接口

SqlSession

之前定义的 sqlsession (xmlFile)为一个 HashMap,现将其封装成一个对象,并由 SqlSessionFactory 构造,该工厂持有 MapperRegistry(封装多个IDAO服务)
通过该工厂,可创建 SqlSession,但此时 SqlSession 中未定义具体实现

为何要创建一个 SqlSessionFactory ?直接 new SqlSession 不行吗?

一般,使用 new 创建对象,适用于创建一些简单的对象
如果要创建一个复杂的对象,建议使用工厂方法,优点如下

  1. 语义清晰
  2. 代码整洁,可使业务逻辑和技术细节相分离

第3章:实现映射器的注册和使用

在这里插入图片描述

一些术语解释

01_SqlSession 负责连接

Mybatis 中的 SqlSession 是什么

SqlSession 是 Mybatis 中的一个接口,用于

  • 管理数据库连接 🔗⭐⭐⭐
  • 提供操作数据库的方法 👨‍🏭
    核心的类之一⭐,负责与数据库进行交互,执行 SQL 语句并返回结果

在使用 Mybatis 进行数据访问时,首先需要创建 SqlSession 对象
SqlSession 对象的创建通常是通过 SqlSessionFactory 工厂类的 openSession 方法完成的

SqlSession 对象可以使用该方法创建一个新的实例,这个实例是一个完全独立的、和线程安全的会话。SqlSession 会维护一个独立的数据库连接👨‍🏭,负责与数据库进行交互

02_Executor 负责执行

作用

Executor 为一个接口
用于执行数据库操作的方法

Executor 负责

  • 管理和执行 SQL 语句
  • 处理输入参数、结果集
  • 提供缓存功能

三种类型

  • SimpleExecutor
    • 每次执行 SQL 语句时,都会创建一个新的 Statement 对象,执行完毕后立即关闭该对象
    • 因此,SimpleExecutor 是一种非常简单的 Executor,它不会进行缓存操作。
  • ResueExecutor
    • 在执行 SQL 语句时,会检查缓存中是否已经存在该 SQL 语句的 Statement 对象,如果存在就重用该对象
    • 这种方式可以减少 Statement 对象的创建和销毁操作,提高性能
  • BatchExecutor
    • 用于批量执行 SQL 语句的
    • 将多条 SQL 语句合并成一个批处理操作,一次性执行
    • 这种方式可以减少数据库连接Statement 对象的创建和销毁操作,提高性能

Executor 接口提供的方法

  1. update:用于执行 insert、update、delete 等更新操作。
  2. query:用于执行 select 查询操作。
  3. flushStatements:用于将缓存的 SQL 语句刷入到数据库中
  4. commit 和 rollback:用于提交和回滚事务

03_MappedStatement 负责存储

用于保存 SQL 语句其它相关的配置信息
用于和其它一些类一起,负责解析映射文件、生成执行 SQL 语句的代码

MappedStatement 对应 Mybatis 映射文件中的一个 select、insert、update、delete 等节点

  • 保存 SQL 语句
  • 输入参数
  • 输出结果类型
  • 缓存配置信息

Mybatis 通过这种方式,实现了将 SQL 语句Java 代码解耦,方便开发人员进行 SQL 语句的维护和修改

public class MappedStatement {
    // SQL 语句
    private String sql;
    // 输入参数类型
    private Class<?> parameterType;
    // 输出结果类型
    private Class<?> resultType;
    // 缓存配置信息
    private Cache cache;

	// MappedStatement 提供了一个 execute 方法,用于执行 SQL 语句,并返回结果
    public <T> List<T> execute(SqlSession sqlSession, Object parameter) {
        List<T> result = sqlSession.selectList(sql, parameter);  // 执行 SQL 语句
        return result;  // 返回查询结果
    }
    
    // getter 和 setter 方法
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值