我们来说一下 Mybatis 的缓存机制

核心概览一级缓存:默认开启,作用范围在 同一个 SqlSession 内。二级缓存:需要手动配置开启,作用范围在 同一个 Mapper 命名空间(即同一个 Mapper 接口)内,可以被多个 SqlSession 共享。一级缓存1. 作用域SqlSession 级别:当同一个 SqlSession 执行相同的 SQL 查询时,MyBatis 会优先从缓存中获取数据,而不是直接查询数据库。它是 默认开启 的,无法关闭,但可以配置其作用范围(SESSION 或 STATEMENT)。2. 工作机制第一次执行查询后,查询结果会被存储到 SqlSession 关联的一级缓存中。在同一个 SqlSession 中,再次执行 完全相同的 SQL 查询(包括语句和参数)时,会直接返回缓存中的对象,而不会去数据库查询。如果 SqlSession 执行了 增(INSERT)、删(DELETE)、改(UPDATE) 操作,或者调用了 commit()、close()、rollback() 方法,该 SqlSession 的一级缓存会被清空。这是为了防止读取到脏数据。3. 示例说明代码语言:javascript复制// 假设获取的 SqlSession 和 UserMapper

try (SqlSession sqlSession = sqlSessionFactory.openSession()) {

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

// 第一次查询,会发送 SQL 到数据库

User user1 = mapper.selectUserById(1L);

System.out.println(user1);

// 第二次查询,SQL 和参数完全相同,直接从一级缓存返回,不查询数据库

User user2 = mapper.selectUserById(1L);

System.out.println(user2);

// 判断是否为同一个对象(是,因为从缓存中返回的是同一个对象的引用)

System.out.println(user1 == user2); // 输出:true

// 执行一个更新操作

mapper.updateUser(user1);

// 此时,一级缓存被清空

// 第三次查询,因为缓存被清空,会再次发送 SQL 到数据库

User user3 = mapper.selectUserById(1L);

System.out.println(user3 == user1); // 输出:false (虽然是同一条数据,但已是新对象)

}4. 注意事项对象相同:一级缓存返回的是 同一个对象的引用,因此在同一个 SqlSession 内,你操作的都是同一个 Java 对象。分布式环境:一级缓存无法在多个应用服务器之间共享,因为它绑定在单个请求的 SqlSession 上。二级缓存1. 作用域Mapper 级别 / Namespace 级别:多个 SqlSession 在访问同一个 Mapper 的查询时,可以共享其缓存。它是 默认关闭 的,需要在全局配置中开启,并在具体的 Mapper XML 中显式配置。2. 开启与配置a. 全局配置文件 (mybatis-config.xml):

必须显式设置开启二级缓存(虽然默认是 true,但显式声明是个好习惯)。

代码语言:javascript复制

b. Mapper XML 文件:

在需要开启二级缓存的 Mapper.xml 中添加 标签。

代码语言:javascript复制

eviction="FIFO"

flushInterval="60000"

size="512"

readOnly="true"/>

标签属性:

eviction:缓存回收策略。

LRU(默认):最近最少使用。

FIFO:先进先出。

SOFT:软引用,基于垃圾回收器状态和软引用规则移除。

WEAK:弱引用,更积极地移除。

flushInterval:缓存刷新间隔(毫秒),默认不清空。

size:缓存存放多少元素。

readOnly:是否为只读。

true:返回相同的缓存对象实例,性能好,但不允许修改。

false(默认):通过序列化返回缓存对象的拷贝,安全,性能稍差。

3. 工作机制当一个 SqlSession 执行查询后,在关闭或提交时,其查询结果会被存入二级缓存。另一个 SqlSession 执行相同的查询时,会先从二级缓存中查找数据。如果找到,则直接返回,否则再去数据库查询。任何一个 SqlSession 执行了 增、删、改 操作并 commit() 后,会清空 整个对应 Mapper 的二级缓存,以保证数据一致性。4. 示例说明代码语言:javascript复制// 第一个 SqlSession

try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {

UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

User user1 = mapper1.selectUserById(1L); // 查询数据库

sqlSession1.close(); // 关闭时,数据存入二级缓存

}

// 第二个 SqlSession(与第一个不同)

try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {

UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

// 查询相同的 SQL,直接从二级缓存获取,不查询数据库

User user2 = mapper2.selectUserById(1L);

}

// 第三个 SqlSession,执行了更新

try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {

UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

User user = mapper3.selectUserById(1L);

user.setName("New Name");

mapper3.updateUser(user); // 执行更新

sqlSession3.commit(); // 提交时,清空 UserMapper 的二级缓存

}

// 第四个 SqlSession

try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {

UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class);

// 因为缓存已被清空,所以会再次查询数据库

User user4 = mapper4.selectUserById(1L);

}5. 注意事项实体类序列化:如果二级缓存的 readOnly="false",那么对应的实体类必须实现 Serializable 接口。事务提交:只有在 SqlSession 执行 commit() 或 close() 时,数据才会从一级缓存转存到二级缓存。缓存粒度:二级缓存是 Mapper 级别的,有时会显得比较粗粒度。可以通过 让多个 Mapper 共享一个缓存,但不推荐,容易引起数据混乱。缓存顺序与总结当发起一个查询请求时,MyBatis 的缓存查询顺序是:

先查二级缓存:查看当前 Mapper 的二级缓存中是否有数据。再查一级缓存:如果二级缓存没有,再查看当前 SqlSession 的一级缓存中是否有数据。最后查数据库:如果两级缓存都没有,才发送 SQL 语句到数据库执行查询。查询到的数据会 先存入一级缓存,在 SqlSession 关闭或提交时,再转存到二级缓存。

特性

一级缓存

二级缓存

作用域

SqlSession

Mapper (Namespace)

默认状态

开启

关闭

是否共享

否,Session 独享

是,跨 Session 共享

清空时机

UPDATE/INSERT/DELETE, commit(), close()

同 Mapper 的 UPDATE/INSERT/DELETE + commit()

使用建议查询多,修改少的数据适合使用二级缓存,如字典表、配置项。数据实时性要求高的场景(如交易、订单)应谨慎使用二级缓存,或者设置较短的刷新间隔。在分布式环境中,默认的二级缓存(基于内存)是无法共享的,需要集成 Redis、Ehcache 等第三方缓存中间件来替代。理解缓存机制有助于解决一些“诡异”的问题,比如在同一个事务中,先后查询和更新,但由于一级缓存的存在,后续查询可能看不到其他线程的更新。​

复利基金有哪些?全面解析热门基金及投资策略
依纷内衣怎么样?从舒适到科技的深度评测