互联网架构-Mybatis深入源码分析-015:SqlSessionFactory二级缓存原理分析

1 如何禁止mybatis一级缓存

课程内容:
1.如何禁止使用Mybatis一级缓存
2.SessionFactory二级缓存原理分析
3.Mybatis源码分析之TransactionalCache
4.Mybatis源码分析之EhCache二级缓存淘汰策略
5.SqlSession的Close方法源码分析
6.Mybatis源码分析之StatementHandler
7.Mybatis源码分析之SpringBoot整合Mybatis源码分析

一级缓存
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。

具体流程:
1.第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来;
2.第二次执行select会从缓存中查数据,如果sql语句相同且传参数一样,那么就能从缓存中返回数据,不用去查找数据库,从而提高了效率;

注意事项:
如果SqlSession执行了DML操作(insert、update、delete)并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中存的数据永远和数据库中一致,避免出现脏读。

当一个SqlSession结束后它里面的一级缓存也不存在了,mybatis默认开启一级缓存,不需要配置。mybatis的缓存是基于[namespace:sql:参数…]来进行缓存的,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数…]作为key。
注意:服务器集群的时候,每个sqlSession有自己独立的缓存,相互之间不存在共享,所以在服务器集群的时候容易产生数据冲突问题。

如何禁止一级缓存
方案1 在sql语句上随机生成不同的参数 存在缺点:map集合可能爆 内存溢出的问题
方案2 开启二级缓存
方案3 使用sqlSession强制清除缓存 sqlSession.clearCache();
方案4 创建新的sqlSession连接。

2 mybatis一级缓存与二级缓存的区别

二级缓存SessionFactory
二级缓存是mapper级别的缓存,也就是同一个namespace的mapper.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域。二级缓存默认是开启的,但是没有配置缓存介质不实际生效。

在这里插入图片描述
一级缓存与二级缓存区别
1、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(sqlHashMap)是互相不影响的。
2、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
注意:sqlSession缓存底层存在线程安全问题。

3 mybatis使用Redis作为二级缓存

1.maven引包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.4.0</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.0.1</version>
</dependency>

2.需要在setting全局参数中配置开启二级缓存 mybatis_config.xml

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

3.在UserMapper.xml添加配置

<cache eviction="LRU" type="com.mayikt.cache.MybatisRedisCache" />

4.redis相关类

/**
 * mybatis二级缓存整合Redis
 */
public class MybatisRedisCache implements Cache {
    private static Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private Jedis redisClient = createReids();

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private String id;

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>MybatisRedisCache:id=" + id);
        this.id = id;
    }


    @Override
    public String getId() {
        return this.id;
    }


    @Override
    public int getSize() {

        return Integer.valueOf(redisClient.dbSize().toString());
    }


    @Override
    public void putObject(Object key, Object value) {
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject:" + key + "=" + value);
        redisClient.set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value));
    }

    @Override
    public Object getObject(Object key) {
        Object value = SerializeUtil.unserialize(redisClient.get(SerializeUtil.serialize(key.toString())));
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject:" + key + "=" + value);
        return value;
    }


    @Override
    public Object removeObject(Object key) {
        return redisClient.expire(SerializeUtil.serialize(key.toString()), 0);
    }


    @Override
    public void clear() {
        redisClient.flushDB();
    }


    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    protected static Jedis createReids() {
        JedisPool pool = new JedisPool("127.0.0.1", 6379);
        return pool.getResource();
    }
}
/**
 * mybatis二级缓存整合Redis
 */
public class MybatisRedisCache implements Cache {
    private static Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private Jedis redisClient = createReids();

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private String id;

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>MybatisRedisCache:id=" + id);
        this.id = id;
    }


    @Override
    public String getId() {
        return this.id;
    }


    @Override
    public int getSize() {

        return Integer.valueOf(redisClient.dbSize().toString());
    }


    @Override
    public void putObject(Object key, Object value) {
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject:" + key + "=" + value);
        redisClient.set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value));
    }

    @Override
    public Object getObject(Object key) {
        Object value = SerializeUtil.unserialize(redisClient.get(SerializeUtil.serialize(key.toString())));
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject:" + key + "=" + value);
        return value;
    }


    @Override
    public Object removeObject(Object key) {
        return redisClient.expire(SerializeUtil.serialize(key.toString()), 0);
    }


    @Override
    public void clear() {
        redisClient.flushDB();
    }


    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    protected static Jedis createReids() {
        JedisPool pool = new JedisPool("127.0.0.1", 6379);
        return pool.getResource();
    }
}

5.实体类实现序列化

public class UserEntity implements Serializable

4 mybatis二级缓存源码分析01

测试类

public class TestMyBatis06 {
    public static void main(String[] args) {

        try {
            // 1.定义mybatis文件
            String resource = "mybatis_config.xml";
            // 2.使用java的api读取mybatis_config.xml文件,获取InputStreamReader
            Reader resourceAsReader = Resources.getResourceAsReader(resource);
            // 3.获取SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
            // 4.获取Session
            SqlSession sqlSession1 = sqlSessionFactory.openSession();
            System.out.println("第一次查询...");
            UserEntity userEntity1 = sqlSession1.selectOne("com.mayikt.mapper.UserMapper.getUser", 2);
            System.out.println(userEntity1.getName());
            SqlSession sqlSession2 = sqlSessionFactory.openSession();
            System.out.println("第二次查询...");
//            sqlSession1.close();
            UserEntity userEntity2 = sqlSession2.selectOne("com.mayikt.mapper.UserMapper.getUser", 2);
            System.out.println(userEntity2.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

测试结果:
在这里插入图片描述
此时第二次查询走缓存代码二级缓存仍为null,原因是中间执行了一次openSession()方法,该方法每次执行创建一个新的缓存执行器,二级缓存管理器(tcm)也会重新创建一个。TransactionalCacheManager管理TransactionalCache和sqlSession绑定。

中间插入执行sqlSession1.close(),流程正常,第二次查询从二级缓存中获取数据:
在这里插入图片描述

5 mybatis二级缓存源码分析02

1.通过cache对象创建transactionalCache
在这里插入图片描述
TransactionalCache:继承自Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务过程中的数据可能会回滚,所以不能直接把数据就提交二级缓存,而是暂存在TransactionalCache中,在事务提交后再将过程中存放在其中的数据提交到二级缓存,如果事务回滚,则将数据清除掉)

2.执行getObject方法,底层查询redis是否存在缓存数据
在这里插入图片描述
1)List list = (List)this.tcm.getObject(cache, key); 会从redis中获取缓存数据;
2)this.tcm.putObject(cache, key, list); 只是将数据存放到TransactionalCache的entriesToAddOnCommit;entriesToAddOnCommit存放一级到二级缓存的缓存数据;
3)sqlSession1.close()/commit(); 将entriesToAddOnCommit中的数据放入二级缓存;单个sqlSession使用二级缓存可以手动调用以上方法。

6 mybatis缓存淘汰策略

二级缓存回收策略
LRU:最近最少使用的策略,移除最长时间不被使用的对象。
FIFO:先进先出策略,按对象进入缓存的顺序来移除它们。
SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象。
WEAK:弱引用策略,更积极地移除基于垃圾收集器状态和弱引用规则的对象。

软引用与弱引用的区别:
软引用: 软引用是用来描述一些有用但并不是必需的对象, 对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象;
弱引用: 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象;

7 实际SpringBoot项目sqlSession源码分析

实际项目中整合springboot sqlSession会创建多次吗?
会,每个线程创建自己独立的sqlSession
sqlSession因为线程不安全,所以不会共享;而sqlSessionFactory会共享。

SpringBoot整合redis作为二级缓存:
1.Mapper类加上注解@CacheNamespace(implementation = MybatisRedisCache.class)
2.启动类加上注解@EnableCaching
Spring整合mybatis的时候,有一个SqlSessionInterceptor类,当执行完sql语句的时候,会判断sqlSession事务有没有提交。有需要缓存数据提交的时候,底层封装sqlSession.commit方法;当每次当前sqlSession执行结束时候会调用closeSqlSession方法关闭当前sqlSession。因此,每次访问请求,都会单独有一个独立的线程创建独立的sqlSession。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值