Mybatis的执行原理

前置知识

JDBC

要说这个首先要知道JDBC的原理:首先JDBC其实也是远程调用的一种应用,然后Sun公司已经提供好对应的API了,然后JDBC访问数据库编码步骤就是先加载驱动Driver,然后创建数据库链接Connection,然后创建一个发送Sql的发送器Statement,然后通过Statement发送sql语句,然后Statement返回结果集,然后处理然后关闭资源就好了。

反射

Java 反射的实现依赖于 JVM 的类加载机制运行时的元数据存储

  1. 类加载过程
  • 当 JVM 加载一个类时,会创建一个对应的 Class 对象。
  • 这个 Class 对象包含了类的所有元信息:字段、方法、构造器、注解、父类、接口等。
  • 这些信息存储在 JVM 的 方法区(Method Area)元空间(Metaspace,JDK 8+)
  1. 反射如何工作
  • Class.forName() 触发类加载,返回 Class 实例。
  • 通过 Class 实例可以获取 FieldMethodConstructor 等对象。
  • 调用 invoke()set() 等方法时,JVM 会通过 JNI(Java Native Interface)调用底层 C++ 代码,动态执行对应的方法或字段操作。

静态代理

image-20251013042305051

一个租房子问题,中介就是这个原理。

缺点代理需要手动创建,简单来说就是房主和中介都要实现租房接口,这个租房接口规定了租房的规范,然后消费者只用调用中介的方法就能实现中介的很多方法并且最终实际租房的时候还是走的房东的租房方法,只是多加了很多中介的额外方法。

但是每次想要租房都得自己手动new一个中介出来。

动态代理

主要了解JDK动态代理,这个动态代理无需导入jar包,然后步骤上还是先定义好租房接口,也就是租房规则,然后房主要实现接口,这里有个区别了,这里的中介是万能中介,能代理任何出租方,出租房屋,汽车什么都行,怎么实现呢?

这里需要有一个动态中介处理器xxxHandler,这个类要有真是的代理对象然后实现InvocationHandler接口实现invoke方法,这样就能反射了,invoke方法里面传入代理对象也就是房主,然后方法,也就是rent方法,然后就是方法调用传入的参数值,然后这个invoke方法里面调用房东的租房方法比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.reflect.*;

public class DynamicAgentHandler implements InvocationHandler {
private Object target; // 被代理的真实对象(房东、公寓公司等)

public DynamicAgentHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tenant = (String) args[0];
double budget = (double) args[1];

System.out.println("【动态中介】欢迎 " + tenant + ",正在为您查找合适房源...");
System.out.println("【动态中介】验证您的信用和支付能力...");

// 调用真实对象的方法(房东、公寓公司等)
Object result = method.invoke(target, args);

System.out.println("【动态中介】合同已生效,收取中介费600元!");
System.out.println("【动态中介】后续服务(保洁、维修)可升级会员!");

return result;
}
}

最后消费者通过调用动态中介来租房

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class XiaoMingWithDynamicAgent {
public static void main(String[] args) {
// 1. 真实房东
Landlord landlord = new Landlord();

// 2. 创建“动态中介”代理
InvocationHandler handler = new DynamicAgentHandler(landlord);
RentService agent = (RentService) Proxy.newProxyInstance(
landlord.getClass().getClassLoader(), // 类加载器
landlord.getClass().getInterfaces(), // 代理 RentService 接口
handler // 中介逻辑
);

// 3. 小明通过动态中介租房
agent.rentHouse("小明", 5800);
}
}

流程

首先是初始化阶段,在这个阶段首先会加载配置文件,这里是先加载mybatis-config.xml全局配置文件和Mapper XML文件,比如xxxMapper.xml这种,这些文件包含了数据库链接信息,书屋管理器配置,SQL映射语句等关键信息。然后创建Configuration全局配置对象 在加载全局配置文件的过程中,会初始化Configuration对象,这个对象是全局配置对象,拥有MyBatis运行的所有配置信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>

然后注册Mapper接口 在创建好全局配置对象后,MyBatis会开始扫描Mapper xml文件或者注解,根据其中的信息,注册Mapper接口,这个步骤不会直接创建Mapper接口的实例,而是创建了MapperProxyFactory实例,并将他们和Mapper接口关联起来,存储在全局配置对象的某个结构中,当需要执行某个Mapper接口中的方法是,MyBatis会使用对应的MapperProxyFactory实例来创建Mapper接口的代理对象MapperProxy。

这里就扫描了UserMapper.xml这个文件了

1
2
3
4
5
6
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>

然后创建MappedStatement对象 在扫描Mapper XML文件或者注解的时候,对于Mapper XML文件中的每个SQL映射语句 select \ insert 这些,MyBatis会解析内容,并且创建MappedStatement对象。这些对象包含执行SQL语句所需要的所有信息,比如SQL语句本身,参数类型,返回类型等。这些对象会被添加到全局配置对象的映射器配置中。

类比JDBC的Statement

然后创建SqlSessionFactory 完成这些后,全局配置对象来创建SQL会话工厂实例,这个工厂用于生成会话实例,进而执行SQL语句。

然后执行阶段

然后是获取SqlSession阶段创建SqlSession,通过调用SqlSessionFactory的openSession方法,可以获得一个Sql会话实例,SqlSession是MyBatis提供的操作数据库的主要接口,它包含了执行SQL语句,管理事务和获取Mapper代理对象等方法。

然后是获取Mapper代理对象MapperProxy

当调用SqlSession的getMapper方法时,MyBatis会根据传入的Mapper接口类型,从MapperRegistry中找到对应的MapperProxyFactory实例。然后通过该工厂实例创建一个MapperProxy对象作为Mapper接口的代理对象。

然后是执行MapperProxy中的方法

调用Mapper方法,当通过Mapper接口代理对象MapperProxy调用某个方法时,实际上是调用了MapperProxy的invoke方法。实际上这个就方便我们只要写好xxxmapper.xml就能执行我们自定义好的sql语句了。然后那些连接所有的操作都是动态代理完成的,不用我们担心,JDBC也蕴含在其中。

查找MappedStatement,在invoke方法内部,MyBatis会根据传来的方法名和参数类型,从Configuration对象中查找到对应的MappedStatement对象。

创建Executor ,MyBatis会根据当前事务的状态和配置信息,创建一个Executor执行器对象,它负责处理SQL语句的编译、缓存、执行以及结果映射等工作。

执行SQL语句,这个就跟JDBC一样了,Executor获取连接池或JDBC中的数据库链接Connection创造Statement对象,使用Statement对象的execute方法执行SQL语句,通过resultSetHandler获取到执行结果,然后将结果映射到Java对象中。