MyBatis 的缓存机制 笔记250320

106 阅读1小时+

MyBatis 的缓存机制

MyBatis 的缓存机制分为一级缓存二级缓存,旨在减少数据库查询次数,提高性能。以下是详细的机制说明:


一、一级缓存(Local Cache)

  1. 作用范围

    • SqlSession 级别:默认开启,仅在同一个 SqlSession 生命周期内有效。
    • 在同一个会话中,执行相同的 SQL 和参数时,直接从缓存返回结果,避免重复查询数据库。
  2. 缓存失效条件

    • 执行了 INSERT/UPDATE/DELETE 操作(会清空当前缓存)。
    • 手动调用 sqlSession.clearCache()
    • 提交事务(commit())或关闭 SqlSessionclose())。
    • 配置了 <select flushCache="true"> 的查询会强制清空缓存。
  3. 实现原理

    • 底层使用 PerpetualCache,内部通过 HashMap 存储缓存数据。
    • 生命周期与 SqlSession 绑定,无法跨会话共享。
  4. 注意事项

    • 分布式环境下不适用:一级缓存是单机、单会话的,无法解决跨节点数据一致性问题。
    • 事务隔离问题:如果两个操作在同一个事务中,可能读到未提交的脏数据(需结合数据库隔离级别分析)。

二、二级缓存(Second Level Cache)

  1. 作用范围

    • Mapper 级别:跨 SqlSession 共享,属于 namespace 作用域。
    • 需要手动开启(配置 <cache> 标签),多个会话访问同一 Mapper 时可共享缓存。
  2. 配置方式

    • 全局开关:在 mybatis-config.xml 中设置 <setting name="cacheEnabled" value="true"/>(默认开启)。
    • Mapper 配置:在对应的 Mapper XML 文件中添加 <cache/> 标签,支持以下属性:
      <cache
        eviction="LRU"          <!-- 缓存淘汰策略LRU/FIFO/SOFT/WEAK -->
        flushInterval="60000"   <!-- 自动刷新间隔(毫秒) -->
        size="1024"             <!-- 最大缓存对象数量 -->
        readOnly="true"         <!-- 是否只读(建议为false以支持序列化修改) -->
      />
      
    • 实体类要求:若需缓存对象,对应的 Java 类必须实现 Serializable 接口。
  3. 缓存失效条件

    • 执行了同 Mapper 的 INSERT/UPDATE/DELETE 操作(清空整个二级缓存)。
    • 手动调用 sqlSession.commit()sqlSession.close()(缓存会提交到二级缓存)。
  4. 工作机制

    • 查询顺序:二级缓存 → 一级缓存 → 数据库。
    • 二级缓存数据在事务提交(commit())后才会写入,未提交的事务修改对其他会话不可见。
    • 默认使用 TransactionalCacheManager 管理缓存的提交和回滚。
  5. 注意事项

    • 脏读风险:若多个 Mapper 操作同一张表,需通过 <cache-ref> 共享缓存空间,否则可能出现数据不一致。
    • 性能权衡:高并发写场景下频繁清空缓存可能降低性能。
    • 分布式缓存:默认实现不适用于分布式系统,需整合 Redis、Ehcache 等第三方缓存(如通过 org.mybatis.caches 扩展)。

三、缓存控制

  1. 禁用缓存

    • <select> 标签中设置 useCache="false",避免特定查询使用二级缓存。
    • <insert>/<update>/<delete> 中设置 flushCache="true"(默认 true),清空缓存。
  2. 强制刷新

    • 调用 sqlSession.clearCache() 仅清空一级缓存。
    • 通过 <select flushCache="true"> 在查询前清空一、二级缓存。

四、第三方缓存整合

MyBatis 支持通过实现 org.apache.ibatis.cache.Cache 接口扩展缓存:

  1. 常用实现

    • Ehcache:适合单机高并发,支持内存+磁盘缓存。
    • Redis:分布式缓存,解决多服务节点数据共享问题。
  2. 配置示例(以 Redis 为例):

    <cache type="org.mybatis.caches.redis.RedisCache" />
    

五、总结

特性一级缓存二级缓存
作用范围SqlSession 内Mapper 跨 SqlSession
默认开启否(需配置 <cache/>
存储结构HashMap可扩展(如 Redis、Ehcache)
失效机制自动随 DML 操作失效事务提交后生效
适用场景单会话重复查询跨会话共享数据

最佳实践

  • 对读多写少的数据使用二级缓存,注意配置合理的淘汰策略和刷新间隔。
  • 在分布式环境中,优先使用 Redis 等分布式缓存替代默认实现。










MyBatis 缓存机制详解

MyBatis 作为 Java 持久层框架,通过两级缓存机制显著提升数据库查询性能。其核心设计围绕一级缓存(SqlSession 级)和二级缓存(Mapper 级)展开,以下是详细解析:

一、一级缓存(本地缓存)

作用范围:单个 SqlSession 实例内
生命周期:与 SqlSession 绑定,会话关闭时自动清空
特点

  • 默认开启,无需配置
  • 自动缓存:相同 SQL 且参数一致时,直接返回缓存结果
  • 失效场景
    • 执行 INSERT/UPDATE/DELETE 操作后提交(session.commit()
    • 手动调用 session.clearCache()
    • SqlSession 关闭

示例验证

try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user1 = mapper.getUserById(1); // 查询数据库并存入缓存
    User user2 = mapper.getUserById(1); // 直接读取缓存,不执行SQL
    System.out.println(user1 == user2); // 输出 true
}

二、二级缓存(全局缓存)

作用范围:Mapper 命名空间级别,跨 SqlSession 共享
生命周期:与应用进程共存,需手动配置
配置步骤

  1. 全局启用mybatis-config.xml):
    <configuration>
      <settings>
        <setting name="cacheEnabled" value="true"/>
      </settings>
    </configuration>
    
  2. Mapper 配置(XML 或注解):
    <mapper namespace="com.example.mapper.UserMapper">
      <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
      <!-- 或通过注解(MyBatis 3.2+) -->
      @CacheNamespace
      public interface UserMapper { ... }
    

失效场景

  • 任意 SqlSession 执行该 Mapper 的 INSERT/UPDATE/DELETE 操作
  • 手动清空缓存(session.clearCache()mapper.clearCache()

序列化要求:缓存对象需实现 Serializable 接口。

三、缓存配置策略

  1. 回收策略(eviction
    • LRU(默认):移除最久未使用的对象
    • FIFO:按插入顺序移除
    • SOFT/WEAK:基于垃圾回收器状态
  2. 刷新间隔(flushInterval:毫秒为单位,默认不刷新
  3. 容量(size:缓存对象数量上限,默认 1024
  4. 只读(readOnly
    • true:返回对象引用(高性能,但需保证对象不可变)
    • false(默认):返回对象拷贝(安全但较慢)

四、集成第三方缓存(如 Redis)

步骤

  1. 添加依赖(以 Redis 为例):
    <dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-redis</artifactId>
      <version>1.0.0</version>
    </dependency>
    
  2. 配置 Mapper 使用自定义缓存:
    <cache type="org.mybatis.caches.redis.RedisCache"/>
    

五、最佳实践

  1. 监控命中率:通过日志或监控工具分析缓存效率
  2. 缓存穿透处理:对空结果设置短过期时间(如 30 秒)
  3. 雪崩防护:为不同缓存设置随机过期时间
  4. 一致性保障
    • 写操作后手动清空关联缓存
    • 分布式环境使用 Redis 等中央缓存
  5. 高频查询优化:对复杂报表查询启用二级缓存

六、注意事项

  • 脏读风险:二级缓存需结合事务隔离级别使用
  • 内存溢出:高并发场景需限制缓存容量
  • 调试技巧:启用日志(log4j.logger.org.apache.ibatis=DEBUG)观察缓存行为

通过合理配置缓存策略,可显著提升应用性能。例如,在读写分离的架构中,二级缓存可减少 30%-50% 的数据库压力。建议根据实际业务场景测试不同配置组合的效果。










MyBatis 提供了两种类型的缓存来提高查询性能:一级缓存和二级缓存。

1. 一级缓存

  • 默认开启:一级缓存是SqlSession级别的缓存,默认情况下是自动开启的,无需额外配置。
  • 作用范围:它存在于SqlSession的生命周期中。当在同一个SqlSession中执行两次相同的SQL查询时,第二次查询会直接从缓存中获取数据,不会发起数据库请求。
  • 清空机制:如果在同一个SqlSession中对数据库进行了增、删、改等操作,MyBatis会自动清空该SqlSession下的一级缓存,以保证数据的一致性。

2. 二级缓存

  • 手动开启:二级缓存是Mapper级别的缓存,需要手动开启。可以通过在MyBatis配置文件中设置<cache/>标签或者在具体的Mapper XML文件中添加该标签来启用。
  • 作用范围:它的作用范围比一级缓存更广,可以被多个SqlSession共享。这意味着不同的SqlSession对象如果访问的是同一个Mapper下的SQL语句,它们可以从二级缓存中读取数据,而不需要每次都去查询数据库。
  • 存储方式:二级缓存可以配置不同的存储方式,比如PERPETUAL(永久)、FIFO(先进先出)、LRU(最近最少使用)等策略。
  • 注意事项:使用二级缓存时需要注意对象的序列化问题,因为缓存的数据可能会被持久化到磁盘或者其他存储介质上。因此,如果实体类没有实现Serializable接口,则无法使用二级缓存。

此外,MyBatis还允许通过自定义缓存实现来满足特定的需求,例如整合第三方缓存解决方案(如EhCache、Redis等)。这通常涉及到创建一个实现了org.apache.ibatis.cache.Cache接口的类,并将其配置到MyBatis中。

合理利用这两种缓存机制可以帮助减少数据库的压力,提升系统的响应速度。但是,使用缓存的同时也要注意数据一致性的问题,特别是在并发环境下。










MyBatis 有两级缓存机制,下面为你详细介绍:

一级缓存

一级缓存是 SqlSession 级别的缓存,在同一个 SqlSession 中,执行相同的 SQL 查询时,MyBatis 会先从缓存中查找,如果找到则直接返回,不再执行数据库查询。当 SqlSession 关闭或提交事务时,一级缓存会被清空。

原理

在同一个 SqlSession 中,MyBatis 会为每个查询创建一个 CacheKey 对象,该对象包含了 SQL 语句、参数等信息。当执行查询时,会先根据 CacheKey 从缓存中查找,如果找到则返回缓存中的结果,否则执行数据库查询,并将结果存入缓存。

示例代码
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class FirstLevelCacheExample {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = FirstLevelCacheExample.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 第一次查询
            Object result1 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第一次查询结果: " + result1);

            // 第二次查询,使用相同的 SqlSession
            Object result2 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第二次查询结果: " + result2);
        }
    }
}

在上述示例中,第一次和第二次查询使用的是同一个 SqlSession,因此第二次查询会直接从缓存中获取结果,而不会再次执行数据库查询。

二级缓存

二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享同一个 Mapper 的二级缓存。二级缓存默认是关闭的,需要在 MyBatis 配置文件中进行配置才能开启。

原理

当开启二级缓存后,MyBatis 会为每个 Mapper 创建一个 Cache 对象,用于存储查询结果。当执行查询时,会先从二级缓存中查找,如果找到则返回缓存中的结果,否则执行数据库查询,并将结果存入二级缓存。

配置步骤
  1. 在 MyBatis 配置文件中开启二级缓存:
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在 Mapper XML 文件中配置二级缓存:
<mapper namespace="com.example.mapper.YourMapper">
    <cache/>
    <!-- 其他 SQL 语句 -->
</mapper>

或者在 Mapper 接口上使用 @CacheNamespace 注解:

import org.apache.ibatis.annotations.CacheNamespace;

@CacheNamespace
public interface YourMapper {
    // 方法定义
}
示例代码
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class SecondLevelCacheExample {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = SecondLevelCacheExample.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 第一个 SqlSession
        try (SqlSession session1 = sqlSessionFactory.openSession()) {
            Object result1 = session1.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第一个 SqlSession 查询结果: " + result1);
        }

        // 第二个 SqlSession
        try (SqlSession session2 = sqlSessionFactory.openSession()) {
            Object result2 = session2.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第二个 SqlSession 查询结果: " + result2);
        }
    }
}

在上述示例中,第一个和第二个 SqlSession 分别执行相同的查询,由于开启了二级缓存,第二个 SqlSession 会直接从二级缓存中获取结果,而不会再次执行数据库查询。

缓存的清空和刷新

当执行插入、更新、删除操作时,MyBatis 会自动清空一级缓存和二级缓存,以保证数据的一致性。例如:

try (SqlSession session = sqlSessionFactory.openSession()) {
    // 执行插入操作
    session.insert("com.example.mapper.YourMapper.insertRecord", record);
    session.commit();
    // 插入操作后,缓存会被清空
}

注意事项

  • 一级缓存是默认开启的,且不能关闭。
  • 二级缓存需要手动开启和配置。
  • 缓存可能会导致数据不一致的问题,因此在使用时需要谨慎考虑。例如,当多个应用实例共享同一个数据库时,一个实例更新了数据,但其他实例的缓存中仍然是旧数据,这时就需要考虑使用缓存刷新机制或分布式缓存。










MyBatis 提供了一级缓存和二级缓存两种缓存机制,用于减少数据库的访问次数,提高查询性能。

一级缓存

  • 默认开启:一级缓存是 SqlSession 级别的缓存,当使用同一个 SqlSession 进行查询时,会先从一级缓存中查找数据,如果存在则直接返回缓存数据,否则才会查询数据库。
  • 生命周期:一级缓存的生命周期与 SqlSession 的生命周期一致,当 SqlSession 关闭或提交时,一级缓存会被清空。
  • 限制:一级缓存只能在同一个 SqlSession 中使用,不同的 SqlSession 之间无法共享一级缓存。

二级缓存

  • 手动配置:二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享二级缓存。要使用二级缓存,需要在映射文件中进行配置,并且在核心配置文件中开启缓存。
  • 配置步骤
    • 在核心配置文件(如 mybatis-config.xml)中添加 <setting name="cacheEnabled" value="true"/> 来启用缓存。
    • 在映射文件(如 UserMapper.xml)中添加 <cache/> 标签来启用该 Mapper 的二级缓存。
  • 工作原理:当查询数据时,MyBatis 会先从二级缓存中查找,如果存在则直接返回;如果不存在,再从一级缓存中查找;如果一级缓存也没有,则查询数据库,并将查询结果同时存入一级缓存和二级缓存。
  • 限制:二级缓存中的数据需要是可序列化的,即返回的实体类需要实现 Serializable 接口。

注意事项

  • 缓存的粒度:合理设置缓存的粒度,避免缓存过多数据导致内存溢出。
  • 缓存的刷新:根据业务需求,合理设置缓存的刷新策略,如定时刷新、基于数据更新的刷新等。
  • 数据一致性:在高并发场景下,缓存可能会导致数据不一致的问题,需要根据具体业务场景采取相应的解决措施,如使用分布式缓存、缓存与数据库的双写机制等。

以下是一个简单的二级缓存配置示例:

<!-- mybatis-config.xml -->
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    <cache/>
    <!-- 查询用户信息的 SQL 语句 -->
    <select id="getUserById" parameterType="int" resultType="com.example.entity.User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

在 Java 类中,可以通过以下代码来使用缓存:

SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 第一次查询,会从数据库中获取数据,并存入缓存
User user1 = userMapper.getUserById(1);

// 第二次查询,会从缓存中获取数据
User user2 = userMapper.getUserById(1);

sqlSession.close();

通过合理使用 MyBatis 的缓存机制,可以有效减少数据库的访问次数,提高系统的性能和响应速度。










一、一级缓存(本地缓存)

  1. 作用域
    默认开启,属于 SqlSession 级别。同一个会话中执行相同 SQL 和参数时,直接返回缓存结果。
    示例

    SqlSession session = sqlSessionFactory.openSession();
    User user1 = session.selectOne("getUserById", 1); // 查数据库
    User user2 = session.selectOne("getUserById", 1); // 从一级缓存获取
    
  2. 失效条件

    • 执行 INSERT/UPDATE/DELETE 操作(任何数据变更)。
    • 调用 session.clearCache() 手动清空。
    • 关闭 SqlSession 后缓存销毁。
  3. 注意事项
    一级缓存可能导致脏读(例如其他会话修改数据后,当前缓存未更新)。


二、二级缓存(全局缓存)

  1. 作用域
    属于 Mapper 级别,多个 SqlSession 共享。需手动开启:

    • 配置步骤
      • mybatis-config.xml 中启用:<setting name="cacheEnabled" value="true"/>
      • Mapper.xml 中添加标签:<cache/>
  2. 特性

    • 数据在 SqlSession 提交或关闭后存入缓存。
    • 缓存对象需实现 Serializable 接口。
    • 支持自定义策略(如 LRU、FIFO)和过期时间:
      <cache eviction="LRU" flushInterval="60000" size="1024"/>
      
  3. 失效条件

    • 执行同 Mapper 的 INSERT/UPDATE/DELETE 操作。
    • 配置 flushCache="true" 强制刷新。

三、缓存优先级与对比

对比项一级缓存二级缓存
作用域SqlSession 级别Mapper 级别
默认开启需手动配置
数据共享仅当前会话跨会话共享
存储位置内存(JVM 堆)可扩展至 Redis 等第三方
适用场景高频重复查询(短周期)低频变动数据(长周期)

四、使用建议

  1. 优先依赖一级缓存,避免过度设计。
  2. 二级缓存适合读多写少场景,注意处理序列化和并发问题。
  3. 复杂场景可集成 Redis、Ehcache 等第三方缓存(通过 Cache 接口扩展)。

五、常见问题

  • 脏数据问题:二级缓存跨会话共享,需通过 <cache-ref/> 管理关联 Mapper 的缓存一致性。
  • 性能监控:可通过日志或监控工具观察缓存命中率,调整策略。

通过合理使用缓存机制,可显著优化 MyBatis 的数据库访问性能。










MyBatis 的缓存机制分为 一级缓存二级缓存,两者结合使用可显著提升查询效率。以下是详细讲解:


一、一级缓存(Local Cache)

1. 作用范围

生命周期:绑定到单个 SqlSession 对象,随 SqlSession 的创建而诞生,销毁而消失。 • 默认开启:无需额外配置,自动生效。

2. 工作原理

查询流程

  1. 执行查询时,首先检查一级缓存。
  2. 若命中,直接返回结果;若未命中,查询数据库并将结果存入一级缓存。 • 缓存键:基于 SQL 语句、参数及结果集生成唯一键。
3. 缓存失效场景

手动触发: • 执行 updatedeleteinsert 操作(默认行为)。 • 调用 sqlSession.clearCache()。 • 自动触发: • SqlSession 关闭时自动清空。

4. 配置与优化

全局配置:在 mybatis-config.xml 中可自定义缓存策略(如 LRU、FIFO):

<configuration>
  <settings>
    <setting name="localCacheScope" value="STATEMENT"/>
    <setting name="localCacheSize" value="2048"/>
  </settings>
</configuration>

作用域调整localCacheScope 可设为 STATEMENT(默认)、SESSIONJDBC


二、二级缓存(Global Cache)

1. 作用范围

生命周期:应用级别,所有 SqlSession 共享。 • 需手动启用:通过 Mapper XML 或注解配置。

2. 工作原理

查询流程

  1. 检查当前 SqlSession 的一级缓存。
  2. 若未命中,检查二级缓存。
  3. 若二级缓存命中,返回结果并填充一级缓存。
  4. 若均未命中,查询数据库,结果存入一级和二级缓存。 • 缓存键:与一级缓存一致,确保跨会话复用。
3. 配置步骤

Mapper XML:在 Mapper 标签内添加 <cache/> 启用:

<mapper namespace="com.example.UserMapper">
  <cache/>
  <!-- SQL 映射 -->
</mapper>

全局配置:可自定义缓存实现(如集成 EhCache、Redis):

<configuration>
  <plugins>
    <plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
      <property name="configLocation" value="classpath:ehcache.xml"/>
    </plugin>
  </plugins>
</configuration>
4. 缓存策略与序列化

序列化机制:MyBatis 默认使用 JdkSerialization,可将对象序列化为字节流存储。支持自定义序列化器(如 Jackson)。 • 淘汰策略:支持 LRU、FIFO、LRU_JDBC 等,可通过配置或自定义 Cache 接口实现。

5. 数据一致性保障

自动刷新:执行更新操作时,MyBatis 会自动清除受影响实体的二级缓存。 • 手动刷新:通过 sqlSession.clearCache()<flushCache> 标签强制刷新:

<update id="updateUser" parameterType="User" flushCache="true">
  UPDATE users SET name=#{name} WHERE id=#{id}
</update>

三、缓存使用注意事项

  1. 适用场景: • 一级缓存:适用于单次会话内的重复查询(如页面内多次加载同一数据)。 • 二级缓存:适用于跨会话、高频读取且低频更新的静态数据(如配置表)。

  2. 性能权衡: • 内存消耗:二级缓存占用更多内存,需合理设置大小。 • 一致性风险:长时间未刷新的二级缓存可能导致数据脏读,需结合业务场景设置合适的刷新策略。

  3. 并发处理: • MyBatis 默认使用非线程安全的 PerpetualCache,多线程环境下需配置线程安全缓存(如 EhCache)。

  4. 第三方缓存集成: • 支持 EhCache、Redis 等,需引入依赖并配置插件,实现分布式缓存能力。


四、示例代码

1. 启用二级缓存
<!-- UserMapper.xml -->
<mapper namespace="com.example.UserMapper">
  <cache/>
  <select id="findByUserId" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  </select>
</mapper>
2. 自定义缓存实现
public class CustomCache implements Cache {
    @Override
    public void putObject(Object key, Object value) {
        // 自定义存储逻辑(如存入 Redis)
    }

    @Override
    public Object getObject(Object key) {
        // 自定义读取逻辑
        return null;
    }

    // 其他方法实现...
}
3. 强制刷新缓存
// Java 代码
sqlSession.update("com.example.UserMapper.updateUser", user);
sqlSession.clearCache(); // 清除当前会话及全局缓存

五、总结

一级缓存:轻量级,生命周期短,适合会话内重复查询。 • 二级缓存:跨会话共享,需权衡一致性与性能,适合静态数据。 • 最佳实践: • 高频读写场景优先使用一级缓存。 • 静态数据配置二级缓存,结合定时任务或事件监听刷新。 • 分布式系统推荐集成 Redis/EhCache 实现二级缓存。

通过合理配置缓存,可大幅减少数据库访问次数,提升应用性能,但需警惕数据一致性问题。



















Mybatis 开启关闭一级缓存

MyBatis 的一级缓存默认开启且无法全局关闭,但可以通过特定方式绕过或限制其效果。以下是控制一级缓存的方法和注意事项:


一、一级缓存默认行为

  1. 自动开启
    SqlSession 生命周期内,所有查询(SELECT)默认使用一级缓存,重复的相同查询会直接返回缓存结果,不会访问数据库。

  2. 验证缓存存在

    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 第一次查询(访问数据库)
        User user1 = mapper.selectById(1); 
        // 第二次查询(直接从一级缓存获取)
        User user2 = mapper.selectById(1); 
        System.out.println(user1 == user2); // 输出 true(同一对象)
    }
    

二、绕过或“关闭”一级缓存的方法

方法 1:修改 SQL 或参数

每次查询使用不同的 SQL 或参数,使 MyBatis 无法命中缓存:

// 添加无意义的参数(如时间戳)
User user1 = mapper.selectById(1, System.currentTimeMillis());
User user2 = mapper.selectById(1, System.currentTimeMillis()); // 重新查询
方法 2:强制清空一级缓存

手动调用 clearCache() 清空当前 SqlSession 的缓存:

User user1 = mapper.selectById(1);
sqlSession.clearCache(); // 清空缓存
User user2 = mapper.selectById(1); // 重新查询
方法 3:配置 flushCache="true"

<select> 标签中设置 flushCache="true",使该查询每次执行前清空一级缓存:

<select id="selectById" resultType="User" flushCache="true">
  SELECT * FROM user WHERE id = #{id}
</select>
方法 4:执行写操作(DML)

任何 INSERT/UPDATE/DELETE 操作都会自动清空当前 SqlSession 的一级缓存:

User user1 = mapper.selectById(1);
mapper.updateName(1, "NewName"); // 执行更新操作,清空缓存
User user2 = mapper.selectById(1); // 重新查询
方法 5:使用不同的 SqlSession

每次查询创建新的 SqlSession(不推荐,影响性能):

try (SqlSession session1 = factory.openSession()) {
    User user1 = session1.getMapper(UserMapper.class).selectById(1);
}
try (SqlSession session2 = factory.openSession()) {
    User user2 = session2.getMapper(UserMapper.class).selectById(1); // 重新查询
}

三、注意事项

  1. 无法全局关闭
    MyBatis 未提供全局禁用一级缓存的配置,只能通过上述方法绕过。

  2. 事务提交影响

    • 调用 sqlSession.commit() 会清空缓存(与 flushCache 行为相关)。
    • 未提交的事务中,缓存可能持有未持久化的数据(需结合数据库隔离级别)。
  3. 性能权衡
    频繁清空缓存会增加数据库压力,需根据场景(如读多写少 vs 实时性要求高)选择策略。


四、最佳实践

  • 需要实时数据:在关键查询中配置 flushCache="true" 或手动调用 clearCache()
  • 批量读取:利用一级缓存减少重复查询,但确保数据一致性。
  • 结合二级缓存:若需跨会话共享缓存数据,启用二级缓存(注意脏读问题)。

通过合理控制 SqlSession 生命周期和查询配置,可间接实现一级缓存的管理。










MyBatis 一级缓存的开启与关闭机制解析

MyBatis 的一级缓存是默认自动开启的 SqlSession 级缓存,其生命周期与 SqlSession 紧密相关。以下是详细操作指南:

一、一级缓存的默认行为

  1. 自动开启
    无需任何配置,创建 SqlSession 时即自动启用一级缓存。

  2. 智能缓存
    当执行相同 SQL 且参数一致时,直接返回缓存结果,避免重复查询数据库。

  3. 自动失效场景

    • 执行 INSERT/UPDATE/DELETE 操作后提交事务(sqlSession.commit()
    • 手动调用 sqlSession.clearCache() 方法
    • SqlSession 关闭(sqlSession.close()

二、手动控制缓存

1. 强制刷新缓存
// 获取 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 第一次查询(缓存未命中)
    User user1 = mapper.getUserById(1); 
    
    // 执行数据修改操作
    mapper.updateUser(user1);
    session.commit(); // 提交事务后,一级缓存自动清空
    
    // 强制刷新缓存(可选)
    session.clearCache(); 
    
    // 第二次查询(缓存重新加载)
    User user2 = mapper.getUserById(1); 
}
2. 禁用一级缓存(不推荐)

若需全局关闭一级缓存,需在创建 SqlSession 时指定配置:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
    .build(inputStream, "mybatis-config.xml");

// 获取 SqlSession 时禁用一级缓存
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.SIMPLE)) {
    // 此会话将不启用一级缓存
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
}

三、最佳实践建议

  1. 写操作后主动刷新
    执行 INSERT/UPDATE/DELETE 后,建议调用 clearCache() 确保后续查询获取最新数据。

  2. 高频读场景保留缓存
    对于只读或低频更新的数据,可充分利用一级缓存减少数据库压力。

  3. 复杂事务管理
    在包含多个写操作的事务中,通过 clearCache() 控制缓存状态,避免脏读。

四、调试与监控

  1. 日志观察
    启用 MyBatis DEBUG 日志,可看到缓存命中/未命中的详细过程:

    # log4j.properties 配置示例
    log4j.logger.org.apache.ibatis=DEBUG
    
  2. 性能分析
    使用 APM 工具(如 SkyWalking)监控缓存命中率,优化查询效率。

通过合理使用一级缓存,可在保证数据一致性的前提下,显著提升数据库访问性能。建议根据实际业务场景选择是否手动控制缓存行为。










在 MyBatis 中,一级缓存默认是开启的,无需额外配置即可使用。但根据实际需求,也可以通过以下方式手动关闭或调整其行为。以下是具体的开启和关闭方法及注意事项:


一、一级缓存的默认行为

  1. 作用范围
    一级缓存是 SqlSession 级别的缓存,即在同一个 SqlSession 内的多次查询会共享该缓存。
  2. 默认开启
    MyBatis 默认开启一级缓存,无需任何配置即可生效。

二、关闭一级缓存的方法

1. 全局关闭(修改配置文件)

在 MyBatis 的全局配置文件(如 mybatis-config.xml)中设置 localCacheScopeSTATEMENT

<settings>
    <!-- 默认值为 SESSION,改为 STATEMENT 后一级缓存失效 -->
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>
  • 作用
    将一级缓存的作用域从 会话级(SESSION) 改为 语句级(STATEMENT),即每次执行完 SQL 后立即清空缓存,相当于关闭了一级缓存。
2. 局部关闭(在单个 SQL 中配置)

在 Mapper XML 的 <select> 标签中设置 flushCache="true"

<select id="selectUserById" resultType="User" flushCache="true">
    SELECT * FROM user WHERE id = #{id}
</select>
  • 作用
    执行该 SQL 后,会强制清空当前 SqlSession 的一级缓存。通常用于需要避免缓存的场景(如频繁更新的数据)。
3. 在代码中手动清空缓存

通过 SqlSessionclearCache() 方法手动清空当前会话的一级缓存:

sqlSession.clearCache();

三、验证一级缓存是否生效

场景示例
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.selectUserById(1);  // 第一次查询,数据库有记录
    User user2 = mapper.selectUserById(1);  // 第二次查询,命中缓存
    System.out.println(user1 == user2);      // 输出 true(同一对象)
} finally {
    sqlSession.close();
}
  • 若输出 true:说明一级缓存生效。
  • 若输出 false:说明一级缓存未生效(可能被关闭或配置错误)。

四、注意事项

1. Spring 集成时的一级缓存问题

在 Spring 中,SqlSession 默认由 SqlSessionTemplateSqlSessionFactory 管理,可能导致一级缓存失效:

  • 原因:Spring 每次调用 Mapper 方法时,可能会生成新的 SqlSession,导致无法共享缓存。
  • 解决方案
    确保在同一个业务方法内使用同一个 SqlSession(通过 @Transactional 确保事务边界内的 SqlSession 共享)。
2. 缓存失效的其他场景

即使未显式关闭一级缓存,以下操作也会清空当前 SqlSession 的一级缓存:

  • 执行 insertupdatedelete 操作。
  • 手动调用 sqlSession.commit()sqlSession.rollback()
  • 执行带有 flushCache="true" 的 SQL。
3. 脏读问题
  • 问题:同一 SqlSession 内,如果其他线程修改了数据库数据,当前会话的一级缓存可能返回旧数据(脏读)。
  • 解决
    在需要实时数据的场景中,可手动清空缓存或调整 localCacheScopeSTATEMENT

五、总结

操作方法
开启一级缓存默认开启,无需配置。
全局关闭设置 <setting name="localCacheScope" value="STATEMENT"/>
局部关闭<select> 标签中添加 flushCache="true"
手动清空缓存调用 sqlSession.clearCache()

通过合理配置一级缓存,可以在保证数据一致性的同时提升查询性能。在分布式或高并发场景中,建议结合二级缓存或第三方缓存(如 Redis)进一步优化。










在 MyBatis 里,一级缓存是SqlSession级别的缓存,默认处于开启状态,并且无法直接将其关闭。不过,你可以通过一些手段来达成类似关闭一级缓存的效果。下面为你详细介绍相关情况:

一级缓存默认开启

一级缓存默认开启,在同一个SqlSession中执行相同的 SQL 查询时,MyBatis 会先从缓存里查找,如果找到就直接返回结果,不会再执行数据库查询。

示例代码
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class FirstLevelCacheDefaultExample {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = FirstLevelCacheDefaultExample.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 第一次查询
            Object result1 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第一次查询结果: " + result1);

            // 第二次查询,使用相同的 SqlSession
            Object result2 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第二次查询结果: " + result2);
        }
    }
}

在这个示例里,第二次查询会直接从一级缓存获取结果,不会再次执行数据库查询。

实现类似关闭一级缓存的效果

虽然不能直接关闭一级缓存,但可以通过以下方法实现类似效果:

1. 每次查询后清空缓存

在每次查询完成后,调用SqlSession.clearCache()方法来清空一级缓存。

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class ClearCacheExample {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = ClearCacheExample.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 第一次查询
            Object result1 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第一次查询结果: " + result1);

            // 清空缓存
            session.clearCache();

            // 第二次查询
            Object result2 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第二次查询结果: " + result2);
        }
    }
}

在这个示例中,第一次查询后调用了session.clearCache()方法,这样第二次查询时就不会使用缓存,而是重新执行数据库查询。

2. 每次查询都使用新的SqlSession

每次查询都创建一个新的SqlSession,因为一级缓存是SqlSession级别的,不同的SqlSession有各自独立的缓存。

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class NewSqlSessionExample {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = NewSqlSessionExample.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 第一次查询
        try (SqlSession session1 = sqlSessionFactory.openSession()) {
            Object result1 = session1.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第一次查询结果: " + result1);
        }

        // 第二次查询,使用新的 SqlSession
        try (SqlSession session2 = sqlSessionFactory.openSession()) {
            Object result2 = session2.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第二次查询结果: " + result2);
        }
    }
}

这里两次查询分别使用了不同的SqlSession,所以每次查询都会执行数据库查询,不会使用缓存。










在 MyBatis 中,一级缓存是 SqlSession 级别的缓存,默认是开启的。你可以通过以下方式来关闭或开启一级缓存:

关闭一级缓存

在每次执行增删改操作后,MyBatis 会默认清空一级缓存。如果你希望在查询时也关闭一级缓存,可以在执行查询前调用 sqlSession.clearCache() 方法来手动清除缓存。

SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 执行查询,结果会从数据库中获取,并存入一级缓存
User user1 = userMapper.getUserById(1);

// 手动清除一级缓存
sqlSession.clearCache();

// 再次执行查询,结果会再次从数据库中获取
User user2 = userMapper.getUserById(1);

sqlSession.close();

开启一级缓存

一级缓存是默认开启的,因此通常不需要额外的操作来开启它。只要在同一个 SqlSession 中进行查询,MyBatis 会自动使用一级缓存。

注意事项

  • 一级缓存的生命周期与 SqlSession 的生命周期一致,当 SqlSession 关闭时,一级缓存也会被清空。
  • 如果在同一个 SqlSession 中进行了增删改操作,MyBatis 会自动清空一级缓存,以保证数据的一致性。
  • 如果你需要更细粒度的缓存控制,可以考虑使用 MyBatis 的二级缓存。

通过合理使用一级缓存和二级缓存,可以有效减少数据库的访问次数,提高应用程序的性能。










MyBatis 一级缓存机制及控制方法如下:


一、一级缓存的基础概念

  1. 作用范围
    SqlSession 级别,同一个会话中多次执行相同 SQL + 参数的查询时,默认直接从内存读取结果。

  2. 默认状态
    自动开启,无需手动配置。

  3. 缓存失效场景

    • 执行了 INSERT/UPDATE/DELETE 操作(任何写操作)。
    • 调用 SqlSession.clearCache()
    • 事务提交或回滚(取决于事务管理器配置)。

二、关闭一级缓存的方法

1. 全局关闭(所有查询禁用)

修改 MyBatis 全局配置文件,将 localCacheScope 设置为 STATEMENT

<configuration>
  <settings>
    <setting name="localCacheScope" value="STATEMENT"/>
  </settings>
</configuration>
  • 效果
    每个 SQL 执行后立即清空缓存,所有查询强制访问数据库。

2. 单次查询绕过缓存

在 Mapper 的 SQL 配置中,添加 flushCache="true"

<select id="selectUser" resultType="User" flushCache="true">
  SELECT * FROM user WHERE id = #{id}
</select>
  • 效果
    每次执行该查询前清空缓存,确保获取最新数据。

3. 手动清空缓存

在代码中主动调用 SqlSession.clearCache()

try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.selectUser(1);
    
    // 清空缓存
    sqlSession.clearCache();
    
    User user2 = mapper.selectUser(1); // 重新查询数据库
}

三、注意事项

  1. 性能权衡
    关闭一级缓存会增加数据库压力,需根据业务场景(如高实时性要求)谨慎选择。

  2. 二级缓存无关性
    一级缓存与二级缓存(Mapper 级别)相互独立,关闭一级缓存不影响二级缓存。

  3. 分布式环境问题
    一级缓存仅在单机 SqlSession 内有效,分布式系统中需通过其他方案(如 Redis)保证数据一致性。


四、验证缓存行为

可通过日志观察 SQL 执行次数:

# 在 mybatis-config.xml 中启用日志
<configuration>
  <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
  </settings>
</configuration>
  • 预期结果
    关闭缓存后,相同查询会重复打印 SQL 执行日志。

通过上述方法,可灵活控制 MyBatis 一级缓存的开启与关闭。










好的,我现在需要回答用户关于如何开启和关闭MyBatis的一级缓存的问题。用户之前已经询问过MyBatis的缓存机制,现在他们可能想更具体地了解一级缓存的配置和使用。

首先,我得回忆一下MyBatis一级缓存的基本信息。根据之前的对话,一级缓存是默认开启的,生命周期绑定到SqlSession,不需要额外配置。不过用户可能想知道是否有方法可以显式地控制它的开启或关闭,或者在不同作用域下的行为。

接下来,我需要考虑用户可能的场景。例如,他们可能在某个特定的操作中不希望使用一级缓存,比如执行更新操作后希望立即刷新缓存,或者在多线程环境中处理缓存问题。此外,用户可能误认为一级缓存需要手动配置,所以需要明确说明它是默认开启的,并解释如何通过配置调整其作用域。

然后,我应该详细说明如何配置一级缓存的作用域,比如设置为STATEMENT、SESSION或JDBC。这会影响缓存的生命周期,用户可能需要根据不同的需求选择合适的模式。例如,STATEMENT作用域下,每次执行SQL语句后缓存就会被清除,这可能适合某些需要频繁更新的场景。

另外,用户可能关心在代码中如何手动管理一级缓存,比如调用clearCache()方法。这时候需要举例说明在Java代码中如何调用,以及在Mapper XML中使用标签来强制刷新缓存。

还需要提到一些注意事项,比如一级缓存的数据一致性问题和多线程环境下的处理。虽然MyBatis默认使用非线程安全的缓存,但用户可以通过配置第三方缓存如EhCache来解决并发问题。

最后,总结开启和关闭的方法,强调默认开启的情况,以及如何通过配置和代码进行控制。确保用户能够根据实际需求灵活调整一级缓存的行为。

MyBatis 的**一级缓存(Local Cache)**默认自动开启,无需手动配置。但其作用范围和刷新行为可以通过配置或代码灵活控制。以下是详细说明如何管理一级缓存:


一、一级缓存的默认行为

自动开启:MyBatis 在初始化时默认启用一级缓存,绑定到每个 SqlSession 对象。 • 生命周期:随 SqlSession 的创建而诞生,随 SqlSession 的关闭或调用 clearCache() 方法而销毁。 • 缓存范围:默认作用域为 STATEMENT(每次执行 SQL 后清空缓存),可通过配置修改为 SESSIONJDBC


二、配置一级缓存的作用域

mybatis-config.xml 中通过 <settings> 配置 localCacheScope,可选值如下:

<configuration>
  <settings>
    <!-- 默认值:STATEMENT -->
    <setting name="localCacheScope" value="STATEMENT"/>
    <!-- 可选值:SESSION 或 JDBC -->
    <!-- <setting name="localCacheScope" value="SESSION"/></setting>
    <!-- <setting name="localCacheScope" value="JDBC"/></setting>
  </settings>
</configuration>
1. STATEMENT(默认)

行为:每次执行 SQL 语句(SELECT/INSERT/UPDATE/DELETE)后,一级缓存自动清空。 • 适用场景:适用于需要频繁更新数据的场景,确保每次操作后缓存立即刷新。

2. SESSION

行为:仅在调用 sqlSession.commit()sqlSession.rollback() 时清空一级缓存。 • 适用场景:适用于需要批量操作的场景(如事务处理),减少缓存刷新频率。

3. JDBC

行为:一级缓存完全失效,直接使用数据库查询结果。 • 适用场景:极少使用,仅用于调试或特殊需求(如避免缓存干扰)。


三、手动控制一级缓存

1. 清空当前 SqlSession 的缓存

Java 代码

sqlSession.clearCache(); // 清空一级缓存

Mapper XML:在增删改操作中强制刷新缓存:

<update id="updateUser" parameterType="User" flushCache="true">
  UPDATE users SET name=#{name} WHERE id=#{id}
</update>
2. 在事务中管理缓存

提交事务时自动刷新:当 localCacheScope 设置为 SESSION 时,提交事务会自动清空一级缓存。 • 回滚事务时不清空缓存:回滚不会触发缓存刷新,需手动调用 clearCache()


四、关闭一级缓存(高级场景)

虽然 MyBatis 无法直接“关闭”一级缓存,但可以通过以下方式模拟关闭效果:

1. 将作用域设为 JDBC

mybatis-config.xml 中配置:

<setting name="localCacheScope" value="JDBC"/>

此时一级缓存功能被禁用,每次查询直接从数据库读取。

2. 在代码中频繁清空缓存

在每次操作后手动调用 sqlSession.clearCache(),但需谨慎使用,避免性能问题。


五、示例代码

1. 配置 localCacheScopeSESSION
<!-- mybatis-config.xml -->
<configuration>
  <settings>
    <setting name="localCacheScope" value="SESSION"/>
  </settings>
</configuration>
2. 在 Mapper XML 中强制刷新缓存
<mapper namespace="com.example.UserMapper">
  <update id="deleteUser" parameterType="int" flushCache="true">
    DELETE FROM users WHERE id = #{id}
  </update>
</mapper>
3. 在 Java 代码中手动清空缓存
public class UserService {
  public void updateUser(User user) {
    sqlSession.update("com.example.UserMapper.updateUser", user);
    sqlSession.clearCache(); // 强制清空一级缓存
  }
}

六、注意事项

  1. 数据一致性: • 一级缓存是线程不安全的,多线程环境下需结合事务或同步机制。 • 若使用 SESSION 作用域,需确保事务提交后缓存及时刷新。

  2. 性能权衡: • 频繁调用 clearCache() 会降低性能,需根据业务场景合理使用。 • 对于读多写少的场景,保持默认 STATEMENT 作用域即可。

  3. 与二级缓存的关系: • 一级缓存是二级缓存的基础,二级缓存的命中数据会先填充到一级缓存。 • 若关闭一级缓存(如设为 JDBC),二级缓存将直接查询数据库,失去意义。


七、总结

开启:默认开启,无需配置。 • 关闭:通过设置 localCacheScope="JDBC" 或手动频繁清空缓存模拟关闭。 • 最佳实践: • 默认使用 STATEMENT 作用域,平衡性能与一致性。 • 在事务场景中配置 SESSION 作用域,减少不必要的缓存刷新。 • 显式调用 flushCache() 仅在必要时(如关键数据更新)。

通过灵活配置和代码控制,可以最大化发挥一级缓存的优势,同时避免潜在问题。



















Mybatis开启关闭二级缓存

MyBatis 的二级缓存默认关闭,需要手动配置开启,且支持全局或细粒度控制。以下是详细的开启和关闭方法:


一、开启二级缓存

1. 全局启用二级缓存(必须)

mybatis-config.xml 中确保开启缓存功能(默认已开启,但需显式配置避免冲突):

<settings>
  <setting name="cacheEnabled" value="true"/> <!-- 默认 true,可省略 -->
</settings>
2. 在 Mapper XML 中配置 <cache>

为需要缓存的 Mapper 添加 <cache> 标签:

<mapper namespace="com.example.UserMapper">
  <!-- 启用二级缓存 -->
  <cache
    eviction="LRU"           <!-- 淘汰策略LRU/FIFO/SOFT/WEAK-->
    flushInterval="60000"    <!-- 自动刷新时间(毫秒) -->
    size="1024"              <!-- 缓存最大对象数 -->
    readOnly="true"          <!-- 是否只读(建议 false 以支持序列化修改) -->
  />
  
  <select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{id}
  </select>
</mapper>
3. 实体类实现序列化

如果缓存对象需要序列化(当 readOnly="false" 时),对应的 Java 类需实现 Serializable

public class User implements Serializable {
  // 字段、Getter/Setter...
}

二、关闭二级缓存

1. 全局关闭(不推荐)

mybatis-config.xml 中关闭所有 Mapper 的二级缓存:

<settings>
  <setting name="cacheEnabled" value="false"/>
</settings>
2. 关闭指定 Mapper 的缓存

在对应的 Mapper XML 中不添加 <cache> 标签,或显式设置 useCache="false"

<mapper namespace="com.example.UserMapper">
  <!-- 不配置 <cache> 标签,则默认不启用二级缓存 -->
  
  <!-- 或在特定查询中关闭缓存 -->
  <select id="selectById" resultType="User" useCache="false">
    SELECT * FROM user WHERE id = #{id}
  </select>
</mapper>
3. 清空二级缓存

执行任意 INSERT/UPDATE/DELETE 操作后,同 Mapper 的二级缓存会被清空:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(user); // 执行更新操作,清空 UserMapper 的二级缓存

三、注意事项

  1. 脏读问题

    • 多个 Mapper 操作同一张表时,需通过 <cache-ref namespace="其他Mapper"/> 共享缓存,否则可能导致数据不一致。
    • 建议对读多写少的数据使用二级缓存。
  2. 事务提交机制

    • 二级缓存在事务提交(sqlSession.commit())后才会生效,未提交的修改对其他会话不可见。
  3. 分布式环境

    • 默认的二级缓存是单机缓存,分布式场景需整合 Redis、Ehcache 等:
      <cache type="org.mybatis.caches.redis.RedisCache"/>
      
  4. 序列化要求

    • 若未设置 readOnly="true",缓存对象必须实现 Serializable,否则会抛出序列化异常。

四、代码验证示例

// 验证二级缓存(跨 SqlSession)
try (SqlSession session1 = sqlSessionFactory.openSession()) {
  UserMapper mapper1 = session1.getMapper(UserMapper.class);
  User user1 = mapper1.selectById(1); // 访问数据库
  session1.commit(); // 提交后写入二级缓存
}

try (SqlSession session2 = sqlSessionFactory.openSession()) {
  UserMapper mapper2 = session2.getMapper(UserMapper.class);
  User user2 = mapper2.selectById(1); // 命中二级缓存
  System.out.println(user1 == user2); // 输出 false(不同对象,但数据一致)
}

五、最佳实践

  • 慎用二级缓存:在写操作频繁的场景中,频繁清空缓存可能降低性能。
  • 明确缓存范围:通过 <cache-ref> 管理关联 Mapper 的缓存依赖。
  • 监控缓存命中率:通过日志或监控工具分析缓存效果(如整合 SLF4J 日志)。










MyBatis 二级缓存的开启与关闭操作指南

二级缓存是 MyBatis 的跨 SqlSession 全局缓存,需手动配置。以下是详细操作步骤:

一、开启二级缓存

1. 全局配置(mybatis-config.xml)
<configuration>
  <settings>
    <!-- 启用二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
  </settings>
</configuration>
2. Mapper 配置(XML 或注解)
  • XML 方式:在 Mapper 文件中添加 <cache> 标签

    <mapper namespace="com.example.mapper.UserMapper">
      <!-- 配置二级缓存 -->
      <cache 
        eviction="LRU"          <!-- 缓存淘汰策略 -->
        flushInterval="60000"   <!-- 60秒自动刷新 -->
        size="512"              <!-- 最大缓存对象数 -->
        readOnly="true"/>       <!-- 返回对象引用 -->
      
      <select id="getUserById" resultType="User">
        SELECT * FROM users WHERE id = #{id}
      </select>
    </mapper>
    
  • 注解方式(MyBatis 3.2+):

    @CacheNamespace
    public interface UserMapper {
        @Select("SELECT * FROM users WHERE id = #{id}")
        User getUserById(int id);
    }
    
3. 实体类要求

缓存对象需实现 Serializable 接口:

public class User implements Serializable {
    private Integer id;
    private String name;
    // getters/setters
}

二、关闭二级缓存

1. 全局关闭

修改 mybatis-config.xml

<settings>
  <setting name="cacheEnabled" value="false"/>
</settings>
2. Mapper 级关闭
  • XML 方式:移除 <cache> 标签
  • 注解方式:移除 @CacheNamespace 注解
3. 动态控制(可选)

在特定查询中禁用缓存:

<select id="getRealtimeData" resultType="Data" useCache="false">
  SELECT * FROM real_time_table
</select>

三、验证缓存行为

// 测试二级缓存跨会话共享
try (SqlSession session1 = sqlSessionFactory.openSession()) {
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    User user1 = mapper1.getUserById(1); // 缓存加载
    session1.commit(); // 提交后缓存生效
}

try (SqlSession session2 = sqlSessionFactory.openSession()) {
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    User user2 = mapper2.getUserById(1); // 从二级缓存获取
    System.out.println(user1 == user2); // 输出 true
}

四、最佳实践

  1. 高频查询场景:对复杂报表查询启用二级缓存
  2. 写操作后刷新:执行 INSERT/UPDATE/DELETE 后调用 mapper.clearCache()
  3. 分布式环境:建议使用 Redis 等中央缓存替代本地二级缓存
  4. 监控缓存命中率:通过日志分析缓存效率,优化配置参数

五、注意事项

  • 缓存一致性:二级缓存可能引发脏读,需结合业务场景评估
  • 内存管理:设置合理的 size 值防止内存溢出
  • 序列化开销:复杂对象序列化可能影响性能

通过合理配置二级缓存,可显著减少数据库压力,提升系统吞吐量。建议通过压力测试确定最佳缓存策略。










在 MyBatis 中,二级缓存的开启和关闭需要通过多层配置控制,包括全局开关、命名空间(Mapper)级别的开关以及单个查询语句的开关。以下是具体步骤和注意事项:


一、开启二级缓存

1. 全局开启二级缓存

二级缓存的全局开关默认是开启的,但为了明确配置或团队协作,建议显式开启。

Spring Boot 配置(application.properties/application.yml):

# 全局开启二级缓存
mybatis.configuration.cache-enabled=true

MyBatis 核心配置文件(mybatis-config.xml):

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
2. 在 Mapper 文件中开启二级缓存

在需要使用二级缓存的 Mapper XML 文件中添加 <cache/> 标签,表示开启该命名空间(Mapper)的二级缓存。

<mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache/>

    <!-- 其他 SQL 语句 -->
    <select id="selectUserById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>
3. 配置二级缓存参数(可选)

通过 <cache> 标签的属性进一步控制缓存行为:

<cache
    eviction="LRU"       <!-- 缓存回收策略默认LRU -->
    flushInterval="60000"<!-- 自动刷新间隔(毫秒),60秒 -->
    size="1024"          <!-- 缓存最大条目数 -->
    readOnly="true"/>    <!-- 是否只读,true 可提升性能但需确保数据不可变 -->
  • eviction:缓存淘汰策略(LRU/FIFO/SOFT/WEAK)。
  • flushInterval:自动刷新间隔,单位毫秒。
  • size:缓存最大容量。
  • readOnlytrue 表示缓存对象只读,直接返回引用(需实体类不可变);false 表示返回拷贝(需序列化)。
4. 实体类实现序列化

如果 readOnly="false",实体类必须实现 Serializable 接口,否则会抛出 NotSerializableException

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // 属性和方法
}

二、关闭二级缓存

1. 全局关闭二级缓存

在全局配置中关闭二级缓存:

Spring Boot 配置:

mybatis.configuration.cache-enabled=false

MyBatis 核心配置文件:

<settings>
    <setting name="cacheEnabled" value="false"/>
</settings>
2. 关闭 Mapper 级别的二级缓存

在 Mapper XML 文件中不添加 <cache/> 标签,或显式禁用缓存:

<mapper namespace="com.example.mapper.UserMapper">
    <!-- 禁用二级缓存 -->
    <cache-usage="none"/>
    
    <!-- 其他 SQL 语句 -->
</mapper>
3. 关闭单个查询的二级缓存

<select> 标签中设置 useCache="false",表示该查询不使用二级缓存:

<select id="selectUserById" resultType="User" useCache="false">
    SELECT * FROM user WHERE id = #{id}
</select>

三、注意事项

1. 缓存生效条件
  • 必须同时满足以下条件
    1. 全局缓存开关开启(cacheEnabled=true)。
    2. Mapper 文件中配置了 <cache/>
    3. 查询语句未显式禁用缓存(useCache="false")。
2. 缓存失效场景
  • 执行 insertupdatedelete 操作时,会清空当前 Mapper 的二级缓存。
  • 手动调用 SqlSession.clearCache()SqlSessionFactory.clearCache()
  • 达到 flushInterval 设置的刷新时间间隔。
3. 一级缓存与二级缓存的关系
  • 二级缓存的数据来源于一级缓存:只有当 SqlSession 提交(commit)或关闭(close)时,一级缓存的数据才会提交到二级缓存。
  • 二级缓存是跨 SqlSession 的全局缓存,而一级缓存是单个 SqlSession 内的缓存。
4. 并发控制
  • 如果 readOnly="false",缓存会返回对象的拷贝(通过序列化),以避免并发修改问题。
  • readOnly="true",需确保缓存的数据是不可变的(Immutable),否则可能导致数据不一致。

四、验证二级缓存是否生效

示例代码:
// 第一个 SqlSession
try (SqlSession session1 = sqlSessionFactory.openSession()) {
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    User user1 = mapper1.selectUserById(1); // 第一次查询,从数据库获取
    System.out.println("第一次查询:" + user1); // 数据库查询
}

// 第二个 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    User user2 = mapper2.selectUserById(1); // 第二次查询,从二级缓存获取
    System.out.println("第二次查询:" + user2); // 缓存命中
}
  • 若输出相同对象地址(如 user1 == user2true),说明二级缓存生效。
  • 若输出不同地址,则二级缓存未生效,需检查配置是否正确。

五、总结

操作方法
全局开启二级缓存mybatis.configuration.cache-enabled=true<setting name="cacheEnabled" value="true"/>
Mapper 级开启在 Mapper XML 中添加 <cache/>
全局关闭二级缓存mybatis.configuration.cache-enabled=false<setting name="cacheEnabled" value="false"/>
Mapper 级关闭不添加 <cache/> 或设置 <cache-usage="none"/>
单个查询关闭缓存<select> 标签中设置 useCache="false"

通过合理配置二级缓存,可以显著提升跨 SqlSession 的查询性能,但需注意数据一致性、序列化和并发问题。










MyBatis 的二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享同一个 Mapper 的二级缓存。二级缓存默认是关闭的,下面为你详细介绍如何开启和关闭它。

开启二级缓存

要开启二级缓存,需要完成以下几个步骤:

1. 在 MyBatis 配置文件中开启全局二级缓存

mybatis-config.xml 文件中添加如下配置:

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

这行配置将全局的二级缓存开关打开,允许使用二级缓存。

2. 在 Mapper XML 文件中配置二级缓存

在对应的 Mapper XML 文件中添加 <cache> 标签来启用该 Mapper 的二级缓存,示例如下:

<mapper namespace="com.example.mapper.YourMapper">
    <cache/>
    <!-- 其他 SQL 语句 -->
    <select id="selectById" parameterType="int" resultType="com.example.entity.YourEntity">
        SELECT * FROM your_table WHERE id = #{id}
    </select>
</mapper>

这里的 <cache> 标签启用了该 Mapper 的二级缓存。

或者在 Mapper 接口上使用 @CacheNamespace 注解

如果你使用的是注解方式开发,可以在 Mapper 接口上添加 @CacheNamespace 注解,示例如下:

import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;

@CacheNamespace
public interface YourMapper {
    @Select("SELECT * FROM your_table WHERE id = #{id}")
    YourEntity selectById(int id);
}

关闭二级缓存

关闭二级缓存也有几种方式:

1. 关闭全局二级缓存

mybatis-config.xml 文件中,将 cacheEnabled 设置为 false

<settings>
    <setting name="cacheEnabled" value="false"/>
</settings>

这样所有 Mapper 的二级缓存都会被关闭。

2. 针对单个 Mapper 关闭二级缓存

如果你只想关闭某个特定 Mapper 的二级缓存,可以在 Mapper XML 文件中移除 <cache> 标签,或者在 Mapper 接口上移除 @CacheNamespace 注解。

代码示例

以下是一个完整的 Java 代码示例,展示了如何使用开启了二级缓存的 Mapper 进行查询:

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class SecondLevelCacheExample {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = SecondLevelCacheExample.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 第一个 SqlSession
        try (SqlSession session1 = sqlSessionFactory.openSession()) {
            Object result1 = session1.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第一个 SqlSession 查询结果: " + result1);
        }

        // 第二个 SqlSession
        try (SqlSession session2 = sqlSessionFactory.openSession()) {
            Object result2 = session2.selectOne("com.example.mapper.YourMapper.selectById", 1);
            System.out.println("第二个 SqlSession 查询结果: " + result2);
        }
    }
}

在这个示例中,如果二级缓存开启,第二个 SqlSession 的查询会直接从二级缓存中获取结果,而不会再次执行数据库查询。

注意事项

  • 二级缓存中的对象需要实现 java.io.Serializable 接口,因为缓存可能会将对象序列化到磁盘。
  • 当执行插入、更新、删除操作时,MyBatis 会自动清空对应的 Mapper 的二级缓存,以保证数据的一致性。










在 MyBatis 中,二级缓存默认是关闭的,需要手动开启和配置。以下是开启和关闭二级缓存的方法:

开启二级缓存

  1. 在 MyBatis 核心配置文件中开启全局缓存: 在 mybatis-config.xml 文件的 <settings> 标签中添加以下配置来开启全局缓存:

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    

    这一步是可选的,因为 MyBatis 默认的 cacheEnabled 值就是 true

  2. 在映射文件中开启二级缓存: 在需要使用二级缓存的 Mapper 的 XML 文件中添加 <cache> 标签来开启该 Mapper 的二级缓存:

    <mapper namespace="com.example.mapper.UserMapper">
        <!-- 开启二级缓存 -->
        <cache/>
        <!-- 或者可以自定义缓存属性 -->
        <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
        <!-- 其他 SQL 映射语句 -->
    </mapper>
    

    其中,eviction 属性指定缓存的回收策略,flushInterval 指定缓存刷新的时间间隔(毫秒),size 指定缓存可以存储的对象数量,readOnly 指定缓存是否只读。

  3. 确保返回的实体类可序列化: 使用二级缓存时,返回的实体类需要实现 Serializable 接口,否则会抛出异常。

关闭二级缓存

  1. 全局关闭二级缓存: 在 mybatis-config.xml 文件中,将 cacheEnabled 设置为 false

    <settings>
        <setting name="cacheEnabled" value="false"/>
    </settings>
    
  2. 关闭特定 Mapper 的二级缓存: 在映射文件中移除 <cache> 标签即可关闭该 Mapper 的二级缓存。

  3. 关闭单个查询的二级缓存: 在 <select> 标签中设置 useCache="false" 属性,可以禁用该查询的二级缓存:

    <select id="findById" parameterType="int" resultType="com.example.entity.User" useCache="false">
        SELECT * FROM user WHERE id = #{id}
    </select>
    

通过以上方法,可以灵活地控制 MyBatis 中二级缓存的开启和关闭,以满足不同的应用场景和需求。










嗯,用户想了解MyBatis如何开启和关闭二级缓存。我需要先回忆一下MyBatis的缓存机制,特别是二级缓存的特点。首先,二级缓存是Mapper级别的,多个SqlSession共享的,默认应该是关闭的,需要手动开启。

然后,用户可能想知道具体的配置步骤。记得在MyBatis的全局配置文件(比如mybatis-config.xml)里有一个标签,里面可以设置cacheEnabled选项。不过这里可能有个误区,MyBatis文档中其实从3.4.5版本开始,cacheEnabled默认是true,但即使开启,各个Mapper的二级缓存还是需要单独配置的。所以需要提醒用户即使全局开启了,每个Mapper还需要单独决定是否使用。

接下来,开启二级缓存的具体方法是在Mapper XML文件中添加标签。这个标签有几个属性可以配置,比如eviction策略、flushInterval等。同时,对应的POJO需要实现Serializable接口,因为二级缓存可能涉及序列化。这部分需要详细说明,避免用户遇到序列化的问题。

另外,用户可能需要知道如何关闭某个Mapper的二级缓存。这时候可以在标签里设置blocking或者useCache属性,或者在方法上设置useCache="false"。或者在全局配置里将cacheEnabled设为false,但这样会影响所有Mapper,可能不是用户想要的。

还要注意二级缓存的使用场景和潜在问题,比如数据一致性。在分布式环境下或者多表操作时,二级缓存可能导致脏数据,这时候可能需要谨慎使用或者结合其他缓存方案。

用户可能的误区是认为全局开启cacheEnabled后所有Mapper都会自动使用二级缓存,但实际上每个Mapper需要单独配置。所以需要明确说明这一点,避免配置错误。

最后,总结一下步骤:全局配置是否开启,Mapper文件添加,实体类序列化,以及如何关闭单个Mapper或方法的缓存。同时提醒注意事项,比如测试时查看日志确认缓存是否生效,缓存策略的选择等。 好的,我将分步骤为您解释MyBatis二级缓存的开启和关闭机制:

━━━━━━━━━━━━━━ 一、二级缓存核心特性 ━━━━━━━━━━━━━━

  1. 作用范围:Mapper级别(namespace维度)
  2. 共享范围:多个SqlSession共享
  3. 存储介质:默认内存,可通过插件扩展(Redis等)
  4. 生效条件:事务提交后生效

━━━━━━━━━━ 二、开启二级缓存 ━━━━━━━━━━

  1. 全局配置(mybatis-config.xml)
<configuration>
  <settings>
    <!-- 3.4.5+版本默认true,建议显式声明 -->
    <setting name="cacheEnabled" value="true"/>
  </settings>
</configuration>
  1. Mapper级配置(XXXMapper.xml)
<mapper namespace="com.example.UserMapper">
  <!-- 基础配置 -->
  <cache/>
  
  <!-- 完整配置示例 -->
  <cache 
    eviction="LRU"
    flushInterval="60000"
    size="1024"
    readOnly="true"/>
</mapper>
  1. 实体类要求
public class User implements Serializable {
    // 必须实现序列化接口
}

━━━━━━━━━ 三、关闭缓存 ━━━━━━━━━

  1. 全局关闭(禁用所有二级缓存)
<setting name="cacheEnabled" value="false"/>
  1. Mapper级关闭
<mapper namespace="...">
  <!-- 完全禁用 -->
  <cache blocking="true"/>
</mapper>
  1. 语句级关闭(CRUD标签)
<select id="selectUser" resultType="User" useCache="false">
    SELECT * FROM user
</select>

━━━━━━━━━━━━ 四、注意事项 ━━━━━━━━━━━━

  1. 数据一致性风险

    • 多表操作时建议关闭关联表的缓存
    • 分布式环境慎用默认缓存
  2. 最佳实践

    • 读多写少场景适用
    • 高并发写入场景建议关闭
    • 建议配合日志分析命中率

需要特别注意:当缓存配置为readOnly="false"时,MyBatis会返回缓存对象的拷贝,此时进行对象修改不会影响缓存内容。










嗯,用户之前已经问过MyBatis的一级缓存开启关闭,现在又转向二级缓存。我需要先回顾一下之前的对话,确保内容连贯。用户可能是在学习MyBatis的缓存机制,逐步深入。他们可能正在开发一个需要优化数据库查询的项目,所以想了解如何利用缓存提升性能。

首先,要明确二级缓存和一级缓存的区别。一级缓存是默认的,会话级别的,而二级缓存是全局的,跨会话的。用户已经知道如何管理一级缓存,现在想知道二级缓存的配置和使用。

接下来,需要解释如何开启二级缓存。这包括在Mapper XML中添加标签,以及在mybatis-config.xml中配置插件,比如集成EhCache或Redis。还要提到自定义缓存实现的方法,因为用户可能希望使用不同的缓存技术。

然后,关于关闭二级缓存,虽然通常不需要,但用户可能想了解在某些情况下如何禁用它,比如测试环境或者不需要缓存的时候。这时候需要说明移除Mapper中的标签,或者通过配置禁用插件。

还需要注意数据一致性的问题,比如执行更新操作时如何刷新缓存,使用标签或者调用clearCache方法。此外,序列化配置也很重要,特别是当对象不能被正确序列化时,会导致错误。

用户可能关心在实际应用中的最佳实践,比如哪些场景适合使用二级缓存,如何配置合理的淘汰策略,以及多线程环境下的注意事项。需要给出具体的例子,比如配置EhCache的步骤,或者如何在代码中手动刷新缓存。

最后,总结开启和关闭二级缓存的方法,强调合理使用缓存的重要性,避免数据不一致和性能问题。确保回答结构清晰,分点明确,方便用户理解和应用。

MyBatis 的**二级缓存(Global Cache)**是跨 SqlSession 的全局缓存,合理配置可显著减少数据库访问次数。以下是详细说明如何开启和关闭二级缓存,以及相关配置与注意事项:


一、开启二级缓存

1. 核心配置步骤

(1) 启用 Mapper 的二级缓存
在 Mapper XML 文件中添加 <cache/> 标签,标识该 Mapper 使用二级缓存:

<mapper namespace="com.example.UserMapper">
  <cache/> <!-- 开启二级缓存 -->
  <select id="findByUserId" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  </select>
</mapper>

(2) 全局配置缓存插件
mybatis-config.xml 中引入缓存插件(如 EhCache、Redis):

<configuration>
  <plugins>
    <!-- 集成 EhCache -->
    <plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
      <property name="configLocation" value="classpath:ehcache.xml"/>
    </plugin>
    
    <!-- 或集成 Redis -->
    <plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
      <property name="host" value="localhost"/>
      <property name="port" value="6379"/>
    </plugin>
  </plugins>
</configuration>
2. 自定义缓存实现(可选)

如果不想依赖第三方库,可自定义缓存实现类(实现 org.apache.ibatis.cache.Cache 接口):

public class CustomCache implements Cache {
  private Map<Object, Object> cacheStore = new ConcurrentHashMap<>();

  @Override
  public void putObject(Object key, Object value) {
    cacheStore.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cacheStore.get(key);
  }

  // 其他方法实现(如 removeObject、clear 等)
}

然后在 Mapper XML 中指定自定义缓存:

<cache type="com.example.CustomCache"/>

二、关闭二级缓存

1. 禁用特定 Mapper 的缓存

方法:在 Mapper XML 中移除 <cache/> 标签,或显式设置 useCache="false"

<mapper namespace="com.example.UserMapper">
  <cache useCache="false"/> <!-- 禁用二级缓存 -->
  <!-- SQL 映射 -->
</mapper>
2. 全局关闭缓存插件

方法:在 mybatis-config.xml 中移除缓存插件配置:

<!-- 删除以下插件配置 -->
<!--
<plugins>
  <plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
    ...
  </plugin>
</plugins>
-->
3. 代码中手动刷新缓存(间接关闭效果)

方法:在增删改操作中强制刷新二级缓存:

<update id="deleteUser" parameterType="int" flushCache="true">
  DELETE FROM users WHERE id = #{id}
</update>

Java 代码

sqlSession.update("com.example.UserMapper.deleteUser", userId);
sqlSession.clearCache(); // 清空当前 SqlSession 的一级缓存和二级缓存

三、二级缓存关键配置与优化

1. 缓存序列化

默认行为:MyBatis 使用 JdkSerialization 序列化对象,但效率较低。 • 自定义序列化:在 Mapper XML 中配置 serialize="true" 并实现 Serializable 接口:

<cache serialization="true"/>

或全局配置:

<configuration>
  <settings>
    <setting name="cacheSerializationEnabled" value="true"/>
  </settings>
</configuration>

• 推荐使用 Jackson 或 Kryo 替代 JdkSerialization。

2. 缓存淘汰策略

默认策略:LRU(Least Recently Used)。 • 自定义策略:通过实现 org.apache.ibatis.cache.LruCache 或配置第三方缓存(如 EhCache)的策略。

3. 数据一致性保障

自动刷新:执行 INSERT/UPDATE/DELETE 时,MyBatis 会自动刷新受影响的二级缓存。 • 手动刷新:通过 <flushCache> 标签或 sqlSession.clearCache() 强制刷新。


四、示例代码

1. 启用二级缓存并集成 EhCache

ehcache.xml(配置文件):

<ehcache>
  <cache name="userCache"
           maxEntriesLocalHeap="1000"
           eternal="false"
           timeToLiveSeconds="3600"
           timeToIdleSeconds="1800"
           overflowToDisk="false">
  </cache>
</ehcache>

mybatis-config.xml

<configuration>
  <plugins>
    <plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
      <property name="configLocation" value="classpath:ehcache.xml"/>
    </plugin>
  </plugins>
</configuration>
2. 禁用某个 Mapper 的缓存
<mapper namespace="com.example.OrderMapper">
  <cache useCache="false"/> <!-- 禁用该 Mapper 的二级缓存 -->
  <select id="findOrderById" resultType="Order">
    SELECT * FROM orders WHERE id = #{id}
  </select>
</mapper>

五、注意事项

  1. 适用场景: • 开启:适用于读多写少、跨会话复用的数据(如配置表、统计信息)。 • 关闭:适用于写频繁、数据实时性要求高的场景(如订单表)。

  2. 性能权衡: • 内存占用:二级缓存占用更多内存,需根据业务需求合理设置缓存大小。 • 一致性风险:长时间未刷新的缓存可能导致脏读,需结合业务场景设置 timeToLive 等参数。

  3. 多线程环境: • 默认缓存实现 PerpetualCache 不是线程安全的,需使用线程安全缓存(如 EhCache、Redis)。

  4. 分布式缓存: • 单机部署可选 EhCache,分布式系统推荐 Redis。


六、总结

开启二级缓存:通过 Mapper XML 的 <cache/> 标签和全局插件配置。 • 关闭二级缓存:移除 <cache/> 标签、禁用插件或手动刷新。 • 最佳实践: • 对静态数据(如字典表)开启二级缓存,配置合理的过期时间。 • 高频更新数据禁用二级缓存,或结合 <flushCache> 实现手动刷新。 • 使用第三方缓存(如 Redis)提升分布式环境下的缓存一致性。

通过合理配置二级缓存,可显著减少数据库压力,但需权衡一致性、内存和性能成本。



















Mybatis二级缓存详解

MyBatis 的二级缓存是跨 SqlSession 的缓存机制,属于 Mapper 级别的缓存,适用于需要多个会话共享数据的场景。以下是其核心原理、配置细节、工作机制及注意事项的详细解析:


一、二级缓存的核心原理

  1. 作用范围

    • Mapper 级别:以 Mapper 的 namespace 为作用域,同一 namespace 下的所有查询共享缓存。
    • 跨 SqlSession:不同会话(SqlSession)访问同一 Mapper 时,可命中缓存。
  2. 缓存结构

    • 存储结构:默认使用 PerpetualCache(基于 HashMap),但可扩展为 Redis、Ehcache 等第三方缓存。
    • 事务管理:通过 TransactionalCache 包装,确保事务提交后缓存生效。
  3. 缓存生命周期

    • 缓存创建:首次查询后,结果存入缓存(需事务提交后生效)。
    • 缓存失效:执行同 Mapper 的增删改操作(DML)后,整个 namespace 的缓存被清空。

二、二级缓存的配置与使用

1. 开启二级缓存

步骤 1:全局启用
mybatis-config.xml 中确保缓存开关开启(默认已开启):

<settings>
  <setting name="cacheEnabled" value="true" /> <!-- 默认 true -->
</settings>

步骤 2:Mapper 配置
在需要缓存的 Mapper XML 中添加 <cache> 标签:

<mapper namespace="com.example.UserMapper">
  <!-- 二级缓存配置 -->
  <cache
    eviction="LRU"           <!-- 淘汰策略LRU最近最少使用)、FIFO先进先出)、SOFT软引用)、WEAK弱引用-->
    flushInterval="60000"    <!-- 自动刷新间隔(毫秒),默认不刷新 -->
    size="1024"              <!-- 缓存最大对象数 -->
    readOnly="true"          <!-- 是否只读:true(返回缓存对象的引用,高效但不安全);false(返回深拷贝对象,需序列化) -->
  />
</mapper>

步骤 3:实体类序列化
readOnly="false",需确保实体类实现 Serializable 接口:

public class User implements Serializable {
  private static final long serialVersionUID = 1L;
  // 其他字段及方法...
}
2. 关闭二级缓存
  • 关闭指定 Mapper 的缓存:不添加 <cache> 标签。
  • 关闭特定查询的缓存:在 <select> 中设置 useCache="false"
    <select id="selectById" resultType="User" useCache="false">
      SELECT * FROM user WHERE id = #{id}
    </select>
    

三、二级缓存的工作流程

  1. 查询顺序
    执行查询时,MyBatis 按以下顺序查找数据:

    二级缓存 → 一级缓存 → 数据库
    
  2. 缓存写入时机

    • 查询结果在事务提交(commit())后才会写入二级缓存。
    • 未提交的事务修改对其他会话不可见,避免脏读。
  3. 缓存更新机制

    • 执行同 Mapper 的增删改操作(DML)后,清空该 namespace 下所有缓存。
    • 可通过 flushCache="true" 强制清空缓存:
      <update id="updateUser" parameterType="User" flushCache="true">
        UPDATE user SET name=#{name} WHERE id=#{id}
      </update>
      

四、二级缓存的常见问题与解决方案

1. 脏读问题
  • 场景:多个 Mapper 操作同一张表,但未共享缓存。
  • 解决:通过 <cache-ref> 共享缓存命名空间:
    <mapper namespace="com.example.UserMapper">
      <cache-ref namespace="com.example.OrderMapper"/> <!-- 共享 OrderMapper 的缓存 -->
    </mapper>
    
2. 缓存穿透与雪崩
  • 缓存穿透:频繁查询不存在的数据(如无效 ID)。
    解决:缓存空对象或使用布隆过滤器。
  • 缓存雪崩:大量缓存同时过期。
    解决:设置随机过期时间(flushInterval)。
3. 分布式环境问题
  • 默认缓存不共享:单机缓存无法跨服务节点共享。
    解决:整合 Redis 等分布式缓存:
    <cache type="org.mybatis.caches.redis.RedisCache" />
    

五、二级缓存的最佳实践

  1. 适用场景

    • 读多写少:如配置表、静态数据表。
    • 低实时性要求:允许短暂数据延迟。
  2. 避坑指南

    • 避免跨 Mapper 操作同一表:除非使用 <cache-ref> 共享缓存。
    • 慎用 readOnly="true":若缓存对象被修改,可能导致数据不一致。
    • 监控缓存命中率:通过日志分析缓存效果(如开启 MyBatis 调试日志)。
  3. 性能优化

    • 合理设置 eviction 策略:LRU 适用于多数场景。
    • 调整 flushInterval:根据数据更新频率设置刷新间隔。
    • 限制缓存大小:避免内存溢出(size 参数)。

六、二级缓存 vs 一级缓存

对比项一级缓存二级缓存
作用范围SqlSessionSqlSession(Mapper 级别)
存储位置内存(HashMap可扩展(内存、Redis、Ehcache 等)
默认开启否(需手动配置)
失效机制DML 操作自动清空同 Mapper 的 DML 操作清空
事务影响未提交的修改对其他会话不可见事务提交后缓存生效

七、总结

二级缓存的核心价值:通过跨会话共享高频访问数据,减少数据库压力,但需谨慎处理一致性问题。
推荐方案

  • 单机环境:使用默认缓存 + Ehcache 优化。
  • 分布式环境:整合 Redis 实现共享缓存。
  • 高并发写入场景:直接禁用二级缓存,依赖数据库或外部缓存(如 Caffeine)。



















Mybatis的二级缓存的eviction(缓存回收策略,缓存驱逐淘汰策略)详解

MyBatis 的二级缓存提供了多种 缓存驱逐策略(Eviction Policy),用于在缓存达到容量上限时决定哪些对象应被移除。以下是各策略的详细说明、适用场景及配置示例:


一、缓存驱逐策略类型

MyBatis 支持四种内置策略,通过 eviction 属性配置:

策略名实现类说明适用场景
LRULruCache最近最少使用(Least Recently Used)高频访问数据,保留热点数据
FIFOFifoCache先进先出(First In First Out)数据访问模式均匀
SOFTSoftCache软引用(Soft Reference),内存不足时由 GC 回收防止内存溢出,缓存大对象
WEAKWeakCache弱引用(Weak Reference),GC 运行时立即回收临时性缓存,允许快速回收

二、策略详解与工作原理

1. LRU(最近最少使用)
  • 原理
    • 维护一个链表,记录缓存对象的访问顺序。
    • 当缓存满时,移除最久未被访问的对象。
  • 优点
    • 适合热点数据,保留高频访问对象。
  • 缺点
    • 需要维护访问顺序链表,轻微性能开销。
  • 配置示例
    <cache eviction="LRU" size="1024" />
    
    @CacheNamespace(eviction = LruCache.class, size = 1024)
    
2. FIFO(先进先出)
  • 原理
    • 维护一个队列,按对象进入缓存的顺序淘汰。
    • 移除最先进入缓存的对象。
  • 优点
    • 实现简单,无额外性能开销。
  • 缺点
    • 可能淘汰高频访问的旧数据。
  • 配置示例
    <cache eviction="FIFO" size="512" />
    
    @CacheNamespace(eviction = FifoCache.class, size = 512)
    
3. SOFT(软引用)
  • 原理
    • 使用 SoftReference 包装缓存对象。
    • 当 JVM 内存不足时,GC 会自动回收这些对象。
  • 优点
    • 避免 OutOfMemoryError,适合缓存大对象(如文件、图片)。
  • 缺点
    • 回收时间不可控,可能导致缓存命中率下降。
  • 配置示例
    <cache eviction="SOFT" />
    
    @CacheNamespace(eviction = SoftCache.class)
    
4. WEAK(弱引用)
  • 原理
    • 使用 WeakReference 包装缓存对象。
    • 只要发生 GC,无论内存是否充足,都会立即回收这些对象。
  • 优点
    • 快速释放内存,适合临时性缓存(如测试环境)。
  • 缺点
    • 缓存对象生命周期极短,频繁访问时性能差。
  • 配置示例
    <cache eviction="WEAK" />
    
    @CacheNamespace(eviction = WeakCache.class)
    

三、策略对比与选型建议

对比维度LRUFIFOSOFTWEAK
淘汰逻辑访问时间进入时间内存压力GC 触发
内存管理主动移除主动移除依赖 GC依赖 GC
性能开销
适用场景热点数据均匀访问数据大对象缓存临时缓存

选型建议

  • 常规场景:优先选择 LRU(默认策略),平衡性能和缓存效率。
  • 内存敏感场景:使用 SOFTWEAK 避免内存溢出。
  • 简单场景FIFO 适用于无明确访问模式的数据。

四、高级配置与注意事项

1. 自定义缓存策略

通过实现 org.apache.ibatis.cache.Cache 接口,可扩展自定义驱逐策略:

public class CustomCache implements Cache {
  // 实现必要方法(putObject、getObject、removeObject 等)
}

配置使用:

<cache type="com.example.CustomCache" />
@CacheNamespace(implementation = CustomCache.class)
2. 缓存大小(size)调优
  • 设置过小:频繁淘汰,缓存命中率低。
  • 设置过大:内存占用高,可能引发 GC 压力。
  • 建议:根据监控数据(如缓存命中率)动态调整。
3. 与引用策略的兼容性
  • readOnly 配置
    • readOnly="true":直接返回缓存对象引用,与 SOFT/WEAK 结合时需注意对象可能被 GC 回收后访问异常。
    • readOnly="false":返回深拷贝对象,避免 GC 影响,但增加序列化开销。
4. 分布式环境下的策略
  • Redis 集成:使用 RedisCache 时,驱逐策略由 Redis 服务端控制(如 maxmemory-policy)。
  • 配置示例
    <cache type="org.mybatis.caches.redis.RedisCache" />
    

五、常见问题与解决方案

1. 缓存命中率低
  • 原因size 设置过小或策略不适合数据访问模式。
  • 解决:增大 size 或切换为 LRU
2. 内存泄漏
  • 原因:未正确实现自定义缓存策略,或 SOFT/WEAK 未生效。
  • 解决:检查 GC 日志,确认对象是否被正常回收。
3. 缓存雪崩
  • 场景:大量缓存同时过期。
  • 解决:设置随机 flushInterval 或使用分层缓存。

六、总结

  • 核心机制:通过 eviction 策略管理缓存对象的生命周期,平衡内存使用与命中率。
  • 最佳实践
    • 默认使用 LRU,根据监控调整 size
    • 内存敏感场景选择 SOFT,临时数据使用 WEAK
    • 分布式环境整合 Redis 等外部缓存。










嗯,用户现在问的是MyBatis二级缓存的eviction详解。之前的对话里,他们已经讨论过一级缓存、二级缓存的开启关闭、配置方式,以及缓存标签和注解的对比。现在用户想深入了解二级缓存的淘汰机制,也就是eviction。

首先,我需要回忆一下MyBatis二级缓存的淘汰策略相关知识。MyBatis默认使用LRU(Least Recently Used)算法,但也可以配置其他策略,比如FIFO、LRU_JDBC,或者集成第三方缓存如EhCache、Redis,这些缓存库通常提供更多的淘汰策略选择。

用户可能想知道如何配置不同的淘汰策略,每种策略的适用场景,以及如何自定义淘汰逻辑。此外,他们可能关心在分布式环境下如何保证缓存一致性,以及如何结合事务管理来刷新缓存。

接下来,我需要整理MyBatis内置的淘汰策略,比如LRU、FIFO、LRU_JDBC,以及如何通过配置或自定义实现这些策略。还要提到第三方缓存的集成,比如EhCache支持的多种策略,如LFU、LRU、FIFO等,以及Redis的过期时间设置。

另外,用户可能遇到的问题,比如缓存雪崩、脏读,以及如何通过调整淘汰策略来缓解这些问题。还需要考虑性能优化,比如合理设置缓存大小,避免内存溢出。

在结构上,应该分点讲解,每种策略的原理、配置方法、适用场景,然后是高级配置如自定义策略和第三方集成。最后总结最佳实践和常见问题解决方案。

需要注意的是,用户可能有不同的使用场景,比如单机应用和分布式系统,因此在讲解时要区分这两种情况,并提供相应的配置示例。例如,分布式系统推荐使用Redis,而单机可以使用EhCache。

还要确保术语准确,比如LRU和LFU的区别,避免混淆。同时,提供代码示例和配置片段,帮助用户更好地理解和应用这些策略。

最后,检查是否有遗漏的要点,比如缓存预热、监控和调优,这些也是优化缓存性能的重要部分,应该包含在回答中。

MyBatis 的二级缓存 Eviction(淘汰机制) 是缓存管理的关键策略,用于在缓存容量达到上限时移除最不常使用或最久未使用的条目,以确保内存效率和数据新鲜度。以下是详细解析:


一、MyBatis 内置淘汰策略

1. LRU(Least Recently Used)

原理:淘汰最久未使用的条目。 • 配置

<!-- Mapper XML -->
<cache eviction="LRU" size="512"/>

适用场景:读多写少场景,如热点数据频繁访问。

2. FIFO(First In First Out)

原理:按进入缓存的顺序淘汰最早进入的条目。 • 配置

<cache eviction="FIFO" size="1024"/>
3. LRU_JDBC

原理:基于数据库的LRU策略,通过记录访问时间戳淘汰最久未使用的条目。 • 配置

<cache eviction="LRU_JDBC" size="2048"/>

特点:依赖数据库支持,需表中包含 last_accessed 字段。


二、第三方缓存集成与高级策略

1. EhCache

支持策略:LRU、LFU(Least Frequently Used)、FIFO、RANDOM、LRU_JDBC 等。 • 配置示例ehcache.xml):

<cache name="userCache"
       maxEntriesLocalHeap="1000"
       eternal="false"
       timeToLiveSeconds="3600"
       timeToIdleSeconds="1800"
       overflowToDisk="false"
       eviction="LRU"
       memoryStoreEvictionPolicy="LRU">
</cache>
2. Redis

支持策略:通过 EXPIREPEXPIRE 设置键的过期时间实现自动淘汰。 • 配置示例mybatis-config.xml):

<plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
  <property name="host" value="localhost"/>
  <property name="port" value="6379"/>
  <property name="keyPrefix" value="mybatis:cache:user:"/>
</plugin>

动态过期时间:在 Java 代码中动态设置:

redisTemplate.opsForValue().set("user:1", user, 3600, TimeUnit.SECONDS);

三、自定义淘汰策略

1. 实现 org.apache.ibatis.cache.Eviction 接口

步骤

  1. 创建自定义淘汰策略类,实现 getremove 方法。
  2. 在 Mapper XML 或注解中指定策略类型。 • 示例代码
public class CustomLFUEviction implements Eviction {
    @Override
    public Object get(String key) {
        // LFU 逻辑:获取最不常使用的条目
        return null;
    }

    @Override
    public void remove(String key) {
        // 移除指定条目
    }
}

配置

<cache eviction="com.example.CustomLFUEviction" size="512"/>

四、淘汰策略选择指南

策略适用场景配置复杂度性能开销
LRU热点数据频繁访问(如商品详情页)
FIFO简单场景,无明确访问频率差异
LRU_JDBC需与数据库紧密配合的场景
EhCache LRU分布式环境,需高可用性和灵活性
Redis TTL微服务架构,需自动过期和分布式支持

五、高级配置与最佳实践

1. 缓存预热(Cache Warmup)

目的:应用启动时预先加载高频数据到缓存。 • 实现方式

@PostConstruct
public void warmUpCache() {
    List<User> users = userService.getAllUsers();
    users.forEach(user -> {
        sqlSession.selectOne("com.example.UserMapper.findById", user.getId());
    });
}
2. 缓存监控与调优

日志启用

<settings>
  <setting name="logImpl" value="SLF4J"/>
</settings>

监控指标: • 缓存命中率(hitCount / accessCount)。 • 缓存大小(size)和淘汰次数(evictedCount)。

3. 分布式缓存一致性

Redis:使用 Redis 的原子操作保证多节点缓存一致性。 • EhCache:集成 Terracotta 实现分布式缓存。


六、常见问题与解决方案

1. 缓存雪崩(Cache Avalanche)

场景:大量缓存键同时过期,导致数据库瞬时压力过大。 • 解决: • 使用随机 TTL(如 1-5秒)而非固定值。 • 添加二级缓存(如 Redis + 本地缓存)。

2. 脏读(Dirty Read)

场景:缓存数据未及时刷新,读取到旧数据。 • 解决: • 设置合理的 timeToLive(TTL)。 • 在关键操作后手动刷新缓存: xml <update id="updateUser" flushCache="true"> UPDATE users SET name=#{name} WHERE id=#{id} </update>

3. 内存泄漏

场景:缓存未正确释放,长期堆积占用内存。 • 解决: • 配置淘汰策略(如 LRU)和最大容量(size)。 • 定期清理无用缓存键。


七、总结

核心原则:根据业务场景选择合适的淘汰策略(如 LRU 适合热点数据,FIFO 适合简单场景)。 • 配置技巧: • 结合 size 控制缓存容量,避免内存溢出。 • 使用第三方缓存(如 Redis、EhCache)增强功能和扩展性。 • 最佳实践: • 对高频读写的静态数据(如配置表)启用缓存,配置 LRU + 合理 TTL。 • 对动态数据(如订单表)禁用缓存或结合 <flushCache> 手动刷新。 • 分布式系统中优先使用 Redis,保障缓存一致性和高可用性。

通过合理配置淘汰策略,可以在内存效率、查询性能和数据一致性之间找到平衡点,最大化 MyBatis 二级缓存的价值。



















Mybatis使用Redis作为缓存

在 MyBatis 中集成 Redis 作为二级缓存可以有效解决分布式环境下的缓存共享问题,并提升系统性能。以下是详细配置步骤、示例代码及注意事项:


一、实现原理

MyBatis 的二级缓存支持扩展,通过实现 org.apache.ibatis.cache.Cache 接口,可以将缓存存储到 Redis。核心流程如下:

  1. 缓存查询:优先从 Redis 读取数据,若未命中则查询数据库。
  2. 缓存写入:事务提交后,将查询结果序列化并存入 Redis。
  3. 缓存失效:执行增删改操作时,清空对应命名空间的 Redis 缓存。

二、配置步骤(以 Spring Boot + MyBatis 为例)

1. 添加依赖

pom.xml 中引入 Redis 和 MyBatis 缓存扩展依赖:

<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- MyBatis Redis Cache 适配器(可选第三方库,或自定义实现) -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
</dependency>
2. 配置 Redis 连接

application.yml 中配置 Redis 服务器地址:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 
    database: 0  # 默认使用 DB 0
3. 配置 MyBatis 使用 Redis 缓存

在 Mapper XML 或接口中指定 Redis 缓存实现:

方式 1:XML 配置
<mapper namespace="com.example.UserMapper">
  <!-- 使用 Redis 缓存 -->
  <cache type="org.mybatis.caches.redis.RedisCache" />
</mapper>
方式 2:注解配置(需自定义适配器)

若使用注解,需自定义 Cache 实现类(参考下方 自定义 Redis 缓存 部分)。

4. 实体类序列化

确保实体类实现 Serializable 接口:

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // 字段、Getter/Setter...
}

三、自定义 Redis 缓存(可选)

如果默认的 RedisCache 不满足需求,可自行实现缓存逻辑:

1. 实现 Cache 接口
public class CustomRedisCache implements Cache {
    private final String id; // Mapper namespace
    private final RedisTemplate<String, Object> redisTemplate;

    public CustomRedisCache(String id) {
        this.id = id;
        this.redisTemplate = (RedisTemplate<String, Object>) 
            SpringContextHolder.getBean("redisTemplate"); // 从 Spring 容器获取
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForValue().set(key.toString(), value, 1, TimeUnit.HOURS); // 设置 1 小时过期
    }

    @Override
    public Object getObject(Object key) {
        return redisTemplate.opsForValue().get(key.toString());
    }

    @Override
    public Object removeObject(Object key) {
        redisTemplate.delete(key.toString());
        return null;
    }

    @Override
    public void clear() {
        redisTemplate.delete(redisTemplate.keys(id + ":*")); // 清空当前命名空间缓存
    }

    // 其他方法(如 getSize())...
}
2. 配置使用自定义缓存
<mapper namespace="com.example.UserMapper">
  <cache type="com.example.cache.CustomRedisCache" />
</mapper>

四、验证 Redis 缓存生效

1. 测试代码
@SpringBootTest
public class CacheTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testRedisCache() {
        // 第一次查询(访问数据库并写入 Redis)
        User user1 = userMapper.selectById(1);
        
        // 第二次查询(命中 Redis 缓存)
        User user2 = userMapper.selectById(1);
        
        System.out.println(user1 == user2); // 输出 false(反序列化生成新对象)
    }
}
2. 检查 Redis 数据

通过 Redis CLI 查看缓存数据:

127.0.0.1:6379> KEYS *
1) "com.example.UserMapper:selectById:1"  # 缓存键格式:namespace:queryId:params

五、高级配置与优化

1. 缓存键设计
  • 默认键规则namespace:queryId:params(如 com.example.UserMapper:selectById:1)。
  • 自定义键:重写 CacheKey 或修改缓存实现逻辑。
2. 缓存过期策略
  • 全局过期:在 Redis 配置中设置默认 TTL。
  • 按需设置:在自定义缓存中针对不同查询设置不同过期时间。
3. 序列化优化
  • 使用高效序列化:如 JSON(Jackson)、MsgPack、Protobuf。
  • 配置 RedisTemplate
    @Configuration
    public class RedisConfig {
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            return template;
        }
    }
    
4. 缓存雪崩与穿透
  • 雪崩:随机设置过期时间(如 30分钟 + 随机0~300秒)。
  • 穿透:缓存空值或使用布隆过滤器拦截无效请求。

六、注意事项

  1. 事务一致性

    • MyBatis 二级缓存在事务提交后生效,未提交的修改对其他会话不可见。
    • 确保 Redis 缓存更新与数据库事务同步。
  2. 缓存清理

    • 执行 DML 操作时,需清空对应命名空间的缓存(clear() 方法)。
    • 分布式环境下,可通过 Redis 的 PUB/SUB 通知其他节点清理缓存。
  3. 性能监控

    • 监控 Redis 内存使用、命中率、延迟等指标。
    • 使用 slowlog 分析慢查询。

七、总结

通过将 MyBatis 二级缓存替换为 Redis,可以实现:

  • 分布式缓存共享:多服务节点共享同一缓存源。
  • 高性能缓存:利用 Redis 内存存储和高效数据结构提升查询速度。
  • 灵活扩展:支持自定义序列化、过期策略和集群部署。

推荐场景

  • 高并发读、低频写的业务(如商品详情、配置信息)。
  • 需要跨服务节点共享缓存的分布式系统。



















MyBatis 使用 Redis 作为缓存

1. 自定义 MyBatis 缓存实现

要将 Redis 作为 MyBatis 的二级缓存,需要自定义一个类实现 MyBatis 的 Cache 接口。以下是一个示例实现:

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MybatisRedisCache implements Cache {

    private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private final RedisTemplate<String, Object> redisTemplate;

    private final String namespace;

    public MybatisRedisCache(String namespace) {
        if (namespace == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.namespace = namespace;
        this.redisTemplate = SpringUtil.getBean("redisTemplate");
    }

    @Override
    public String getId() {
        return this.namespace;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForValue().set(getKey(key), value, 10, TimeUnit.MINUTES);
    }

    @Override
    public Object getObject(Object key) {
        try {
            return redisTemplate.opsForValue().get(getKey(key));
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("缓存出错 ");
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        Object value = redisTemplate.opsForValue().get(getKey(key));
        redisTemplate.delete(getKey(key));
        return value;
    }

    @Override
    public void clear() {
        Set<String> keys = redisTemplate.keys(getKeys());
        if (CollectionUtil.isNotEmpty(keys)) {
            assert keys != null;
            redisTemplate.delete(keys);
        }
    }

    @Override
    public int getSize() {
        Set<String> keys = redisTemplate.keys(getKeys());
        if (CollectionUtil.isNotEmpty(keys)) {
            assert keys != null;
            return keys.size();
        }
        return 0;
    }

    private String getKeys() {
        return "mybatis::" + namespace + "::*";
    }

    private String getKey(Object key) {
        return "mybatis::" + namespace + "::" + DigestUtils.md5Hex(String.valueOf(key));
    }
}
2. 配置 Redis 模板

在 Spring 配置中,需要配置 Redis 模板,以便与 Redis 进行交互:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
}
3. 使用自定义缓存

在 MyBatis 的映射文件中,使用自定义的缓存实现:

<mapper namespace="com.example.mapper.UserMapper">
    <cache type="com.example.cache.MybatisRedisCache"/>
    <select id="getUserById" parameterType="int" resultType="com.example.entity.User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

或者在 Mapper 接口中使用 @CacheNamespace 注解:

@Mapper
@CacheNamespace(implementation = MybatisRedisCache.class)
public interface UserMapper {
    User getUserById(Long id);
}
4. 注意事项
  • 缓存失效:在高并发场景下,缓存可能会导致数据不一致的问题,需要根据具体业务场景采取相应的解决措施。
  • 缓存清理:MyBatis 支持通过 <cache> 标签的 flushInterval 属性设置缓存清理时间。
  • 缓存类型:MyBatis 支持多种缓存类型,可以通过 <cache> 标签的 type 属性指定。

通过以上步骤,可以将 Redis 作为 MyBatis 的二级缓存,提高查询性能,减少数据库的访问次数。










好的,我现在需要帮助用户了解如何在MyBatis中使用Redis作为缓存。首先,用户之前已经询问过MyBatis的二级缓存驱逐策略,现在可能想进一步扩展缓存的使用,特别是集成Redis。我需要确定用户对MyBatis缓存机制的基础了解,可能他们已经熟悉二级缓存的基本概念,现在想提升性能或分布式支持。

首先,我应该回顾MyBatis的二级缓存默认是基于PerpetualCache的内存缓存,这在单机应用中是可行的,但在分布式环境下会有一致性问题。用户可能遇到了多个服务实例之间的缓存不一致,或者希望缓存能跨应用共享,这时候引入Redis是合理的。

接下来,需要解释如何将Redis集成到MyBatis中。关键点在于实现MyBatis的Cache接口,这样MyBatis就能使用自定义的Redis缓存实现。需要提到具体的步骤,比如添加Redis依赖,创建自定义Cache类,配置MyBatis使用这个实现,以及可能的Redis配置。

然后,用户可能需要了解配置示例,包括XML和注解两种方式。需要展示如何在mapper文件中添加标签,指定type为自定义的RedisCache,或者在Java配置中使用@CacheNamespace注解。

此外,需要讨论高级特性,比如缓存过期时间、序列化方式的选择(如Jackson、Kryo等),以及分布式锁的处理,避免缓存击穿。可能用户关心如何设置不同的过期时间,或者如何处理高并发下的缓存一致性问题。

还需要考虑可能的问题,比如缓存雪崩、穿透、击穿,以及解决方案。比如设置随机过期时间,使用布隆过滤器,或者分布式锁。用户可能在实际应用中遇到这些问题,需要给出实际的解决建议。

另外,性能调优方面,连接池配置、选择合适的Redis部署模式(单机、哨兵、集群)以及监控缓存命中率都是重点。用户可能希望优化缓存性能,所以需要提供这些调优建议。

最后,总结使用Redis作为MyBatis二级缓存的好处,比如支持分布式、持久化、高可用,以及注意事项,如序列化兼容性和缓存策略的选择。用户可能需要权衡这些因素来决定是否适合他们的场景。

需要确保回答结构清晰,分步骤说明,并给出代码示例和配置示例,帮助用户实际操作。同时,注意之前用户得到的回答格式,使用表格、代码块等,保持一致性。可能用户是开发者,需要具体的技术细节和实现步骤,所以内容要详细但不过于冗长,重点突出关键步骤和常见问题的解决方法。

MyBatis集成Redis作为二级缓存详解

MyBatis通过自定义缓存实现,可轻松将Redis作为二级缓存存储介质,解决单机缓存的数据一致性问题并提升分布式系统的扩展能力。以下是完整实现方案:


一、核心实现原理
  1. MyBatis缓存机制

    • 默认二级缓存:基于PerpetualCache的内存缓存(单机局限)
    • 自定义缓存:实现org.apache.ibatis.cache.Cache接口,重写读写逻辑
  2. Redis优势

    • 分布式缓存共享
    • 数据持久化
    • 自动过期策略
    • 高可用集群支持

二、集成步骤与代码实现
1. 添加依赖
<!-- MyBatis核心 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

<!-- Redis客户端(以Lettuce为例) -->
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.2.3</version>
</dependency>

<!-- 序列化工具(Jackson) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.4</version>
</dependency>
2. 实现Cache接口
import org.apache.ibatis.cache.Cache;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import com.fasterxml.jackson.databind.ObjectMapper;

public class RedisCache implements Cache {
    private final String id;
    private final RedisClient redisClient;
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    public RedisCache(String id) {
        this.id = id;
        this.redisClient = RedisClient.create("redis://localhost:6379");
    }
    
    // 生成唯一缓存键
    private String getKey(Object key) {
        return id + ":" + key.hashCode();
    }
    
    @Override
    public void putObject(Object key, Object value) {
        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            String json = objectMapper.writeValueAsString(value);
            connection.sync().setex(getKey(key), 3600, json); // 默认1小时过期
        } catch (Exception e) {
            throw new RuntimeException("Redis写入失败", e);
        }
    }
    
    @Override
    public Object getObject(Object key) {
        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            String json = connection.sync().get(getKey(key));
            return json != null ? objectMapper.readValue(json, Object.class) : null;
        } catch (Exception e) {
            throw new RuntimeException("Redis读取失败", e);
        }
    }
    
    // 实现其他必要方法(clear、remove等)
}
3. 配置MyBatis使用Redis缓存

XML配置方式

<mapper namespace="com.example.UserMapper">
    <cache type="com.example.cache.RedisCache" 
           eviction="LRU" 
           flushInterval="60000" 
           size="1024"/>
</mapper>

注解配置方式

@CacheNamespace(implementation = RedisCache.class, eviction = LruCache.class)
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectById(int id);
}

三、高级配置与优化
1. 缓存过期策略
// 动态设置不同Mapper的过期时间
public class RedisCache implements Cache {
    private int ttl = 3600; // 默认1小时

    @Override
    public void putObject(Object key, Object value) {
        // 从注解获取自定义TTL
        if (key instanceof CacheKey) {
            MappedStatement ms = (MappedStatement) ((CacheKey) key).getUpdateList().get(0);
            CacheNamespace anno = ms.getMapperClass().getAnnotation(CacheNamespace.class);
            if (anno != null) ttl = anno.ttl();
        }
        connection.sync().setex(..., ttl, ...);
    }
}
2. 序列化优化
序列化方案优点缺点
Jackson JSON可读性好,兼容性强性能较低,占用空间较大
Kryo序列化速度快,体积小需要注册类,兼容性差
Protobuf高效,跨语言支持需要预定义Schema

Kryo配置示例

Kryo kryo = new Kryo();
kryo.register(User.class);
Output output = new Output(new ByteArrayOutputStream());
kryo.writeObject(output, user);
byte[] bytes = output.toBytes();
3. 分布式锁防击穿
public Object getObject(Object key) {
    String lockKey = getKey(key) + ":LOCK";
    String requestId = UUID.randomUUID().toString();
    
    try {
        // 尝试获取锁
        while (!"OK".equals(redis.setnx(lockKey, requestId))) {
            Thread.sleep(100);
        }
        // 双重检查缓存
        Object value = getFromRedis(key);
        if (value == null) {
            value = queryDatabase(key); // 查库
            putObject(key, value);      // 回填
        }
        return value;
    } finally {
        // 释放锁(Lua脚本保证原子性)
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    }
}

四、生产环境注意事项
  1. 连接池配置

    RedisClient client = RedisClient.create(
        RedisURI.builder()
            .withHost("redis-cluster.example.com")
            .withPort(6379)
            .withPassword("password")
            .withTimeout(Duration.ofSeconds(2))
            .build()
    );
    client.setOptions(ClientOptions.builder()
        .socketOptions(SocketOptions.builder().tcpNoDelay(true).build())
        .build());
    
  2. 高可用架构

    • 哨兵模式:自动故障转移
      RedisURI uri = RedisURI.Builder.sentinel("sentinel1.example.com", 26379, "mymaster")
                     .withSentinel("sentinel2.example.com", 26379)
                     .build();
      
    • 集群模式:数据分片
      RedisClusterClient clusterClient = RedisClusterClient.create(
          RedisURI.Builder.redis("node1.example.com", 6379).build()
      );
      
  3. 监控指标

    • 缓存命中率:Hit Rate=缓存命中次数总请求次数×100%\text{Hit Rate} = \frac{\text{缓存命中次数}}{\text{总请求次数}} \times 100\%
    • Redis内存使用率
    • 连接数/网络延迟

五、常见问题解决方案

问题1:缓存穿透

  • 现象:大量查询不存在的数据,绕过缓存直击数据库
  • 方案
    // 缓存空值并设置短过期时间
    if (databaseResult == null) {
        redis.setex(key, 300, "NULL"); // 空值标记
        return null;
    }
    

问题2:缓存雪崩

  • 现象:大量缓存同时过期导致数据库压力激增
  • 方案
    // 添加随机偏移量
    int ttl = baseTtl + new Random().nextInt(300); // 基础时间+0~5分钟随机
    

问题3:序列化兼容性

  • 现象:类结构变更后反序列化失败
  • 方案:使用@JsonIgnoreProperties(ignoreUnknown = true)忽略未知字段

六、性能对比测试
场景内存缓存(默认)Redis单节点Redis集群
QPS12,0009,5008,200
平均延迟(ms)1.23.85.1
分布式一致性不支持支持支持

结论:Redis会引入约30%的性能损耗,但换取分布式能力,需根据业务需求权衡。


通过以上方案,MyBatis可高效集成Redis,实现跨服务的缓存共享。建议在分布式系统、需要水平扩展或严格数据一致性的场景下优先采用此方案。










嗯,用户问的是如何在MyBatis中使用Redis作为缓存。我之前已经回答过关于MyBatis缓存机制的多个问题,包括一级缓存、二级缓存、配置方法以及淘汰策略等。现在用户可能希望将缓存升级到Redis,特别是在分布式系统中,或者需要更高效的缓存方案。

首先,我需要回顾之前的对话,确保这次回答的内容不与之前的重复,同时补充新的信息。用户可能已经了解了二级缓存的基本配置,但现在想具体知道如何与Redis集成。他们可能是在微服务架构中运行,需要跨服务共享缓存,或者希望利用Redis的高性能和持久化特性。

接下来,我需要考虑用户的实际使用场景。例如,用户可能有一个Spring Boot应用,想要整合MyBatis和Redis。他们可能需要步骤化的指导,包括依赖添加、配置文件的设置、Mapper XML的修改以及可能的代码调整。此外,用户可能关心如何配置序列化、刷新策略、以及如何处理缓存一致性问题。

要注意的是,用户可能对Redis的基础知识有一定了解,但不确定如何与MyBatis结合。因此,回答需要详细但不过于基础,重点放在集成步骤和配置细节上。同时,应该提到常见的配置选项,比如连接超时、重试次数、键前缀等,以及如何自定义序列化器,避免JDK序列化的性能问题。

另外,用户可能遇到的一些问题,比如缓存未命中、数据不一致、或者配置错误导致应用启动失败,也需要在回答中涵盖。例如,如何确保在更新操作后刷新缓存,或者如何处理多级缓存(一级和二级)的协同工作。

最后,需要总结使用Redis的优势,比如高性能、分布式支持、持久化等,并提供一些最佳实践,比如合理设置过期时间、监控缓存命中率、避免缓存雪崩等。这样用户不仅知道如何集成,还能优化缓存的使用效果。

在 MyBatis 中集成 Redis 作为二级缓存,可以显著提升查询性能,尤其适用于分布式系统和高频读取场景。以下是详细的配置和使用教程:


一、核心依赖与配置

1. 添加依赖(以 Maven 为例)

pom.xml 中引入 MyBatis-Redis 插件和 Redis 客户端依赖:

<dependencies>
  <!-- MyBatis-Redis 插件 -->
  <dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-caches-redis</artifactId>
    <version>3.0.3</version>
  </dependency>
  
  <!-- Spring Boot Starter Data Redis(可选,若使用 Spring) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
  
  <!-- Redis 客户端 -->
  <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
  </dependency>
</dependencies>

2. 全局配置(mybatis-config.xml)

在 MyBatis 配置文件中启用 Redis 缓存插件,并配置连接参数:

<configuration>
  <!-- 启用 Redis 缓存插件 -->
  <plugins>
    <plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
      <!-- Redis 连接配置 -->
      <property name="host" value="localhost"/>
      <property name="port" value="6379"/>
      <property name="database" value="0"/>
      <property name="password" value="your_redis_password"/>
      
      <!-- 可选配置 -->
      <property name="keyPrefix" value="mybatis:cache:"/> <!-- 缓存键前缀,避免冲突 -->
      <property name="connectTimeout" value="6000"/>     <!-- 连接超时时间(毫秒) -->
      <property name="soTimeout" value="3000"/>          <!-- 读超时时间 -->
      <property name="retryAttempts" value="3"/>          <!-- 重试次数 -->
    </plugin>
  </plugins>
  
  <!-- 其他配置(如数据源、Mapper 文件扫描) -->
</configuration>

二、Mapper XML 配置

在需要使用 Redis 缓存的 Mapper XML 文件中添加 <cache/> 标签,并可自定义缓存参数:

<mapper namespace="com.example.UserMapper">
  <!-- 启用二级缓存(默认使用 Redis) -->
  <cache 
    name="userCache"       <!-- 缓存实例名称可选-->
    size="1024"            <!-- 缓存最大条目数(Redis 不受限制) -->
    readOnly="true"      <!-- 是否只读(建议设为 true 提高性能) -->
    flushInterval="300000"<!-- 自动刷新间隔(毫秒,如 5 分钟) -->
    serialization="Jackson"<!-- 使用 Jackson 序列化 -->
  />
  
  <select id="findByUserId" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  </select>
  
  <update id="updateUser" parameterType="User" flushCache="true">
    UPDATE users SET name=#{name}, age=#{age} WHERE id=#{id}
  </update>
</mapper>

三、关键配置项详解

1. Redis 连接参数
参数名说明默认值
hostRedis 服务器地址localhost
portRedis 服务端口6379
databaseRedis 数据库编号0
passwordRedis 密码(无密码则无需配置)(空)
keyPrefix缓存键前缀(避免多项目冲突)
connectTimeout连接超时时间(毫秒)6000
soTimeout读写超时时间(毫秒)3000
retryAttempts连接失败重试次数0
2. 缓存参数
参数名说明默认值
name缓存实例名称(用于区分不同 Mapper 的缓存)
size缓存最大容量(仅对 MyBatis 内置缓存有效)无限制
readOnly是否只读(true:不可写,提升性能)false
flushInterval自动刷新间隔(毫秒)
serialization自定义序列化方式(如 Jackson、Kryo)JdkSerialization

四、代码示例与高级配置

1. 自定义序列化

application.properties(Spring Boot)或全局配置中启用 Jackson 序列化:

# Spring Boot 配置
mybatis.cache.serialization.type=jackson

或自定义 Jackson 序列化器:

public class CustomJacksonSerializer implements Serializer<Object> {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public byte[] serialize(Object object) throws SerializationException {
        try {
            return objectMapper.writeValueAsBytes(object);
        } catch (JsonProcessingException e) {
            throw new SerializationException("JSON serialization failed", e);
        }
    }
}

在 Mapper XML 中指定:

<cache serialization="com.example.CustomJacksonSerializer"/>

2. 动态刷新缓存

在增删改操作后手动刷新 Redis 缓存:

<!-- Mapper XML -->
<update id="deleteUser" parameterType="int" flushCache="true">
  DELETE FROM users WHERE id = #{id}
</update>

3. 分布式缓存一致性

Key 设计:使用唯一业务标识(如用户 ID)作为缓存键:

<cache keyGenerator="org.apache.ibatis.cache	org.apache.ibatis.cache.impl.PerpetualCache$RedisKeyGenerator"/>

Redis事务支持:通过 <flushCache> 触发 Redis 的 FLUSHDBFLUSHALL


五、监控与调优

1. 启用缓存日志

mybatis-config.xml 中启用日志,观察缓存命中率:

<settings>
  <setting name="logImpl" value="SLF4J"/>
</settings>
2. Redis 监控命令

通过 Redis CLI 查看缓存状态:

# 查看缓存键
SCAN mybatis:cache:user:

# 查看缓存命中率(需自定义统计)
GET /_mget/<key1>/<key2>  # 手动统计命中率

六、常见问题与解决方案

1. 缓存未命中

原因:Redis 未启动、连接配置错误、缓存键生成不一致。 • 解决: • 检查 Redis 服务状态。 • 使用 keyPrefix 统一缓存键格式。 • 在代码中打印缓存键(如 System.out.println(cacheKey))。

2. 数据不一致

原因:更新操作未触发缓存刷新。 • 解决: • 在增删改操作的 Mapper XML 中添加 flushCache="true"。 • 配置 readOnly="true" 减少并发写入风险。

3. 连接超时

原因:Redis 服务器地址或端口错误、网络延迟。 • 解决: • 使用 pingtelnet 测试 Redis 连接。 • 调整 connectTimeoutsoTimeout 参数。


七、总结

优势: • 高性能:Redis 的基于内存的存储和原子操作。 • 分布式支持:多节点缓存一致,适合微服务架构。 • 持久化:支持 RDB/AOF 持久化,避免数据丢失。 • 最佳实践: • 对高频静态数据(如字典表)启用缓存,配置 readOnly="true"。 • 对动态数据(如订单)结合 <flushCache> 手动刷新。 • 使用 JacksonKryo 替代默认 JdkSerialization。 • 设置合理的 flushInterval 自动刷新,平衡性能与新鲜度。

通过以上配置,MyBatis 可无缝集成 Redis,构建高效、可靠的二级缓存系统。



















Mybatis的一级缓存的配置详解

MyBatis 的一级缓存(Local Cache)SqlSession 级别的缓存,默认开启且无法全局关闭。它的设计初衷是为了减少同一会话内的重复数据库查询,但需特别注意其生命周期和潜在的数据一致性问题。以下是关于一级缓存的详细配置解析、工作机制及优化建议:


一、一级缓存的核心特性

  1. 作用范围

    • 单会话内有效:仅在同一 SqlSession 生命周期内生效,不同会话间隔离。
    • 自动管理:无需手动配置,MyBatis 默认启用。
  2. 存储结构

    • 底层使用 PerpetualCache 类(基于 HashMap 实现),无容量限制或淘汰策略。
  3. 缓存键(CacheKey)生成规则

    • 由以下因素共同决定:
      • SQL 语句 ID(如 selectById
      • 查询参数(如 id=1
      • 分页参数(RowBounds
      • 环境 ID(environmentId

二、验证一级缓存的存在

1. 代码示例:验证缓存命中
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    // 第一次查询(访问数据库)
    User user1 = mapper.selectById(1);
    
    // 第二次查询(命中一级缓存)
    User user2 = mapper.selectById(1);
    
    System.out.println(user1 == user2); // 输出 true(同一对象引用)
}
2. 日志输出验证

开启 MyBatis 的调试日志(log4jSLF4J),观察 SQL 执行次数:

# log4j.properties
log4j.logger.org.apache.ibatis=DEBUG

日志结果
第一次查询打印 SQL 语句,第二次查询无 SQL 日志,证明命中缓存。


三、一级缓存的失效条件

以下操作会自动清空当前 SqlSession 的一级缓存:

操作类型示例代码/配置说明
执行 DML 操作mapper.updateUser(user);INSERT/UPDATE/DELETE 操作
手动调用 clearCache()sqlSession.clearCache();显式清空缓存
事务提交或回滚sqlSession.commit();sqlSession.close();提交或关闭会话时自动清空缓存
配置 flushCache="true"<select id="selectAll" flushCache="true">查询前强制清空缓存

四、一级缓存的“关闭”与绕过方法

虽然一级缓存无法全局关闭,但可通过以下方式绕过其效果:

1. 强制清空缓存
User user1 = mapper.selectById(1);
sqlSession.clearCache(); // 清空缓存
User user2 = mapper.selectById(1); // 重新查询数据库
2. 修改 SQL 或参数

每次查询时生成不同的 SQL 或参数(如添加随机值):

User user1 = mapper.selectById(1, System.currentTimeMillis());
User user2 = mapper.selectById(1, System.currentTimeMillis()); // 无法命中缓存
3. 配置 flushCache="true"

在特定查询中强制清空缓存:

<select id="selectById" resultType="User" flushCache="true">
  SELECT * FROM user WHERE id = #{id}
</select>
4. 使用不同的 SqlSession

每次查询创建新的会话(不推荐,性能损耗大):

try (SqlSession session1 = factory.openSession()) {
    User user1 = session1.getMapper(UserMapper.class).selectById(1);
}
try (SqlSession session2 = factory.openSession()) {
    User user2 = session2.getMapper(UserMapper.class).selectById(1); // 新会话,重新查询
}

五、一级缓存的注意事项

1. 事务隔离与脏读
  • 场景:在未提交的事务中修改数据,同一会话内可能读取到未提交的脏数据。
  • 解决:结合数据库的隔离级别(如 READ_COMMITTED)或手动清空缓存。
2. Spring 集成下的行为
  • 默认行为:Spring 管理的 SqlSession 在事务提交后清空缓存。
  • 问题:在 @Transactional 方法中多次查询可能命中缓存,导致读取到未提交的数据。
  • 解决:在需要实时性的查询中配置 flushCache="true"
3. 性能优化
  • 合理利用缓存:对频繁查询且数据变化少的操作(如配置表查询),利用一级缓存减少数据库压力。
  • 避免滥用:写操作频繁的场景中,频繁清空缓存可能降低性能。

六、一级缓存与二级缓存的协作

1. 查询优先级
二级缓存 → 一级缓存 → 数据库
2. 数据同步
  • 二级缓存在事务提交后写入,一级缓存立即生效。
  • 若同时启用二级缓存,需注意跨会话数据一致性问题。

七、常见问题与解决方案

1. 一级缓存导致脏读
  • 场景:会话内先查询数据,随后修改但未提交,再次查询读到未提交数据。
  • 解决:设置数据库隔离级别为 READ_COMMITTED,或在修改后调用 clearCache()
2. 缓存对象共享问题
  • 现象:同一对象被多个线程共享时,可能引发并发修改异常。
  • 解决:返回深拷贝对象(需实体类实现 Serializable 并配置 readOnly="false")。
3. 日志监控
  • 配置 MyBatis 日志:观察 SQL 执行情况,验证缓存命中效果。
  • 自定义拦截器:通过 Interceptor 接口统计缓存命中率。

八、总结

特性说明
默认状态自动开启,无法全局关闭
缓存生命周期SqlSession 创建而创建,随其关闭或清空操作而失效
适用场景单会话内重复查询,减少数据库访问
优化建议合理利用缓存减少 IO,避免在写操作频繁的场景中依赖缓存
风险点脏读、线程安全问题(尤其在 Spring 事务管理中)

最佳实践

  • 在需要强一致性的查询中,使用 flushCache="true" 或手动清空缓存。
  • 结合二级缓存(如 Redis)解决分布式环境下的数据共享问题。
  • 通过日志和监控工具(如 Prometheus + Grafana)分析缓存效果。




























Mybatis的一级缓存的 localCacheScope

MyBatis 的 localCacheScope 配置项用于控制一级缓存(Local Cache)的作用范围,允许开发者根据实际需求调整缓存的生命周期。以下是该配置的详细说明、可选值及适用场景:


一、localCacheScope 的作用

一级缓存默认作用域为 SESSION,即同一个 SqlSession 内的多次相同查询会命中缓存。
通过设置 localCacheScope,可以修改一级缓存的作用范围,控制其生效的粒度。


二、可选值及行为对比

可选值值类型说明适用场景
SESSION默认值缓存作用于整个 SqlSession 生命周期,执行 DML 操作或手动清空前缓存有效。常规场景,需减少重复查询
STATEMENT可选值每次执行查询后立即清空缓存,相当于“禁用”一级缓存。需要严格数据一致性,避免脏读

三、配置方式

1. 全局配置(推荐)

mybatis-config.xml 中全局设置:

<configuration>
  <settings>
    <!-- 设置一级缓存作用域为 STATEMENT -->
    <setting name="localCacheScope" value="STATEMENT"/>
  </settings>
</configuration>
2. Spring Boot 配置

application.yml 中通过 MyBatis 属性配置:

mybatis:
  configuration:
    local-cache-scope: statement # 可选值:session(默认) / statement

四、不同作用域的行为验证

1. SESSION(默认)
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    // 第一次查询(访问数据库)
    User user1 = mapper.selectById(1);
    
    // 第二次查询(命中一级缓存)
    User user2 = mapper.selectById(1);
    
    System.out.println(user1 == user2); // 输出 true
}
2. STATEMENT
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    // 第一次查询(访问数据库)
    User user1 = mapper.selectById(1);
    
    // 第二次查询(缓存已清空,重新访问数据库)
    User user2 = mapper.selectById(1);
    
    System.out.println(user1 == user2); // 输出 false
}

五、适用场景分析

1. localCacheScope = SESSION
  • 优点
    • 减少同一会话内的重复查询,提升性能。
    • 适合读多写少的场景(如报表生成、配置表查询)。
  • 缺点
    • 可能导致脏读(如事务中未提交的数据被缓存)。
    • 高并发写场景下缓存频繁失效,性能提升有限。
2. localCacheScope = STATEMENT
  • 优点
    • 每次查询后清空缓存,确保数据强一致性。
    • 避免因缓存导致的脏读或过时数据问题。
  • 缺点
    • 无法利用缓存优化性能,增加数据库压力。
    • 适合写多读少或对实时性要求极高的场景。

六、与其他配置的协作

1. flushCache 的配合

即使 localCacheScope = SESSION,仍可通过 <select flushCache="true"> 强制清空缓存:

<select id="selectById" resultType="User" flushCache="true">
  SELECT * FROM user WHERE id = #{id}
</select>
2. 与二级缓存的共存
  • 一级缓存(SESSION):会话内优先使用。
  • 二级缓存:跨会话共享,事务提交后生效。
  • 执行顺序二级缓存 → 一级缓存 → 数据库

七、常见问题与解决方案

1. Spring 事务中的一级缓存问题
  • 现象:在 @Transactional 方法中多次查询可能命中缓存,读取到未提交的数据。
  • 解决
    • 设置 localCacheScope = STATEMENT
    • 或在关键查询中配置 flushCache="true"
2. 性能下降
  • 现象:设置 localCacheScope = STATEMENT 后数据库压力增大。
  • 解决
    • 对高频查询启用二级缓存(如 Redis)。
    • 优化 SQL 或增加数据库索引。

八、最佳实践

  1. 默认配置

    • 大多数场景保持 localCacheScope = SESSION,利用缓存减少数据库访问。
  2. 严格一致性场景

    • 金融交易、实时监控等场景使用 localCacheScope = STATEMENT
  3. 调试与监控

    • 通过 MyBatis 日志观察 SQL 执行情况,分析缓存命中率。
    • 使用 APM 工具(如 SkyWalking)监控数据库负载。

九、总结

配置项行为特点适用场景注意事项
SESSION(默认)会话级缓存,减少重复查询读多写少,允许短暂延迟需处理脏读风险
STATEMENT语句级缓存,每次查询后清空写多读少,强一致性要求可能增加数据库压力