在經過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緩存。