經過項目逐步深刻了解Mybatis(四)

相關閱讀:

一、經過項目逐步深刻了解Mybatis<一>java

二、經過項目逐步深刻了解Mybatis<二>git

三、經過項目逐步深刻了解Mybatis<三>github

本項目全部代碼及文檔都託管在 Github地址:https://github.com/zhisheng17/mybatisredis

延遲加載

什麼是延遲加載?

resultMap能夠實現高級映射(使用association、collection實現一對一及一對多映射),association、collection具有延遲加載功能。
需求:
若是查詢訂單而且關聯查詢用戶信息。若是先查詢訂單信息便可知足要求,當咱們須要查詢用戶信息時再查詢用戶信息。把對用戶信息的按需去查詢就是延遲加載。spring

延遲加載:先從單表查詢、須要時再從關聯表去關聯查詢,大大提升 數據庫性能,由於查詢單表要比關聯查詢多張錶速度要快。sql

打開延遲加載開關

在mybatis核心配置文件中配置:數據庫

lazyLoadingEnabled、aggressiveLazyLoadingapache

設置項 描述 容許值 默認值
lazyLoadingEnabled 全局性設置懶加載。若是設爲‘false’,則全部相關聯的都會被初始化加載。 true false false
aggressiveLazyLoading 當設置爲‘true’的時候,懶加載的對象可能被任何懶屬性所有加載。不然,每一個屬性都按需加載。 true false true
<settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
</settings>

使用 association 實現延遲加載

需求:查詢訂單而且關聯查詢用戶信息緩存

Mapper.xml

須要定義兩個 mapper 的方法對應的 statement。安全

一、只查詢訂單信息

SQL 語句: select * from orders

在查詢訂單的 statement 中使用 association 去延遲加載(執行)下邊的 statement (關聯查詢用戶信息)

<!--查詢訂單而且關聯查詢用戶信息,關聯用戶信息須要經過 association 延遲加載-->
    <select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap">
        select * from orders
    </select>

二、關聯查詢用戶信息

經過上面查詢訂單信息中的 user_id 來關聯查詢用戶信息。使用 UserMapper.xml 中的 findUserById

SQL語句:select * from user where id = user_id

<select id="findUserById" parameterType="int" resultType="user">
        select * from user where id = #{value}
    </select>

上邊先去執行 findOrdersUserLazyLoading,當須要去查詢用戶的時候再去執行 findUserById ,經過 resultMap的定義將延遲加載執行配置起來。也就是經過 resultMap 去加載 UserMapper.xml 文件中的 select = findUserById

延遲加載的 resultMap

<!--定義 關聯用戶信息(經過 association 延遲加載)的resultMap-->
    <resultMap id="OrdersUserLazyLoadingResultMap" type="cn.zhisheng.mybatis.po.Orders">
        <!--對訂單信息映射-->
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
        <!-- 實現對用戶信息進行延遲加載
        select:指定延遲加載須要執行的statement的id(是根據user_id查詢用戶信息的statement)
        要使用userMapper.xml中findUserById完成根據用戶id(user_id)用戶信息的查詢,若是findUserById不在本mapper中須要前邊加namespace
        column:訂單信息中關聯用戶信息查詢的列,是user_id
        關聯查詢的sql理解爲:
            SELECT orders.*,
            (SELECT username FROM USER WHERE orders.user_id = user.id)username,
            (SELECT sex FROM USER WHERE orders.user_id = user.id)sex
            FROM orders-->
        <association property="user" javaType="cn.zhisheng.mybatis.po.User" select="cn.zhisheng.mybatis.mapper.UserMapper.findUserById" column="user_id">
        </association>
    </resultMap>

OrderMapperCustom.java

public List<Orders> findOrdersUserLazyLoading() throws Exception;

測試代碼:

@Test
    public void testFindOrdersUserLazyLoading() throws Exception
    {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //建立OrdersMapperCustom對象,mybatis自動生成代理對象
        OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
        //查詢訂單信息
        List<Orders> list = ordersMapperCustom.findOrdersUserLazyLoading();
        //遍歷所查詢的的訂單信息
        for (Orders orders : list)
        {
            //查詢用戶信息
            User user = orders.getUser();
            System.out.println(user);
        }
        sqlSession.close();
    }

測試結果:

整個延遲加載的思路:

一、執行上邊mapper方法(findOrdersUserLazyLoading),內部去調用cn.zhisheng.mybatis.mapper.OrdersMapperCustom 中的 findOrdersUserLazyLoading 只查詢 orders 信息(單表)。

二、在程序中去遍歷上一步驟查詢出的 List<Orders>,當咱們調用 Orders 中的 getUser 方法時,開始進行延遲加載。

三、延遲加載,去調用 UserMapper.xml 中 findUserbyId 這個方法獲取用戶信息。

思考:

不使用 mybatis 提供的 association 及 collection 中的延遲加載功能,如何實現延遲加載??

實現方法以下:

定義兩個mapper方法:

一、查詢訂單列表

二、根據用戶id查詢用戶信息

實現思路:

先去查詢第一個mapper方法,獲取訂單信息列表

在程序中(service),按需去調用第二個mapper方法去查詢用戶信息。

總之:

使用延遲加載方法,先去查詢 簡單的 sql(最好單表,也能夠關聯查詢),再去按須要加載關聯查詢的其它信息。

一對多延遲加載

上面的那個案例是一對一延遲加載,那麼若是咱們想一對多進行延遲加載呢,其實也是很簡單的。

一對多延遲加載的方法同一對一延遲加載,在collection標籤中配置select內容。

延遲加載總結:

做用:

當須要查詢關聯信息時再去數據庫查詢,默認不去關聯查詢,提升數據庫性能。
只有使用resultMap支持延遲加載設置。

場合:

當只有部分記錄須要關聯查詢其它信息時,此時可按需延遲加載,須要關聯查詢時再向數據庫發出sql,以提升數據庫性能。

當所有須要關聯查詢信息時,此時不用延遲加載,直接將關聯查詢信息所有返回便可,可以使用resultType或resultMap完成映射。

查詢緩存

什麼是查詢緩存?

mybatis提供查詢緩存,用於減輕數據壓力,提升數據庫性能。

mybaits提供一級緩存,和二級緩存。

  • 一級緩存是SqlSession級別的緩存。在操做數據庫時須要構造 sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不一樣的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。

  • 二級緩存是mapper級別的緩存,多個SqlSession去操做同一個Mapper的sql語句,多個SqlSession能夠共用二級緩存,二級緩存是跨SqlSession的。

爲何要用緩存?

若是緩存中有數據就不用從數據庫中獲取,大大提升系統性能。

一級緩存

工做原理

第一次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,若是沒有,從數據庫查詢用戶信息。

獲得用戶信息,將用戶信息存儲到一級緩存中。

若是sqlSession去執行commit操做(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣作的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。

第二次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。

一級緩存測試

Mybatis 默認支持一級緩存,不須要在配置文件中配置。

因此咱們直接按照上面的步驟進行測試:

//一級緩存測試
    @Test
    public void  testCache1() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //建立UserMapper對象,mybatis自動生成代理對象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //查詢使用的是同一個session
        //第一次發起請求,查詢Id 爲1的用戶信息
        User user1 = userMapper.findUserById(1);
        System.out.println(user1);
        //第二次發起請求,查詢Id 爲1的用戶信息
        User user2 = userMapper.findUserById(1);
        System.out.println(user2);
        sqlSession.close();
    }

經過結果能夠看出第二次沒有發出sql查詢請求,

因此咱們須要在中間執行 commit 操做

//若是sqlSession去執行commit操做(執行插入、更新、刪除),
// 清空SqlSession中的一級緩存,這樣作的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。
//更新user1的信息,
user1.setUsername("李飛");
//user1.setSex("男");
//user1.setAddress("北京");
userMapper.updateUserById(user1);
//提交事務,纔會去清空緩存
sqlSession.commit();

測試

一級緩存應用

正式開發,是將 mybatis 和 spring 進行整合開發,事務控制在 service 中。

一個 service 方法中包括不少 mapper 方法調用。

service{

//開始執行時,開啓事務,建立SqlSession對象

     //第一次調用mapper的方法findUserById(1)

     //第二次調用mapper的方法findUserById(1),從一級緩存中取數據

     //方法結束,sqlSession關閉

}

若是是執行兩次service調用查詢相同的用戶信息,不走一級緩存,由於session方法結束,sqlSession就關閉,一級緩存就清空。

二級緩存

原理

首先開啓mybatis的二級緩存。

sqlSession1去查詢用戶id爲1的用戶信息,查詢到用戶信息會將查詢數據存儲到二級緩存中。

若是SqlSession3去執行相同 mapper下sql,執行commit提交,清空該 mapper下的二級緩存區域的數據。

sqlSession2去查詢用戶id爲1的用戶信息,去緩存中找是否存在數據,若是存在直接從緩存中取出數據。

二級緩存與一級緩存區別,二級緩存的範圍更大,多個sqlSession能夠共享一個UserMapper的二級緩存區域。

UserMapper有一個二級緩存區域(按namespace分) ,其它mapper也有本身的二級緩存區域(按namespace分)。

每個namespace的mapper都有一個二緩存區域,兩個mapper的namespace若是相同,這兩個mapper執行sql查詢到數據將存在相同的二級緩存區域中。

開啓二級緩存

mybaits的二級緩存是mapper範圍級別,除了在SqlMapConfig.xml設置二級緩存的總開關,還要在具體的mapper.xml中開啓二級緩存

在 SqlMapConfig.xml 開啓二級開關

<!-- 開啓二級緩存 -->
<setting name="cacheEnabled" value="true"/>

而後在你的 Mapper 映射文件中添加一行: <cache/> ,表示此 mapper 開啓二級緩存。

調用 pojo 類實現序列化接口

二級緩存須要查詢結果映射的pojo對象實現java.io.Serializable接口實現序列化和反序列化操做(由於二級緩存數據存儲介質多種多樣,在內存不同),注意若是存在父類、成員pojo都須要實現序列化接口。

public class Orders implements Serializable
public class User implements Serializable

測試

//二級緩存測試
    @Test
    public void testCache2() throws Exception
    {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        SqlSession sqlSession3 = sqlSessionFactory.openSession();


        //建立UserMapper對象,mybatis自動生成代理對象
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        //sqlSession1 執行查詢 寫入緩存(第一次查詢請求)
        User user1 = userMapper1.findUserById(1);
        System.out.println(user1);
        sqlSession1.close();


        //sqlSession3  執行提交  清空緩存
        UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
        User user3 = userMapper3.findUserById(1);
        user3.setSex("女");
        user3.setAddress("山東濟南");
        user3.setUsername("崔建");
        userMapper3.updateUserById(user3);
        //提交事務,清空緩存
        sqlSession3.commit();
        sqlSession3.close();
        
        //sqlSession2 執行查詢(第二次查詢請求)
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.findUserById(1);
        System.out.println(user2);
        sqlSession2.close();
   }

結果

useCache 配置

在 statement 中設置 useCache=false 能夠禁用當前 select 語句的二級緩存,即每次查詢都會發出sql去查詢,默認狀況是true,即該sql使用二級緩存。

<select id="findUserById" parameterType="int" resultType="user" useCache="false">

總結:針對每次查詢都須要最新的數據sql,要設置成useCache=false,禁用二級緩存。

刷新緩存(清空緩存)

在mapper的同一個namespace中,若是有其它insert、update、delete操做數據後須要刷新緩存,若是不執行刷新緩存會出現髒讀。

設置statement配置中的flushCache="true" 屬性,默認狀況下爲true即刷新緩存,若是改爲false則不會刷新。使用緩存時若是手動修改數據庫表中的查詢數據會出現髒讀。

以下:

<insert id="insetrUser" parameterType="cn.zhisheng.mybatis.po.User" flushCache="true">

通常下執行完commit操做都須要刷新緩存,flushCache=true表示刷新緩存,這樣能夠避免數據庫髒讀。

Mybatis Cache參數

flushInterval(刷新間隔)能夠被設置爲任意的正整數,並且它們表明一個合理的毫秒形式的時間段。默認狀況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。

size(引用數目)能夠被設置爲任意正整數,要記住你緩存的對象數目和你運行環境的可用內存資源數目。默認值是1024。

readOnly(只讀)屬性能夠被設置爲true或false。只讀的緩存會給全部調用者返回緩存對象的相同實例。所以這些對象不能被修改。這提供了很重要的性能優點。可讀寫的緩存會返回緩存對象的拷貝(經過序列化)。這會慢一些,可是安全,所以默認是false。

以下例子:

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

這個更高級的配置建立了一個 FIFO 緩存,並每隔 60 秒刷新,存數結果對象或列表的 512 個引用,並且返回的對象被認爲是隻讀的,所以在不一樣線程中的調用者之間修改它們會致使衝突。可用的收回策略有, 默認的是 LRU:

  1. LRU – 最近最少使用的:移除最長時間不被使用的對象。

  2. FIFO – 先進先出:按對象進入緩存的順序來移除它們。

  3. SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。

  4. WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。

Mybatis 整合 ehcache

ehcache 是一個分佈式緩存框架。

分佈緩存

咱們系統爲了提升系統併發,性能、通常對系統進行分佈式部署(集羣部署方式)

不使用分佈緩存,緩存的數據在各各服務單獨存儲,不方便系統 開發。因此要使用分佈式緩存對緩存數據進行集中管理。

mybatis沒法實現分佈式緩存,須要和其它分佈式緩存框架進行整合。

整合方法

mybatis 提供了一個二級緩存 cache 接口(org.apache.ibatis.cache 下的 Cache),若是要實現本身的緩存邏輯,實現cache接口開發便可。

import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
    String getId();
    void putObject(Object var1, Object var2);
    Object getObject(Object var1);
    Object removeObject(Object var1);
    void clear();
    int getSize();
    ReadWriteLock getReadWriteLock();
}

mybatis和ehcache整合,mybatis 和 ehcache 整合包中提供了一個 cache 接口的實現類(org.apache.ibatis.cache.impl 下的 PerpetualCache)。

package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
public class PerpetualCache implements Cache {
    private String id;
    private Map<Object, Object> cache = new HashMap();
    public PerpetualCache(String id) {
        this.id = id;
    }
    public String getId() {
        return this.id;
    }
    public int getSize() {
        return this.cache.size();
    }
    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }
    public Object getObject(Object key) {
        return this.cache.get(key);
    }
    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }
    public void clear() {
        this.cache.clear();
    }
    public ReadWriteLock getReadWriteLock() {
        return null;
    }
    public boolean equals(Object o) {
        if(this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if(this == o) {
            return true;
        } else if(!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }
    public int hashCode() {
        if(this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}

經過實現 Cache 接口能夠實現 mybatis 緩存數據經過其它緩存數據庫整合,mybatis 的特長是sql操做,緩存數據的管理不是 mybatis 的特長,爲了提升緩存的性能將 mybatis 和第三方的緩存數據庫整合,好比 ehcache、memcache、redis等。

  • 引入依賴包

ehcache-core-2.6.5.jarmybatis-ehcache-1.0.2.jar

  • 引入緩存配置文件

classpath下添加:ehcache.xml

內容以下:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  <diskStore path="C:\JetBrains\IDEAProject\ehcache" />
  <defaultCache 
      maxElementsInMemory="1000" 
      maxElementsOnDisk="10000000"
      eternal="false" 
      overflowToDisk="false" 
      timeToIdleSeconds="120"
      timeToLiveSeconds="120" 
      diskExpiryThreadIntervalSeconds="120"
      memoryStoreEvictionPolicy="LRU">
  </defaultCache>
</ehcache>

屬性說明:

  • diskStore:指定數據在磁盤中的存儲位置。

  • defaultCache:當藉助 CacheManager.add("demoCache") 建立Cache時,EhCache 便會採用<defalutCache/>指定的的管理策略

如下屬性是必須的:

  • maxElementsInMemory - 在內存中緩存的element的最大數目

  • maxElementsOnDisk - 在磁盤上緩存的element的最大數目,如果0表示無窮大

  • eternal - 設定緩存的elements是否永遠不過時。若是爲true,則緩存的數據始終有效,若是爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷

  • overflowToDisk- 設定當內存緩存溢出的時候是否將過時的element緩存到磁盤上

如下屬性是可選的:

    • timeToIdleSeconds - 當緩存在EhCache中的數據先後兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閒置時間無窮大

    • timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大

      diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小.默認是30MB.每一個Cache都應該有本身的一個緩衝區.
    • diskPersistent- 在VM重啓的時候是否啓用磁盤保存EhCache中的數據,默認是false。

    • diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每一個120s,相應的線程會進行一次EhCache中數據的清理工做

    • memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)

    • 開啓ehcache緩存

    EhcacheCache 是ehcache對Cache接口的實現;修改mapper.xml文件,在cache中指定EhcacheCache。

    根據需求調整緩存參數:

    <cache type="org.mybatis.caches.ehcache.EhcacheCache" > 
            <property name="timeToIdleSeconds" value="3600"/>
            <property name="timeToLiveSeconds" value="3600"/>
            <!-- 同ehcache參數maxElementsInMemory -->
          <property name="maxEntriesLocalHeap" value="1000"/>
          <!-- 同ehcache參數maxElementsOnDisk -->
            <property name="maxEntriesLocalDisk" value="10000000"/>
            <property name="memoryStoreEvictionPolicy" value="LRU"/>
        </cache>

    測試 :(這命中率就表明成功將ehcache 與 mybatis 整合了)

    應用場景

    對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可採用 mybatis 二級緩存技術下降數據庫訪問量,提升訪問速度,業務場景好比:耗時較高的統計分析sql、電話帳單查詢sql等。

    實現方法以下:經過設置刷新間隔時間,由 mybatis 每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔 flushInterval,好比設置爲30分鐘、60分鐘、24小時等,根據需求而定。

    侷限性

    mybatis 二級緩存對細粒度的數據級別的緩存實現很差,好比以下需求:對商品信息進行緩存,因爲商品信息查詢訪問量大,可是要求用戶每次都能查詢最新的商品信息,此時若是使用 mybatis 的二級緩存就沒法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,由於 mybaits 的二級緩存區域以 mapper 爲單位劃分,當一個商品信息變化會將全部商品信息的緩存數據所有清空。解決此類問題須要在業務層根據需求對數據有針對性緩存。

    相關文章
    相關標籤/搜索