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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...