背景

项目中出现了这样一个问题,就是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是什么时候被清理掉了,其实带事务的等到事务结束之后才会清理掉;而不带事务的把每一个查询当成一个事务,所以每个查询后就被清理到了。

解法

  1. 就是不要修改查询出来的类,如下:
    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);
}
  1. 当然,也可以把一级缓存关掉,如下配置:
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给清理掉了,原理如下:

参考

聊聊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. 【】二次通告--Apache log4j-2.15.0-rc1版本存在绕过风险,请广大用户尽快更新版本

    [转载自360众测] Apache Log4j2是一个基于Java的日志记录工具.该工具重写了Log4j框架,并且引入了大量丰富的特性.我们可以控制日志信息输送的目的地为控制台.文件.GUI组件等,通 ...

  2. XSLT映射文件函数

    任何的编程语言或者是SQL语句都有内置的函数或方法,而强大灵活的xslt技术也是如此.熟练掌握XSLT的常用函数的用法,XSLT的应用将变得如此轻松,你会发现XSLT比想象中还要牛!以下是xslt数值 ...

  3. Decoupling Representation and Classifier for Long-tailed Recognition

    目录 概 主要内容 Sampling 分类器 代码 Kang B., Xie S., Rohrbach M., Yan Z., Gordo A., Feng J. and Kalantidis Y. ...

  4. [C++]C++ STL库函数大全

    #include <assert.h> //设定插入点 #include <ctype.h> //字符处理 #include <errno.h> //定义错误码 # ...

  5. Oracle数据库导入csv文件(sqlldr命令行)

    1.说明 Oracle数据库导入csv文件, 当csv文件较小时, 可以使用数据库管理工具, 比如DBevaer导入到数据库, 当csv文件很大时, 可以使用Oracle提供的sqlldr命令行工具, ...

  6. 微服务探索之路01篇.net6.0项目本地win10系统docker到服务器liunx系统docker的贯通

    本文介绍从创建 net6.0 项目运行在 windows 开发环境的 docker 然后正式部署至 liunx 服务器. 1 windows10 安装 docker 下载docker-desktop ...

  7. Cause: org.postgresql.util.PSQLException: 栏位索引超过许可范围:13,栏位数:12

    Cause: org.postgresql.util.PSQLException: 栏位索引超过许可范围:13,栏位数:12. 这个报错的原因是在mapper文件中的sql语书写错误 <inse ...

  8. js对cookie的操作:读、写、删

    js读写cookie //JS操作cookies方法!//写cookiesfunction setCookie(name,value){var Days = 30;var exp = new Date ...

  9. Whitelabel Error Page错误原因

    前言: 今天在做项目中遇到了一个问题,项目启动成功,但是前段访问接口始终访问不成功,页面一直在404,百度了一番无非两种解决方案: 一.解决方案 1.项目是boot项目查看启动类的位置是否放置正确 要 ...

  10. Redisson-关于使用订阅数问题

    一.前提 最近在使用分布式锁redisson时遇到一个线上问题:发现是subscriptionsPerConnection or subscriptionConnectionPoolSize 的大小不 ...