背景

项目中出现了这样一个问题,就是select出来的数据和数据库里的数据不一样,就非常的奇怪,发现原来是mybatis的缓存导致的,经过查询资料发现这是mybatis的一级缓存。

下面介绍了问题出现的场景以及解决办法

场景

  1. Mapper类:
  2. @Mapper
  3. public interface UserMapper {
  4. @Results(value = {
  5. @Result(property = "id", column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
  6. @Result(property = "age", column = "age", javaType = Integer.class, jdbcType = JdbcType.INTEGER),
  7. @Result(property = "name", column = "name", javaType = String.class, jdbcType = JdbcType.VARCHAR),
  8. @Result(property = "createTime", column = "create_time", javaType = Date.class, jdbcType = JdbcType.DATE)
  9. })
  10. @Select("SELECT id, age, name, create_time FROM user WHERE id = #{id}")
  11. User selectUser(Long id);
  12. }
  1. 一个普通的bean
  2. @Slf4j
  3. @Component
  4. public class MyBean {
  5. @Autowired
  6. private UserMapper userMapper;
  7. public void test1() {
  8. User user = userMapper.selectUser(1L);
  9. log.info("user:{}", user);
  10. user.setAge(3); // 更新其中一个属性
  11. user = userMapper.selectUser(1L);
  12. log.info("user:{}", user);
  13. }
  14. @Transactional
  15. public void test2() {
  16. test1();
  17. }
  18. }
  19. 配置类:
  20. @Configuration
  21. @MapperScan("cn.eagle.li.mybatis.cache.session")
  22. @EnableTransactionManagement
  23. public class Config {
  24. @Bean
  25. public MyBean myBean() {
  26. return new MyBean();
  27. }
  28. @Bean(name = "sqlSessionFactory")
  29. @ConditionalOnMissingBean(name = "sqlSessionFactory")
  30. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  31. final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  32. sessionFactory.setDataSource(dataSource);
  33. return sessionFactory.getObject();
  34. }
  35. @Bean(name = "transactionManager")
  36. public DataSourceTransactionManager transactionManager(DataSource dataSource) {
  37. return new DataSourceTransactionManager(dataSource);
  38. }
  39. @Bean
  40. public DataSource dataSource() {
  41. MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
  42. dataSource.setUser("root");
  43. dataSource.setPassword("root");
  44. dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8");
  45. return dataSource;
  46. }
  47. }
  1. 测试类
  2. @Slf4j
  3. public class Main {
  4. public static void main(String[] args) throws Exception {
  5. AnnotationConfigApplicationContext context =
  6. new AnnotationConfigApplicationContext(Config.class);
  7. MyBean myBean = context.getBean(MyBean.class);
  8. myBean.test1();
  9. log.info("==================");
  10. myBean.test2();
  11. }
  12. }
  1. 运行结果:
  2. 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)
  3. 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)
  4. 1996 [main] INFO c.e.l.m.c.session.DataSourceMain - ==================
  5. 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)
  6. 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是什么时候被清理掉了,其实带事务的等到事务结束之后才会清理掉;而不带事务的把每一个查询当成一个事务,所以每个查询后就被清理到了。

解法

  1. 就是不要修改查询出来的类,如下:
  1. public void test1() {
  2. User user = userMapper.selectUser(1L);
  3. log.info("user:{}", user);
  4. User user2 = User.builder().name(user.getName()).age(3).id(user.getId()).build();
  5. log.info("user:{}", user);
  6. }
  1. 当然,也可以把一级缓存关掉,如下配置:
  1. mybatis-spring-config.xml 文件如下:
  2. <?xml version="1.0" encoding="UTF-8" ?>
  3. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <settings>
  7. <setting name="localCacheScope" value="STATEMENT"/>
  8. </settings>
  9. </configuration>
  1. @Bean(name = "sqlSessionFactory")
  2. @ConditionalOnMissingBean(name = "sqlSessionFactory")
  3. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  4. final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  5. sessionFactory.setDataSource(dataSource);
  6. sessionFactory.setConfigLocation(new ClassPathResource("mybatis-spring-config.xml")); // 这里加载配置文件
  7. return sessionFactory.getObject();
  8. }

其实就是每次查询后,都把localCache给清理掉了,原理如下:

参考

聊聊MyBatis缓存机制

被mybatis一级缓存坑了的更多相关文章

  1. MyBatis 一级缓存避坑

    MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选: package org.apache.ibatis.session; /** * @autho ...

  2. Mybatis一级缓存和二级缓存总结

    1:mybatis一级缓存:级别是session级别的,如果是同一个线程,同一个session,同一个查询条件,则只会查询数据库一次 2:mybatis二级缓存:级别是sessionfactory级别 ...

  3. Mybatis一级缓存和二级缓存 Redis缓存

    一级缓存 Mybatis的一级缓存存放在SqlSession的生命周期,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对 ...

  4. MyBatis 一级缓存与二级缓存

    MyBatis一级缓存 MyBatis一级缓存默认开启,一级缓存为Session级别的缓存,在执行以下操作时一级缓存会清空 1.执行session.clearCache(); 2.执行CUD操作 3. ...

  5. MyBatis一级缓存引起的无穷递归

    MyBatis一级缓存引起的无穷递归 引言: 最近在项目中参与了一个领取优惠劵的活动,当多个用户领取同一张优惠劵的时候,使用了数据库锁控制并发,起初的设想是:如果多个人同时领一张劵,第一个到达的人领取 ...

  6. mybatis一级缓存详解

    mybatis缓存分为一级缓存,二级缓存和自定义缓存.本文重点讲解一级缓存 一:前言 在介绍缓存之前,先了解下mybatis的几个核心概念: * SqlSession:代表和数据库的一次会话,向用户提 ...

  7. 0065 MyBatis一级缓存与二级缓存

    数据库中数据虽多,但访问频率却不同,有的数据1s内就会有多次访问,而有些数据几天都没人查询,这时候就可以将访问频率高的数据放到缓存中,就不用去数据库里取了,提高了效率还节约了数据库资源 MyBatis ...

  8. 关于mybatis 一级缓存引发的问题

    场景: 由于在一个方法中存在多个不同业务操作 private void insertOrUpdateField(CompanyReport entity) { //计算并数据 calcReportDa ...

  9. MyBatis一级缓存(转载)

    <深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 http://demo.netfoucs.com/luanlouis/article/details/41 ...

随机推荐

  1. 【LeetCode】45. Jump Game II 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 贪心 日期 题目地址:https://leetcod ...

  2. 【LeetCode】16. 3Sum Closest 最接近的三数之和

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:3sum, three sum, 三数之和,题解,lee ...

  3. 【LeetCode】60. Permutation Sequence 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  4. The All-purpose Zero(hdu5773)

    The All-purpose Zero Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Oth ...

  5. 警惕!PHP、Node、Ruby 和 Python 应用,漏洞还没结束!

    12 月 10 日凌晨,Apache 开源项目 Log4j2 的远程代码执行漏洞细节被公开,作为当前全球使用最广泛的 java 日志框架之一.该漏洞影响着很多全球使用量前列的开源组件,如 Apache ...

  6. 牛客练习赛44 B:小y的线段

    链接:https://ac.nowcoder.com/acm/contest/634/B 来源:牛客网 题目描述 给出\(n\)条线段,第\(i\)条线段的长度为\(a_i\),每次可以从第\(i\) ...

  7. Interval Bound Propagation (IBP)

    目录 概 主要内容 IBP CROWN CROWN-IBP 训练的技巧 写在最后 代码 Gowal S., Dvijotham K., Stanforth R., Bunel R., Qin C., ...

  8. Vue.js高效前端开发 • 【Ant Design of Vue框架基础】

    全部章节 >>>> 文章目录 一.Ant Design of Vue框架 1.Ant Design介绍 2.Ant Design of Vue安装 3.Ant Design o ...

  9. 你在寻找Vue3移动端项目框架嘛?请看这里

    现在web开发变得更加美妙高效,在于开发工具设计得更好了,丰富性与易用性,都有所提高.丰富性带来了一个幸福的烦恼,就是针对实际应用场景,如何选择工具 ? 1. Vue Cli和Vite之间的选择 Vi ...

  10. javascript实现base64格式转码与解码

    最近碰到一个需求,后端返回base64格式的数据,前端需要进行base64格式解码,好了,前端采用内部提供的atob函数进行解码,开完成,交付测试,然后测试小哥哥小姐姐反馈说中文乱码! 然后查了一下, ...