前言 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、SqlSessionFactoryBuilder、SqlSession和Mapper的生命周期對於優化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中的插件生成(具體網上有很多教程),但還要進行適當修改!