在經過Mybatis入門學習和Mybatis實現增刪改查後,又學習了後續整體的框架
MyBatis核心介面和類
1. SqlSessionFactoryBuilder負責構建SqlSessionFactory,並且提供了多個build()方法的過載。也就是說:此物件可以從xml配置檔案,或從Configuration物件來構建SqlSessionFactory。
2. SqlSessionFactory就是建立SqlSession例項的工廠。通過openSession方法來獲取SqlSession物件。而且,SqlSessionFactory一旦被建立,那麼在整個應用程式期間都存在。
3. SqlSession是一個面向程式設計師的介面,它提供了面向資料庫執行sql命令所需的所有方法。SqlSession對應一次資料庫會話,它是執行緒不安全的。
封裝持久層
MyBatis開發DAO層有兩種方式:
- 原始dao方式
- mapper代理方式
原始dao方式
按照JDBC課程中封裝dao層的方式,我們可以先封裝一個 Util 工具類,在此工具類中封裝一個獲取SqlSessionFactory的方法。然後建立dao介面和實現類。
SqlSessionFactory工具類:
package com.neusoft.util;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Util {
public static SqlSessionFactory sqlSessionFactory = null;
public static SqlSessionFactory getSqlSessionFactory() {
if(sqlSessionFactory==null){
String resource = "mybatis/SqlMapConfig.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}
}
return sqlSessionFactory;
}
}
dao介面:
package com.neusoft.dao;
import java.util.List;
import com.neusoft.po.Emp;
public interface EmpDao {
public Emp getEmpById(int empno);
public List<Emp> listEmp();
}
dao的實現類:
package com.neusoft.dao.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.neusoft.dao.EmpDao;
import com.neusoft.po.Emp;
import com.neusoft.util.Util;
public class EmpDaoImpl implements EmpDao{
@Override
public Emp getEmpById(int empno){
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
Emp emp = sqlSession.selectOne("emp.getEmpById",empno);
sqlSession.close();
return emp;
}
@Override
public List<Emp> listEmp(){
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
List<Emp> list = sqlSession.selectList("emp.listEmp");
sqlSession.close();
return list;
}
}
測試:
EmpDao dao = new EmpDaoImpl();
Emp emp = dao.getEmpById(7369);
System.out.println(emp);
List<Emp> list = dao.listEmp();
for(Emp emp : list) {
System.out.println(emp);
}
從上面程式碼中可以發現,使用原始dao方式存在很多問題:
- dao實現類中存在大量重複程式碼
- 呼叫sqlSession方法時,將statement的id硬編碼了
- 呼叫sqlSession方法時傳入的引數,由於sqlSession使用了泛型,所以即使傳入引數的資料型別錯誤,在編譯階段也不會報錯。
mapper代理方式
只需要mapper介面和mapper.xml對映檔案,Mybatis可以自動生成mapper介面實現類代理物件。編mapper介面需要遵循4個一致
- Mapper對映檔案的名字和mapper介面的名字一致
- Mapper對映檔案中statementId的值,與mapper介面中對應的方法名一致
- Mapper對映檔案中statement的輸入引數parameterType的型別,與mapper介面中對應方法的引數型別一致。
- Mapper對映檔案中statement的輸出引數resultType的型別,與mapper介面中對應方法的返回值型別一致
解釋一下:
-
第一個一致:
而且前面學習提到過,xml對映檔案的namespace屬性的取值問題,當使用原始dao開發時,可以隨意取值;使用mapper代理開發時,取值為mapper介面的全路徑
-
第二個一致:方法名一致
-
第三個一致:輸入型別一致
-
第四個一致:輸出型別一致
還要記得在SqLMapConfig中註冊對映檔案
-
優化:
如果對映檔案與mapper介面名稱一致,且處在同一個資料夾內,那麼就可以使用介面來批量載入對映檔案。
注意一個是“/”,一個是“.”
在第四個一致中,xml檔案中的輸出型別寫的很長,也可以進行簡化,同樣在SqlMapConfig中
-
程式碼:
對映檔案EmpMapper.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">
<!-- namespace屬性: 現在可以隨意給值 ,但當使用mapper代理方式開發時,有特點的取值。即為mapper介面的全路徑-->
<!-- 1. Mapper對映檔案的名字和mapper介面的名字一致-->
<!-- 2. Mapper對映檔案中statementId的值,與mapper介面中對應的方法名一致-->
<!-- 3. Mapper對映檔案中statement的輸入引數parameterType的型別,與mapper介面中對應方法的引數型別一致。-->
<!-- 4. Mapper對映檔案中statement的輸出引數resultType的型別,與mapper介面中對應方法的返回值型別一致-->
<mapper namespace="com.neuedu.mapper.EmpMapper">
<!-- 按id查詢員工 -->
<select id="findEmpById" parameterType="int" resultType="Employee">
<!-- id就是這條語句的唯一標識,parameterType是員工id的屬性,resultType是返回型別,要把實體類的路徑寫完整 -->
select * from tb_emp where id = #{value}
<!-- 佔位符要使用#{} parameterType的型別如果為 簡單型別(基本型別和String),#{}中的值任意。-->
</select>
<!-- 按名稱模糊查詢,當查詢結果有多個時,resultType的型別為pojo-->
<select id="findEmpByName" parameterType="string" resultType="Employee">
<!-- 不使用拼接,要在test中加% -->
SELECT * FROM tb_emp WHERE NAME LIKE #{value}
<!-- 字串拼接的方法。注意:慎用,會產生sql注入。-->
<!-- SELECT * FROM tb_emp WHERE NAME LIKE '%${value}%'-->
</select>
<!-- 刪除員工 -->
<delete id="deleteEmp" parameterType="int">
delete from tb_emp where id=#{value}
</delete>
<!-- 更新員工-->
<!-- 如果輸入引數為pojo型別,#{pojo物件的屬性名} -->
<update id="editEmp" parameterType="com.neuedu.pojo.Employee">
update tb_emp set
loginName=#{loginName},name=#{name},email=#{email},
status=#{status},deptId=#{deptId},photoPath=#{photoPath}
where id=#{id}
</update>
<!-- 插入員工 -->
<insert id="saveEmp" parameterType="com.neuedu.pojo.Employee">
INSERT INTO tb_emp
(loginname,PASSWORD,NAME,hiredate,email,photopath,deptId)
VALUES (#{loginName},#{password},#{name},#{hiredate},#{email},#{photoPath},#{deptId})
<!-- order: 執行時機 keyColumn:表中自動名稱 keyProperty:對映的pojo屬性名稱 -->
<selectKey order="AFTER" resultType="int" keyColumn="id" keyProperty="id">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
</mapper>
介面類EmpMapper.java
package com.neuedu.mapper;
import java.util.List;
import com.neuedu.pojo.Employee;
public interface EmpMapper {
/**
* 登入方法
* @param loginName 登入名
* @param password 密碼
* @return 登入員工的資訊
*/
Employee login(String loginName, String password);
/**
* 新增員工
* @param emp 插入資訊
*/
void saveEmp(Employee emp);
/**
* 刪除員工
* @param id 員工id
*/
void deleteEmp(Integer id);
/**
* 修改員工
* @param emp 員工資訊
*/
void updateEmp(Employee emp);
/**
* 按id查詢
* @param id 員工id
* @return 員工資訊
*/
Employee findEmpById(Integer id);
/**
* 按照name查詢
* @param name 員工姓名
* @return 員工列表
*/
Employee findEmpByName(String name);
}
測試類TstMybatis
package com.neuedu.test;
import com.neuedu.mapper.EmpMapper;
import com.neuedu.pojo.Employee;
import com.neuedu.utils.DBUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class TestMyMapper {
@Test
public void testFindById() {
SqlSession session = DBUtils.getSession();
EmpMapper empMapper = session.getMapper(EmpMapper.class);
Employee emp = empMapper.findEmpById(2);
System.out.println(emp.getName());
session.close();
}
}
輸入引數
parameterType 屬性:表示執行sql語句時,需要使用的資料。
簡單型別或String
在statement語句中#{}
的值可以任意
pojo型別
輸入引數為pojo型別,#{}
中的值為pojo物件的屬性名
擴充套件的pojo屬性
需求查詢員工所在的部門名稱
問題提出:員工表emp中沒有部門名的欄位,需要聯合到部門表dept;即Employee實體類無法滿足需求
解決方法:
- 在Employee類中加入一個屬性dname,這樣是最簡單的,但是可能會出現問題。因為Employee類是Mybatis根據資料庫的欄位自動生成的,新增屬性不規範後再次生成的能會覆蓋掉手動新增的屬性。所以可以在現有的pojo上做出擴充套件
- 建立一個EmployeeVo繼承類,當中只宣告一個屬性dname,其餘的繼承Employee類
這個類與資料庫沒有對映關係可以隨便修改,稱之為擴充套件的pojo。
sql
在寫sql語句時可以直接用
測試:
記得保持四個一致,要在EmpMapper介面類中加入方法
包裝的pojo
在包裝POJO類中,關聯多個POJO物件,比如按照上例可以把emp和dept聯合起來組成EmpDept類;
sql語句
寫sql語句時輸入型別也可以直接用:
注意#{}裡面的值,empdept裡沒有name屬性只有emp與dept,但是可以用emp與dept來訪問他們的name
測試
寫測試的時候會複雜一點
在介面中加入方法
輸出引數:resultType
簡單型別
如果resultType為簡單型別,查詢結果必須為一個值。一行一列。
四個一致:在介面中定義方法
測試方法
pojo
如果select語句使用投影查詢時。即查詢列表只定義表中部分欄位。
此時結果中只會封裝查詢列表中有的欄位,查詢列表中沒有的欄位,輸出引數pojo屬性值為預設值
Employee中的所有屬性
寫一個投影查詢
可以看出查詢語句並沒有把所有的屬性都查出來
介面定義方法:
在Employee.java實體類中加入一個tostring方法,只選取部分資料庫中的欄位:
測試:
輸出結果:
同樣能對映成功。
思考:此時用的是表中欄位的名字與屬性對映還是用的查詢列表的名字與屬性對映?
驗證:
在查詢列表中起別名再測試
結果:
email為空。
說明輸出引數進行對映時,是使用的查詢列表的別名和pojo屬性名進行對映,如果欄位別名和pojo屬性名不對應,則會對映失敗
把所有的都起別名,只剩下id能對應上
結果:
結論:即使只有一個屬效能對應,也會建立pojo物件
如果所有的都對應不上呢?
結果:
不報錯,輸出為空。
結論:所有屬性都對應不上則不會建立pojo物件
如果必須要起別名來查詢,怎麼解決?
可以利用resultMap
輸出引數:resultMap
在statement語句中把resultType的位置寫為resultMap
- resultMap是一個標籤,每一個statement語句都會根據標籤的id去找他的對映型別,上例中的型別就是Employee,有不同的對應不上的自己定義。
- 主鍵屬性對映用
<id>
普通屬性對映用<result>
再次測試:
email對映成功
但是resultMap的主要作用是實現關聯對映,在後續會學到
動態sql
where和if
運用:條件查詢
在前幾天j學習aveweb時用servlet寫條件查詢時,寫了一個"1=1",再連線其他的條件
測試
- 沒有輸入時
- 給name賦值查詢:
- 再給email賦值查詢:
- 再給部門賦值查詢:
說明SQL語句正確可以正常拼接條件,但是在sql語句中加一個"1=1"不得勁
優化:放在<where>
標籤裡面,<if>
語句會加上and
測試能成功,但是發現sql語句中沒有了and
解釋:where標籤會為sql語句新增where字句,同時會去掉第一個條件前面的and,所以放心的加and,不管哪條語句是第一個條件都會自動去掉前面的and。
把所有條件去掉
測試:
所有條件都不滿足where自然也就不起作用了
第二個sql語句:
在介面類中定義:
測試:
結果:
都沒什麼問題。
重點是兩個語句的where標籤中的語句是一模一樣的,就是說這段程式碼可以複用,那就可以封裝起來。
封裝sql程式碼
把重複程式碼寫在<sql>
標籤內
在statement中用<include>
來包含<sql>
語句
注意:
- 程式碼片段中,封裝的sql語法儘量能夠多被複用。所以where不太適合在片段中
- 程式碼片段中儘量進行單表操作,為了提高複用性。能重複用到很多表的畢竟是少數。
改進:把sql片段的where刪除
在<include>
前加上<where>
,
foreach
用於批量刪除。
輸入引數中,應該包含一個陣列(List)型別的資料,該資料包含要刪除的所有id。
此時輸入引數為pojo,即Employee。需要擴充套件屬性,新增一個int[] ids屬性
刪除語句
測試
結果
對比著理解sql的書寫:
引數的含義:
tem表示集合中每一個元素進行迭代時的別名
index指定一個名字,用於表示在迭代過程中,每次迭代到的位置
open表示該語句以什麼開始
separator表示在每次進行迭代之間以什麼符號作為分隔符
close表示以什麼結束
第二條的書寫
遇到and與or時,and的優先順序更高,所以可以在or的語句外加一個"()"
sql語句中,除了上面用到的幾個子標籤,還有其他的
choose:多條件判斷,按順序判斷其內部when標籤中的test條件出否成立,如果有一個成立,則 choose 結束。
當 choose 中所有 when 的條件都不滿則時,則執行 otherwise 中的sql。
類似於Java 的 switch 語句,choose 為 switch,when 為 case,otherwise 則為 default。
trim:插入資料,動態拼接insert語句.主要功能是可以在自己包含的內容前加上某些字首,也可以在其後加上某些字尾,與之對應的屬性是prefix和suffix
可以把包含內容的首部某些內容覆蓋,即忽略,也可以把尾部的某些內容覆蓋,對應的屬性是prefixOverrides和suffixOverrides;
幫助我們去除末尾的“,”並填上“()”。
逆向工程
(當前階段只是瞭解)
Mybatis提供了一個專案,可以通過資料庫中的表自動匯出對應的pojo、mapper.xml、mapper.java。逆向工程只適合單表操作。
建立
修改配置檔案
執行逆向工程
關聯對映
以下圖的電商業務為例,查詢名叫張三的人買過哪些東西,就需要多表查詢
資料庫表詳情:
需求:查詢訂單關聯的使用者
resultType:一對一關聯對映
測試
結果
沒什麼問題,但是當業務需求越來越多,需要的包裝的pojo就會越來越多,與資料庫無關的實體類越來越多,導致系統維護起來很麻煩
resultMap:一對一關聯對映
準備工作:在pojo的order類中加入user,即定義關聯欄位並提供get與set
查詢語句
resultMap進行對映
- resultMap的type屬性即為select語句的輸出型別,即為order(進行了打包,完整路徑為com.nenedu.pojo.Order)
- 先對order類中本身有的屬性進行對映,主鍵用
<id>
,普通屬性用<result>
- 再對order類中加入的User進行對映,一對一對映用
<association>
,其中的JavaType表示關聯屬性的型別,即為User(com.neuedu.pojo.User)- 細心,一個屬性一個屬性的來。
介面方法
測試
結果與resultTyoe相同。
注意其結構型別:
resultMap:一對多關聯對映
需求:查詢使用者及其關聯的所有訂單
要在User類中關聯訂單order的型別,訂單有很多,採用list
查詢語句
resultMap進行對映
- 先對映User本身的屬性,再對映關聯屬性
- 一對多的關聯屬性用collection,使用ofType屬性,他表示集合中存放的物件的型別(泛型屬性)
- JavaType表示的是關聯屬性的型別,但在user中關聯的是list,我們需要的是list內部的物件的屬性
介面方法:
測試:
結果:
resultMap:多對多關聯對映
需求:查詢劉備的資訊及其購買過的商品資訊
sql語句:
關聯了4個表,主表為User
在之前例子的基礎上再往order表中關聯訂單明細OrderDetail,一個訂單有多個明細用list;訂單明細表OrderDetail中關聯商品Item,一個明細只針對一個商品.
關聯環為:User->Order->OrderDetail->Item
查詢語句
介面方法:
叫劉備的可能有很多,用list裝起來。
resultMap進行對映
<resultMap type="user" id="findUserAndItemMap">
<!-- user表 -->
<id column="uid" property="id"/>
<result column="uname" property="name"/>
<!-- 一對多 <list>order表 -->
<collection property="orderList" ofType="Order">
<id column="oid" property="id"/>
<result column="orderNum" property="orderNum"/>
<result column="uid" property="userId"/>
<!-- 一對多 <list>orderdetail表 -->
<collection property="detailList" ofType="orderDetail">
<id column="oid" property="orderId"/>
<id column="iid" property="itemId"/>
<result column="count" property="count"/>
<!-- 一對一 item表 -->
<association property="item" javaType="item">
<id column="iid" property="id"/>
<result column="iname" property="name"/>
</association>
</collection>
</collection>
</resultMap>
每個表看清楚對映的單個(association)還是多個(collection),找好每個表的屬性,一步一步來就不容易錯
測試:
結果:
總結
使用resultMap實現關聯對映時:
- 使用association標籤完成多對一或一對一對映。
a. association標籤:將關聯查詢資訊對映到一個po物件中。
b. association標籤中的javaType屬性:表示該po物件的型別。
c. association標籤中的select屬性:表示應用哪一個關聯查詢。
d. association標籤中的column屬性:表示應用關聯查詢的條件。- 使用collection標籤完成一對多,多對多對映。
a. collection標籤:將關聯查詢資訊對映到一個list集合中。
b. collection標籤的ofType屬性:表示該集合中的元素物件的型別。
c. collection標籤中的select屬性:表示應用哪一個關聯查詢。
d. collection標籤中的column屬性:表示應用關聯查詢的條件。
延遲載入
含義
延遲載入:執行查詢時,關聯查詢不會立即載入。只有在使用關聯資料時才會載入。
1. 優點:按需載入,提高效率。
2. 具有關聯關係的物件才會存在延遲載入
3. 例如:查詢訂單關聯使用者。預設當查詢訂單資訊時,會立即載入關聯的使用者資訊。如果程式中只需要使用訂單資訊,而不需要使用關聯的使用者資訊,則立即載入關聯的使用者資訊就沒有必要。可以對訂單關聯的使用者資訊實現延遲載入,即使用到使用者資訊是再載入,不用就不載入,提供系統的執行效率。
4. 在mybatis中,只有association、collection標籤具有延遲載入的功能。
配置
注意: 要使用關聯查詢的延遲載入,就必須要使用單獨查詢形式。並且,需要先啟用MyBatis的延遲載入配置(需要配置兩項):
- azyLoadingEnabled:延遲載入的全域性開關(預設為false)。
- aggressiveLazyLoading:延遲載入整個物件(預設為true; false:物件的每個屬性都會延遲載入,即屬性按需載入)
<!-- 如果aggressiveLazyLoading為true,那麼lazyLoadingEnabled即使為true也無效。 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
查詢語句
在UserMapper.xml
中有語句:
<select id="findUserById" parameterType="int" resultType="user">
select id,name from users where id=#{value}
</select>
在OrderMapper.xml
有語句:
<!-- 查詢訂單關聯使用者lazy -->
<resultMap type="Order" id="findOrderAndUserLazyMap">
<id column="id" property="id"/>
<result column="orderNum" property="orderNum"/>
<result column="userId" property="userId"/>
<!-- 對映關聯屬性 -->
<!-- select 屬性 呼叫已經存在的statement -->
<association property="user" select="com.neuedu.mapper.UserMapper.findUserById" column="userId"></association>
</resultMap>
<select id="findOrderAndUserLazy" parameterType="string" resultMap="findOrderAndUserLazyMap">
SELECT id,orderNum,userId FROM orders WHERE orderNum = #{orderNum }
</select>
呼叫了在另一個xml檔案的查詢語句,用到的時候才執行。
進行斷點測試:
- 第一條語句執行的時候,user的值為空,第二條語句沒有載入執行;
- 執行到session.commit();傳送了資料,user中也有值
註釋掉一條語句測試:
結果:只傳送了一條資料,也只執行了一條語句
但是在配置檔案中將lazyLoadingEnabled
的true改為false,即使註釋了語句也還是會載入
確認了mybatis預設是立即載入的。
註解開發
在上面的開發過程中我們用的都是對映檔案開發,就是把查詢語句寫在xml檔案當中,然後在介面類中呼叫。
MyBatis也支援使用註解來配置對映語句。直接把語句寫在介面類中。
記得把檔案改一下,已經沒有Employee.xml檔案了。
主要有四種註解來實現增刪改查:@Select、@Insert、@Update、@Delete
基礎的書寫如下
package com.neuedu.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select;
import com.neuedu.pojo.Employee;
public interface EmpMapper {
@Select(value="select * from tb_emp where id=#{value}")
Employee findEmpById(Integer id);
@Select(value="select * from tb_emp where name like '%${value}%'")
List<Employee> findEmpByName(String name);
void saveEmp(Employee emp);
@Delete(value="delete from tb_emp where id=#{value}")
void deleteEmp(Integer id);
void editEmp(Employee emp);
}
測試類:
@Test
public void testFindById() throws Exception{
SqlSession session = sf.openSession();
EmpMapper mapper = session.getMapper(EmpMapper.class);
mapper.findEmpById(3);
session.commit();
session.close();
}
測試一個:
成功。
其他的如用註解實現動態sql、懶載入等等在這個裡面寫的非常詳細了
參考文件:mybatis註解開發
查詢快取
一級快取
Mybatis的一級快取是SqlSession級別的快取,每個SqlSession使用獨立的一級快取空間。
當執行Mapper介面中方法時,獲得一個sqlSession的物件。sqlSession物件執行緒不安全
每次操作都會使用獨立的sqlSession物件。
當sqlSession物件被建立時,sqlSession物件的內部會開闢一個快取區域(HashMap)
工作原理
當通過同一個SqlSession物件進行查詢操作時。一級快取會起作用。
Mybatis預設是開啟一級快取的,而且不能關閉一級快取。
當一個sqlSession物件發起了一次查詢操作時,首先會到一級快取中查詢是否存在該物件
如果在快取中沒有找到對應的物件,則發生sql語句到資料庫中進行查詢。並把查詢到的物件儲存到一級快取中一份
當通過同一個sqlSession物件發起第二次查詢操作時,首先會到一級快取中查詢,如果找到對應的物件,則不會在傳送sql語句到資料庫。這樣,可以提高查詢效率。
在兩次查詢的中間,如果執行了commit操作(update、insert、delete),會清除快取中的所有資料。
測試
@Test
public void testFindUserCache() {
SqlSession session = sf.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查詢
User u1 = userMapper.findUserById(1);//傳送sql
u1.setName("tom");
userMapper.modifyUser(u1);
session.commit();//清空一級快取
//第二次查詢
User u2 = userMapper.findUserById(1);//不會傳送,使用快取中的物件
session.close();
SqlSession session2 = sf.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
User u3 = userMapper2.findUserById(1);//傳送sql,新的sqlSession物件,使用新的一級快取
session2.close();
}
二級快取
二級快取就是,Mapper級別的快取,當不同的sqlSession物件訪問同一個Mapper介面中的方法時,會使用到二級快取。
當使用一個sqlSession物件進行查詢操作時,到二級快取中查詢,如果沒有找到,則傳送sql語句到資料庫中查詢,並把查詢結果儲存到二級快取
在兩次查詢過程中,如果執行了commit操作,則清空二級快取。
當使用另外一個sqlSession物件進行查詢操作時,到二級快取中查詢,如果找到,則不傳送sql語句查詢資料庫。
預設mybatis是開啟二級快取的,但可以在sqlMapConfig.xml檔案中配置二級快取。
要在需要被快取的xxxMapper.xml檔案中配置,該Mapper使用二級快取。
測試
第一次查詢時,到二級快取中查詢是否存在物件
快取命中率。 在快取中查詢物件的命中率。
第二次查詢時,二級快取中存在物件,則命中率為0.5
使用二級快取要注意: 一些異常
被二級快取快取的物件,有可能會被執行序列化或反序列化操作,所以 被快取的物件所屬的類必須支援序列化, 要實現Sericlizable介面。
分散式快取外掛 ehcache
需求:分散式系統,系統存在兩個子系統,在一個系統中登入,在另外一個系統上是否也能夠使用登入資訊?
mybatis與ehcache整合
引入jar包
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.3</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
假日ehcache 實現類
在系統中加入ehcache的配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true">
<!-- 磁碟的快取路徑 -->
<diskStore path="d:\\ehcache\temp"/>
<defaultCache
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="1200"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
<!-- Cache配置
· name:Cache的唯一標識
· maxElementsInMemory:記憶體中最大快取物件數。
· maxElementsOnDisk:磁碟中最大快取物件數,若是0表示無窮大。
· eternal:Element是否永久有效,一但設定了,timeout將不起作用。
· overflowToDisk:配置此屬性,當記憶體中Element數量達到maxElementsInMemory時,Ehcache將會Element寫到磁碟中。
· timeToIdleSeconds:設定Element在失效前的允許閒置時間。僅當element不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
· timeToLiveSeconds:設定Element在失效前允許存活時間。最大時間介於建立時間和失效時間之間。僅當element不是永久有效時使用,預設是0.,也就是element存活時間無窮大。
· diskPersistent:是否快取虛擬機器重啟期資料。(這個虛擬機器是指什麼虛擬機器一直沒看明白是什麼,有高人還希望能指點一二)。
· diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
· diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
· memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。 -->
</ehcache>
應用場景:經常被訪問,但很少被修改。適合進行快取。
電商系統中的商品資訊不適合mybatis快取。