问题:在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.*.*(..))" />
<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
namespace="com.test.dao.IUserDAO">
<select id="getById" parameterType="int" resultType="user">
SELECT * FROM T_USER WHERE ID=#{id}
select>
于是就出现了以下的情况。
@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.csdn.net/u010558660/article/details/51801099
这里提供了关闭缓存的三种方法
http://blog.csdn.net/theoffspring/article/details/6111907
我用了第二种方法,单独关闭一条sql语句的缓存。
在Sql映射文件标签中加入属性
flushCache=”true” useCache=”false”
IUserDao.xml
namespace="com.test.dao.IUserDAO">
<select id="getById" parameterType="int" resultType="user" flushCache="true" useCache="false">
SELECT * FROM T_USER WHERE ID=#{id}
select>
getById的缓存已被关闭。
继续测试,发现起作用的其实是 flushCache=”true”,useCache=”false” 不起作用。
原因在这里:
http://blog.csdn.net/ssssny/article/details/52248960
flushCache的作用是自动刷新一级缓存,而useCache的作用是禁用二级缓存,可见在这里起作用的是一级缓存。
一级缓存以SqlSession为作用范围,二级缓存是Application级别的。
当SqlSession遇到一个和先前完全一样的查询时,会去取出上一次查询的对象作为结果。
当记录发生更改时,一级缓存自动刷新。
在我的情况中,第二次查询调用的Sql语句、参数和之前完全相同,记录也没有被更新,符合缓存的条件。系统去内存中查找之前查到的对象。
但这个时候,查询出来的对象已经被我修改了,所以取到的不是数据库中的值而是修改的值。
在映射文件的配置中加入flushCache=”false”,强制每次查询都刷新缓存。问题解决。
但是不知道缓存和事务管理有什么关系,不知道其内部机制。估计是同一个事务内使用同一个SqlSession的原因。
其他参考文献
http://blog.csdn.net/chris_mao/article/details/48804493
mybatis完全配置