Mybatis一級緩存及其存在的問題

概要:默認情況下,MyBatis只啓用了本地的會話緩存,它僅僅對一個會話中的數據進行緩存。這也就是大家常說的MyBatis一級緩存,一級緩存的作用域是SqlSession。

  • 作用域
    取值有兩個:SqlSessionStatement

    文檔:https://mybatis.org/mybatis-3/configuration.html#settings

    Mybatis plus 基於springboot的配置爲:

    mybatis-plus:
      configuration:
        local-cache-scope: statement
    

    或者

    mybatis-plus:
      configuration:
        local-cache-scope: session
    
  • SqlSession
    每個SqlSession中持有了Executor,每個Executor中有一個LocalCache
    執行SQL語句的過程中,首次執行它時從數據庫獲取的所有數據會被存儲在一段高速緩存中,今後執行這條語句時就會從高速緩存中讀取結果,而不是再次查詢數據庫。MyBatis提供了默認下基於HashMap的緩存實現。
    在這裏插入圖片描述
    類圖如下:
    在這裏插入圖片描述

測試

  • SqlSessionConfig

    package com.example.demo.config;
    
    import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
    import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
    
    import org.apache.ibatis.plugin.Interceptor;
    import org.mybatis.spring.mapper.MapperScannerConfigurer;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    
    /**
     * @author liuteng
     */
    @Configuration
    public class SqlSessionConfig {
    
        private Logger logger = LoggerFactory.getLogger(SqlSessionConfig.class);
    
        @Bean("sqlSessionFactoryBean")
        public MybatisSqlSessionFactoryBean createSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource,
                                                                    @Qualifier("paginationInterceptor") PaginationInterceptor paginationInterceptor) {
    
            // MybatisSqlSessionFactory
            MybatisSqlSessionFactoryBean sqlSessionFactoryBean = null;
            try {
                // 實例SessionFactory
                sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
                // 配置數據源
                sqlSessionFactoryBean.setDataSource(dataSource);
                // 設置 MyBatis-Plus 分頁插件
                Interceptor [] plugins = {paginationInterceptor};
                sqlSessionFactoryBean.setPlugins(plugins);
                // 加載MyBatis配置文件
                PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
                sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath*:mapper/*.xml"));
            } catch (Exception e) {
                logger.error("創建SqlSession連接工廠錯誤:{}", e.getMessage());
            }
            return sqlSessionFactoryBean;
        }
    
        @Bean("dataSource")
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dataSource(){
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        public MapperScannerConfigurer myGetMapperScannerConfigurer() {
            MapperScannerConfigurer myMapperScannerConfigurer = new MapperScannerConfigurer();
            myMapperScannerConfigurer.setBasePackage("com.example.demo.mapper");
            myMapperScannerConfigurer.setSqlSessionFactoryBeanName("mySqlSessionFactoryBean");
            return myMapperScannerConfigurer;
        }
    
    }
    
  • UserServiceImpl

    package com.example.demo.service.impl;
    
    import com.alibaba.fastjson.JSON;
    import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
    import com.example.demo.mapper.User1Mapper;
    import com.example.demo.mapper.UserMapper;
    import com.example.demo.entity.User;
    import com.example.demo.service.UserService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.session.SqlSession;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    
    @Slf4j
    @Service
    public class UserServiceImpl implements UserService {
    
    
        @Resource
    	DefaultSqlSessionFactory sqlSessionFactoryBean;
        
        @Override
        public int updateById(User user) {
            try {
                SqlSession sqlSession1 = sqlSessionFactoryBean.openSession();
            	SqlSession sqlSession2 = sqlSessionFactoryBean.openSession();
                UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
                UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    
                User firstUser = userMapper1.selectById(user.getId());
                log.debug("=========SqlSession1查詢======== User:{}", JSON.toJSONString(firstUser));
                userMapper2.updateById(user);
                log.debug("=========SqlSession2修改======== User:{}", JSON.toJSONString(user));
                User secondUser = userMapper2.selectById(user.getId());
                log.debug("=========SqlSession2查詢======== User:{}", JSON.toJSONString(secondUser));
                User thirdUser = userMapper1.selectById(user.getId());
                log.debug("=========SqlSession1查詢======== User:{}", JSON.toJSONString(thirdUser));
            } catch (Exception e) {
                log.error(e.getMessage());
            }
            return 1;
        }
    }
    
    

    數據庫預先錄入數據,然後測試修改用戶名

  • 執行結果

    2020-05-26 14:52:21.869 DEBUG 3992 --- [nio-8080-exec-7] o.s.jdbc.datasource.DataSourceUtils      : Fetching JDBC Connection from DataSource
    o.m.s.t.SpringManagedTransaction         : JDBC Connection [HikariProxyConnection@1725242542 wrapping com.mysql.cj.jdbc.ConnectionImpl@7bd24a07] will not be managed by Spring
    c.e.demo.mapper.UserMapper.selectById    : ==>  Preparing: SELECT id,name,sex,phone,create_time,enable,version FROM user WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.selectById    : ==> Parameters: 12121212(Long)
    c.e.demo.mapper.UserMapper.selectById    : <==      Total: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession1查詢======== User:{"createTime":1590475920000,"enable":true,"id":12121212,"name":"wangwu","phone":"18111111111","sex":"MAN","version":1}
    o.s.jdbc.datasource.DataSourceUtils      : Fetching JDBC Connection from DataSource
    o.m.s.t.SpringManagedTransaction         : JDBC Connection [HikariProxyConnection@691008575 wrapping com.mysql.cj.jdbc.ConnectionImpl@4a631491] will not be managed by Spring
    c.e.demo.mapper.UserMapper.updateById    : ==>  Preparing: UPDATE user SET name=?, phone=?, create_time=? WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.updateById    : ==> Parameters: lisi(String), 18111111111(String), 2020-05-26 14:52:21.869(Timestamp), 12121212(Long)
    c.e.demo.mapper.UserMapper.updateById    : <==    Updates: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession2修改======== User:{"createTime":1590475941869,"enable":false,"id":12121212,"name":"lisi","phone":"18111111111"}
    c.e.demo.mapper.UserMapper.selectById    : ==>  Preparing: SELECT id,name,sex,phone,create_time,enable,version FROM user WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.selectById    : ==> Parameters: 12121212(Long)
    c.e.demo.mapper.UserMapper.selectById    : <==      Total: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession2查詢======== User:{"createTime":1590475942000,"enable":true,"id":12121212,"name":"lisi","phone":"18111111111","sex":"MAN","version":1}
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession1查詢======== User:{"createTime":1590475920000,"enable":true,"id":12121212,"name":"wangwu","phone":"18111111111","sex":"MAN","version":1}
    

    由執行結果可知,同一個Session中,相同查詢會使用緩存,不同Session之間互不影響。當有多個SqlSession或者分佈式的環境下,數據庫寫操作會引起髒數據
    在這裏插入圖片描述

  • 設置一級緩存級別爲Statement

    修改SqlSessionConfig配置,新增如下

    MybatisConfiguration configuration = new MybatisConfiguration();
    configuration.setLocalCacheScope(LocalCacheScope.STATEMENT);
    sqlSessionFactoryBean.setConfiguration(configuration);
    

    執行結果:

    com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@a4a722
    com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
    o.m.s.t.SpringManagedTransaction         : JDBC Connection [HikariProxyConnection@1748010355 wrapping com.mysql.cj.jdbc.ConnectionImpl@a4a722] will not be managed by Spring
    c.e.demo.mapper.UserMapper.selectById    : ==>  Preparing: SELECT id,name,sex,phone,create_time,enable,version FROM user WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.selectById    : ==> Parameters: 12121212(Long)
    com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Pool stats (total=1, active=1, idle=0, waiting=0)
    c.e.demo.mapper.UserMapper.selectById    : <==      Total: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession1查詢======== User:{"createTime":1590489590000,"enable":true,"id":12121212,"name":"關羽","phone":"12345678910","sex":"MAN","version":1}
    o.s.jdbc.datasource.DataSourceUtils      : Fetching JDBC Connection from DataSource
    com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6eac3382
    o.m.s.t.SpringManagedTransaction         : JDBC Connection [HikariProxyConnection@1875164390 wrapping com.mysql.cj.jdbc.ConnectionImpl@6eac3382] will not be managed by Spring
    c.e.demo.mapper.UserMapper.updateById    : ==>  Preparing: UPDATE user SET name=?, phone=?, create_time=? WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.updateById    : ==> Parameters: 趙雲(String), 12345678910(String), 2020-05-26 18:40:20.098(Timestamp), 12121212(Long)
    c.e.demo.mapper.UserMapper.updateById    : <==    Updates: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession2修改======== User:{"createTime":1590489620098,"enable":false,"id":12121212,"name":"趙雲","phone":"12345678910"}
    c.e.demo.mapper.UserMapper.selectById    : ==>  Preparing: SELECT id,name,sex,phone,create_time,enable,version FROM user WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.selectById    : ==> Parameters: 12121212(Long)
    c.e.demo.mapper.UserMapper.selectById    : <==      Total: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession2查詢======== User:{"createTime":1590489620000,"enable":true,"id":12121212,"name":"趙雲","phone":"12345678910","sex":"MAN","version":1}
    c.e.demo.mapper.UserMapper.selectById    : ==>  Preparing: SELECT id,name,sex,phone,create_time,enable,version FROM user WHERE id=? AND enable=1 
    c.e.demo.mapper.UserMapper.selectById    : ==> Parameters: 12121212(Long)
    c.e.demo.mapper.UserMapper.selectById    : <==      Total: 1
    c.e.demo.service.impl.UserServiceImpl    : =========SqlSession1查詢======== User:{"createTime":1590489620000,"enable":true,"id":12121212,"name":"趙雲","phone":"12345678910","sex":"MAN","version":1}
    

    總結:Mybatis作爲ORM框架,爲了避免存在的髒讀問題,建議修改一級緩存作用域爲:Statement

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章