zoukankan      html  css  js  c++  java
  • 【Mybatis】14 缓存

    1、什么是缓存?

    - 缓存是指把经常需要读写的数据,保存到一个高速的缓冲区中,这个行为叫缓存

    - 也可以是指被保存在高速缓冲区的数据,也叫缓存

    2、Mybatis缓存

    Mybatis中分为一级缓存和二级缓存

    - 一级缓存,数据缓存在这个SqlSession的作用范围内

    - 二级缓存,数据缓存在这个SqlSesssionFactory的作用范围内

    一级缓存:

    一级缓存是默认开启的,那么如何证实是开启的呢?

    同一个SQL语句只会执行一次,并留下缓存,

    如果在这个SqlSession存在的期间,再次调用,那么Mybatis将不会执行SQL

    而是直接调用缓存执行

    案例:

    映射接口

    User getUserById(Integer id);

    映射器

        <select id="getUserById" resultType="user" parameterType="int">
            SELECT *
            FROM t_user
            WHERE id = #{id}
        </select>

    测试类

        @Test
        public void getUserById(){
            SqlSession sqlSession = MybatisUtil.getSqlSession(true);
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
            User user1 = userMapper.getUserById(1);
            System.out.println(user1);
    
            User user2 = userMapper.getUserById(1);
            System.out.println(user2);
    
            User user3 = userMapper.getUserById(1);
            System.out.println(user3);
    
            User user4 = userMapper.getUserById(1);
            System.out.println(user4);
    
            sqlSession.close();
        }

    测试结果:

    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 336371513.
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=1, last_name=阿伟, gender=0)
    User(id=1, last_name=阿伟, gender=0)
    User(id=1, last_name=阿伟, gender=0)
    User(id=1, last_name=阿伟, gender=0)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@140c9f39]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 336371513 to pool.
    
    Process finished with exit code 0

    这里可以看到SQL语句只执行了一次

    4次查询只执行了一次,这证明了缓存的存在

    也就是说,实际上缓存存放的数据是首次查询出来的一个结果

    如果我们反复调用相同的结果,Mybatis就会从缓存中返回数据给我们

    但是在查询不同情况下的值的时候,Mybatis还是无法调用缓存来完成

    例如我们这样查询不同的数据出来:

        @Test
        public void getUserById(){
            SqlSession sqlSession = MybatisUtil.getSqlSession(true);
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
            User user1 = userMapper.getUserById(1);
            System.out.println(user1);
    
            User user2 = userMapper.getUserById(2);
            System.out.println(user2);
    
            User user3 = userMapper.getUserById(3);
            System.out.println(user3);
    
            User user4 = userMapper.getUserById(4);
            System.out.println(user4);
    
            sqlSession.close();
        }

    结果就是不会触发缓存,因为每次查询的都不一样

    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 336371513.
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=1, last_name=阿伟, gender=0)
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 2(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=2, last_name=阿伟, gender=1)
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 3(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=3, last_name=杰哥, gender=0)
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 4(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=4, last_name=阿强, gender=0)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@140c9f39]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 336371513 to pool.
    
    Process finished with exit code 0

    原理示意:

    一级缓存失败的四种情况:

    - 不在同一个SqlSession对象中【同一个SQL语句】

    - 执行的语句的参数不一样,缓存中也不存在数据【就是上面的演示】

    - 执行增、删、改、会清除缓存

    - 自行手动清除

    手动清除是指SqlSession调用清除缓存方法

    sqlSession.clearCache();

    为什么增、删、改、也会清除缓存?

    是因为底层在SQL执行完默认就调用了这个方法清除了

    二级缓存:

    首先,二级缓存默认是不开启的,我们需要在Mybatis的核心配置文件中

    配置关于二级缓存的SETTINGS选项,和在映射器的配置文件中加入cache标签

    并且,需要被二级缓存的对象,必须要实现序列化接口

    示意图:

    开启二级缓存的配置操作:

    1、核心配置中添加二级缓存配置

    2、映射器加入cache标签

    3、被缓存的对象所属类必须实现序列化接口

    二级缓存开启配置

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

    映射器配置cache标签

    <cache/>

    测试类

        public void cacheTest(){
            SqlSession sqlSession = MybatisUtil.getSqlSession(true);
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
            User userById = mapper.getUserById(1);
            System.out.println(userById);
            
            sqlSession.close();
        }
        
        @Test
        public void sync(){
            cacheTest();
            cacheTest();
        }

    如果不实现序列化接口,二级缓存在调用时,就会出现未序列化异常

    [cn.dai.mapper.UserMapper]-Cache Hit Ratio [cn.dai.mapper.UserMapper]: 0.0
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1025309396.
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=1, last_name=阿伟, gender=0)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d1cfad4]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1025309396 to pool.
    
    org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: cn.dai.pojo.User
    
        at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:94)
        at org.apache.ibatis.cache.decorators.SerializedCache.putObject(SerializedCache.java:55)
        at org.apache.ibatis.cache.decorators.LoggingCache.putObject(LoggingCache.java:49)
        at org.apache.ibatis.cache.decorators.SynchronizedCache.putObject(SynchronizedCache.java:43)
        at org.apache.ibatis.cache.decorators.TransactionalCache.flushPendingEntries(TransactionalCache.java:116)
        at org.apache.ibatis.cache.decorators.TransactionalCache.commit(TransactionalCache.java:99)
        at org.apache.ibatis.cache.TransactionalCacheManager.commit(TransactionalCacheManager.java:44)
        at org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor.java:61)
        at org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession.java:263)
        at BuildTest.cacheTest(BuildTest.java:59)
        at BuildTest.sync(BuildTest.java:64)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
    Caused by: java.io.NotSerializableException: cn.dai.pojo.User
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at java.util.ArrayList.writeObject(ArrayList.java:766)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:90)
        ... 35 more
    
    
    Process finished with exit code -1

    所以需要我们自己来把实体类序列化

    再次测试:

    [cn.dai.mapper.UserMapper]-Cache Hit Ratio [cn.dai.mapper.UserMapper]: 0.0
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1025309396.
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=1, last_name=阿伟, gender=0)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d1cfad4]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1025309396 to pool.
    [cn.dai.mapper.UserMapper]-Cache Hit Ratio [cn.dai.mapper.UserMapper]: 0.5
    User(id=1, last_name=阿伟, gender=0)
    
    Process finished with exit code 0

    可以看到二次调用时不再调用SQL查询,而是使用了二级缓存保留的数据返回结果

    一些说明:

    useCache属性,这个属性是放在SQL查询标签中的<SELECT>

    默认TRUE(就是不写也表示开启的),表示使用二级缓存,前提是二级缓存是开启的

    在上面的全局测试中已经演示了结果

    如果更改为False就是取消这个SQL的二级缓存

    测试结果就是第二次查询就需要再次调用SQL了

    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1504642150.
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=1, last_name=阿伟, gender=0)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@59af0466]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1504642150 to pool.
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Checked out connection 1504642150 from pool.
    [cn.dai.mapper.UserMapper.getUserById]-==>  Preparing: SELECT * FROM t_user WHERE id = ? 
    [cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [cn.dai.mapper.UserMapper.getUserById]-<==      Total: 1
    User(id=1, last_name=阿伟, gender=0)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@59af0466]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1504642150 to pool.
    
    Process finished with exit code 0

    flushCache属性,这个属性是放在增、删、改、SQL语句中,

    表示自动清除缓存,默认值TRUE,另外不要手贱改FALSE

    如果不清除缓存,二次调用就从缓存的数据进行返回

    为什么这么说?

    - 先查询一个结果

      【主键:01,名字:阿伟,性别:男】

    - 不清除缓存进行修改记录,变更为

      【主键:01,名字:杰哥,性别:男】

    - 当二次查询时,Mybatis不会再调用SQL重新查询,

      直接跑到缓存中返回数据,这个查询返回的结果就是

      【主键:01,名字:阿伟,性别:男】

      但实际上数据库已经更改,这样查询返回的结果是不对的

      所以不要修改flushCache属性为False!!!

    3、Cache标签:

    当你在映射器中标注了此标签,Mybatis会默认开启这些功能:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。【就是能放多少个缓存】
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    1、最近最少使用算法

      【Least Recently Used 】LRU算法来清除不需要的缓存。

      移除符合这个描述的对象,优化内存空间

    除此之外还有其他算法,这个属性值由eviction配置

    2、先进先出算法

      FIFO,First In First Out 先进先出

      按对象进入缓存的顺序来移除

    3、软引用算法

      SOFT,移除基于GC回收状态和软引用规则的对象

    4、弱引用算法

      WEAK,弱引用,更积极的移除基于GC回收状态和弱引用规则的对象

    当然,默认使用的是LRU最少使用原则清除

    LRU
    FIFO
    SOFT
    WEAK

    二、可读可写的说明:

    readOnly="true"

    可读是共享对象的,所有的SqlSession如果需要调用这个二级缓存

    指针就会直接引用缓存返回对象

    可读是非共享的,所有SqlSession如果调用二级缓存,

    那么Mybatis会分别new一个对象,并把缓存对象的属性值赋值给这些new出来的对象

    指针则引用这些对象进写入修改

    三、自定义二级缓存

    可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

    <cache type="com.domain.something.MyCustomCache"/>

    我们可以查看Mybatis的缓存实现类是怎么写的

    package org.apache.ibatis.cache.impl;
    
    import java.util.HashMap;
    import java.util.Map;
    import org.apache.ibatis.cache.Cache;
    import org.apache.ibatis.cache.CacheException;
    
    public class PerpetualCache implements Cache {
        private final String id;
        private final Map<Object, Object> cache = new HashMap();
    
        public PerpetualCache(String id) {
            this.id = id;
        }
    
        public String getId() {
            return this.id;
        }
    
        public int getSize() {
            return this.cache.size();
        }
    
        public void putObject(Object key, Object value) {
            this.cache.put(key, value);
        }
    
        public Object getObject(Object key) {
            return this.cache.get(key);
        }
    
        public Object removeObject(Object key) {
            return this.cache.remove(key);
        }
    
        public void clear() {
            this.cache.clear();
        }
    
        public boolean equals(Object o) {
            if (this.getId() == null) {
                throw new CacheException("Cache instances require an ID.");
            } else if (this == o) {
                return true;
            } else if (!(o instanceof Cache)) {
                return false;
            } else {
                Cache otherCache = (Cache)o;
                return this.getId().equals(otherCache.getId());
            }
        }
    
        public int hashCode() {
            if (this.getId() == null) {
                throw new CacheException("Cache instances require an ID.");
            } else {
                return this.getId().hashCode();
            }
        }
    }

    四、缓存的执行顺序

    1、当我们执行一个查询语句的时候,mybatis会先去二级缓存中查询数据,如果二级缓存中没有,就到一级缓存中查找

    2、如果一级缓存也没有,调用SQL执行

    3、执行返回,并且结果保存进一级缓存

    4、SqlSession关闭,一级缓存保存到二级缓存中

  • 相关阅读:
    Postfix邮件服务器搭建及配置
    利用linux漏洞进行提权
    NFS部署和优化
    LAMP环境搭建
    Apache2.4.6服务器安装及配置
    linux笔记_防止ddos攻击
    CentOS6.5恢复误删除的文件
    linux计划任务
    linux软连接和硬链接
    linux用户和用户组的基本操作
  • 原文地址:https://www.cnblogs.com/mindzone/p/12996323.html
Copyright © 2011-2022 走看看