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


前言

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中的插件生成(具體網上有很多教程),但還要進行適當修改!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM