开发者

MyBatis 延迟加载与缓存的实现

开发者 https://www.devze.com 2025-05-19 10:38 出处:网络 作者: lyrhhhhhhhh
目录一、延迟加载策略:按需加载,优化性能1. 延迟加载 vs 立即加载:核心区别 2. 多对一延迟加载实现(Account → User) 3. 一对多延迟加载实现(User → Accounts)关键区别: 二、MyBATi
目录
  • 一、延迟加载策略:按需加载,优化性能
    • 1. 延迟加载 vs 立即加载:核心区别
    •  2. 多对一延迟加载实现(Account → User)
    •  3. 一对多延迟加载实现(User → Accounts)
    • 关键区别:
  •  二、MyBATis 缓存机制:减少数据库访问
    •  1. 缓存的核心价值
    •  2.http://www.devze.com 一级缓存:SqlSession 级别的缓存
    •  3. 二级缓存:SqlSessionFactory 级别的缓存
  •  三、总结:性能优化核心要点

    一、延迟加载策略:按需加载,优化性能

    1. 延迟加载 vs 立即加载:核心区别

    • 立即加载:主查询(如查询用户)执行时,主动关联加载关联数据(如用户的所有账号)。
      • 场景:多对一查询(如账号关联用户),需立即获取关联数据。
    • 延迟加载:主查询执行时暂不加载关联数据,仅当程序访问关联数据时,再触发子查询。
      • 场景:一对多查询(如用户关联多个账号),减少初始查询压力。

    举个小例子: 

    场景:查询用户信息时不立即加载其订单,仅在需要查看订单时再触发查询。

    示例:电商用户详情页先展示用户姓名、地址,点击 “查看订单” 按钮时,才加载该用户的订单列表。 

     2. 多对一延迟加载实现(Account → User)

    步骤解析:

    1、定义关联查询:主查询仅查账号表,关联用户信息通过子查询延迟加载。

    <!-- 主查询:仅查账号 -->
    <select id="findAll" resultMap="accountMap">
      SELECT * FROM account
    </select>
    
    <!-- 子查询:通过用户ID查用户信息 -->
    <select id="findById" parameterType="int" resultType="User">
      SELECT * FROM user WHERE id = #{id}
    </select>

    2、配置延迟加载:通过 association 标签指定子查询路径和参数。

    <resultMap编程客栈 type="Account" id="accountMap">
      <association 
        property="user"          <!-- Account类中的User属性 -->
        JavaType="User"          <!-- 关联对象类型 -->
        select="findById"        <!-- 子查询方法名 -->
        column="uid"             <!-- 主查询结果中用于关联的列(账号表的uid) -->
      />
    </resultMap>

    3、全局开启延迟加载:在 SqlMapConfig.XML 中配置。

    <settings>
      <setting name="lazyLoadingEnabled" value="true"/>   <!-- 开启延迟加载 -->
      <setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载(默认会加载所有关联数据) -->
    </settings>

     测试验证:

    @Test
    public void testLazyLoading() {
      List<Account> accounts = accountMapper.findAll();
      for (Account account : accounts) {
        System.out.println("账号金额:" + account.getMoney()); // 主查询执行时仅输出金额
        System.out.println("用户名称:" + account.getUser().getUsername()); // 首次访问user时触发子查询
      }
    }

     3. 一对多延迟加载实现(User → Accounts)

     核心配置:

    <!-- 主查询:仅查用户表 -->
    <select id="findAll" resultMap="userMap">
      SELECT * FROM user
    </select>
    
    <resultMap type="User" id="userMap">
      <collection 
        property="accounts"       <!-- User类中的账号列表属性 -->
        ofType="Account"          <!-- 集合元素类型 -->
        select="com.qcbyjy.mapper.AccountMapper.findByUid" <!-- 子查询:通过用户ID查账号 -->
        column="id"                <!-- 主查询结果中的用户ID -->
      />
    </resultMap>
    
    <!-- 子查询:根据用户ID查账号 -->
    <select id="findByUid" parame编程客栈terType="int" resultType="Account">
      SELECT * FROM account WHERE uid = #{uid}
    </select>

    关键区别:

    • 多对一用 association(单个对象),一对多用 collection(集合)。
    • 子查询参数通过 column 传递主查询结果中的字段(如用户表的 id)。

     二、MyBatis 缓存机制:减少数据库访问

     1. 缓存的核心价值

    • 定义:将频繁查询的数据临时存储在内存中,避免重复访问数据库,提升查询速度。
    • 适用场景:读多写少、数据更新不频繁的数据(如字典表、配置信息)。

     2. 一级缓存:SqlSession 级别的缓存

    (1)本质与作用

    • 作用域:基于 SqlSession 对象,同一 SqlSession 内的相同查询会直接从缓存获取结果。
    • 实现原理SqlSession 内部维护一个 HashMap,键为查询的唯一标识(SQL + 参数),值为查询结果对象。

    (2)验证一级缓存

    @Test
    public void testFirstLevelCache() {
      // 同一 SqlSesswww.devze.comion 内的两次相同查询
      User user1 = userMapper.findById(1);
      User user2 = userMapper.findById(1); // 直接从缓存获取,不执行 SQL
      System.out.println(user1 == user2); // 输出 true(对象引用相同)
    }

    (3)缓存失效场景

    • SqlSession 关闭或提交(commit)。
    • 执行 update/insert/delete 操作(会清空缓存)。
    • 手动调用 session.clearCache() 清空缓存。

     3. 二级缓存:SqlSessionFactory 级别的缓存

    (1)核心概念

    • 作用域:基于 SqlSessionFactory,跨 SqlSess编程客栈ion 共享缓存(如多个 SqlSession 执行相同查询)。
    • 实现条件

      实体类需实现 Serializable 接口(支持序列化存储)。

      在 SqlMapConfig.xml 中开启二级缓存(默认已开启)。

      在 Mapper 中配置 <cache/> 标签。

    (2)配置步骤

    1、 实体类实现序列化

    public class User implements Serializable {
      // 省略属性和方法
    }

    2、Mapper 中启用缓存

    <mapper namespace="com.qcbyjy.mapper.UserMapper">
      <cache/> <!-- 启用二级缓存 -->
      
      <select id="findById" resultType="User" useCache="true">
        SELECT * FROM user WHERE id = #{id}
      </select>
    </mapper>

    3、配置缓存策略(可选)

    <cache 
      eviction="LRU"       <!-- 缓存淘汰策略:LRU(最近最少使用) -->
      flushInterval="60000" <!-- 自动刷新间隔(毫秒) -->
      size="512"           <!-- 最大缓存对象数 -->
      readOnly="true"      <!-- 是否只读:true(共享对象)/ false(复制对象) -->
    />

    (3)缓存优先级与刷新

    • 优先级:二级缓存 > 一级缓存 > 数据库查询。
    • 刷新机制:执行 update/insert/delete 时,会清空对应 Mapper 的二级缓存。

    (4)测试验证

    @Test
    public void testSecondLevelCache() {
      try (SqlSession session1 = factory.openSession()) {
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        User user1 = mapper1.findById(1); // 首次查询,命中数据库
      }
    
      try (SqlSession session2 = factory.openSession()) {
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        User user2 = mapper2.findById(1); // 第二次查询,命中二级缓存,不执行 SQL
      }
    }

     三、总结:性能优化核心要点

    技术核心作用关键配置
    延迟加载减少初始查询数据量,提升响应速度lazyLoadingEnabledassociation/collection 的 select 属性
    一级缓存减少同一会话内的重复查询自动生效,无需额外配置(注意 SqlSession 生命周期)
    二级缓存跨会话共享缓存,减少数据库压力实体类序列化、<cache/> 标签、缓存策略配置

    合理运用延迟加载和缓存,能显著提升 MyBatis 应用的性能,但需根据业务场景灵活选择,避免过度使用导致数据不一致或内存溢出。

    到此这篇关于MyBatis 延迟加载与缓存的实现的文章就介绍到这了,更多相关MyBatis 延迟加载与缓存内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号