【面試題】【Mybatis】面試題3、mybatis如何實現二級緩存

一、問題

1.1、環境
電腦環境:Windows 10;
開發工具:IntelliJ IDEA;
數據庫環境:MySQL 11.0.10;
JDK環境: Jdk1.8;
Maven環境:Maven3.5.0;
1.2、問題
如何使用Mybatis框架實現二級緩存?

二、解答

2.1、基本知識;
1.一級緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該Session中的所有 Cache 就將清空。
2. 二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap存儲,不同在於其存儲作用域爲 Mapper ,並且可自定義存儲源,如 Ehcache

2.2、實戰
首先我們搭建一個簡單的項目:
①、實體類:
如果要實現二級緩存,必須要實現序列化,即實現實現Serializable接口,爲什麼要實現序列化接口?我的理解是,Mybatis如何能判斷你這個User實體類的查詢和另一個User實體類查詢時不一樣的?序列化就像身份證一樣,是標記一個對象是否相同的一個唯一驗證碼;並且,我們再調用外部接口或者傳輸過程中,都需要序列化,也是爲了將一個立體的實體類,在保證對象結構和對象數據的基礎上,做一個線性,二進制化的一個處理;就像一個方陣要快速通過一個窄橋,寬度只能滿足一個人通過,大家按照一定的規則(從前到後,從左到右)依次快速通過,通過窄橋後,再恢復爲原來的方陣(反序列化);總而言之就是:唯一標記和快速傳輸;

package com.example.student.domain;


import java.io.Serializable;

public class User  implements Serializable{
    private int id;
    private int age;
    private String userName;
    private String userAddress;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserAddress() {
        return userAddress;
    }
    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString()
    {
        final StringBuilder builder = new StringBuilder();
        builder.append("id=").append(id).append(",");
        builder.append("age=").append(age).append(",");
        builder.append("userName=").append(userName).append(",");
        builder.append("userAddress=").append(userAddress).append(".");

        return builder.toString();

    }
}


②、mapper.xml文件
mapper.xml文件很簡單,這裏我們再select標籤上新加了一個cache標籤,用來配置緩存配置;eviction表示緩存回收的策略;flushInterval表示清理間隔,滿了多少條之後清空緩存;這裏如果設置1,則第2次查詢都會新開一個sqlSession;你也可以設置大一些;例如60000等;

映射語句文件中的所有select 語句將會被緩存。
映射語句文件中的所有insert,update 和delete 語句會刷新緩存。
緩存會使用Least Recently Used(LRU,最近最少使用的)算法來收回。
根據時間表(比如no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序來刷新。
緩存會存儲列表集合或對象(無論查詢方法返回什麼)的1024 個引用。
緩存會被視爲是read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。

<?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">
<!-- 這裏namespace必須是PostsMapper接口的路徑,不然要運行的時候要報錯 “is not known to the MapperRegistry”-->
<mapper namespace="com.example.student.mapper.UserMapper">
    <!-- 這兒的resultType是配置在mybatis-config.xml中得別名 -->

    <!--<cache/>-->

<!--  回收策略:
    LRU – 最近最少使用的:移除最長時間不被使用的對象。
    FIFO – 先進先出:按對象進入緩存的順序來移除它們。
    SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
    WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。-->
    <!--<cache eviction="FIFO" flushInterval="1" size="512" readOnly="true" />-->
    <select id="getUserById" parameterType="int" resultType="com.example.student.domain.User">
        select * from user where id=#{id}
    </select>
</mapper>

③、mapper.java類

package com.example.student.mapper;

import com.example.student.domain.User;

//通過面向接口的mybatis編程方式,需要保證方法名和配置文件中的id名稱一致
public interface UserMapper {
    public User getUserById(int id);
}

④、測試類
注意導入的包的版本和路徑;

import com.alibaba.fastjson.JSON;
import com.example.student.domain.User;
import com.example.student.mapper.UserMapper;
import junit.framework.TestCase;
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.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;

public class TestClass extends TestCase {

        Logger logger = LoggerFactory.getLogger(TestClass.class);
        private static SqlSessionFactory sqlSessionFactoy;

        static{
//            獲取mybatis配置文件的方式:
//            String resource = "org/mybatis/builder/mybatis-config.xml";
//            InputStream inputStream = Resources.getResourceAsStream(resource);
//            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//            SqlSessionFactory factory = builder.build(inputStream);
            String resource = "mybatis.config.xml";
            try {
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactoy = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Test
        public void testgetUserById() {

//            默認的 openSession()方法沒有參數,它會創建有如下特性的 SqlSession:
//            1.會開啓一個事務(並且不自動提交)。
//            2.將從由當前環境配置的 DataSource 實例中獲取 Connection 對象。
//            3.事務隔離級別將會使用驅動或數據源的默認設置。
//            4.預處理語句不會被複用,也不會批量處理更新。

            //這裏開啓兩個SqlSession來模擬兩次請求;
            SqlSession sqlSession = sqlSessionFactoy.openSession();
            SqlSession sqlSession2 = sqlSessionFactoy.openSession();
            try{
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

                User u1=mapper.getUserById(1);
//                事務控制方法;
//                void commit()
//                void commit(boolean force)
//                void rollback()
//                void rollback(boolean force)
                sqlSession.commit();
                logger.debug(JSON.toJSONString(u1));

                User u2=mapper2.getUserById(1);
                sqlSession2.commit();
                logger.debug(JSON.toJSONString(u2));

            } finally {
                sqlSession.close();
            }
        }
    }

⑤、建表語句(MySQL):

drop table if exists user;


create table user(
    id int primary key auto_increment,
    username varchar(50) unique,
    password varchar(100),
    useraddress varchar(50)
);

select * from user ;
insert into user values(1,"123","123","beijing");
insert into user values(2,"46","456","shanghai");

⑥配置文件
配置mybatis.config.xml文件;這裏要放開settings標籤,讓緩存生效;你可以對比放開前後日誌打印的情況,是否開啓了兩個sqlSession;還是調用2次查詢,但是隻開啓依次sqlSession;下面還要注意一點,現在mysql的Driver廢棄了com.mysql.jdbc.Driver,最好改用com.mysql.cj.jdbc.Driver;並且url後面設置服務器的時區serverTimezone;而且url裏【&】符號要用轉移字符代替,即【&amp;】;別忘了修改本地MySQL數據庫的連接信息;關聯的實體類的路徑,關聯的mapper.xml的路徑;一般mapper.xml文件都放在resources下面;我這裏放在resources下面的mapper文件夾裏的;

<?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">
<configuration>
    <!--必須要配置可緩存-->
<!--    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>-->
    <typeAliases>
        <typeAlias alias="User" type="com.example.student.domain.User" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/sys?characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>

</configuration>

測試:

11:45:00.844 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.11:45:01.099 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
11:45:01.105 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
11:45:02.716 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1496949625.
11:45:02.716 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.718 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==>  Preparing: select * from user where id=? 
11:45:02.746 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
11:45:02.769 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <==      Total: 1
11:45:02.831 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
11:45:02.831 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
11:45:02.831 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
11:45:02.841 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1665197552.
11:45:02.841 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6340e5f0]
11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==>  Preparing: select * from user where id=? 
11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
11:45:02.843 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <==      Total: 1
11:45:02.843 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.843 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1496949625 to pool.

Process finished with exit code 0

截圖:
在這裏插入圖片描述

如上圖所示的報文裏,就開啓了請求查詢的兩個事務;

本篇說明內容完畢;

三、總結

1、實體類序列化;
2、mybatis.config.xml(或者你自己起的別的名稱)配置文件裏導入settings標籤,配置setting標籤,屬性name爲cacheEnabled,value值爲true;

    <!--必須要配置可緩存-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

3、具體的mapper.xml文件標籤上配置cache標籤;配置屬性eviction爲FIFO(先入先出),就是優先移除最早的緩存;flushInterval間隔可以自定義設置,設置爲1則每次查詢過後都會清空緩存;readOnly看你業務是否需要只讀與否;

<cache eviction="FIFO" flushInterval="1" size="512" readOnly="true" />

參考博客:
1、JAVABEAN爲什麼需要序列化
2、mybatis 介紹cache和使用cache
3、mybatis 緩存(cache)的使用
4、mybatis入門教程(九)------mybatis緩存
5、mybatis中文文檔

歡迎關注我的
CSDN博客: https://blog.csdn.net/River_Continent
微信公衆號:幕橋社區
在這裏插入圖片描述
知乎:張牧野, https://www.zhihu.com/people/zhang-mu-ye-37-76/activities
簡書: https://www.jianshu.com/u/02c0096cbfd3

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