MyBatis框架(11) —— 緩存機制

簡介

  • 什麼是緩存?
    • 緩存是存在於內存中的臨時數據。
  • 爲什麼使用緩存?
    • 緩存能夠減少和數據庫的交互次數,提高執行效率。
  • 什麼樣的數據適用於緩存,什麼樣的數據不適用於緩存?
    • 適用於緩存:
      • 經常查詢,並且不經常改變的數據。
      • 數據的正確與否對最終結果的影響不大。
    • 不適用於緩存:
      • 經常改變的數據。
      • 數據的正確與否對最終結果的影響非常大。
  • 像大多數的持久化框架一樣,Mybatis 也提供了緩存策略,通過緩存策略來減少數據庫的查詢次數,從而提
    高性能。Mybatis 中緩存分爲一級緩存,二級緩存。
    • 一級緩存
      • 當我們執行查詢操作之後,Mybatis 框架會將在數據庫中的查詢的結果自動存儲到 SqlSession 中爲一級緩存劃分的一塊區域中。(該區域的低層結構是 Map集合)
      • 當我們再次執行同樣的查詢操作之後,Mybatis 框架會先從 SqlSession 中的一級緩存區域查詢。如果存在數據,就直接拿出來;如果不存在數據,纔會去數據庫中進行查詢。
      • 因爲一級緩存區域被設置在 SqlSession 中,所以當 SqlSession 消失時,一級緩存也就消失。
    • 二級緩存
      • 二級緩存被存儲在 SqlSessionFactory 中,同一個 SqlSessionFactory 創建的 SqlSession 共享二級緩存。
        在這裏插入圖片描述

目錄結構

src

  • main
    • java
      • cn.water.dao
        • UserDao.java(持久層接口)
      • cn.water.domain
        • User.java(實體類)
    • resources
      • cn.water.dao
        • UserDao.xml(映射配置文件)
      • SqlMapConfig.xml(MyBatis主配置文件)
      • jdbcConfig.properties(數據庫連接信息文件)
  • test
    • java.cn.water
      • MybatisTest.java(測試類)

MyBatis配置文件

jdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

SqlMapConfig.xml

  • 開啓 二級緩存
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">


        <!-- mybatis的主配置文件 -->
        <configuration>
    
            <!-- 外部配置 -->
            <properties resource="jdbcConfig.properties"></properties>

            <!-- 配置參數 -->
            <settings>
                <!-- 開啓 二級緩存 -->
                <setting name="cacheEnabled" value="true"/>
            </settings>

            <!-- 指定包:實體類-->
            <typeAliases>
                <package name="cn.water.domain"/>
            </typeAliases>

            <!-- 配置環境 -->
            <environments default="mysql">
                <environment id="mysql">
                    <transactionManager type="JDBC"></transactionManager>
                    <dataSource type="POOLED">
                        <!-- 配置連接數據庫的4個基本信息 -->
                        <property name="driver" value="${jdbc.driver}"/>
                        <property name="url" value="${jdbc.url}"/>
                        <property name="username" value="${jdbc.username}"/>
                        <property name="password" value="${jdbc.password}"/>
                    </dataSource>
                </environment>
            </environments>

            <!-- 指定包:持久層接口 -->
            <mappers>
                <package name="cn.water.dao"/>
            </mappers>

        </configuration>

實體類

User.java

package cn.water.domain;

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

/**
 * @author Water
 * @date 2019/10/12 - 7:51
 * @description
 */
public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

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

    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 Date getBirthday() {
        return birthday;
    }

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

    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;
    }
}

持久層接口

UserDao.java

package cn.water.dao;

import cn.water.domain.User;

/**
 * @author Water
 * @date 2019/10/12 - 7:51
 * @description
 */
public interface UserDao {

    /*  */
    User findById(Integer userId);

}

映射配置文件

UserDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="cn.water.dao.UserDao">

    <cache></cache>

    <select id="findById" parameterType="INT" resultType="user" useCache="true">
<!--    <select id="findById" parameterType="INT" resultType="user" >-->
        SELECT * FROM user WHERE id = #{userId};
    </select>


</mapper>

測試類

MyBatisTest.java

package cn.water;

import cn.water.dao.UserDao;
import cn.water.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.IOException;
import java.io.InputStream;

/**
 * @author Water
 * @date 2019/10/12 - 7:56
 * @description
 */
public class MybatisTest {


    /* 成員變量 */
    private InputStream inputStream;
    private SqlSessionFactory factory;
    private SqlSession session;

    /* 初始化操作 */
    @Before
    public void init() throws IOException {
        /* 加載 MyBatis配置文件 */
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        /* 獲取 工廠類 */
        factory = new SqlSessionFactoryBuilder().build(inputStream);
        /* 獲取 產品類 */
        session = factory.openSession(true);/* 設置自動提交 */
    }

    /* 銷燬操作 */
    @After
    public void destroy() throws IOException {
        session.close();
        inputStream.close();
    }

    /** 一級緩存:同一個 SqlSession */
    @Test
    public void test01(){
        UserDao dao = session.getMapper(UserDao.class);
        User user01 = dao.findById(41);
        System.out.println("第一次查詢:"+user01);
        User user02 = dao.findById(41);
        System.out.println("第二次查詢:"+user02);
        System.out.println("兩次查詢結果是否相等:"+(user01==user02)); //true
    }

    /** 一級緩存:不同 SqlSession */
    @Test
    public void test02(){
        UserDao dao = session.getMapper(UserDao.class);
        User user01 = dao.findById(41);
        System.out.println("第一次查詢:"+user01);

        /* commit()(執行插入、更新、刪除)、close() */
        session.close();
        session = factory.openSession();

        UserDao dao02 = session.getMapper(UserDao.class);
        User user02 = dao02.findById(41);
        System.out.println("第二次查詢:"+user02);
        System.out.println("兩次查詢結果是否相等:"+(user01==user02));//false
    }


}

一級緩存

測試類

/** 一級緩存:同一個 SqlSession */
@Test
public void test01(){
    UserDao dao = session.getMapper(UserDao.class);
    User user01 = dao.findById(41);
    System.out.println("第一次查詢:"+user01);
    User user02 = dao.findById(41);
    System.out.println("第二次查詢:"+user02);
    System.out.println("兩次查詢結果是否相等:"+(user01==user02));
}

運行結果

  • 一級緩存是SqlSession級別的緩存,只要SqlSession沒有flush或close,它就存在。
    • 我們可以發現,在測試一中雖然我們查詢了兩次,但最後只執行了一次數據庫操作,這就是Mybatis提供給我們的一級緩存在起作用了。
    • 因爲一級緩存的存在,導致第二次查詢id爲41的記錄時,並沒有發出sql語句從數據庫中查詢數據,而是從一級緩存中查詢。
      在這裏插入圖片描述

一級緩存的清空

測試類

/** 一級緩存:不同 SqlSession */
@Test
public void test02(){
    UserDao dao = session.getMapper(UserDao.class);
    User user01 = dao.findById(41);
    System.out.println("第一次查詢:"+user01);
    /* commit()(執行插入、更新、刪除)、close() */
    session.close();
    session = factory.openSession();
    UserDao dao02 = session.getMapper(UserDao.class);
    User user02 = dao02.findById(41);
    System.out.println("第二次查詢:"+user02);
    System.out.println("兩次查詢結果是否相等:"+(user01==user02));
}

運行結果

  • 一級緩存是SqlSession範圍的緩存,當調用SqlSession的commit()(修改,添加,刪除),close()等方法時,就會清空一級緩存。
  • 第一次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,如果沒有,從數據庫查
    詢用戶信息。得到用戶信息,將用戶信息存儲到一級緩存中。如果sqlSession去執行commit操作(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣做的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。
  • 第二次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。
    在這裏插入圖片描述

二級緩存

  • 當我們在使用二級緩存時,所緩存的類一定要實現 java.io.Serializable接口,這種就可以使用序列化
    方式來保存對象。

開啓二級緩存

  • 二級緩存的使用步驟:
    • 第一步:讓Mybatis框架支持二級緩存(在SqlMapConfig.xml中配置)
    • 第二步:讓當前的映射文件支持二級緩存(在IUserDao.xml中配置
    • 第三步:讓當前的操作支持二級緩存(在select標籤中配置)

MyBatis主配置文件

  1. 開啓二級緩存支持

    cacheEnabled 默認值爲 true(此配置可以省略)

<!-- 配置參數 -->
<settings>
    <!-- 開啓 延遲加載 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 開啓 二級緩存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

映射配置文件

  1. 設置 <cache> 標籤

    表示當前 mapper 映射將使用二級緩存,區分的標準爲 mapper 標籤的 namespace 值。

<mapper namespace="cn.water.dao.UserDao">

    <cache></cache>

</mapper>
  1. <select> 標籤 設置 useCache=”true” 屬性

    注意:針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁用二級緩存。

<mapper namespace="cn.water.dao.UserDao">

    <cache></cache>
    
    <select id="findById" parameterType="INT" resultType="user" useCache="true" >
        SELECT * FROM user WHERE id = #{userId};
    </select>


</mapper>

測試類

  • 依然執行 Test02()
 @Test
    public void test02(){
        UserDao dao = session.getMapper(UserDao.class);
        User user01 = dao.findById(41);
        System.out.println("第一次查詢:"+user01);

        /* commit()(執行插入、更新、刪除)、close() */
        session.close();
        session = factory.openSession();

        UserDao dao02 = session.getMapper(UserDao.class);
        User user02 = dao02.findById(41);
        System.out.println("第二次查詢:"+user02);
        System.out.println("兩次查詢結果是否相等:"+(user01==user02));//false
    }

運行結果

  • 經過上面的測試,我們發現執行了兩次查詢,並且在執行第一次查詢後,我們關閉了一級緩存,再去執行第二
    次查詢時,我們發現並沒有對數據庫發出sql語句,所以此時的數據就只能是來自於我們所說的二級緩存。

一級緩存

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bQWezfwy-1580952138442)(11.緩存\控制檯 證明 一級緩存02.png)]

一級緩存和二級緩存

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章