MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)

Posted Lotus_dong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)相关的知识,希望对你有一定的参考价值。

MyBatis缓存

本文中的部分案例代码来自MyBtais动态sql和特殊符号处理(案例集锦:不定条件查询+模糊查询+批量查询+批量删除…)

概述

为什么使用缓存?

缓存(即cache)的作用是为了减去数据库的压力,提高数据库的性能。缓存实现的原理是从数据库中查询出来的对象在使用完后不销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存中获取,不再向数据库执行select语句,减少对数据库的查询次数,提高了数据库的性能。缓存是使用Map集合存储数据。

MyBatis缓存

MyBatis有一级缓存和二级缓存之分。

一级缓存的作用域是同一个SqlSession,在同一个SqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库查询的数据写到缓存(内存),第二次会从缓存中获取数据而不进行数据库查询,大大提高了查询效率。当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。MyBtais默认启动以及缓存。

二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递的参数也相同时,第一次执行完毕会将数据库中查询到的数据写到缓存(内存),第二次会直接从缓存中获取,从而提高了查询效率。MyBatis默认不开启二级缓存,需要在MyBtais全局配置文件中进行setting配置开启二级缓存。

一级缓存

MyBatis默认开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数个sql完全一致的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次sql,使用SqlSession第一次查询后,MyBatis会将其放在缓存中,之后再查询时若没有缓存失效或超时,SqlSession都会取出当前缓存的数据,不会再发送sql到数据库。

我们做一个查询的测试

/**
* 批量查询缓存
* 缓存:将数据临时存储在(本地硬盘,内存),减少对数据的访问
*
* 一级缓存是SqlSession级别的,MyBatis默认开启
* 在同一个SqlSession中可以将第一次查询的数据缓存到SqlSession
* 第二次查询相同数据时,就可以直接从SqlSession获取
*/
@Test
public void test5() {
    try {
        Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = sessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        //处理,假设从客户端传递过来要删除的多个参数为一个集合
        List<Integer> list = new ArrayList<>();
        list.add(1);
        //查询
        System.out.println("同一个sqlSession中第一次查询id=1的数据为"+userDao.findUser(list));
        System.out.println("---------------");
        //第二次查询	
        System.out.println("---------------");
        System.out.println("同一个sqlSession中第二次查询id=1的数据为"+userDao.findUser(list));
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
}

在这里插入图片描述

一级缓存的生命周期

一级缓存的工作过程如图所示

在这里插入图片描述

MyBatis开启一个数据库会话时,会创建一个新的SqlSession对象,其中有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象,当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象会一并释放掉。

缓存失效

1.当SqlSession调用close()方法时,SqlSession对象关闭,直接会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

2.如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是SQlSession对象还可以用。

/**
* 缓存失效: close clear 执行新增,修改,删除操作会清空一级缓存
*/
@Test
public void test5() {
    try {
        Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = sessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        //处理,假设从客户端传递过来要删除的多个参数为一个集合
        List<Integer> list = new ArrayList<>();
        list.add(1);
        //查询
        System.out.println("同一个sqlSession中第一次查询id=1的数据为"+userDao.findUser(list));
        System.out.println("---------------");
        //清空SqlSession缓存(一级缓存)
        sqlSession.clearCache();
        System.out.println("清空一级缓存");
        //第二次查询	
        System.out.println("---------------");
        System.out.println("同一个sqlSession中第二次查询id=1的数据为"+userDao.findUser(list));
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
}

在这里插入图片描述

3.SqlSession中执行了任何一个update(修改)、delete(删除)、insert(新增)操作,都会清空PerpetualCache对象中的数据,但是SqlSession对象仍然可用。

/**
* 缓存失效: close clear 执行新增,修改,删除操作会清空一级缓存
*/
@Test
public void test5() {
    try {
        Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = sessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        //处理,假设从客户端传递过来要删除的多个参数为一个集合
        List<Integer> list = new ArrayList<>();
        list.add(1);
        //查询
        System.out.println("同一个sqlSession中第一次查询id=1的数据为"+userDao.findUser(list));
        System.out.println("---------------");
        //执行修改语句后缓存会失效
        User user = new User();
        user.setId(1);
        user.setAge(20);
        userDao.updateUser(user);
        System.out.println("执行修改语句缓存失效");
        //第二次查询	
        System.out.println("---------------");
        System.out.println("同一个sqlSession中第二次查询id=1的数据为"+userDao.findUser(list));
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
}

在这里插入图片描述

二级缓存

二级缓存的作用域是SqlSessionFactory级别,整个应用程序只有一个。二级缓存区域是根据mapper的namespace划分的,相同的namespace的mapper查询的数据缓存在同一个区域,如果使用mapper代理方法每一个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper进行划分的。

每次查询都会先从缓存区域查找,如果找不到则从数据库进行查询,并将查询到的数据写入缓存。MyBtais内部缓存使用HashMap,key为hashCode+sqlid+sql语句,value为从查询出来映射生成的java对象。SqlSession执行任何一个update(修改)、delete(删除)、insert(新增)操作commit提交后都会清空缓存区域,防止脏读。

工作模式

在这里插入图片描述

配置二级缓存

1.在MyBtais全局配置文件中开启二级缓存,具体视版本而言。

在这里插入图片描述

<!--mybatis的全局设置-->
<settings>
    <!--开启二级缓存,默认是开启的为true-->
    <setting name="cacheEnabled" value="true"/>
    <!--配置日志组件-->
    <setting name="logImpl" value="LOG4J"/>
    <!--开启驼峰映射-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!--开启全局懒加载-->
    <setting name="lazyLoadTriggerMethods" value="true"/>
</settings>

2.POJO序列化,将所有的POJO类(bean)实现序列化接口java.io.Serializable。

package com.cwd.mybatis.bean;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
import java.util.Date;

@Alias("User")
public class User implements Serializable {
    //生成实体类序列化id
    private static final long serialVersionUID = -7383964035746655660L;

    private Integer id;
    private String name;//姓名
    private Integer age;//年龄
    private Date birthday;//生日

    public Integer getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}

3.配置映射文件

在Mapper.xml映射文件中添加cache标签,表示次mapper开启二级缓存。

<!--
    表示此namespace使用二级缓存
    cache标签的属性:
        eviction="LRU" :清除策略,默认为LRU,移除最长时间不被使用的对象。
        flushInterval="60000" :刷新间隔,以毫秒为单位的合理时间量
        size="512" :引用数目
        readOnly="true" :只读的缓存会给所有调用者返回缓存对象的相同实例
-->
<cache></cache>
<!--按照学号迭代查询-->
<!--
    二级缓存
        属性useCache="false",sql语句自己可以决定是否使用二级缓存
        属性flushCache="false",sql语句自己可以决定是否刷新缓存,多用与新增,修改,删除语句
-->
<select id="findUser" resultType="User">
    SELECT * FROM t_user WHERE id in
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
</select>

测试

/**
 * 二级缓存是SqlSessionFactory级别的,整个应用程序只有一个
 * 以namespace划分缓存区域
 */
@Test
public void test6() {
    try {
        Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

        //SqlSession1,使用SqlSession查询第一次查询数据
        SqlSession sqlSession1 = sessionFactory.openSession();
        UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
        //处理,假设从客户端传递过来要删除的多个参数为一个集合
        List<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        System.out.println("SqlSession1查询id=1,2的数据为"+userDao1.findUser(list1));
        sqlSession1.commit();
        sqlSession1.close();//SqlSession关闭时会将数据写入二级缓存
        System.out.println("----------------------------------");

        //SqlSession2,使用SqlSession查询第二次查询数据
        SqlSession sqlSession2 = sessionFactory.openSession();
        UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
        //处理,假设从客户端传递过来要删除的多个参数为一个集合
        List<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        System.out.println("SqlSession2查询id=1,2的数据为"+userDao2.findUser(list1));
        sqlSession2.commit();
        sqlSession2.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

以上是关于MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)的主要内容,如果未能解决你的问题,请参考以下文章

浏览器缓存看这一篇就够了

初识Redis,看这一篇就够了

初识Redis,看这一篇就够了

MyBatis多条件查询看这一篇就够了

高性能的本地缓存方案选型,看这篇就够了!

高性能的本地缓存方案选型,看这篇就够了!