1. 程式人生 > >【MyBatis】緩存配置

【MyBatis】緩存配置

big dtd 應用 使用註解 sql mapper utf actor note

前言

  使用緩存可以使應用更快的獲取數據,避免頻繁的數據庫交互,尤其是在查詢越多、緩存命中率越高的情況下,使用緩存的作用就越明顯。MyBatis作為持久化框架,提供了非常強大的查詢緩存特性,可以非常方便地配置和定制使用。一般提到MyBatis緩存的時候,都是指二級緩存,一級緩存默認會啟用,並且不能控制,因此很少會提到。不過,知道一級緩存的存在可以避免產生一些難以發現的錯誤。

一級緩存
SqlSession sqlSession = sessionFactory.openSession();
try{
TUserMapper tUserMapper = sqlSession.getMapper(TUserMapper.class);

TUser result1 = tUserMapper.selectByPrimaryKey(11L);
System.out.println("第一次查詢:" + result1.getuName());
result1.setuName("cache");
TUser result2 = tUserMapper.selectByPrimaryKey(new Long(11));
System.out.println("第二次查詢:" + result2.getuName());
tUserMapper.deleteByPrimaryKey(8L);
TUser result3 = tUserMapper.selectByPrimaryKey(new Long(11));
System.out.println("第三次查詢:" + result3.getuName());
}finally {
sqlSession.close();
}

查詢結果:

第一次查詢:mh
第二次查詢:cache
第三次查詢:mh

說明:
  第一次執行selectByPrimaryKey方法查詢時,真正執行了數據庫查詢,得到了result1的結果。第二次查詢獲取result2時,使用了MyBatis的一級緩存,沒有查詢數據庫,直接從緩存中獲取了結果。所以result1和result2是一個對象。

  MyBatis的一級緩存存在於SqlSession的生命周期中,在同一個SqlSession中查詢時,MyBatis會把執行的方法和參數通過算法生成緩存的鍵值,將鍵值和查詢結果存入一個Map對象中。如果同一個SqlSession中執行的方法和參數完全一致,那麽通過算法會生成相同的鍵值,當Map緩存對象中已經存在該鍵值時,則會返回緩存中的對象。如果我們不想讓selectByPrimaryKey使用一級緩存,可以對方法做如下修改:

<select id="selectByPrimaryKey" flushCache="true" resultMap="BaseResultMap">
select id, u_name, u_password
from t_user
where
id = #{id,jdbcType=BIGINT}
</select>

  設置了flushCache屬性為true。這種方式表示在查詢前清空一級緩存,會影響其他的查詢,所以避免這麽使用。
  另外,第三次查詢時又真正執行了數據庫查詢,是因為前面執行了delete操作。任何的Insert、update、delete操作都會清空一級緩存。

二級緩存
  MyBatis的二級緩存非常強大,它不同於一級緩存只存在於SqlSession的生命周期中,而是可以理解為存在於SqlSessionFactory的生命周期中。

配置二級緩存
  MyBatis的二級緩存是默認開啟的。也可以在mybatis-config.xml中顯式聲明,配置如下:

<settings>
<setting name="cacheEnabled" value="true"/>
</settings>

  如果將cacheEnabled設為false,即使有後面的二級緩存配置,也不會生效。
  MyBatis的二級緩存是和命名空間綁定的,即二級緩存需要配置在Mapper.xml映射文件中,或者配置在Mapper.java接口中。在映射文件中,命名空間就是XML根節點mapper的namespace屬性。在接口中,命名空間就是接口的全限定名稱。

Mapper.xml中配置二級緩存
  在保證二級緩存的全局配置開啟的情況下,給TUserMapper.xml開啟二級緩存只需要在其中添加<cache/>元素即可,添加後的TUserMapper.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="cn.jujianfei.demo.dao.TUserMapper">
<cache/>
..........
</mapper>

在Mapper接口中配置二級緩存
  當使用註解配合接口開發時,開啟二級緩存就需要在接口上配置了。具體如下:

@CacheNamespace
public interface TUserMapper {
//接口方法
}

使用二級緩存
//TUserMapper.xml
<cache readOnly="false"></cache>

  cache的屬性readOnly設置為flase表示此二級緩存為可讀寫緩存,可以使用SerializedCache序列化緩存。這個緩存類要求所有被序列化的對象必須實現Serializable(java.io.Serializable)接口,所以還需要修改TUser對象,代碼如下:

public class TUser implements Serializable {
......
}

測試類
SqlSession sqlSession = sessionFactory.openSession();
try{
TUserMapper tUserMapper = sqlSession.getMapper(TUserMapper.class);
TUser result1 = tUserMapper.selectByPrimaryKey(11L);
System.out.println("第一次查詢:" + result1.getuName());
result1.setuName("cache");
TUser result2 = tUserMapper.selectByPrimaryKey(new Long(11));
System.out.println("第二次查詢:" + result2.getuName());
Assert.assertEquals(result1,result2);
}finally {
sqlSession.close();
}

sqlSession = sessionFactory.openSession();
try{
TUserMapper tUserMapper = sqlSession.getMapper(TUserMapper.class);
TUser result3 = tUserMapper.selectByPrimaryKey(new Long(11));
System.out.println("第三次查詢:" + result3.getuName());
TUser result4 = tUserMapper.selectByPrimaryKey(new Long(11));
System.out.println("第四次查詢:" + result4.getuName());
Assert.assertNotEquals(result4,result3);
}finally {
sqlSession.close();
}

打印結果:

第一次查詢:mh
第二次查詢:cache
第三次查詢:cache
第四次查詢:cache

  由於配置的是可讀寫的緩存,而MyBatis使用SerializedCache序列化緩存來實現可讀寫緩存類,並通過序列化和反序列化來保證通過緩存獲取數據時,得到的是一個新的實例。如果配置為只讀緩存,MyBatis就會使用Map來存儲緩存值,這種情況下,從緩存中獲取的對象就是同一個實例。
  第一次查詢,是從數據庫查詢;第二次查詢是從一級緩存查詢,所以第一次和第二次查詢是同一個實例。當調用close方法關閉SqlSession時,SqlSession才會保存查詢數據到二級緩存中。在這之後二級緩存才有了數據。第三次查詢就是從二級緩存中獲取數據了。第四次查詢也是從二級緩存中獲取數據。result3和result4都是反序列化得到的結果,所以它們不是相同的實例。在這一部分,這兩個實例是讀寫安全的,其屬性不會互相影響。
  實際上,示例代碼並不安全。result1.setuName("cache");這裏修改result1的屬性值後,按照常理應該更新數據,更新後會清空一、二級緩存,但是沒有。導致後續查詢結果都是cache。所有要想安全使用,需要避免毫無意義的修改。這樣就可以避免人為產生的臟數據,避免緩存和數據庫的數據不一致。
  MyBatis默認提供的緩存實現時基於Map實現的內存緩存,已經可以滿足基本的應用。但是當需要緩存大量的數據時,不能僅僅通過提高內存來使用MyBatis的二級緩存,還可以選擇一些類似EhCache的緩存框架或Redis緩存數據庫等工具來保存MyBatis的二級緩存數據。

臟數據的產生和避免
  二級緩存雖然能提高應用效率,減輕數據庫服務器的壓力,但是如果使用不當,很容易產生臟數據,影響使用效果。

產生

  MyBatis的二級緩存是和命名空間綁定的,所以通常情況下每一個Mapper映射文件都擁有自己的二級緩存,不同Mapper的二級緩存互不影響。在常見的數據庫操作中,多表聯查非常常見,而多表聯查肯定會將該查詢放到某個命名空間下的映射文件中,這樣一個多表的查詢就會緩存在該命名空間的二級緩存中。涉及到這些表的增、刪、改操作通常不在一個映射文件中,它們的命名空間不同,因此當有數據變化時,多表查詢的緩存未必會被清空,這種情況下就會產生臟數據。

避免

  該如何避免臟數據的出現呢?這時就需要用到參照緩存了。當某幾個表可以作為一個業務整體時,通常是讓幾個會關聯的ER表同時使用同一個二級緩存,這樣就能解決臟數據問題。配置如下:


<mapper namespace="cn.jujianfei.demo.dao.TUserMapper">
    <cache readOnly="false"></cache>
    ......
</mapper>

<mapper namespace="cn.jujianfei.demo.dao.UserMapper">
    <cache-ref namespace="cn.jujianfei.demo.dao.TUserMapper"/>
    ......
</mapper>

  雖然這樣可以解決臟數據的問題,但是並不是所有的關聯查詢都可以這麽解決,如果有幾十個表甚至所有表都以不同的關聯關系存在於各自的映射文件中時,使用參照緩存顯然沒有意義。

二級緩存適用的場景
以查詢為主的應用中,只有盡可能少的增、刪、改操作。
絕大多數以單表操作存在時,由於很少存在互相關聯的情況,因此不會出現臟數據。
可以按業務劃分對表進行分組時,如關聯的表比較少,可以通過參照緩存進行配置。
除了推薦使用的情況,如果臟讀對系統沒有影響,也可以考慮使用。在無法保證數據不出現臟讀的情況下,建議在業務層使用可控制的緩存代替二級緩存。

總結語

  更新操作會清空一、二級緩存。

【MyBatis】緩存配置