mybatis的缓存机制

14 篇文章 3 订阅
11 篇文章 0 订阅

🍅 Java学习路线:搬砖工的Java学习路线
🍅 作者:程序员小王
🍅 程序员小王的博客:https://www.wolai.com/wnaghengjie/ahNwvAUPG2Hb1Sy7Z8waaF
🍅 扫描主页左侧二维码,加我微信 一起学习、一起进步
🍅 欢迎点赞 👍 收藏 ⭐留言 📝

mybatis的缓存机制

一、缓存:缓存存储

1、现有的查询策略:

现有查询策略:每次查询都会链接访问数据库

存在的问题:每次都需要获取链接,释放连接资源,降低程序运行效率,解决方案使用缓存

2、使用缓存:

缓存:内存中的一块存储空间,用于存放多个用户反复查询数据,有了缓存之后
后续的查询数据都会直接从缓存中获取

3、使用缓存的好处:

减少每次使用链接的占用,提升查询效率,
提升程序的运行效率

4、使用缓存的缺点:

占用大量的内存资源(成本高)

5、缓存的机制:

以空间换时间(效率)

二、mybatis缓存

1、mybatis的缓存机制

(1)一级缓存 基于sqlSession的缓存【默认开启】
  • 一级缓存:也称为本地缓存,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,一级缓存是自动开启的,不允许关闭。

注意:查询语句最后都是关闭资源,如果不关闭资源数据会出现闪动

不关闭资源,第一次查询四条,第二次5条,SqlSession.close()关闭资源就是为了关闭sqlSession的缓存

一级缓存的特点:

1、查询第一次时,获取到数据写入一级缓存,再次查询时从缓存获取,不再执行sql语句
2、若当前SqlSession发生修改、增加、删除动作时,就会立即把当前缓存的所有数据清空
3、对于查询操作,只要SqlSession没有调用flush或者close方法,它就一直存在

注意:一次缓存造成查询数据结果闪动

(2)二级缓存:基于mapper的缓存(基于指定实体类的缓存)(末默认不开启)

二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。(也可以说是,在同一namespace下,共享一块缓存空间,如果多个mapper (dao.xml)共享同一namesapce 则也共享一块缓存,二级缓存是跨sqlsession,多个sqlsession可以去二级缓存获取数据。即可以针对同一个dao接口或者同一个命名空间(namespace)创建多个SqlSession )

二级缓存特点:

1、只要发生增删改,就会将·同一命名空间(namespace)下的缓存清空
2、使用二级缓存实体类必须实现序列化,否则报错
3、使用查询语句,默认只写入一级缓存,只有调用close(),commit()方法,
   才会将数据提交到二级缓存,其他的sqlsession才能拿到,不再执行sql语句

2、二级缓存 开启手工设置

1、在Mybatis框架中声明 使用二级缓存 在mybatis-config.xml中配置

mybatis-config.xml中

mybatis-config.xml:
<!--    设置缓存-->
    <settings>
        <!--开启Mybatis二级缓存
             cacheEnabled:启动mybatis二级缓存   值:布尔值   false:不启用  【默认值】   true :开启
         -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

注意:

2、在需要进行缓存的操作Mapper中指定使用缓存 在mapper文件中声明
  <!--开启当前查询操作缓存-->
    <cache></cache>
3、要缓存的实体类必须实现序列化接口(实体类中)
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Date birthday;
  • 实体类实现序列化接口的原因:

数据的数据唤出:长时间不用的放到缓存文件里

在这里插入图片描述

数据的唤入:当再次使用时,

4、缓存测试

1、在mybatis工具类中重写一个执行sqlSession的方法:

作用:用于测试缓存,实际开发不使用

//mybatisUtil.java 工具类
public class MybatisUtil {
    /**
     * 线程绑定对象,保证是同一个对象
     */
    private static final ThreadLocal<SqlSession> t1 = new ThreadLocal<>();
    private static SqlSessionFactory factory;

    static {
        InputStream is = null;
        try {
            is = Resources.getResourceAsStream("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        factory = new SqlSessionFactoryBuilder().build(is);
    }

    /**
     * 创建SqlSession
     *
     * @return sqlSession
     */
    private static SqlSession openSession() {
        SqlSession sqlSession = t1.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession();
            t1.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 仅用于测试缓存  每次调用方法获取的都是全新的连接  实际开发不使用
     * @return sqlSession
     */
    public static SqlSession getSqlSession(){
        return factory.openSession();
    }
    /**
     * 调用功能方法
     *
     * @param clazz
     * @return Object
     */
public  static Object getMapper(Class clazz) {
        return openSession().getMapper(clazz);
    }

    /**
     * 关闭资源
     */
    public static void close() {
        if (openSession() != null) {
            openSession().close();
        }
        t1.remove();
    }

    /**
     * 提交事务,关闭资源
     */
    public static void commit() {
        //1、提交事务
        commit();
        //2、关闭资源
        close();
        t1.remove();
    }

    /**
     * 回滚事务,关闭资源
     */
    public static void rollback() {
        //1、提交事务
        openSession().rollback();
        //2、关闭资源
        close();
        t1.remove();
    }

}

  • 工具类的sqlSession不开启线程绑定对象【这样每次查询都会创建一个sqlSession】
    /**
     * 仅用于测试缓存  每次调用方法获取的都是全新的连接  实际开发不使用
     * @return sqlSession
     */
    public static SqlSession getSqlSession(){
        return factory.openSession();
    }

2、测试

//注意:测试之前mybatis工具类调用的方法不能开启ThreadLocal线程绑定对象,否则调用的都是同一个sqlSession
//必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存区
@Test
    public void CacheTest() {
    
        //第一次id查询id为1的同学
        SqlSession sqlSession1 = MybatisUtil.getSqlSession();
        UserDao mapper = sqlSession1.getMapper(UserDao.class);
        User user = new User();
        user.setId(1);
        List<User> users = mapper.selectUserAll(user);
        System.out.println("第一次id查询id为1的同学:");
        for (User user1 : users) {
            System.out.println(user1);
        }
        //必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存
        MybatisUtil.close();
        
        
        //第二次id查询id为1的同学
        SqlSession sqlSession2 = MybatisUtil.getSqlSession();
        UserDao mapper2 = sqlSession1.getMapper(UserDao.class);
        User user2 = new User();
        user.setId(1);
        List<User> users2 = mapper.selectUserAll(user);
        System.out.println("第二次id查询id为1的同学:");
        for (User user3: users2) {
            System.out.println(user3);
        }
        //必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存
        MybatisUtil.close();
    }

3、注意事项:

1、注意:测试之前mybatis工具类调用的方法不能开启ThreadLocal线程绑定对象,
        否则调用的都是同一个sqlSession

2**必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存区中** 

3、二级缓存执行机制

开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。

在这里插入图片描述

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

1、一级缓存与二级缓存调用顺序:
获取数据先去二级缓存获取,如果有得到,写入(更新)一级缓存**

若没有得到,再去一级缓存,如果仍没有,再去数据库**

优先级: 二级缓存--->一级缓存--->数据库**

三、缓存中的脏数据

1、出现脏数据的原因:

1、主要原因:
数据库增删改之后,缓存的数据和数据库中的数据不匹配

2、详细原因:
使用二级缓存,当有一个session发生修改(增删改)时,
将二级缓存清空了,然而另一个session缓存过以前的查询结果
(此时二级缓存没数据,去一级缓存却拿到了之前的),则可能产生脏数据。

如上图:

1、第一步sqlsession1 去查询id=1的数据,
  并写入了sqlsession1的一级缓存与共同的二级缓存
2、第二步sqlsession2 去查询id=1的数据,
   发现二级缓存有数据,就不执行sql,并将其写入sqlsession2的一级缓存
3、第三步sqlsession2执行**修改** id=1的数据,
   清空了二级缓存与sqlsession2的一级缓存
4、第四步sqlsession1 再去查询id=1的数据,二级缓存没有,
   但却从sqlsession1的一级缓存读到了数据,但这却是修改之前的,就产生了脏数据

2、脏数据解决方案

判断是否刷新缓存flushCache;在默认的设置的select语句是不会刷新缓存的,insert/update/delte会刷新缓存。

3、Mybatis脏数据解决:

根据是否控制事务来判断是否刷新(清空)缓存:

SqlSession.commit()
SqlSession.rollback()

注意:

1、Mybatis会在事务控制时清空缓存,

2、后续功能查询一定不要控制事务(清空缓存)

3、增删改一定要控制事务

文末

👉在此,鸣谢:刘浩老师讲解

📌 作者:王恒杰

📃 更新: 2021.10.15

❌ 勘误: 无

📜 声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!
🍅 欢迎点赞 👍 收藏 ⭐留言 📝

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小王java

学习java的路上,加油!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值