关于MyBatis的一些面试题

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 中的执行器(SimpleExecutorReuseExecutorBatchExecutor 等)负责执行 SQL 语句。执行器负责与数据库进行交互,并处理缓存等事务。
  • 二级缓存:在执行 SQL 之前,MyBatis 会先检查二级缓存,如果缓存中有结果,则直接返回缓存数据;否则继续查询数据库。

6. 映射结果

  • ResultSetHandler 结果集处理器 :SQL 查询结果返回后,ResultSetHandler 会将查询结果集映射到 Java 对象。它会根据配置的映射规则(如 XML 文件中的 resultMap,或通过注解的方式)将结果映射成对象。
  • 类型转换:MyBatis 支持多种数据类型的转换,如将数据库中的字段映射为 JavaBean 中的属性。

7. 事务管理

  • 手动提交和自动提交 :默认情况下,MyBatis 会自动提交事务;如果配置为手动提交,则需要调用 commit() 方法来提交事务。
  • 事务控制 :MyBatis 通过 Transaction 接口管理事务,包括事务的提交、回滚等操作。

8. 关闭 SqlSession

  • 资源释放 :在执行完操作后,必须关闭 SqlSession,以释放数据库连接资源。SqlSession 的生命周期由开发者管理,建议使用 try-finally 结构来确保关闭。

MyBatis 执行流程图(可视化)

Dart 复制代码
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];

总结

  • 核心对象SqlSessionFactorySqlSessionExecutor
  • 核心操作:参数绑定、执行 SQL、结果映射、事务管理。

MyBatis延迟加载使用以及原理

MyBatis 的延迟加载(Lazy Loading)是指当需要用到某些数据时,才执行对应的 SQL 语句去查询数据库。这种机制可以有效减少不必要的数据库查询,提高系统性能。

1. MyBatis 延迟加载的使用

延迟加载的应用场景通常出现在一对一一对多关系的查询中。例如,查询订单时,每个订单可能会包含多个商品,但你可能只想在需要查看商品时才加载它们,而不是每次查询订单都加载所有商品数据。

配置方式

在 MyBatis 的核心配置文件 mybatis-config.xml 中,可以通过以下配置来启用延迟加载功能:

XML 复制代码
<configuration>
    <settings>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 代理所有属性,延迟加载时一次性加载所有 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
</configuration>
  • lazyLoadingEnabled:启用延迟加载。
  • aggressiveLazyLoading:如果设置为 false,则只会在访问某个懒加载属性时才加载该属性的数据;如果设置为 true,一旦访问一个延迟加载的属性,所有延迟加载的属性都会被加载。

示例:一对多关系(订单和商品)

假设你有一个 OrderProduct 的一对多关系,Order 中包含一个 List<Product>

SQL Mapper 映射:

XML 复制代码
<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 类:

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),这个代理对象会记录当前对象的状态和代理方法的调用。

延迟加载的步骤:
  1. 第一次查询 : 当调用 selectOrderById 查询 Order 时,MyBatis 只会执行订单表的查询,将 products 属性用代理对象代替,但不会立即查询 products 表中的数据。

  2. 访问延迟加载属性 : 当你调用 order.getProducts() 时,MyBatis 会通过代理对象检测到该属性被调用,从而触发第二次查询,执行 selectProductsByOrderId 语句查询商品数据。

  3. 加载数据 : 查询结果被返回并填充到 products 列表中。

工作原理
  • MyBatis 通过使用 CGLIBJDK 动态代理,为延迟加载的属性生成代理对象。当延迟加载的属性被调用时,代理对象会执行一个回调,动态加载所需的数据。
  • 代理模式 是延迟加载的核心。Proxy 对象会在调用目标属性时检查属性的加载状态,未加载时会触发查询并加载数据。
延迟加载控制的要点
  • 延迟加载需要在事务中使用,因为需要在对象的生命周期内保持数据库会话的连接状态。
  • 只有在属性被真正调用时,才会触发延迟加载。
注意事项
  • 如果你在 MyBatis 配置文件中将 aggressiveLazyLoading 设置为 true,一旦加载了某个延迟属性,所有延迟属性都会加载。这个配置适合当你需要尽快获取所有关联对象时。

3. 延迟加载和立即加载的对比

  • 延迟加载:只有在需要用到相关数据时,才会查询数据库并加载,适合处理关系型数据或减少不必要的查询。
  • 立即加载:一次性查询出所有关联数据,适合那些频繁需要访问的关联对象,避免后续多次查询数据库。

4. 总结

  • 延迟加载在查询一对多、多对一时非常有用,可以避免不必要的数据库查询。
  • MyBatis 通过动态代理机制实现延迟加载,当你访问某个延迟加载的属性时,才会触发相应的 SQL 查询。
  • 通过 lazyLoadingEnabledaggressiveLazyLoading 配置项,可以控制 MyBatis 延迟加载的行为。

MyBatis一级二级缓存

在 MyBatis 中,缓存是为了减少数据库查询的次数,提升性能。MyBatis 提供了两级缓存机制:一级缓存二级缓存

1. 一级缓存

一级缓存是SqlSession 级别 的缓存,它的作用范围仅限于同一个 SqlSession 对象。在同一个 SqlSession 中,多次查询同一个数据时,MyBatis 会将查询结果存储到缓存中,之后的相同查询就可以直接从缓存中获取结果,而不再执行 SQL 查询。

一级缓存的特点
  • 默认开启 :不需要配置,它会自动在 SqlSession 中工作。
  • 作用范围 :仅限于同一个 SqlSession,当 SqlSession 关闭后,一级缓存也会被清除。
  • 缓存机制 :以 statement ID(SQL 语句的唯一标识)查询参数 作为缓存的 key,查询结果作为 value。
  • 失效条件
    • 如果执行了 INSERTUPDATEDELETE 操作,一级缓存会被清空,因为数据发生了变化。
    • 手动清除缓存,例如调用 sqlSession.clearCache() 方法。
    • SqlSession 关闭时,一级缓存也会失效。

一级缓存的示例

java 复制代码
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 存入缓存中。
  • 失效条件
    • 执行 INSERTUPDATEDELETE 操作时,二级缓存会失效。
    • 手动清除缓存或配置的缓存清理策略。
    • 配置了缓存的过期时间,超时后缓存失效。
开启二级缓存的步骤
  1. 在核心配置文件中启用二级缓存
XML 复制代码
<configuration>c
    <settings>
        <!-- 启用全局二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

2.在 Mapper 映射文件中启用二级缓存 : 在对应的 Mapper.xml 文件中,添加如下配置:

XML 复制代码
<cache/>

3.使用 Serializable 接口 : 由于二级缓存中的对象是以序列化的形式存储的,所有被缓存的对象必须实现 Serializable 接口。

二级缓存的示例
  1. Mapper 映射文件中启用二级缓存
XML 复制代码
<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 代码示例

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 共享,可能导致旧数据在缓存中被使用。
相关推荐
Reese_Cool10 分钟前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
TheITSea1 小时前
云服务器宝塔安装静态网页 WordPress、VuePress流程记录
java·服务器·数据库
AuroraI'ncoding1 小时前
SpringMVC接收请求参数
java
九圣残炎1 小时前
【从零开始的LeetCode-算法】3354. 使数组元素等于零
java·算法·leetcode
天天扭码2 小时前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
程序猿小柒2 小时前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
不爱学习的YY酱3 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
丁总学Java3 小时前
Maven项目打包,com.sun.tools.javac.processing
java·maven
kikyo哎哟喂3 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~3 小时前
SpringAOP模拟实现
java·开发语言