mybatis的执行流程
MyBatis 的执行流程主要包括 SQL 解析、参数绑定、执行查询/更新、结果映射等几个步骤。下面详细解释每个步骤的执行流程:
1. 加载配置文件和映射文件
- 加载 MyBatis 配置文件:启动时,MyBatis 通过
SqlSessionFactoryBuilder
读取并解析核心配置文件(mybatis-config.xml
)和映射文件(*.xml
)。 - 创建
SqlSessionFactory
:通过解析配置文件,MyBatis 会构建SqlSessionFactory
对象,该对象负责生成SqlSession
。
2. 创建 SqlSession
- 创建
SqlSession
:通过SqlSessionFactory
获取SqlSession
对象。SqlSession
是 MyBatis 的核心接口之一,负责执行 SQL 语句、获取映射器、提交事务等。 - 绑定 Mapper 接口:
SqlSession
会根据开发者定义的接口,创建与之对应的 Mapper 动态代理对象,负责处理 SQL 操作。
3. 执行 SQL 语句
- 调用 Mapper 接口方法:当调用 Mapper 接口的方法时,MyBatis 会找到与接口方法对应的 SQL 语句(Mapper XML 文件或注解方式定义的 SQL)。
- 动态生成 SQL 语句:根据方法传递的参数,MyBatis 会动态生成 SQL 语句(包括
#{} 或 ${}
占位符的替换)。#{} 占位符
:采用预编译方式,防止 SQL 注入。${}
占位符:直接替换为字符串,不会进行预编译。
4. 参数处理
ParameterHandler
参数处理器:MyBatis 内部使用ParameterHandler
将传递的参数与 SQL 中的占位符进行绑定,并处理各种参数类型(如基本类型、JavaBean、Map、List等)。
5. SQL 执行
Executor
执行器:MyBatis 中的执行器(SimpleExecutor
、ReuseExecutor
、BatchExecutor
等)负责执行 SQL 语句。执行器负责与数据库进行交互,并处理缓存等事务。- 二级缓存:在执行 SQL 之前,MyBatis 会先检查二级缓存,如果缓存中有结果,则直接返回缓存数据;否则继续查询数据库。
6. 映射结果
ResultSetHandler
结果集处理器:SQL 查询结果返回后,ResultSetHandler
会将查询结果集映射到 Java 对象。它会根据配置的映射规则(如 XML 文件中的resultMap
,或通过注解的方式)将结果映射成对象。- 类型转换:MyBatis 支持多种数据类型的转换,如将数据库中的字段映射为 JavaBean 中的属性。
7. 事务管理
- 手动提交和自动提交:默认情况下,MyBatis 会自动提交事务;如果配置为手动提交,则需要调用
commit()
方法来提交事务。 - 事务控制:MyBatis 通过
Transaction
接口管理事务,包括事务的提交、回滚等操作。
8. 关闭 SqlSession
- 资源释放:在执行完操作后,必须关闭
SqlSession
,以释放数据库连接资源。SqlSession
的生命周期由开发者管理,建议使用try-finally
结构来确保关闭。
MyBatis 执行流程图(可视化)
graph TD;
A[加载MyBatis配置文件] --> B[创建SqlSessionFactory];
B --> C[获取SqlSession];
C --> D[调用Mapper接口方法];
D --> E[动态生成SQL];
E --> F[参数绑定];
F --> G[执行SQL];
G --> H[检查二级缓存];
H --> I[查询数据库];
I --> J[映射查询结果];
J --> K[返回结果到调用者];
C --> L[提交或回滚事务];
K --> M[关闭SqlSession];
总结
- 核心对象:
SqlSessionFactory
、SqlSession
、Executor
。 - 核心操作:参数绑定、执行 SQL、结果映射、事务管理。
MyBatis延迟加载使用以及原理
MyBatis 的延迟加载(Lazy Loading)是指当需要用到某些数据时,才执行对应的 SQL 语句去查询数据库。这种机制可以有效减少不必要的数据库查询,提高系统性能。
1. MyBatis 延迟加载的使用
延迟加载的应用场景通常出现在一对一或一对多关系的查询中。例如,查询订单时,每个订单可能会包含多个商品,但你可能只想在需要查看商品时才加载它们,而不是每次查询订单都加载所有商品数据。
配置方式
在 MyBatis 的核心配置文件 mybatis-config.xml
中,可以通过以下配置来启用延迟加载功能:
<configuration>
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 代理所有属性,延迟加载时一次性加载所有 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
lazyLoadingEnabled
:启用延迟加载。aggressiveLazyLoading
:如果设置为false
,则只会在访问某个懒加载属性时才加载该属性的数据;如果设置为true
,一旦访问一个延迟加载的属性,所有延迟加载的属性都会被加载。
示例:一对多关系(订单和商品)
假设你有一个 Order
和 Product
的一对多关系,Order
中包含一个 List<Product>
。
SQL Mapper 映射:
<resultMap id="orderMap" type="com.example.Order">
<id column="order_id" property="id"/>
<result column="order_name" property="name"/>
<!-- 多对一映射(延迟加载) -->
<collection property="products" ofType="com.example.Product"
select="selectProductsByOrderId" lazy="true"/>
</resultMap>
<!-- 查询订单 -->
<select id="selectOrderById" resultMap="orderMap">
SELECT * FROM orders WHERE order_id = #{id}
</select>
<!-- 查询订单的商品(延迟加载部分) -->
<select id="selectProductsByOrderId" resultType="com.example.Product">
SELECT * FROM products WHERE order_id = #{id}
</select>
Java 类:
public class Order {
private Integer id;
private String name;
// 一对多关系,商品列表
private List<Product> products;
// getter and setter
}
public class Product {
private Integer id;
private String productName;
private Double price;
// getter and setter
}
2. MyBatis 延迟加载的原理
MyBatis 延迟加载的实现依赖于Java 的动态代理机制。在加载 Order
对象时,MyBatis 不会立刻查询 products
数据,而是为 products
属性创建一个代理对象(Proxy
),这个代理对象会记录当前对象的状态和代理方法的调用。
延迟加载的步骤:
-
第一次查询: 当调用
selectOrderById
查询Order
时,MyBatis 只会执行订单表的查询,将products
属性用代理对象代替,但不会立即查询products
表中的数据。 -
访问延迟加载属性: 当你调用
order.getProducts()
时,MyBatis 会通过代理对象检测到该属性被调用,从而触发第二次查询,执行selectProductsByOrderId
语句查询商品数据。 -
加载数据: 查询结果被返回并填充到
products
列表中。
工作原理:
- MyBatis 通过使用 CGLIB 或 JDK 动态代理,为延迟加载的属性生成代理对象。当延迟加载的属性被调用时,代理对象会执行一个回调,动态加载所需的数据。
- 代理模式 是延迟加载的核心。
Proxy
对象会在调用目标属性时检查属性的加载状态,未加载时会触发查询并加载数据。
延迟加载控制的要点:
- 延迟加载需要在事务中使用,因为需要在对象的生命周期内保持数据库会话的连接状态。
- 只有在属性被真正调用时,才会触发延迟加载。
注意事项:
- 如果你在 MyBatis 配置文件中将
aggressiveLazyLoading
设置为true
,一旦加载了某个延迟属性,所有延迟属性都会加载。这个配置适合当你需要尽快获取所有关联对象时。
3. 延迟加载和立即加载的对比
- 延迟加载:只有在需要用到相关数据时,才会查询数据库并加载,适合处理关系型数据或减少不必要的查询。
- 立即加载:一次性查询出所有关联数据,适合那些频繁需要访问的关联对象,避免后续多次查询数据库。
4. 总结
- 延迟加载在查询一对多、多对一时非常有用,可以避免不必要的数据库查询。
- MyBatis 通过动态代理机制实现延迟加载,当你访问某个延迟加载的属性时,才会触发相应的 SQL 查询。
- 通过
lazyLoadingEnabled
和aggressiveLazyLoading
配置项,可以控制 MyBatis 延迟加载的行为。
MyBatis一级二级缓存
在 MyBatis 中,缓存是为了减少数据库查询的次数,提升性能。MyBatis 提供了两级缓存机制:一级缓存和二级缓存。
1. 一级缓存
一级缓存是SqlSession 级别的缓存,它的作用范围仅限于同一个 SqlSession
对象。在同一个 SqlSession
中,多次查询同一个数据时,MyBatis 会将查询结果存储到缓存中,之后的相同查询就可以直接从缓存中获取结果,而不再执行 SQL 查询。
一级缓存的特点:
- 默认开启:不需要配置,它会自动在
SqlSession
中工作。 - 作用范围:仅限于同一个
SqlSession
,当SqlSession
关闭后,一级缓存也会被清除。 - 缓存机制:以 statement ID(SQL 语句的唯一标识) 和 查询参数 作为缓存的 key,查询结果作为 value。
- 失效条件:
- 如果执行了
INSERT
、UPDATE
、DELETE
操作,一级缓存会被清空,因为数据发生了变化。 - 手动清除缓存,例如调用
sqlSession.clearCache()
方法。 SqlSession
关闭时,一级缓存也会失效。
- 如果执行了
一级缓存的示例:
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第一次查询
User user1 = sqlSession.selectOne("com.example.mapper.UserMapper.selectUser", 1);
System.out.println(user1); // 查询数据库并缓存结果
// 第二次查询相同数据
User user2 = sqlSession.selectOne("com.example.mapper.UserMapper.selectUser", 1);
System.out.println(user2); // 从缓存中获取结果,不查询数据库
2. 二级缓存
二级缓存是Mapper 级别的缓存,它的作用范围是同一个 Mapper 映射文件,即相同的 Mapper
共享二级缓存。二级缓存是一个跨 SqlSession
的缓存,它允许不同的 SqlSession
共享缓存,提高数据查询的效率。
二级缓存的特点
- 需要手动开启:二级缓存默认是关闭的,必须在
MyBatis
的配置文件或Mapper
映射文件中进行配置才能使用。 - 作用范围:整个
Mapper
级别,不同的SqlSession
可以共享相同的缓存数据。 - 缓存机制:与一级缓存类似,使用 statement ID 和 查询参数 作为 key,查询结果作为 value 存入缓存中。
- 失效条件:
- 执行
INSERT
、UPDATE
、DELETE
操作时,二级缓存会失效。 - 手动清除缓存或配置的缓存清理策略。
- 配置了缓存的过期时间,超时后缓存失效。
- 执行
开启二级缓存的步骤
-
在核心配置文件中启用二级缓存:
<configuration>c
<settings>
<!-- 启用全局二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
2. 在 Mapper 映射文件中启用二级缓存: 在对应的 Mapper.xml
文件中,添加如下配置:
<cache/>
3.使用 Serializable
接口: 由于二级缓存中的对象是以序列化的形式存储的,所有被缓存的对象必须实现 Serializable
接口。
二级缓存的示例
-
Mapper 映射文件中启用二级缓存:
<mapper namespace="com.example.mapper.UserMapper">
<cache/>
<select id="selectUser" parameterType="int" resultType="com.example.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
Java 代码示例:
SqlSession sqlSession1 = sqlSessionFactory.openSession();
User user1 = sqlSession1.selectOne("com.example.mapper.UserMapper.selectUser", 1);
sqlSession1.close(); // 查询结果存入二级缓存
SqlSession sqlSession2 = sqlSessionFactory.openSession();
User user2 = sqlSession2.selectOne("com.example.mapper.UserMapper.selectUser", 1);
sqlSession2.close(); // 直接从二级缓存中获取结果,不再查询数据库
3. 一级缓存与二级缓存的区别
4. 二级缓存的实现原理
二级缓存是基于持久化的缓存机制实现的,缓存数据可以存储到磁盘或内存中。MyBatis 通过 Cache
接口提供二级缓存的基本操作,开发者可以自定义缓存实现。常见的实现有:
- PerpetualCache:MyBatis 默认的缓存实现,使用
HashMap
存储数据。 - LRU (Least Recently Used):最近最少使用算法。
- FIFO (First In First Out):先进先出算法。
- Soft Cache:基于 Java 的软引用,缓存会在内存不足时自动清除。
- Weak Cache:基于 Java 的弱引用,缓存会在下一次 GC 时自动清除。
5. 总结
- 一级缓存 是 MyBatis 默认开启的
SqlSession
级别的缓存,在同一个SqlSession
中重复查询相同数据时,使用缓存。 - 二级缓存 是
Mapper
级别的缓存,需要手动配置,可以在不同的SqlSession
中共享查询结果。 - 二级缓存需要开发者确保数据的一致性,因为二级缓存跨
SqlSession
共享,可能导致旧数据在缓存中被使用。