1. 程式人生 > >(二十一)Mybatis一級查詢快取

(二十一)Mybatis一級查詢快取

注:程式碼已託管在GitHub上,地址是:https://github.com/Damaer/Mybatis-Learning ,專案是mybatis-17-sqlsessionCache,需要自取,需要配置maven環境以及mysql環境(sql語句在resource下的test.sql中),覺得有用可以點個小星星,小菜鳥在此Thanks~
查詢快取的使用,主要是為了提高查詢訪問速度,不用每次都從資料庫查詢資料,可以提高訪問速度,而且可以減少資料庫查詢次數,減小資料庫壓力。

一級查詢快取

1.mybatis一級快取是基於org.apache.ibatis.cache.impl.PerpetualCache類的HashMap本地快取,作用域是SqlSession,也就是在同一個sqlsession中兩次執行相同的sql查詢語句,第一次執行完畢之後,就會將查詢結果寫入快取中,第二次會從快取裡面直接獲取資料,不需要去資料庫查詢。
2.當一個SqlSession結束,這個SqlSession的以及快取就不存在了,mybatis預設開啟一級快取,而且不可以關閉


3.一級快取存的時候是根據sql語句的id,不是根據sql的具體內容。

證明一級快取的存在

在這裡不放大部分程式碼,只放上核心程式碼,sql的介面如下:

public interface IStudentDao {
    public Student selectStudentById(int id);
}

mapper.xml與之對應的sql語句:

    <!-- 通過id來查詢學生 -->
    <select id="selectStudentById" resultType="Student">
        select * from student where id=#{xxx}

    </select
>

單元測試:

    @Test
    public void testselectStudentById(){

        // 第一次查詢
        Student student=dao.selectStudentById(17);
        System.out.println(student);
        // 第二次查詢
        Student student2=dao.selectStudentById(17);
        System.out.println(student2);
    }

結果,我們可以看到我們只執行了一次查詢,第二次查詢的時候直接走的一級快取,沒有對資料庫進行查詢,如果是對資料庫進行查詢,那麼會有sql語句打印出來:

[service] 2018-07-21 09:48:22,534 - dao.IStudentDao.selectStudentById -1349 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 09:48:22,635 - dao.IStudentDao.selectStudentById -1450 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 09:48:22,677 - dao.IStudentDao.selectStudentById -1492 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
Student [id=17, name=hello, age=14, score=94.6]
從快取中讀取資料的依據是sql的id還是sql本身?

我們此時有一個疑問,快取到底是根據什麼進行快取的,那麼我們需要做實驗,寫兩個擁有不同的id但是sql完全一致的sql就知道結果了。下面是在同一個namespace中進行:
sql介面:

public interface IStudentDao {
    public Student selectStudentById(int id);
    // 測試不同的id的sql
    public Student selectStudentById2(int id);
}

mapper檔案:

<?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改成類名的全名,那麼直接呼叫介面的方法的時候才有可能卻找到對應的mapper中的對應的方法 -->
<mapper namespace="dao.IStudentDao">

    <!-- 通過id來查詢學生 -->
    <select id="selectStudentById" resultType="Student">
        select * from student where id=#{xxx}

    </select>

    <!-- 測試不同id的sql -->
    <select id="selectStudentById2" resultType="Student">
        select * from student where id=#{xxx}

    </select>
</mapper>

測試程式碼:

    @Test
    public void testDiffereentId2(){

        // 第一次查詢
        Student student=dao.selectStudentById(17);
        System.out.println(student);
        // 第二次查詢,測試不同的id,同一個namespace
        Student student2=dao.selectStudentById2(17);
        System.out.println(student2);
    }

結果如下,我們可以看到裡面執行了兩次sql,那麼就證明了一級快取不是依據sql本身來的,而是依據sql的id快取的

[service] 2018-07-21 10:26:32,844 - dao.IStudentDao.selectStudentById -957  [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 10:26:32,954 - dao.IStudentDao.selectStudentById -1067 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 10:26:32,989 - dao.IStudentDao.selectStudentById -1102 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
[service] 2018-07-21 10:26:32,990 - dao.IStudentDao.selectStudentById2 -1103 [main] DEBUG dao.IStudentDao.selectStudentById2  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 10:26:32,991 - dao.IStudentDao.selectStudentById2 -1104 [main] DEBUG dao.IStudentDao.selectStudentById2  - ==> Parameters: 17(Integer)
[service] 2018-07-21 10:26:32,996 - dao.IStudentDao.selectStudentById2 -1109 [main] DEBUG dao.IStudentDao.selectStudentById2  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]

不同的namespace下的相同id呢?

不同的namespace。就算是同一個id,那麼一級快取也是不生效的,因為sql快取的時候是根據namespace不同來區分的。
兩個sql介面如下:

public interface IStudentDao {
    public Student selectStudentById(int id);
}

public interface IStudentDao2 {
    //不同的namespace下的相同id的測試
    public Student selectStudentById(int id);
}

兩個mapper檔案如下:

<?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改成類名的全名,那麼直接呼叫介面的方法的時候才有可能卻找到對應的mapper中的對應的方法 -->
<mapper namespace="dao.IStudentDao">
<!-- 通過id來查詢學生 -->
<select id="selectStudentById" resultType="Student">
    select * from student where id=#{xxx}
</select>
</mapper>
<?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改成類名的全名,那麼直接呼叫介面的方法的時候才有可能卻找到對應的mapper中的對應的方法 -->
<mapper namespace="dao.IStudentDao2">
    <!-- 通過id來查詢學生 -->
    <select id="selectStudentById" resultType="Student">
        select * from student where id=#{xxx}
    </select>

</mapper>

單元測試如下:

    @Test
    public void testDiffereentNamespaceSameId(){

        // 第一次查詢
        Student student=dao.selectStudentById(17);
        System.out.println(student);
        // 第二次查詢,測試相同的id,不同的namespace
        IStudentDao2 dao2= sqlSession.getMapper(IStudentDao2.class);
        Student student2=dao2.selectStudentById(17);
        System.out.println(student2);
    }

結果如下,我們可以看到執行了兩次sql。也就證明了一級快取根據的是同一個namespace下的同一個id,就算是同一個id,但是namespace也不會生效,因為一級快取是根據namespace來區分存放的。:

[service] 2018-07-21 10:37:36,916 - dao.IStudentDao.selectStudentById -1545 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 10:37:37,154 - dao.IStudentDao.selectStudentById -1783 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 10:37:37,194 - dao.IStudentDao.selectStudentById -1823 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
[service] 2018-07-21 10:37:37,202 - dao.IStudentDao2.selectStudentById -1831 [main] DEBUG dao.IStudentDao2.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 10:37:37,204 - dao.IStudentDao2.selectStudentById -1833 [main] DEBUG dao.IStudentDao2.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 10:37:37,210 - dao.IStudentDao2.selectStudentById -1839 [main] DEBUG dao.IStudentDao2.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
增刪改對一級快取的影響

增刪改操作無論是否提交sqlsession.commit(),都會清空以及查詢快取,讓查詢快取再次從DB中查詢。
sql的介面如下:

public interface IStudentDao {
    public Student selectStudentById(int id);
    // 測試不同的id的sql
    public Student selectStudentById2(int id);


    // 增加學生
    public void insertStudent(Student student);
    // 根據id刪除學生
    public void deleteStudentById(int id);
    // 更新學生的資訊
    public void updateStudent(Student student);
}

mapper.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改成類名的全名,那麼直接呼叫介面的方法的時候才有可能卻找到對應的mapper中的對應的方法 -->
<mapper namespace="dao.IStudentDao">

    <!-- 通過id來查詢學生 -->
    <select id="selectStudentById" resultType="Student">
        select * from student where id=#{xxx}

    </select>

    <!-- 測試不同id的sql -->
    <select id="selectStudentById2" resultType="Student">
        select * from student where id=#{xxx}
</select>

    <insert id="insertStudent" parameterType="Student">
        insert into student(name,age,score) values(#{name},#{age},#{score})
    </insert>
    <!-- 刪除 -->
    <delete id="deleteStudentById">
        delete from student where id=#{id}
        <!-- 這裡的id放什麼都可以,只是一個佔位符,不表示什麼 -->
    </delete>

    <update id="updateStudent">
        update student set name=#{name},age=#{age},score=#{score} where id=#{id}
    </update>
</mapper>

單元測試:

    public void test03(){

        // 第一次查詢
        Student student=dao.selectStudentById(17);
        System.out.println(student);
        //插入學生
        Student student1 = new Student("12112",12,21.6);
        //dao.insertStudent(student1);
        dao.updateStudent(student1);
        student=dao.selectStudentById(17);
        System.out.println(student);
    }

當我們執行第一次查詢之後,執行一次插入操作的時候,我們發現一級快取已經被更新了:

[service] 2018-07-21 13:07:27,136 - dao.IStudentDao.selectStudentById -1059 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 13:07:27,247 - dao.IStudentDao.selectStudentById -1170 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 13:07:27,288 - dao.IStudentDao.selectStudentById -1211 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
[service] 2018-07-21 13:07:27,289 - dao.IStudentDao.insertStudent -1212 [main] DEBUG dao.IStudentDao.insertStudent  - ==>  Preparing: insert into student(name,age,score) values(?,?,?) 
[service] 2018-07-21 13:07:27,291 - dao.IStudentDao.insertStudent -1214 [main] DEBUG dao.IStudentDao.insertStudent  - ==> Parameters: 12112(String), 12(Integer), 21.6(Double)
[service] 2018-07-21 13:07:27,295 - dao.IStudentDao.insertStudent -1218 [main] DEBUG dao.IStudentDao.insertStudent  - <==    Updates: 1
[service] 2018-07-21 13:07:27,295 - dao.IStudentDao.selectStudentById -1218 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 13:07:27,295 - dao.IStudentDao.selectStudentById -1218 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 13:07:27,302 - dao.IStudentDao.selectStudentById -1225 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]

當我們執行第一次查詢之後,再執行一次更新操作,就算更新的不是查詢的資料,但是屬於同一張表,一級快取同樣被更新:

[service] 2018-07-21 23:43:28,073 - dao.IStudentDao.selectStudentById -1081 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 23:43:28,202 - dao.IStudentDao.selectStudentById -1210 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 23:43:28,236 - dao.IStudentDao.selectStudentById -1244 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
[service] 2018-07-21 23:43:28,239 - dao.IStudentDao.updateStudent -1247 [main] DEBUG dao.IStudentDao.updateStudent  - ==>  Preparing: update student set name=?,age=?,score=? where id=? 
[service] 2018-07-21 23:43:28,241 - dao.IStudentDao.updateStudent -1249 [main] DEBUG dao.IStudentDao.updateStudent  - ==> Parameters: 12112(String), 12(Integer), 21.6(Double), 18(Integer)
[service] 2018-07-21 23:43:28,246 - dao.IStudentDao.updateStudent -1254 [main] DEBUG dao.IStudentDao.updateStudent  - <==    Updates: 1
[service] 2018-07-21 23:43:28,246 - dao.IStudentDao.selectStudentById -1254 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 23:43:28,246 - dao.IStudentDao.selectStudentById -1254 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 23:43:28,251 - dao.IStudentDao.selectStudentById -1259 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]

當我們執行一次查詢操作之後,執行一次刪除操作,那麼一級快取同樣會被更新:

[service] 2018-07-21 23:44:49,296 - dao.IStudentDao.selectStudentById -1172 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 23:44:49,457 - dao.IStudentDao.selectStudentById -1333 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 23:44:49,504 - dao.IStudentDao.selectStudentById -1380 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]
[service] 2018-07-21 23:44:49,505 - dao.IStudentDao.deleteStudentById -1381 [main] DEBUG dao.IStudentDao.deleteStudentById  - ==>  Preparing: delete from student where id=? 
[service] 2018-07-21 23:44:49,505 - dao.IStudentDao.deleteStudentById -1381 [main] DEBUG dao.IStudentDao.deleteStudentById  - ==> Parameters: 18(Integer)
[service] 2018-07-21 23:44:49,508 - dao.IStudentDao.deleteStudentById -1384 [main] DEBUG dao.IStudentDao.deleteStudentById  - <==    Updates: 1
[service] 2018-07-21 23:44:49,509 - dao.IStudentDao.selectStudentById -1385 [main] DEBUG dao.IStudentDao.selectStudentById  - ==>  Preparing: select * from student where id=? 
[service] 2018-07-21 23:44:49,509 - dao.IStudentDao.selectStudentById -1385 [main] DEBUG dao.IStudentDao.selectStudentById  - ==> Parameters: 17(Integer)
[service] 2018-07-21 23:44:49,517 - dao.IStudentDao.selectStudentById -1393 [main] DEBUG dao.IStudentDao.selectStudentById  - <==      Total: 1
Student [id=17, name=hello, age=14, score=94.6]