背景
專案中出現了這樣一個問題,就是select
出來的資料和資料庫裡的資料不一樣,就非常的奇怪,發現原來是mybatis
的快取導致的,經過查詢資料發現這是mybatis
的一級快取。
下面介紹了問題出現的場景以及解決辦法
場景
Mapper類:
@Mapper
public interface UserMapper {
@Results(value = {
@Result(property = "id", column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Result(property = "age", column = "age", javaType = Integer.class, jdbcType = JdbcType.INTEGER),
@Result(property = "name", column = "name", javaType = String.class, jdbcType = JdbcType.VARCHAR),
@Result(property = "createTime", column = "create_time", javaType = Date.class, jdbcType = JdbcType.DATE)
})
@Select("SELECT id, age, name, create_time FROM user WHERE id = #{id}")
User selectUser(Long id);
}
一個普通的bean
@Slf4j
@Component
public class MyBean {
@Autowired
private UserMapper userMapper;
public void test1() {
User user = userMapper.selectUser(1L);
log.info("user:{}", user);
user.setAge(3); // 更新其中一個屬性
user = userMapper.selectUser(1L);
log.info("user:{}", user);
}
@Transactional
public void test2() {
test1();
}
}
配置類:
@Configuration
@MapperScan("cn.eagle.li.mybatis.cache.session")
@EnableTransactionManagement
public class Config {
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean(name = "sqlSessionFactory")
@ConditionalOnMissingBean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public DataSource dataSource() {
MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8");
return dataSource;
}
}
測試類
@Slf4j
public class Main {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.test1();
log.info("==================");
myBean.test2();
}
}
執行結果:
1966 [main] INFO c.e.li.mybatis.cache.session.MyBean - user:User(age=2, name=3, id=1, createTime=Thu Nov 04 00:00:00 CST 2021)
1996 [main] INFO c.e.li.mybatis.cache.session.MyBean - user:User(age=2, name=3, id=1, createTime=Thu Nov 04 00:00:00 CST 2021)
1996 [main] INFO c.e.l.m.c.session.DataSourceMain - ==================
2046 [main] INFO c.e.li.mybatis.cache.session.MyBean - user:User(age=2, name=3, id=1, createTime=Thu Nov 04 00:00:00 CST 2021)
2047 [main] INFO c.e.li.mybatis.cache.session.MyBean - user:User(age=3, name=3, id=1, createTime=Thu Nov 04 00:00:00 CST 2021)
可以看到兩個方法的程式碼內容是一樣的,只不過第二個方法上加了一個事務
第一個方法中,兩次從資料庫選出的結果是一樣的;而在第二個方法中,兩次從資料庫選出的結果是不一樣的(age
的值)
可以猜測是@Transactional
+user.setAge(3);
導致的結果不一樣
原因
經過除錯,是下面的這行程式碼的原因,BaseExecutor.query
如下:
上面兩張圖片分別是不帶事務
和帶事務
執行到第二個查詢的時候經過的地方,可以看出帶事務
的方法,到這裡的時候,直接從localCache
取出來了,這就是原因所在。
大家可以去看一下localCache
是什麼時候被清理掉了,其實帶事務的
等到事務結束之後才會清理掉;而不帶事務
的把每一個查詢當成一個事務,所以每個查詢後就被清理到了。
解法
- 就是不要修改查詢出來的類,如下:
public void test1() {
User user = userMapper.selectUser(1L);
log.info("user:{}", user);
User user2 = User.builder().name(user.getName()).age(3).id(user.getId()).build();
log.info("user:{}", user);
}
- 當然,也可以把一級快取關掉,如下配置:
mybatis-spring-config.xml 檔案如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
</configuration>
@Bean(name = "sqlSessionFactory")
@ConditionalOnMissingBean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigLocation(new ClassPathResource("mybatis-spring-config.xml")); // 這裡載入配置檔案
return sessionFactory.getObject();
}
其實就是每次查詢後,都把localCache
給清理掉了,原理如下: