Mybatis在Spring中鸡肋的一级缓存和二级缓存

前言

上回研究了下Mybatis的log系统,这次再吐槽一下Mybatis的缓存机制。所谓的缓存机制就是当我们需要去数据库查询一个数据的时候,并不会直接去数据库中查询,而是先去内存中去寻找是否这个数据已经被加载过了。如果加载过了,那么就直接返回内存中的数据。由于不需要数据库的开销这种缓存机制确实能够起到优化系统响应时间的目的,直到遇到了Spring。更多Spring内容进入【Spring解读系列目录】

Spring中失效的一级缓存

纯Mybatis的一级缓存确实能够返回内存中的数据,当程序执行第二次,第三次查询的时候应该从内存中直接加载,直观的表现就是无论执行多少次list()方法,都只会返回一次数据。
在这里插入图片描述
但是在Spring中这个缓存就失效了,因为Mybatis的一级缓存是基于SqlSession的,当执行list()方法的时候,会在代理类中得到一个SqlSession对象,并且设置到当前系统中去,一旦完成查询,Spring就会关闭SqlSession。因此每次查询都会重新开启SqlSesion,因此执行多少次list()就返回多少次结果。也就是说Mybatis的一级缓存机制在Spring环境中荡然无存。
在这里插入图片描述
由于以及缓存是针对Session的,因此当Session变了缓存就失效了。也就是说用户1查询的内容被缓存了,用户2不能使用,必须建立自己的缓存Session才行,一级缓存并不具有通用性。
在这里插入图片描述

二级缓存

Mybatis的一级缓存阵亡了,看看二级缓存怎么样。所谓的二级缓存就是共享缓存,任何用户都可以访问,看起来很美好,那么就讲讲Spring怎么开启Mybatis的二级缓存的。

Spring+Mybatis开启二级缓存

Spring中开启二级缓存的方法很简单,只要在Mapper接口上加上@CacheNamespace就可以了。看名字这个缓存是根据命名空间来的,通俗讲就是根据包名+类名来的。那这就导致了一个很大的问题了。

Spring+Mybatis二级缓存的坑

这个问题就是刚刚开启的缓存只能在这一个Mapper中的操作能保证刷新,因为只有写在一个Mapper里的查询方法才算是一个命名空间里的缓存。也就是说如果类A已经对某条数据进行了缓存,然后类B操作了这条数据,类A无法感知到,因此类A就无法去刷新缓存得到最新的数据,博客最后回贴上笔者的代码例子。
在这里插入图片描述
比如,第一次cityDao调用list()存了一个命名空间的Map到缓存里:

package com.demo.mapper.cityDao” “list()

一旦cityDaoAdd调用update,是在更新当前命名空间里的update。于是换了一个命名空间就更新cityDaoAdd里面的缓存,但是cityDaoAdd从来就没有调用list(),所以更新了也没有用,于是就变成了。

package com.demo.mapper.cityDao” “list() “
“package com.demo.mapper.cityDaoAdd” “ “

因此当cityDao调用list()的时候永远拿出来的就是原来的list()而不是更新后的。但是如果写到一个空间里,每次cityDao 调用update()都会更新同一个list(),因此得到的list()永远都是最新的。

package com.demo.mapper.cityDao” “list()

正因为有这样的坑,也就意味着必须确定某个对象一定保持在一个命名空间里才能保证数据的一致性。如果一个对象在别的命名空间种被引用了并且被修改了,就很有可能导致当前的命名空间无法拿到最新的值,因此说这个二级缓存也挺鸡肋的。

代码示例

AppConfig

@Configuration
@ComponentScan("com.demo")
@MapperScan("com.demo.mapper")
public class AppConfig {
    
    
    //配置数据源
    @Bean
    public DataSource dataSource(){
    
    
        DriverManagerDataSource driverManagerDataSource=new DriverManagerDataSource();
        driverManagerDataSource.setPassword("111111");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/new_schema");
        driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return driverManagerDataSource;
    }

    //配置SqlSessionFactory
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
    
    
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration();
       configuration.setLogImpl(Log4jImpl.class);
        factoryBean.setConfiguration(configuration);
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }
}

CityDao

@CacheNamespace  //这个注解是根据明明空间来的
public interface CityDao {
    
    
    @Select("select * from city")
    public List<Map<String,Object>> list();

    @Update("update city set name='AAAA' where id=1")
    public int update();
}

CityDaoAdd

@CacheNamespace
public interface CityDaoAdd {
    
    
    @Update("update city set name='beijing' where id=1")
    public int update();
}

CityService

@Service
public class CityService {
    
    
    //这种注入的写法已经不规范了,实际项目中还是用set方法注入吧
    @Autowired
    CityDao cityDao;
    @Autowired
    CityDaoAdd cityDaoAdd;

    public List<Map<String,Object>> find(){
    
    
        cityDao.list();
        cityDao.list();
        return cityDao.list();
    }

    public int updateName(){
    
    
        //return cityDaoAdd.update();
        return cityDao.update();
    }
}

MybatisTest

public class MybatisTest {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(anno.getBean(CityService.class).find());
        System.out.println(anno.getBean(CityService.class).updateName()); //这里应该更新,数据库更新了
        System.out.println(anno.getBean(CityService.class).find()); //但是因为二级缓存的存在,导致输出没有更新
    }
}

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/108757976