MyBatis技術全講(全)
- 配置
- 簡單查詢
- 高階查詢
- 程式碼生成器
- 快取配置
前言
這裡要提到一個詞:關注點分離。業務邏輯與資料邏輯相分離。沒必要為每一個DAO物件的初始化過程編寫大量程式碼,只需關注其SQL,並將SQL從程式碼中分離出來,mybatis就是這樣一個實現關注點分離思路的框架。
本篇為mybatis學習筆記,設計到mybatis的環境搭建、配置詳解、簡單應用、程式碼生成器、高階查詢、快取配置。基本上是全了。
前身是iBatis,以接近JDBC的效能實現了Java程式碼與SQL語句的分離。但是mybatis有其自己不完美的地方:物理分頁、快取。
mybatis支援宣告式快取,當一條資料被標記為“可快取”後,首次執行它所獲取的資料會被快取到快取記憶體中,後面再次執行直接命中快取,基於HashMap實現。
配置
配置檔案
mybatis-config.xml
mapper.xml
查詢
四個基礎標籤
<select>、<insert>、<update>、<delete>
select
單表
Model類 package com.abc.model; public class User{ public String name; public Integer age; } 查詢寫法 <select id=”selectUserById”resultType=”com.abc.model.User”> select name,age from user where id=#{id} </select> 呼叫 User user = selectUserById(1001);
第二個例子
public class User{ public String name; public Integer age; public Role role; } public class Role{ public String roleId; public String roleName; } <select id=”selectUserById”resultType=”com.abc.model.User”> select name,age,role.Id,role.roleName from user u inner join role r on u.id=r.id where u.id=#{id} </select>
第二個例子另一種寫法
<resultMap id=”userMap” type=”com.abc.model.User”>
<id property=”id” column=”id”/>
<result property=”name” column=”name”/>
<result property=”age” column=”age”>
</resultMap>
<select id=”selectUserById”resultMap=”userMap”>
select name,age
from user u
inner join role r on u.id=r.id
where u.id=#{id}
</select>
多表
當返回值涉及一個model時,和單表一樣。如果有多餘返回值,可以巢狀model,在寫xml寫sql時,select u.user_name as “user.userName”
insert
獲取主鍵值
自增方式的資料庫:
<insert id="insert" userGenerateKeys="true" keyProperty="id">
insert into ..
</insert>
序列的:
<insert id="insert" userGenerateKeys="true" keyProperty="id">
insert into ..
<selectKey keyColume=="id" resultType="long" keyProperty="id" order="before"> //oracle的序列是先獲取的,隨意要before。mysql的自增主鍵是插入後才有的,要改成after。
//mysql SELECT LAST_INSERT_ID()
//oracle SELECT SEQ_ID.nextval from dual
//這裡的寫法不同資料庫不一樣
</selectKey>
</insert>
用法
<update id="updateById">
updatre sys_user
set user_name=#{userName},
user_password=#{userPassword},
user_email=#{userEmail},
user_info = #{userInfo}
hear_img = #{headImg,jdbcType=BLOB},
create_time=#{createTime,jdbcType=TIMESTAMP}
where id=#{id}
</update>
在UserMapper中新增介面
int updateById(SysUser sysUser);
測試類:
public void testUpdateById(){
Reader reader = Resource.GetResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().builder(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.updateById.......//大概流程如此
}
多查詢條件,介面需要修改為:
List<SysRole> selectRolesByUserIdAndRoleEnable(@Param("userId")Long userId,@Param("enable")Integer enabled);
如果入參為物件
List<SysRole> selectRolesByUserIdAndRoleEnable(@Param("user")User user,@Param("role")SysRole role);
在xml使用的時候,要用user.userId和role.enable了。和上面查詢一樣。
delete update略
Mapper動態代理實現
首先宣告一個介面
public interface TestMapper{
List<User> selectAll();
}
建立一個代理類
public class MyMapperProxy<T> implements InvocationHandler{
private Class<T> myMapper;
private SqlSession sqlSession;
public MyMapperProxy(Class<T> myMapper,SqlSession sqlSession){
this.myMapper = myMapper;
this.sqlSession = sqlSession;
}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
List<T> list = sqlSeesion.selectList(mapperInterface.getCanonicalName()+"."+method.getName());
return list;
}
}
這樣就獲取了interface的全限定名,所以mybatis是一種約定大於配置的方式簡化了操作。
Mybatis註解的用法
Insert註解,不需要返回主鍵的情況:
@Insert("insert into sys_role(id,name) values(#{id},#{name,jdbcType=String})")
需要返回主鍵的情況(自增主鍵)
@Insert("insert into sys_role(name) values(#{name,jdbcType=String})")
@Options(useGeneratedKsys=true,keyProperty="id")
需要返回主鍵的情況(非自增主鍵),xml裡用的selectKey標籤
@Insert("insert into sys_role (name,enable,create_time),values(#{name},#{enable},#{createTime,jdbcType=TIMESTAMP})")
@SelectKey(statement="SELECT LAST_INSERT_ID()",keyProperty="id",resultType=Long.class,before=false)
Update和Delete註解
同上,沒什麼特殊的。
Provider註解
除了上述增刪改查意外,mybatis還提供了四種Provider註解。分別為:@SelectProvider @InsertProvider @UpdateProvider @DeleteProvider。
就是將SQL封裝到程式碼裡。
@SelectProvider(type=PrivilegeProvider.class,method="selectById")
SysPrivilege selectById(Long id);
然後PrivilegeProvider類如下
public class PrivilegeProvider{
public String selectById(final Long id){
return new SQL(){
SELECT("id,privilege_name,privilege_url");
FROM("sys_privilege");
WHERE("id=#{id}");
}.toString();
}
}
由於MyBatis註解方式不是主流,僅做了解。
動態SQL
MyBatis強大特性之一便是這一節的東西。MyBatis使用OGNL表示式,用於xml配置中。支援如下幾種標籤:
<if /> 判斷
<choose /> 選擇
<where /> 條件
<set /> 更新設值
<trim /> 按規則替換
<foreach />
<bind />
其中where、set、trim這三個標籤解決了類似問題,並且where、set都屬於trim的一種具體用法。
if標籤
有如下查詢場景:當用戶傳入name時,根據name查,當輸入的是email時,根據email查。如何用OGNL表示式來寫這條sql呢?
<select id="selectByUser" resultType="com.abc.simple.model.user">
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo
from sys_user
where 1=1
<if test="userName != null and userName!=">
and user_name like concat('%',#{userName},'%') //concat是mysql語法
</if>
<if test="userEmail !=null and userEmail!=">
and user_email = #{userEmail}
</if>
</select> //注意首先判斷非空,然後再判斷值。兩個if標籤可同時為真。
if標籤有一個必填的屬性test,test的屬性值一個符合OGNL要求的判斷表示式,裡面就是一個條件判斷語句,支援and or,分組判斷,巢狀判斷。
update中使用if標籤
<update id="updateById">
update sys_user
set
<if test="userName!=null and userName!=">
user_name = #{userName},
</if>
<if test="userPassword!=null and userPassword!=">
user_password = #{userPassword},
</if>
id=#{id}
where id=#{id}
</update> //1、注意逗號 2、注意where條件前的語句id=#{id},這條語句可以最大限度的保證不出錯。
insert中使用if標籤
<insert id="insert1" useGeneratedKey="true" keyProperty="id">
insert into sys_user(
user_name,user_password,<if test="userEmail !=null and userEmail!=">user_email,</if>userinfo
)values(#{userName},#{userPassword},<if test="userEmail !=null and userEmail!=">#{userEmail},</if>)#{userInfo}
</insert>
欄位和值的條件判斷要一致。
choose標籤
choose的作用就如同if…else,語句為choose when otherwise
<select id=”selectByUser” resultType=”com.abc.model.User”>
select * from user where 1=1
from user
where 1=1
<choose>
<when test=”id!=null”>
and id =#{id}
</when>
<when test=”userName!=null and userName!='' ”>
and userName=#{userName}
<otherwise>
and 1=2
<otherwise>
</choose>
</select>
最後這個1=2是為了當什麼都沒有輸入的時候,返回為空。因為這個條件肯定是false的。
where標籤
where標籤的作用:如果標籤包含的元素中有返回值,那麼就插入一個where,如果where後面的字串以and和or開頭,就刪除它。
<select id=”selectUser” resultType=”com.abc.model.User”>
select * from user
<where>
<if test=”userName!=null and userName!='' ”>
and user_name like concat('%',#{userName},'%')
</if>
<if>
and user_email = #{userEmail}
</if>
</where>
</select>
set標籤
set標籤作用:如果該標籤包含的元素中有返回值,就插入一個set,如果set後面的字串是以逗號結尾的,就將逗號去掉。
<update id=”updateUserById”>
update user
<set>
<if test=”userName!=null and userName!='' ”>
user_name=#{userName},
</if>
<if test=”userEmail!=null and userEmail!='' ”>
user_email=#{userEmail},
</if>
<if test=”headImg!=null”>
head_img=#{headImg,jbdcType=BLOB},
</if>
where id=#{id}
</set>
</update>
trim標籤
where和set標籤的功能都是通過trim來實現的
where對應的是:
<trim prefix=”WHERE”prefixOverrides=”AND |OR ”>
</trim>
set對應的是:
<trim prefix=”SET” suffixOverrides=”,”>
</trim>
prefixOverrides:當trim元素內包含內容時,會把內容中匹配的字首字串去掉
suffixOverrides:當trim元素內包含內容時,會把內容中匹配的字尾字串去掉
foreach標籤
批量查詢,在SQL中會使用類似 id in (1,2,3)
<select id=”selectByIdList”resultType=”com.abc.model.User”>
select * from user
where id in
<foreach collection=”list”
open=”(”
close=”)”
separator=”,”
item=”id”
index=”i”>
#{id}
</foreach>
</select>
批量插入是SQL-92新增特性,目前支援的資料庫有DB2、SQL Server2008、MySQL、SQLite3.7.11以上版本。(2017年11月再版)
INSERT INTO tablename (column-a,column-b,column-c) values (value-a’,value-b’,‘value-c’);
<insert id="insertList">
insert into sys_user(user_name,user_password,user_email)
values
<foreach collection="list" item="user" separator=",">
(
{user.userName},#{user.userPassword},#{user.userEmail}
)
</foreach>
</insert>
foreach插入map型別
<foreach collection="_parameter" item="val" index="key" separator=",">
(
${key}=#{val}
)
</foreach>
注:$取值和#取值的區別。
1.取值會將輸入當成字串,比如輸入123,輸出"123",可以防sql注入
$取值將輸入原樣輸出,比如輸出123,輸出123。
bind標籤
bind可以繫結變數,避免更換資料庫造成的修改sql,當然只是在一定語法程度上。
<if test="userName!=null">
<bind name="nameLike" value="'%'+userName+'%'">
and user_name like #{nameLike}
</if>
多資料支援
提供了一個databaseId的屬性值,增刪改查、selectKey,sql標籤都包含這個屬性。
在mybatis-config.xml中配置,並給對應的資料庫id起了一個別名。
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver">
<property name="DB2" value="db2">
...
</databaseIdProvider>
然後在寫select標籤時,加上
<select id=”selectUserById” databaseId=”mysql”></select>
<select id=”selectUserById” databaseId=”oracle”></select>
比如針對oracle和mysql的like查詢
<select id="selectByUser" databaseId="mysql" resultType="com.abc.model.user">
select * from sys_user where user_name like concat('%',#{userName},'%')
</select>
<select id="selectByUser" databaseId="oracle" resultType="com.abc.model.user">
select * from sys_user where user_name like '%'||#{userName}||'%'
</select>
就可以根據不同的資料庫進行操作,比如mysql有自增主鍵,oralce有序列。
OGNL語法
a or b
a and b
a==b 或 a eq b
a!=b 或 a neq b
a lt b 小魚
a lte b 小魚等魚 gt大魚 gte大魚等魚
a+b a*b a/b a-b a%b
!a not a
a.method(args) 呼叫物件方法 比如判斷 list.size()>0
a.property 物件屬性值 比如user.address.name,比如保證存在這個欄位,不然會報錯
a[index]按索引取值
@[email protected](args)呼叫靜態類方法 比如test="@[email protected](userName)"
@[email protected]呼叫類的靜態欄位
MyBatis程式碼生成器
MyBatis程式碼生成器又叫MyBatis Generator,縮寫MBG,這東西是用來根據資料庫來自動生成mapper檔案、model檔案、dao檔案。
執行方式1:下載mybatis-generatorjar檔案,然後編寫一個xml檔案。用
java -jar mybatis-generator-core-1.3.2.jar -configfile generatorConfig.xml -overwrite
執行,還要準備一個數據庫連線的jar。
執行方式2:通過maven獲取
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.3</version>
</dependency>
然後建立Generator.java類
public static void main(String args[]){
List<String> warnings = new ArrayList<String>();
boolean override = true;
InputStream is = Generator.class.getResourceAsStream("/generator/generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(is);
is.close();
DefaultShellCallBack callback = new DefaultShellCallBack(override);
//建立MBG
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(conifg,callback,warnings);
//執行生成程式碼
myBatisGenerator.generate(null);
//輸出警告資訊
for(String warning : warnings){
syso(warning);
}
}
使用java程式碼好處是,可以在當前專案中找一些自定義類,比如裡配置的自定義註釋類。
執行方式3:maven plugin方式
執行方式4:eclipse外掛方式 安裝mybatis-generator的eclipse外掛
MBG有豐富的配置可供使用,所以首先來看MBG的xml配置。首先在xml檔案中引入MBG的dtd檔案。dtd是用來定義標籤的。mybatis-generator-config_1_0.dtd
然後所有的配置要寫在
<generatorConfiguration>
<properties></properties> 最多配置一個
<classPathEntry></classPathEntry> 選填
<context></context> 至少配置一個
</generatorConfiguration>
MyBatis高階查詢
高階結果對映
一對一
<select>
select u.id,
u.user_name userName,
u.user_info userInfo,
r.id "role.id",
r.role_name "role.roleName"
from sys_user u
inner join sys_user_role ur on u.id=ur.user_id
where u.id=#{id}
</select>
User這樣寫
public User{
@setter @getter public UserRole role;
}
就可以實現巢狀結果對映,mybatis就將查詢結果對映到兩個類裡了。這樣寫的好處就是降低資料庫的訪問次數,缺點是要寫複雜的sql。
要實現上述功能,mybatis還可以用reusltMap來實現啊。其實這倆是一個東西。
<resultMap>
<result property="role.roleName" column="role_name" jdbcType="String"/>
</resultMap>
如果有十來個屬性,那麼寫起來比較費勁。這時候mybatis的resultMap的繼承就起作用了。
將通用的寫一個resultMap,需要inner join查其他表的屬性,就繼承一下。
<resultMap id="userRoleMap" extends="userMap" type="com.abc.model.user">
</result>
在此基礎上,繼續做修改,resultMap有一個子標籤用來配置複雜對映關係
<resultMap id="userRoleMap" extends="userMap" type="com.abc.model.user">
<association property="role" columnPrefix="role_" javaType="com.abc.mode.role">
<result />
</association>
</resultMap>
在inner join的時候,和sys_role相關的要加上role_字首。
<association resultMap="">功能也可以將association內的result標籤獨立出來。
<association>還實現了一個重要的功能:巢狀查詢的延遲載入。
<association
property="role"
fetchType="lazy"
select="com.abc.mybatis.mapper.RoleMapper.selectRoleById"
column="{id=role_id}"
/>
在RoleMapper.xml中加入
<select id="selectRoleById" resultMap="roleMap">
select * from sys_role where id=#{id}
</select>
然後在mybatis-config.xml中配置
<settings>
<setting name="aggressiveLazyLoading" value="false" />
</settings>
即可。但是注意,在和Sping整合時,要確保只能在Service中使用延遲載入屬性,因為SqlSession的生成周期交給了Spring來管理,當物件的生命週期超出了SqlSession的生命週期時,就會報錯。在Controller查詢會因為SqlSession已經關閉。
當我們想主動觸發延遲載入時,mybatis提供了另一個引數來解決lazyLoadingTriggerMethods,當呼叫預設方法“equals、clone、hashCode、toString”時會觸發。
一對多
就是將結果對映成一個List,如下:
@setter @getter List roleList;
<resutMap>
<result />
...
<collection property="roleList" columnPrefix="role_" ofType="com.abc.model.SysRole">
<result property="id" column="id" />
<result property="roleName" column="role_name">
</collection>
</resultMap>
簡而言之就是把association變成了collection。
原理:mybatis會根據
主鍵,來講查出來的資料,userMap id相同的合併。
利用resultMap巢狀的特性,可以實現多層model的巢狀查詢。
collection集合的巢狀查詢
association關聯的巢狀查詢這種方式會執行額外的SQL查詢,對映配置會簡單很多。下面是collection的巢狀對映。
首先新增PriMapper.xml
<select id="selectPriByRoleId" resultMap="priMap">
select p.* from sys_pri p where role_id=#{id}
</select>
<resultMap id="rolePriListMapSelect" extends="roleMap" type="com.abc.model.Role">
<collection property="priList"
fetchType="lazy"
column="{roleId=id}"
select="com.abc.simple.mapper.PriMapper.selectPriByRoleId" />
</resultMap>
<select id="selectRoleByUserId" resultMap="rolePriListMapSelect">
select r.id,r.role_name,r.role_enable,r.create_time from sys_role r inner join sys_user_role ur on ur.role_id=r.id where ur.user_id=#{userId}
</select>
最頂層使用者資訊
<resultMap id="userRoleListMapSelect" extends="userMap" type="com.abc.model.User">
<collection property="roleList"
fetchType="lazy"
select="com.abc.simple.mapper.RoleMapper.selectRoleByUserId"
column="{userId=id}" />
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
select u.id,
u.user_name,
u.user_info,
u.create_time
from sys_user u where u.id=#{id}
</select>
這樣就實現了多層collection巢狀迴圈
單元測試
@Test
public void testSelectAllUserAndRolesSelect(){
SqlSession sqlSession = getSession();
//省略try catch
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user = userMapper.selectAllUserAndRolesSelect(1L);
syso(user.getUserName);
//開始延遲載入
foreach(SysRole role : user.getRoleList()){
syso(role.getRoleName);
//繼續延遲載入
for(SysPri pri : role.getPriList()){
syso(pri.getPriName());
}
}
}
鑑別器對映
有時一個單獨資料庫查詢會返回很多不同型別的資料,類似Java中的switch語句。這個功能很強大。
比如當我們查詢一個分類的科目時,如果該分類的狀態為不可用狀態,那麼就不去查他的科目了。下面這個例子邏輯一樣。
<resultMap id="rolePriListMapChoose" type="com.abc.model.SysRole">
<discriminator column="enable" javaType="int">
<case value="1" resultMap="rolePriListMapSelect" />
<case value="0" resultMap="roleMap" />
</discriminator>
</resultMap>
根據enbale的值,來對映不同的resultMap。
儲存過程
<select id="selectUserById" statementType="CALLABLE" useCache="false">
{
call select_user_by_id(
#{id,mode=IN},
#{userName,mode=OUT,jdbcType=VARCHAR},
#{userInfo,mode=OUT,jdbcType=VARCHAR},
#{createTime,mode=OUT,jdbcType=TIMESTAMP},
#{headImg,mode=OUT,jdbcType=BLOB,javaType=_byte[]}
)}
</select>
使用mybatis呼叫儲存過程,不支援mybatis二級快取,所以直接把快取useCache=“false”,一定要注意基本型別的對映。比如上面的headImg。這個儲存過程沒有返回值,使用出參的方式獲得了,相當於c#中的
out user
使用出參方式獲取返回值,必須保證所有出參在JavaBean中都存在。因為JavaBean物件中不存在出參對應的setter方法。
下面一個儲存過程場景:分頁查詢
需要返回兩個值:一個是List 一個是int total。返回值用resultMap接,total用上述的方式接。
插入、刪除也是
<insert id="insert" statementType="CALLABLE">
{call insert_user(
#{user.id,mode=OUT,jdbcType=BIGINT},
#{user.userName,mode=IN},
#{user.headImg,mode=IN,jdbcType=BLOB},
#{roleIds,mode=IN}
)}
</insert>
<delete id="delete" statementType="CALLABLE">
{call delete_user(
{id,mode=IN}
)}
</delete>
oracle帶遊標的儲存過程
遊標其實對應多個sql語句。建立兩個引數的遊標:
create or replace procedure SELECT_COUNTRY(ref_cur1 out sys_refcursor,ref_cur2 out sys_refcursor)
is begin
open ref_cur1 for select * from country where id<3;
open ref_cur1 for select * from country where id>=3;
end SELECT_COUNTRY;
CountryMapper.xml寫法
<select id="selectCountry" statementType="CALLABLE" useCache="false">
{
call SELECT_COUNTRY(
#{list1,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=BaseResultMap},
#{list2,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=BaseResultMap},
)
}
</select>
對列舉值的對映
MyBatis在遇到列舉值時,預設使用TypeHandler處理,這個處理器會將列舉值轉換為字串型別的字面值並使用,對Enable而言便是disable和enable字串
所以不能使用預設的,使用另一個EnumOrdinalTypeHandler,這個處理器使用列舉的索引進行處理,在mybatis-config.xml中新增
<typeHandlers>
<typeHandler javaType="com.abc.type.Enable這個是列舉類" handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</typeHandlers>
還可以實現自己的型別處理器,繼承實現TypeHandler介面。1.6對Java 8日期(JSR-310)的支援,在pom.xml中引入依賴
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>
MyBatis快取配置
一級快取
在一個SqlSession中,如果查詢的方法和引數都一樣,那麼第二次查的就不會從資料庫裡查了,第一次查詢的結果會以Map形式存在快取裡。這時候如果對第一次查詢的結果進行修改操作,也會影響第二次查詢的結果。因為這倆的引用完全一樣。如何避免這種情況?在查詢語句上新增一個屬性
<select id="selectById" flushCache="true" resultMap="userMap">
select * from user where id =#{id}
</select>
flushCache="true"會保證每次查詢都從資料庫裡查,這時候如果查兩次,那麼返回的例項物件的引用就不同了。要避免這麼做。還有一種重新整理快取的方式,就是執行insert update delete的時候,會自動清空一級快取。如果在一個SqlSession中,先查id=1的User1,再查一次id=1的User2,那麼User1和User2是同一個東西。如果先查id=1的User1,然後隨便刪除或更新一個id=2的User,然後再查id=1的User2,那麼User1和User2就不是同一個引用了。因為快取被清空了。
剛才說到,是在一個SqlSession中。如果打開了兩個SqlSession,那麼第一個SqlSession查出來的User和第二個SqlSession查出來的User物件,儘管值一樣,但是是不同的引用物件。
這就是MyBatis的一級快取。
二級快取
我們常說的MyBatis快取其實是值的它的二級快取,因為一級快取是預設不可被控制的。
MyBatis二級快取非常強大。在存在於比SqlSession範圍更大的SqlSessionFactory生命週期中。
二級快取配置
mybatis-config.xml中新增配置,開啟二級快取
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
xml中新增快取配置
保證上面快取全域性開啟的情況下進行,在UserMapper.xml中追加cache標籤即可
<mapper namespace="com.abc.mapper.UserMapper">
<cache />
</mapper>
新增這句話有什麼效果呢?
1、所有SELECT語句會被快取
2、所有insert update delete會重新整理快取
3、使用LRU演算法來回收 least recently used
4、根據時間表,快取不會以任何時間來重新整理,可以設定
5、快取會儲存集合或物件的1024個引用
6、快取是可讀可寫的
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readonly="true"
/>
這個配置更為高階,建立了一個FIFO快取,每60秒重新整理一次,儲存512個引用,只讀的。
cache的eviction回收策略屬性值如下:
LRU 最近最少使用
FIFO 按進入快取的順序
SOFT 移除基於GC規則的軟引用
WEAK 移除基於GC規則的弱引用
接口裡配置
在使用註解方式時,如果想對註解方法啟用二級快取,還需要在Mapper介面中建配置。如果Mapper介面也存在對應的zml對映檔案,兩者同時開啟快取時,還需要特殊配置。只在Mapper介面中配置的話,在UserMapper的interface中
@CacheNamespace(
eviction=FifoCache.class,
flushInterval=60000,
size=512,
readWrite=true
)
如果介面不存在使用註解式的方法,可以只在xml中配置,否則要在
@CacheNamespaceRef(UserMapper.class)
public interface UserMapper{
}
因為想讓介面方法和xml檔案使用相同的快取,就要這樣。然後在UserMapper.xml中新增
<cache-ref namespace="com.abc.mapper.UserMapper"/>
這樣配置後,xml就會引用Mapper介面中配置的二級快取,同樣可以避免同時配置二級快取導致的衝突。
MyBatis中很少同時使用Mapper介面註解方式和xml對映檔案,所以參照快取不是為了解決這個問題而設計的。參照快取除了能夠通過引用減少配置外,主要作用是用來解決髒讀。
使用二級快取
如果快取配置的是隻讀的,那麼無所謂,從快取裡讀的物件就是同一個例項。
如果配置成可讀可寫的,mybatis使用SerializedCache來序列化可讀可寫快取類。被序列化的物件要實現Serializable介面。
二級快取什麼時候會有資料呢?看下面的流程
1、開啟SqlSession
2、查詢id=1的使用者
3、再次查詢id=1的使用者
這時候2和3查出來的是一個
4、關閉SqlSession
5、開啟新的SqlSession
6、查詢id=1的使用者
7、再次查詢id=1的使用者
這時候雖然6和2查出來的不是一個引用,但是6並沒有去查資料庫,而是去二級快取裡查了。因為是可讀可寫的情況,6查出來的和7查出來的都是經過反序列化的,不是同一個例項。但6和7都沒有去資料庫查,都到二級快取裡去查了。二級快取是當這個查詢所在的SqlSession執行close()方法後,才將資料刷到二級快取裡。
這裡可能會有疑問,如果我在第2步查出來資料後,修改其資訊,那麼後來查出來的不就都出問題了嗎?Yes,所以不要做無意義的修改,避免人為增加髒資料。
MyBatis是通過Map來實現快取的。少量資料可以,大量資料怎麼辦?
可以通過其他工具或框架來儲存。
整合EhCache快取
EhCache是一個純Java程序內快取,快速、簡單、多種策略、記憶體和磁碟兩級、可通過RMI實現分散式。該專案由MyBatis官方提供,叫ehcache-cache。https://github.com/mybatis/ehcache-cache
依賴
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>
整合Redis
MyBatis還提供了二級快取的Redis支援,命名為redis-cache。https://github.com/mybatis/redis-cache。
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
整合基本上的套路都是引用依賴,在src/main/resources新增配置檔案,在mybatis-config.xml中配置快取標籤。
髒資料
二級快取雖然能提高效率,但是也容易產生髒資料,由於多標聯查時。要掌握避免髒資料的技巧。比如參照快取。
二級快取適用場景
1、以查詢為主,少的增刪改
2、絕大多數單表操作
3、按照業務對錶進行分組時,如果關聯表比較少,可以使用參照快取。
在使用二級快取時,一定要考慮髒資料對系統的影響,在任何情況下,都可以考慮在業務層使用可控制的快取來代替二級快取。