Mybatis(三):MyBatis緩存詳解

MyBatis緩存分爲一級緩存和二級緩存java

一級緩存

MyBatis的一級緩存指的是在一個Session域內,session爲關閉的時候執行的查詢會根據SQL爲key被緩存(跟mysql緩存同樣,修改任何參數的值都會致使緩存失效)mysql

1)單獨使用MyBatis而不繼承Spring,使用原生的MyBatis的SqlSessionFactory來構造sqlSession查詢,是可使用以及緩存的,示例代碼以下git

public class Test {
    public static void main(String[] args) throws IOException {
        String config = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(config);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession();
        System.out.println(session.selectOne("selectUserByID", 1));
        // 同一個session的相同sql查詢,將會使用一級緩存 
        System.out.println(session.selectOne("selectUserByID", 1));
        // 參數改變,須要從新查詢
        System.out.println(session.selectOne("selectUserByID", 2));
        // 清空緩存後須要從新查詢
        session.clearCache();
        System.out.println(session.selectOne("selectUserByID", 1));
        // session close之後,仍然使用同一個db connection
        session.close();
        session = factory.openSession();
        System.out.println(session.selectOne("selectUserByID", 1));
    }
}

輸出以下github

DEBUG - Openning JDBC Connection
DEBUG - Created connection 10044878.
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing
1|test1|19|beijing
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 2(Integer)
2|test2|18|guangzhou
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - Returned connection 10044878 to pool.
DEBUG - Openning JDBC Connection
DEBUG - Checked out connection 10044878 from pool.
DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijingweb

看以看出來,當參數不變的時候只進行了一次查詢,參數變動之後,則須要從新進行查詢,而清空緩存之後,參數相同的查詢過的SQL也須要從新查詢,並且使用的數據庫鏈接是同一個數據庫鏈接,這裏要得益於咱們在mybatis-config.xml裏面的datasource設置算法

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">

            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

注意datasource使用的是POOLED,也就是使用了鏈接池,因此數據庫鏈接可回收利用,固然這個environment屬性在集成spring的時候是不須要的,由於咱們須要另外配置datasource的bean.spring

 

2) 跟Spring集成的時候(使用mybatis-spring)sql

直接在dao裏查詢兩次一樣參數的sql數據庫

@Repository
public class UserDao extends SqlSessionDaoSupport {
    public User selectUserById(int id) {
        SqlSession session = getSqlSession();
        session.selectOne("dao.userdao.selectUserByID", id);
        // 因爲session的實現是SqlSessionTemplate的動態代理實現
        // 它已經在代理類內執行了session.close(),因此無需手動關閉session
        return session.selectOne("dao.userdao.selectUserByID", id);
    }
}

觀察日誌apache

DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e389b8] was not registered for synchronization because synchronization is not active
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e389b8]
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169da74] was not registered for synchronization because synchronization is not active
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169da74]
DEBUG - Returning JDBC Connection to DataSource

 

這裏執行了2次sql查詢,看似咱們使用了同一個sqlSession,可是實際上由於咱們的dao繼承了SqlSessionDaoSupport,而SqlSessionDaoSupport內部sqlSession的實現是使用用動態代理實現的,這個動態代理sqlSessionProxy使用一個模板方法封裝了select()等操做,每一次select()查詢都會自動先執行openSession(),執行完close()之後調用close()方法,至關於生成了一個新的session實例,因此咱們無需手動的去關閉這個session()(關於這一點見下面mybatis的官方文檔),固然也沒法使用mybatis的一級緩存,也就是說mybatis的一級緩存在spring中是沒有做用的.

官方文檔摘要

MyBatis SqlSession provides you with specific methods to handle transactions programmatically. But when using MyBatis-Spring your beans will be injected with a Spring managed SqlSession or a Spring managed mapper. That means that Spring will always handle your transactions.

You cannot call SqlSession.commit()SqlSession.rollback() or SqlSession.close() over a Spring managed SqlSession. If you try to do so, a UnsupportedOperationException exception will be thrown. Note these methods are not exposed in injected mapper classes.

 

二級緩存

二級緩存就是global caching,它超出session範圍以外,能夠被全部sqlSession共享,它的實現機制和mysql的緩存同樣,開啓它只須要在mybatis的配置文件開啓settings裏的

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

以及在相應的Mapper映射文件(例如userMapper.xml)裏開啓

<cache />
添加這一行後,會產生以下效果:
  • 映射語句文件中的全部select 語句將會被緩存。
  • 映射語句文件中的全部insert,update 和delete 語句會刷新緩存。
  • 緩存會使用Least Recently Used(LRU,最近最少使用的)算法來收回。
  • 根據時間表(好比no Flush Interval,沒有刷新間隔), 緩存不會以任什麼時候間順序來刷新。
  • 緩存會存儲列表集合或對象(不管查詢方法返回什麼)的1024 個引用。
  • 緩存會被視爲是read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,並且能夠安全地被調用者修改,而不干擾其餘調用者或線程所作的潛在修改。

以上全部的屬性均可以經過<cache /> 元素來修改:

<mapper namespace="dao.userdao">
   ...  select statement ...
       <!-- Cache 配置 -->
    <cache
        eviction="FIFO"// 回收策略
        flushInterval="60000"// 刷新間隔
        size="512" // 引用數目
        readOnly="true" // 只讀
/> </mapper>

以上代表首先建立了一個先進先出策略的緩存,並每隔 60 刷新,存數結果對象或列表的 512 個引用,並且返回的對象被認爲是隻讀的,所以在不一樣線程中的調用者之間修改它們會致使衝突

可用的收回策略有:

  • LRU – 最近最少使用的:移除最長時間不被使用的對象。
  • FIFO – 先進先出:按對象進入緩存的順序來移除它們。
  • SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
  • WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。

默認的是LRU。

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

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

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

須要注意的是global caching的做用域是針對Mapper的Namespace而言的,也就是說只在有在這個Namespace內的查詢才能共享這個cache.例如上面的 dao.userdao namespace, 下面是官方文檔的介紹。

It's important to remember that a cache configuration and the cache instance are bound to the namespace of the SQL Map file. Thus, all statements in the same namespace as the cache are bound by it.

例以下面的示例,咱們執行兩次對同一個sql語句的查詢,觀察輸出日誌

@RequestMapping("/getUser")
    public String getUser(Model model) {
        User user = userDao.selectUserById(1);
        model.addAttribute(user);
        return "index";
    }

當咱們訪問兩次 /getUser 這個url,查看日誌輸出

DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659812] was not registered for synchronization because synchronization is not active
DEBUG - Cache Hit Ratio [dao.userdao]: 0.0
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659812]
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Invoking afterPropertiesSet() on bean with name 'index'
DEBUG - Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/index.jsp]] in DispatcherServlet with name 'dispatcher'
DEBUG - Added model object 'org.springframework.validation.BindingResult.user' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'index'
DEBUG - Added model object 'user' of type [bean.User] to request in view with name 'index'
DEBUG - Forwarding to resource [/index.jsp] in InternalResourceView 'index'
DEBUG - Successfully completed request
DEBUG - Returning cached instance of singleton bean 'sqlSessionFactory'
DEBUG - DispatcherServlet with name 'dispatcher' processing GET request for [/user/getUser]
DEBUG - Looking up handler method for path /user/getUser
DEBUG - Returning handler method [public java.lang.String controller.UserController.getUser(org.springframework.ui.Model)]
DEBUG - Returning cached instance of singleton bean 'userController'
DEBUG - Last-Modified value for [/user/getUser] is: -1
DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@539a92] was not registered for synchronization because synchronization is not active
DEBUG - Cache Hit Ratio [dao.userdao]: 0.5
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@539a92]
DEBUG - Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/index.jsp]] in DispatcherServlet with name 'dispatcher'
DEBUG - Added model object 'org.springframework.validation.BindingResult.user' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'index'
DEBUG - Added model object 'user' of type [bean.User] to request in view with name 'index'
DEBUG - Forwarding to resource [/index.jsp] in InternalResourceView 'index'
DEBUG - Successfully completed request

能夠看出第二次訪問同一個url的時候相同的查詢 hit cache了,這就是global cache的做用。

<select />標籤中關於緩存的使用

屬性

flushCache:將其設置爲 true,任什麼時候候只要語句被調用,都會致使本地緩存和二級緩存都會被清空,默認值:false。
useCache:將其設置爲 true,將會致使本條語句的結果被二級緩存,默認值:對 select 元素爲 true。若是設置爲false,那麼即便啓用了<cache />該條select的結構也不會被二級緩存

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

Mybatis緩存使用注意事項

若是數據緩存在本地,另外一個系統修改數據庫或者手動修改數據庫時,會出現髒數據問題。

  • 一級緩存
    Myatis的一級緩存默認爲SESSION,底層用PerpetualCache,裏面使用map作爲存儲,並無作太多條件限制。

  • 二級緩存
    MyBatis雖然全局配置開啓緩存,可是仍是取決因而否使用了<cache/>標籤,若是使用了二級緩存,須要注意:
    每一個<cache />表明一個單獨的二級緩存,若是多個Mapper須要共享同一個二級緩存,就須要使用<cache-ref/>。若是一個Mapper中查詢數據時,使用了多表聯查,當另外一個Mapper更新相關數據時,若是沒有共享一個Cache,那麼下一次該Mapper查詢時,就會出現讀到髒數據。

  • 使用二級緩存通常基於如下原則:
    不常常變更的數據,但常常會使用
    數據量比較大,系統多處會用到。或者跨系統用。
    對性能有特別要求的地方。
    濫用二級緩存,有可能反而會下降性能,特別是根據條件查詢緩存

二級緩存源碼淺析

mybatis下關於cache的全部類
mybatis下關於cache的全部類
  • 包內容淺析
    cache包下,分爲裝飾者包(其中有些關於cache的具體類文件,好比阻塞加鎖cache的委派、fifo的cache委派,使用裝飾者模式起到了在不一樣使用場景靈活委派功能的目的,具備良好的擴展性)和cache底層實現包,以上類都實現了Cache接口。
  • 調用順序:
    mybatis的關於cache有個CacheBuilder類,該類使用建造者模式,在其中有個public Cache build()方法負責具體的<cache />屬性的組裝。經過MapperBuilderAssistant類調用CacheBuilder進行具體的裝配順序操做。
CacheBuilder類
CacheBuilder類

CacheBuilder類
CacheBuilder類

MapperBuilderAssistant類
MapperBuilderAssistant類

MapperAnnotationBuilder類(負責處理mybatis的註解)
MapperAnnotationBuilder類(負責處理mybatis的註解)

XMLMapperBuilder類(負責處理mybatismapper的xml)
XMLMapperBuilder類(負責處理mybatismapper的xml)
相關文章
相關標籤/搜索