Mybatis緩存(1)--------系統緩存及簡單配置介紹

来源:https://www.cnblogs.com/jian0110/archive/2018/07/29/9387941.html
-Advertisement-
Play Games

前言 Mybatis的緩存主要有兩種: 系統緩存,也就是我們一級緩存與二級緩存; 自定義的緩存,比如Redis、Enhance等,需要額外的單獨配置與實現,具體日後主要學習介紹。 在這裡主要記錄系統緩存的一些簡單概念, 並沒有涉及原理。其中會涉及Mybatis的相關配置以及生命周期等。 主要參考資料 ...


前言

Mybatis的緩存主要有兩種:

  • 系統緩存,也就是我們一級緩存與二級緩存;
  • 自定義的緩存,比如Redis、Enhance等,需要額外的單獨配置與實現,具體日後主要學習介紹。

在這裡主要記錄系統緩存的一些簡單概念, 並沒有涉及原理。其中會涉及Mybatis的相關配置以及生命周期等。

主要參考資料:《深入淺出Mybatis基礎原理與實戰》,http://www.mybatis.org/mybatis-3/zh/index.html

 


 

1、Mybatis簡單配置介紹

  本文介紹的是基於XML的配置,並不是關於註解的Mybatis配置。當然複雜SQL情況下都建議使用XML配置。

  (1)配置步驟

  這裡記錄的只是Myabtis的簡單配置,並沒有證整合Spring等框架,所以相對簡單。我開始學的時候也是反覆記不住,不知道為什麼要這麼配置,這麼配置的作用是什麼。之後經過研讀《深入淺出Mybatis基礎原理與實戰》(我這裡只有PDF電子版本,有需要的朋友可以評論或者私信我),總結並畫圖讓我對整個配置過程有了全新的認識。

  簡單來說,Mybatis的配置主要分為以下幾步(整合Spring之後有些就不需要了,但是一開始學習不建議直接整合Spring):

  • 編寫POJO即JavaBean,最終的目的是將資料庫中的查詢結果映射到JavaBean上;
  • 配置與POJO對應的Mapper介面:裡面有各種方法,對應mapper.xml中的查詢語句;
  • 配置與POJO對應的XML映射:編寫緩存,SQL查詢等;
  • 配置mybatis-config.xml主要的Mybatis配置文件:配置數據源、掃描mapper.xml等。

  註意:以上的配置並沒有嚴格的前後順序;

  (2)配置流程圖

  

  (3)配置總結

  可以這麼總結Mybatis或者幫助理解Mybatis的配置,我總結了以下三點提供參考:

  • 一切Mybatis配置都是為了創建SqlSession進行SQL查詢
  • 歸根結底程式代碼中我們屏蔽了各種配置映射,只顯式調用使用Mapper介面,那麼介面實現類的獲得是通過SqlSession.getMapper()獲得;
  • 那麼mapper介面實現類的獲得是通過mybatis-config.xml->SqlSessionFactoryBuilder->SqlSessionFacotry->SqlSession->mapper;

  

 2、Mybatis生命周期 

  正確理解SqlSessionFactory、SqlSessionFactoryBuilderSqlSessionMapper的生命周期對於優化Mybatis尤為重要,這樣可以使Mybatis高效正確完成;同為重要時Mybatis的生命周期對於理解Myabtis緩存的配置也尤為重要,我這裡只做簡單的文字介紹(其實也好理解):

  (1)SqlSessionFactoryBuilder:作用就是創建一個構建器,一旦創建了SqlSessionFactory,它的任務就算完成了,可以回收。

  (2)SqlSessionFactory:作用是創建SqlSession,而SqlSession相當於JDBC的一個Connection對象,每次應用程式需要訪問資料庫,我們就要通過SqlSessionFactory創建一個SqlSession,所以SqlSessionFactory在整Mybatis整個生命周期中(每個資料庫對應一個SqlSessionFactory,是單例產生的)。

  (3)SqlSession:生命周期是存在於請求資料庫處理事務的過程中,是一個線程不安全的對象(在多線程的情況下,需要特別註意),即存活於一個應用的請求和申請,可以執行多條SQL保證事務的一致性。

  (4)Mapper:是一個介面,並沒有實現類它的作用是發送SQL,返回我們需要的結果,或者發送SQL修改資料庫表,所以它存活於一個SqlSession內,是一個方法級別的東西。當SqlSession銷毀的時候,Mapper也會銷毀。

 3、Myabtis緩存介紹

  (1)系統緩存:包括一級緩存與二級緩存

  一級緩存:預設情況下Myabtis對於同一個SqlSession開啟一級緩存    

  • 預設沒有配置的情況下,只會開啟一級緩存(只針對同一個SqlSession而言);
  • 參數與SQL完全一樣的情況下並且不聲明刷新緩存沒超時的,使用同一個SqlSession對象調用同一個Mapper方法時(SqlSession對象生命周期為方法級別),SqlSession只會取出當前緩存數據,不會再到資料庫中進行查詢
  • 如果不同的SqlSession,即使同一個Mapper也會進行到資料庫中進行不同的查詢,即不同的SqlSession一級緩存是無效的

  二級緩存:這裡可以結合SqlSessionFactory等的生命周期能加深理解

  • 不同的SqlSession是隔離的,為瞭解決這個問題,我們可以在SqlSessionFactory層面上設置二級緩存提供各個對象SqlSession
  • 二級緩存預設是不開啟的,需要進行配置,Mybatis要求返回的POJO必須是可序列化的,即POJO實現Serializable介面

  緩存的配置只需要在XML配置<cache/>即可,或者指定演算法,刷新時間間隔,緩存狀態,大小等

 <cache eviction="LRU" readOnly="true" flushInterval="100000" size="1024"></cache>

    A. 映射語句文件中所有select語句將會被緩存;

    B. 映射語句文件中所有insert、update和delete語句會被刷新緩存;

    C. 緩存使用預設的LRU最近最少使用演算法回收;

    D. 根據時間表,緩存不會任何時間順序刷新;

    E. 緩存會存儲列表集合或對象的1024個引用

    F. 緩存被視為可read/write的緩存,意味著是不可以被共用的,而可以被安全地修改。

  (2)自定義緩存:結合Redis等主流緩存配置

  我們可以使用比如現在比較火的Redis緩存,需要實現Myabtis為我們提供的介面org.apache.ibatis.cache.Cache。雖然現在主流Mybatis用的都是自定義緩存,但是這裡先不過多介紹,我一步一步來學習記錄!

4、Mybatis系統緩存代碼實現

包結構圖:

資料庫user表:要與User對應,Mybatis會根據駝峰命名進行自動映射,即user表中id欄位映射為User POJO中的id,如果使用的是插件生成,POJO就會自動對應。

mysql> use mybatis;
Database changed
mysql> select*from user;
+----+----------+
| id | name     |
+----+----------+
|  1 | Zhangsan |
|  2 | Lisi     |
+----+----------+
2 rows in set

(1)User.java: POJO

import java.io.Serializable;

/**
 * POJO:User
 * @author Lijian
 *
 */
public class User implements Serializable{

    private int id;
    private String name;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

(2)UserMapper.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="com.lijian.dao.UserMapper">
  <resultMap id="userMap" type="com.lijian.model.User">
       <id property="id" column="id"/>
    <result column="name" property="name" />
  </resultMap>
  <!-- 使用POJO映射結果集 -->
  <select id="findByUserId" parameterType="int" resultType="user">
      select * from user where id = #{id}
  </select>
  <!-- 使用resultMap映射結果集 -->
  <select id="findByUserName" parameterType="string" resultMap="userMap">
    select * from user where name = #{name}      
  </select>
</mapper>

(3)UserMapper.java:

public interface UserMapper {
    User findByUserId(int id);
    User findByUserName(String name);
}

(4)mybatis-config.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">
<configuration>
    <!-- mybatis-config.xml常用的配置及順序:
        (properties?,
        settings?,
        typeAliases?,
        typeHandlers?,
        objectFactory?,
        objectWrapperFactory?,
        reflectorFactory?,
        plugins?,
        environments?,
        databaseIdProvider?,
        mappers?)
     -->

   <!-- jdbc資料庫屬性配置文件db.properties --> <properties resource="db.properties"></properties> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <typeAlias alias="user" type="com.lijian.model.User"/> </typeAliases> <!-- default 預設數數據源 --> <!-- environments配置:可以註冊多個數據源DataSource,每個數據源分為兩部分:一個是數據源的配置,另外一個是資料庫事務配置。 --> <environments default="development"> <!-- dataSource 1--> <environment id="development"> <transactionManager type="JDBC"> <!-- 關閉自動提交 --> <property name="autoCommit" value="false"/> </transactionManager> <!-- POOLED連接池資料庫 --> <dataSource type="POOLED"> <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> <!-- 可以通過包名導入映射器 :但mapper interface與mapper.xml必須在同一個包下--> <!-- <package name="com.lijian.mapper"/> --> <!-- 文件路徑引入 --> <mapper resource="com/lijian/mapper/UserMapper.xml"/> </mappers> </configuration>

(5)SqlSessionFactoryUtils:

/**
 * 創建SqlSession
 * @author Lijian
 * 創建順序:mybatis-config.xml->SqlSessionFactoryBuilder->SqlSessionFactory(Singleton)->SqlSession
 *
 */
import java.io.IOException;
import java.io.InputStream;

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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class SqlSessionFactoryUtils {
    private static final Logger logger = LogManager.getLogger(SqlSessionFactoryUtils.class);
    
    //synchronized lock
    private static final Class CLASS_LOCK = SqlSessionFactoryUtils.class;
    private static SqlSessionFactory sqlSessionFactory = null;
    
    //private constructors
    private SqlSessionFactoryUtils(){};
    /**
     * 因為一個資料庫對應一個SqlSessionFactory,所有採用單例模式生成SqlSessionFactory
     * @return SqlSessionFactory
     */
    public static SqlSessionFactory initSqlSessionFactory() {
        String config = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(config);
        }catch (IOException e) {
            logger.info("SqlSessionFactoryUtils");
        }
        synchronized (CLASS_LOCK) {
            if (sqlSessionFactory == null) {
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            }
            
        }
        return sqlSessionFactory;
    }
    /**
     * openSession進行SQL查詢
     * @return SqlSession
     */
    public static SqlSession openSession() {
        if (sqlSessionFactory == null) {
            initSqlSessionFactory();
        }
        return sqlSessionFactory.openSession();
    }
    
}

(6)MybatisMain.java:測試類

import org.apache.ibatis.session.SqlSession;

import com.lijian.dao.UserMapper;
import com.lijian.utils.SqlSessionFactoryUtils;

public class MybatisMain {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        SqlSession sqlSession2 = null;
        try {
            //獲得SqlSession
            sqlSession = SqlSessionFactoryUtils.openSession();
            sqlSession2 = SqlSessionFactoryUtils.openSession();
            //獲得Mapper:動態代理生成UserMapper實現類
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //預設一級緩存:相同SELECT與param,只查詢一次
            System.out.println("=======================預設使用系統一級緩存=======================");
            userMapper.findByUserId(1);
            userMapper.findByUserId(1);
            //二級緩存commit才會有效
            sqlSession.commit();
            System.out.println("=======================重新創建SqlSession=======================");
            sqlSession2 = SqlSessionFactoryUtils.openSession();
            UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
            userMapper2.findByUserId(1);
            //二級緩存commit才會有效
            sqlSession2.commit();
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        finally {
            if (sqlSession != null) {
                //sqlSession生命周期是隨著SQL查詢而結束的
                sqlSession.close();
            }
            if (sqlSession2 != null) {
                sqlSession2.close();
            }
        }
    }
}

 

註意UserMapper.xml當前是沒有開啟二級緩存的,故預設為一級緩存,如何得到證實呢?在MybatisMain中,我們創建了:

  • sqlSession:開啟兩個一模一樣的SELECT SQL查詢,即userMapper.findByUserId(1),那麼如果一級緩存有效且開啟的話,只會進行一次查詢,之後有一次SQL語句日誌輸出;
  • sqlSession2:開啟與sqlSession中一模一樣的SELECT查詢,如果二級緩存沒有開啟,一級緩存預設開啟的話,是會進行查詢的,會有一次SQL語句日誌輸出。
=======================預設使用系統一級緩存=======================
[DEBUG][main][2018-07-29 21:45:41][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - ==>  Preparing: select * from user where id = ? 
[DEBUG][main][2018-07-29 21:45:41][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - ==> Parameters: 1(Integer)
[TRACE][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==    Columns: id, name
[TRACE][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==        Row: 1, Zhangsan
[DEBUG][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==      Total: 1
=======================重新創建SqlSession=======================
[DEBUG][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - ==>  Preparing: select * from user where id = ? 
[DEBUG][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - ==> Parameters: 1(Integer)
[TRACE][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==    Columns: id, name
[TRACE][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==        Row: 1, Zhangsan
[DEBUG][main][2018-07-29 21:45:42][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==      Total: 1

 

接下來我們開啟二級緩存:在不同SqlSession中所有相同的SELECT語句將會被緩存,只會有一次SQL語句日誌輸出,並且會有Cache Hit Ratio緩存命中率

<?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="com.lijian.dao.UserMapper">
  <!-- 開啟二級緩存 :針對SqlSessionFactory,同時POJO必須實現Serializable介面
      (1)所有select會被緩存
      (2)insert、update、delete會刷新緩存
      (3)預設使用LRU
      (4)緩存是可read/write,不是共用的是可以被安全地修改
  -->
  <cache eviction="LRU" readOnly="true" flushInterval="100000" size="1024"></cache>
  <resultMap id="userMap" type="com.lijian.model.User">
       <id property="id" column="id"/>
    <result column="name" property="name" />
  </resultMap>
  <!-- 使用POJO映射結果集 -->
  <select id="findByUserId" parameterType="int" resultType="user">
      select * from user where id = #{id}
  </select>
  <!-- 使用resultMap映射結果集 -->
  <select id="findByUserName" parameterType="string" resultMap="userMap">
    select * from user where name = #{name}      
  </select>
</mapper>

日誌如下:

=======================預設使用系統一級緩存=======================
[DEBUG][main][2018-07-29 21:49:26][org.apache.ibatis.cache.decorators.LoggingCache] - Cache Hit Ratio [com.lijian.dao.UserMapper]: 0.0
[DEBUG][main][2018-07-29 21:49:27][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - ==>  Preparing: select * from user where id = ? 
[DEBUG][main][2018-07-29 21:49:27][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - ==> Parameters: 1(Integer)
[TRACE][main][2018-07-29 21:49:27][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==    Columns: id, name
[TRACE][main][2018-07-29 21:49:27][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==        Row: 1, Zhangsan
[DEBUG][main][2018-07-29 21:49:27][org.apache.ibatis.logging.jdbc.BaseJdbcLogger] - <==      Total: 1
[DEBUG][main][2018-07-29 21:49:27][org.apache.ibatis.cache.decorators.LoggingCache] - Cache Hit Ratio [com.lijian.dao.UserMapper]: 0.0
=======================重新創建SqlSession=======================
[DEBUG][main][2018-07-29 21:49:27][org.apache.ibatis.cache.decorators.LoggingCache] - Cache Hit Ratio [com.lijian.dao.UserMapper]: 0.3333333333333333

 


 

總結與補充

  (1)作為新手很大可能在配置過程中會遇到很多坑(我就是其中之一),比如Mybatis的日誌配置。這裡可以主要參考:http://www.mybatis.org/mybatis-3/zh/configuration.html

  (2)mybatis的相關配置文件(沒有myabtis-config.xml)可以使用eclipse中的插件生成(具體網上有很多教程),但還要進行適當修改!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 學習計劃 1、Activemq整合spring的應用場景 2、添加商品同步索引庫 3、商品詳情頁面動態展示 4、展示詳情頁面使用緩存 2. Activemq整合spring 2.1. 使用方法 第一步:引用相關的jar包。 第二步:配置Activemq整合spring。配置Connection ...
  • 搭建環境 1. 安裝Java SDK及添加環境變數 2. 安裝Elipse Java及CDT插件 3. 安裝tdm64 gcc及添加環境變數 "百度雲" (密碼:mjdi) 3. 安裝msys及添加環境變數 "百度雲" (密碼:j2i6) JNI使用步驟 創建java工程jni_demo 創建類 J ...
  • 從python轉golang開發已經3個月了,因為寫過c++,所以對golang接受的還算快,這段經歷也不是很痛苦。伯樂線上上看了一些大神關於python轉golang過程中的不適應和吐槽,決定寫下篇博客。接下來,我會列出golang開發過程中與python的不同點,主要是在語法方面,golang的 ...
  • 條件變數同步 有一類線程需要滿足條件之後才能夠繼續執行,Python提供了threading.Condition 對象用於條件變數線程的支持,它除了能提供RLock()或Lock()的方法外,還提供了 wait()、notify()、notifyAll()方法。 lock_con=threading ...
  • 所需環境: 1. IDEA UItimate 2. JDK 3. Maven 創建工程 一開始創建一個普通的maven項目即可, 下麵展示最終完成的工程目錄,其中創建res文件夾以及放入testNG.xml文件的步驟後面會提到: 在pom中添加依賴: 創建package 與測試類 HelloTest ...
  • 列表(list):列表是有序的,且可以修改 說明:列表裡面可以存放:數字、字元串、列表、布爾值,可以嵌套任意類型 列表存放元素的原理:通過鏈表的方式 1.創建列表:通過list(列表)類,創建了一個test_list的對象 test_list=["Tom","Mary","Jim","Disk"] ...
  • 給定一組(串)數據,根據輸入得號碼,查詢歸屬地 如有錯誤,感謝指正! ...
  • Exchanger應該算併發包中工具使用相對少的,因為它主要用於線程之間交換數據,它的用法比較簡單在不同線程之間使用exchange方法交換數據,但是內部實現比較巧妙,使用了unsafe的CAS原子操作、自旋來解決衝突問題,下麵我們通過源碼一探究竟。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...