mybatis开启service层事务时一级缓存的问题

本文探讨了在Spring+Struts2+MyBatis框架下,如何解决同一个事务中因MyBatis的一级缓存导致查询结果不一致的问题,并介绍了通过调整缓存配置来解决该问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:在mybatis service层开启事务管理后,同一个事务中,相同的查询会调用一级缓存,从而查找结果是之前查到的对象而不是数据库中的记录值。

框架:spring+struts2+mybatis
spring对象管理,struts2负责MVC和依赖注入,mybatis数据库访问。

采用以下的配置将事务控制在service层。
applicationContext.xml

<bean name="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*Transaction" propagation="REQUIRED"
            read-only="false"
            rollback-for="java.lang.RuntimeException,com.test.exception.TransactionException" />
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="coreTransactionPointcut"
        expression="execution(public * com.test.service.*.*(..))" /> 

<!--把事务控制在Service层 -->

    <aop:advisor pointcut-ref="coreTransactionPointcut"
        advice-ref="transactionAdvice" />
    </aop:config>
<tx:annotation-driven transaction-manager="transactionManager" />

在需要事务控制的service方法前加注解:
@Transactional

mybatis采用DAO接口+mapper.xml,运行时生成代理类。
IUserDao接口

public interface IUserDao 
{
 public User getById(@Param("id") int id);
}

IUserDao.xml

<mapper namespace="com.test.dao.IUserDAO">

 <select id="getById" parameterType="int" resultType="user">
  SELECT * FROM T_USER WHERE ID=#{id}
 </select>

</mapper>

于是就出现了以下的情况。

@Transactional 
public void testQuery(){
    User first = userDao.getById(3);
    System.out.println("1:"+fisrt.getUserName());
    first.setUserName("Jetty");
    User second = userDao.getById(3);
    System.out.println("2:"+second.getUserName());
}

运行结果

1:Tommy
2:Jetty

看一下数据库,记录并没有被更改。
初步判断是因为mybatis缓存造成的。
在这里了解了一级缓存和二级缓存的定义
http://blog.youkuaiyun.com/u010558660/article/details/51801099

这里提供了关闭缓存的三种方法
http://blog.youkuaiyun.com/theoffspring/article/details/6111907

我用了第二种方法,单独关闭一条sql语句的缓存。
在Sql映射文件标签中加入属性
flushCache=”true” useCache=”false”

IUserDao.xml

<mapper namespace="com.test.dao.IUserDAO">

 <select id="getById" parameterType="int" resultType="user"  flushCache="true" useCache="false">
  SELECT * FROM T_USER WHERE ID=#{id}
 </select>

</mapper>

getById的缓存已被关闭。

继续测试,发现起作用的其实是 flushCache=”true”,useCache=”false” 不起作用。
原因在这里:
http://blog.youkuaiyun.com/ssssny/article/details/52248960

flushCache的作用是自动刷新一级缓存,而useCache的作用是禁用二级缓存,可见在这里起作用的是一级缓存。

一级缓存以SqlSession为作用范围,二级缓存是Application级别的。
当SqlSession遇到一个和先前完全一样的查询时,会去取出上一次查询的对象作为结果。
当记录发生更改时,一级缓存自动刷新。

在我的情况中,第二次查询调用的Sql语句、参数和之前完全相同,记录也没有被更新,符合缓存的条件。系统去内存中查找之前查到的对象。
但这个时候,查询出来的对象已经被我修改了,所以取到的不是数据库中的值而是修改的值。
在映射文件的配置中加入flushCache=”false”,强制每次查询都刷新缓存。问题解决。

但是不知道缓存和事务管理有什么关系,不知道其内部机制。估计是同一个事务内使用同一个SqlSession的原因。

其他参考文献
http://blog.youkuaiyun.com/chris_mao/article/details/48804493
mybatis完全配置

<think>嗯,用户现在在询问MyBatis一级缓存和二级缓存的工作原理及使用方法。在此之前,用户的问题是关于Spring的常用注解,已经详细解答了。现在用户转到了MyBatis缓存机制,并提供了几个引用片段作为背景资料。首先,我需要分析用户提供的引用内容。引用[1]提到MyBatis缓存相关类都在cache包下,PerpetualCache是默认实现类,一级和二级缓存都使用它。引用[2]简要说明了一级和二级缓存的作用是提升查询性能,减少数据库负载。引用[3]展示了一级缓存的清空方法,如调用clearCache()、commit()或执行增删改操作,并给出了测试代码。引用[4]指出在整合Spring时,由于事务控制在Service,每次调用Service可能新建SqlSession导致一级缓存失效,还提到配置一级缓存的选项。用户的需求非常明确:了解MyBatis一级缓存和二级缓存的工作原理和使用方法。因此,回答需要涵盖两者的作用域、生命周期、开启关闭方式、清空机制和使用注意事项。需要注意的是,用户特别引用了关于一级缓存Spring整合中的问题,这点在回复中需要强调。接下来需要组织回答结构。首先区分一级和二级缓存,分别说明各自的作用域和生命周期。一级缓存基于SqlSession,而二级缓存是应用级别的,跨SqlSession。使用方面,一级缓存默认开启,二级缓存需要配置。在整合Spring时,一级缓存可能失效,这点需要根据引用[4]重点说明。工作原理方面,一级缓存的数据结构是HashMap,执行增删改或commit/close会清空。二级缓存则涉及序列化、缓存策略和跨命名空间共享的注意事项。引用[3]中的测试代码可以作为示例展示一级缓存的工作机制,而二级缓存的配置需要手动启用。此外,用户可能存在的疑问包括缓存的适用场景、性能优化、脏数据处理以及与Spring整合时的注意事项。需要在总结部分给出使用建议,比如一级缓存的短会话使用,二级缓存的配置策略和序列化要求。相关问题部分可以引导用户进一步探讨缓存策略选择或整合Spring时的具体配置。最后,需要检查回答是否覆盖了用户提供的所有引用点,特别是引用[4]中关于Spring整合时一级缓存失效的问题,确保回答的准确性和全面性。同时避免引入与引用无关的内容,保持回答的针对性。</think> ### MyBatis 缓存机制详解(工作原理+使用方法) #### 一、一级缓存(Session 级别缓存) 1. **作用域** - **SqlSession 级别**:同一个 `SqlSession` 对象的多次查询共享缓存(线程内有效) - **默认开启**,无需额外配置 2. **数据结构** 底使用 `HashMap` 存储: ```java // PerpetualCache 实现类 public class PerpetualCache implements Cache { private final Map<Object, Object> cache = new HashMap<>(); } // [^1] ``` 3. **缓存生命周期** | 操作 | 效果 | |-------------------------|-----------------------| | `commit()` / `close()` | 清空缓存 | | `clearCache()` | 手动清空缓存 | | 执行 **增删改操作** | 自动清空缓存 | 4. **测试验证(引用[3]示例)** ```java // 同一 SqlSession 查询相同数据 User user1 = mapper1.findById(1); // 查数据库 session.clearCache(); // 清空一级缓存 User user2 = mapper2.findById(1); // 再次查数据库 ``` 5. **Spring 整合注意事项** 在 Spring 管理的事务中,**每次 DAO 调用会创建新 SqlSession**,导致一级缓存失效[^4]: ```java @Service public class UserService { // 两次查询在不同 SqlSession 执行 public void demo() { userMapper.getUser(1); // 首次查询(新建 SqlSession) userMapper.getUser(1); // 第二次查询(新建 SqlSession) } } ``` --- #### 二、二级缓存(Namespace 级别缓存) 1. **作用域** - **Mapper 级别**:多个 `SqlSession` 共享同一命名空间下的缓存 - **需显式开启**(在 mapper.xml 配置) 2. **启用方式** ```xml <!-- UserMapper.xml --> <mapper namespace="com.example.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="512"/> <!-- 其他SQL --> </mapper> ``` 3. **工作原理** ![](https://example.com/cache-flow.png) (图中说明:查询请求优先查二级缓存,未命中则查一级缓存,最后访问数据库) 4. **数据同步机制** - 当某个 `SqlSession` 执行 **commit()** 时,同步更新二级缓存 - **跨 SqlSession 修改数据**会导致缓存不一致(需通过事务保证) 5. **关键配置参数** | 参数 | 说明 | 默认值 | |--------------|------------------------------|------------| | `eviction` | 淘汰策略(LRU/FIFO) | LRU | | `flushInterval` | 缓存刷新间隔(毫秒) | 不刷新 | | `size` | 缓存对象数量上限 | 1024 | | `readOnly` | 是否只读(性能 vs 数据安全) | false | --- #### 三、缓存使用建议 1. **优先使用一级缓存** - 短事务操作(如单表简单查询) - 避免在循环中频繁查询相同数据 2. **二级缓存适用场景** - **读多写少**的静态数据(如省份字典) - 能接受 **分钟级数据延迟** 的场景(通过 `flushInterval` 控制) 3. **避坑指南** - 实体类必须实现 `Serializable` 接口(二级缓存需要序列化) - **分布式环境**需改用 Redis 等集中式缓存 ```xml <!-- 使用 Redis 作为二级缓存 --> <cache type="org.mybatis.caches.redis.RedisCache"/> ``` 4. **强制刷新缓存** ```java sqlSession.clearCache(); // 清空一级缓存 sqlSession.getMapper(UserMapper.class).clearCache(); // 清空二级缓存 ``` --- ### 总结对比 | 特性 | 一级缓存 | 二级缓存 | |-------------|-------------------------|--------------------------| | **作用域** | SqlSession 内部 | Mapper 命名空间全局 | | **生命周期** | SqlSession 关闭则失效 | 应用运行期间持续存在 | | **开启方式** | 默认开启 | 需配置 `<cache/>` 标签 | | **数据共享** | 线程内共享 | 跨线程共享(需序列化) | | **适用场景** | 短事务操作 | 读多写少的静态数据 | >[!NOTE] >- 执行增删改后,会**同时清空**对应的一级缓存和二级缓存[^3] >- 二级缓存可能导致**脏读问题**(需通过事务保证原子性)[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值