Mybatis缓存

Mybatis缓存

  • 缓存的基本概念
  • Mybatis一级缓存
    • 证明一级缓存的存在
      • 编写用户持久层 Dao 接口
      • 编写用户持久层映射文件
      • 编写测试方法
    • 一级缓存的分析
    • 测试一级缓存的清空
      • 添加updateUser方法
      • 测试缓存的同步
  • Mybatis二级缓存
    • 二级缓存结构图
    • 二级缓存的开启与关闭
      • 第一步:在 SqlMapConfig.xml 文件开启二级缓存
    • 第二步:配置相关的 Mapper 映射文件
      • 第三步:配置 statement 上面的 useCache 属性
    • 二级缓存测试
      • 二级缓存注意事项

缓存的基本概念

缓存是存在于内存中的临时数据。
使用缓存减少和数据库的交互次数,提高执行效率。
适用于缓存:
(1)经常查询并且不经常改变的。
(2)数据的正确与否对最终结果影响不大的。
不适用于缓存:
(1)经常改变的数据
(2)数据的正确与否对最终结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价。

Mybatis一级缓存

一级缓存:
它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

证明一级缓存的存在

编写用户持久层 Dao 接口

IUserDao:

package com.keafmd.dao;
import com.keafmd.domain.User;
import java.util.List;

/**
 * Keafmd
 *
 * @ClassName: IUserDao
 * @Description: 用户的持久层接口
 * @author: 牛哄哄的柯南
 * @date: 2021-02-06 19:29
 */

public interface IUserDao {
     
    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     * @return
     */
    List<User> findAll();


    /**
     * 根据id查新用户信息
     * @param id
     * @return
     */
    User findById(Integer id);

}

编写用户持久层映射文件

IUserDao.xml:



<mapper namespace="com.keafmd.dao.IUserDao">


    
    <select id="findAll" resultType="user">
        select * from user
    select>

    
    <select id="findById" parameterType="Integer" resultType="user">
        select * from user where id = #{id}
    select>

mapper>

编写测试方法

MybatisTest:

package com.keafmd.test;

import com.keafmd.dao.IUserDao;
import com.keafmd.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * Keafmd
 *
 * @ClassName: MybatisTest
 * @Description: 测试类,测试crud操作
 * @author: 牛哄哄的柯南
 * @date: 2021-02-08 15:24
 */
public class UserTest {
     

    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before // 用于在测试方法执行前执行
    public void init()throws Exception{
     
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession(true); //里面写个true,下面每次就不用了写 sqlsession.commit(); 了
        //4.使用SqlSession创建Dao接口的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After // 用于在测试方法执行后执行
    public void destory() throws Exception{
     
        //提交事务
        //sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }


    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache(){
     
        User user1 = userDao.findById(41);
        System.out.println(user1);
        User user2 = userDao.findById(41);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

}

运行结果:

2021-02-16 15:52:44,863 359    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2021-02-16 15:52:45,164 660    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 905735620.
2021-02-16 15:52:45,168 664    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==>  Preparing: select * from user where id = ?
2021-02-16 15:52:45,200 696    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==> Parameters: 41(Integer)
2021-02-16 15:52:45,227 723    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - <==      Total: 1
com.keafmd.domain.User@400cff1a
com.keafmd.domain.User@400cff1a
true

我们可以看到,只发起了一次查询,相当于第一次是查询,而第二次是从缓存中取得。

一级缓存的分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

举个例子:
Mybatis缓存_第1张图片
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。

测试一级缓存的清空

改变下测试代码:

/**
 * 测试一级缓存
 */
@Test
public void testFirstLevelCache(){
     
    User user1 = userDao.findById(41);
    System.out.println(user1);

    /*sqlSession.close();

    //再次获取sqlSession对象
    sqlSession = factory.openSession();*/

    sqlSession.clearCache();//此方法也可以清空缓存

    userDao = sqlSession.getMapper(IUserDao.class);

    User user2 = userDao.findById(41);
    System.out.println(user2);

    System.out.println(user1==user2);
}

运行结果:

2021-02-16 16:14:57,171 194    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2021-02-16 16:14:57,498 521    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 93314457.
2021-02-16 16:14:57,503 526    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==>  Preparing: select * from user where id = ?
2021-02-16 16:14:57,541 564    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==> Parameters: 41(Integer)
2021-02-16 16:14:57,560 583    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - <==      Total: 1
com.keafmd.domain.User@57175e74
2021-02-16 16:14:57,561 584    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==>  Preparing: select * from user where id = ?
2021-02-16 16:14:57,561 584    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==> Parameters: 41(Integer)
2021-02-16 16:14:57,562 585    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - <==      Total: 1
com.keafmd.domain.User@c540f5a
false
2021-02-16 16:14:57,563 586    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@58fdd99]
2021-02-16 16:14:57,563 586    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 93314457 to pool.

Process finished with exit code 0

我们可以看到这样就发起了两次查询,我们用这两种方式都可以清空缓存。

添加updateUser方法

IUserDao:

package com.keafmd.dao;
import com.keafmd.domain.User;
import java.util.List;

/**
 * Keafmd
 *
 * @ClassName: IUserDao
 * @Description: 用户的持久层接口
 * @author: 牛哄哄的柯南
 * @date: 2021-02-06 19:29
 */

public interface IUserDao {
     
    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     * @return
     */
    List<User> findAll();


    /**
     * 根据id查新用户信息
     * @param id
     * @return
     */
    User findById(Integer id);

    /**
     * 更新用户信息
     * @param user
     */
    void updateUser(User user);


}

IUserDao.xml:



<mapper namespace="com.keafmd.dao.IUserDao">

    
    <select id="findAll" resultType="user">
        select * from user
    select>

    
    <select id="findById" parameterType="Integer" resultType="user">
        select * from user where id = #{id}
    select>

    
    <select id="updateUser"  resultType="user">
        update user set username=#{username},address=#{address} where id=#{id}
    select>

mapper>

测试缓存的同步

UserTest:

package com.keafmd.test;

import com.keafmd.dao.IUserDao;
import com.keafmd.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * Keafmd
 *
 * @ClassName: MybatisTest
 * @Description: 测试类
 * @author: 牛哄哄的柯南
 * @date: 2021-02-08 15:24
 */
public class UserTest {
     

    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before // 用于在测试方法执行前执行
    public void init()throws Exception{
     
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession(true); //里面写个true,下面每次就不用了写 sqlsession.commit(); 了
        //4.使用SqlSession创建Dao接口的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After // 用于在测试方法执行后执行
    public void destory() throws Exception{
     
        //提交事务
        //sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }


    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache(){
     
        User user1 = userDao.findById(41);
        System.out.println(user1);

        /*sqlSession.close();

        //再次获取sqlSession对象
        sqlSession = factory.openSession();*/

        sqlSession.clearCache();//此方法也可以清空缓存

        userDao = sqlSession.getMapper(IUserDao.class);

        User user2 = userDao.findById(41);
        System.out.println(user2);

        System.out.println(user1==user2);
    }

    /**
     * 测试缓存的同步
     */
    @Test
    public void testClearCache(){
     
        //1.根据id查询用户
        User user1 = userDao.findById(41);
        System.out.println(user1);

        //2.更新用户信息
        user1.setUsername("update user clear cache");
        user1.setAddress("上海");
        userDao.updateUser(user1);

        //手动提交
        sqlSession.commit();

        //3.再次查询id为41的用户
        User user2 = userDao.findById(41);
        System.out.println(user2);

        System.out.println(user1==user2);
    }


}

运行testClearCache():

2021-02-16 16:13:04,277 209    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2021-02-16 16:13:04,560 492    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 93314457.
2021-02-16 16:13:04,565 497    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==>  Preparing: select * from user where id = ?
2021-02-16 16:13:04,618 550    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==> Parameters: 41(Integer)
2021-02-16 16:13:04,645 577    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - <==      Total: 1
com.keafmd.domain.User@57175e74
2021-02-16 16:13:04,646 578    [           main] DEBUG keafmd.dao.IUserDao.updateUser  - ==>  Preparing: update user set username=?,address=? where id=?
2021-02-16 16:13:04,646 578    [           main] DEBUG keafmd.dao.IUserDao.updateUser  - ==> Parameters: update user clear cache(String), 上海(String), 41(Integer)
2021-02-16 16:13:04,647 579    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==>  Preparing: select * from user where id = ?
2021-02-16 16:13:04,648 580    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==> Parameters: 41(Integer)
2021-02-16 16:13:04,649 581    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - <==      Total: 1
com.keafmd.domain.User@c540f5a
false
2021-02-16 16:13:04,650 582    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@58fdd99]
2021-02-16 16:13:04,650 582    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 93314457 to pool.

Process finished with exit code 0

我们在第一次查询后进行的更新操作,所以会清空一级缓存,然后第二次会在进行查询。

Mybatis二级缓存

二级缓存:
它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

二级缓存结构图

Mybatis缓存_第2张图片
首先开启 mybatis 的二级缓存。
sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。
sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存的开启与关闭

第一步:在 SqlMapConfig.xml 文件开启二级缓存

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

因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为false 代表不开启二级缓存。

第二步:配置相关的 Mapper 映射文件

IUserDao.xml:



<mapper namespace="com.keafmd.dao.IUserDao">


    
    <cache/>

    
    <select id="findAll" resultType="user">
        select * from user

    select>

    
    <select id="findById" parameterType="Integer" resultType="user" useCache="true">
        select * from user where id = #{id}
    select>

    
    <select id="updateUser"  resultType="user">
        update user set username=#{username},address=#{address} where id=#{id}
    select>


mapper>

第三步:配置 statement 上面的 useCache 属性


<select id="findById" parameterType="Integer" resultType="user" useCache="true">
    select * from user where id = #{id}
select>

将 UserDao.xml 映射文件中的标签中设置 useCache=”true”代表当前这个statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

二级缓存测试

SecondLevelCacheTest:

package com.keafmd.test;

import com.keafmd.dao.IUserDao;
import com.keafmd.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;

/**
 * Keafmd
 *
 * @ClassName: MybatisTest
 * @Description: 测试类,测试crud操作
 * @author: 牛哄哄的柯南
 * @date: 2021-02-08 15:24
 */
public class SecondLevelCacheTest {
     

    private InputStream in;
    private SqlSessionFactory factory;


    @Before // 用于在测试方法执行前执行
    public void init()throws Exception{
     
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);

    }

    @After // 用于在测试方法执行后执行
    public void destory() throws Exception{
     

        in.close();
    }


    /**
     * 测试二级缓存
     */
    @Test
    public void testSecondLevelCache(){
     
        SqlSession sqlSession1 = factory.openSession();
        IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = dao1.findById(41);
        System.out.println(user1);
        sqlSession1.close(); //一级缓存消失

        SqlSession sqlSession2 = factory.openSession();
        IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
        User user2 = dao2.findById(41);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1==user2);
    }

}

运行结果:

2021-02-16 17:07:05,474 179    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2021-02-16 17:07:05,723 428    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 905735620.
2021-02-16 17:07:05,723 428    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@35fc6dc4]
2021-02-16 17:07:05,726 431    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==>  Preparing: select * from user where id = ?
2021-02-16 17:07:05,752 457    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - ==> Parameters: 41(Integer)
2021-02-16 17:07:05,770 475    [           main] DEBUG m.keafmd.dao.IUserDao.findById  - <==      Total: 1
com.keafmd.domain.User@400cff1a
2021-02-16 17:07:05,777 482    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@35fc6dc4]
2021-02-16 17:07:05,778 483    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@35fc6dc4]
2021-02-16 17:07:05,778 483    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 905735620 to pool.
2021-02-16 17:07:05,782 487    [           main] DEBUG        com.keafmd.dao.IUserDao  - Cache Hit Ratio [com.keafmd.dao.IUserDao]: 0.5
com.keafmd.domain.User@75c072cb
false

Process finished with exit code 0

经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
注意:二级缓存中存放的内容是数据,而不是对象,内容虽然是同样的,但是存放的是数据,第二次虽然没有发起查询,但从二级缓存中获取时,会把内容封装到新的对象返回,所以是false。

二级缓存注意事项

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

User:

package com.keafmd.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * Keafmd
 *
 * @ClassName: User
 * @Description:
 * @author: 牛哄哄的柯南
 * @date: 2021-02-08 15:16
 */

public class User implements Serializable {
     
    private Integer id;
    private String username;
    private String sex;
    private String address;
    private Date birthday;

    public Integer getId() {
     
        return id;
    }

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

    public String getUsername() {
     
        return username;
    }

    public void setUsername(String username) {
     
        this.username = username;
    }

    public String getSex() {
     
        return sex;
    }

    public void setSex(String sex) {
     
        this.sex = sex;
    }

    public String getAddress() {
     
        return address;
    }

    public void setAddress(String address) {
     
        this.address = address;
    }

    public Date getBirthday() {
     
        return birthday;
    }

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

}

以上就是Mybatis缓存的全部内容。

看完如果对你有帮助,感谢点赞支持!
如果你是电脑端的话,看到右下角的 “一键三连” 了吗,没错点它[哈哈]

在这里插入图片描述

加油!

共同努力!

Keafmd

你可能感兴趣的:(Mybatis,mybatis)