Mybatis框架

九尾。發表於2021-08-12

在經過Mybatis入門學習Mybatis實現增刪改查後,又學習了後續整體的框架

MyBatis核心介面和類

1. SqlSessionFactoryBuilder負責構建SqlSessionFactory,並且提供了多個build()方法的過載。也就是說:此物件可以從xml配置檔案,或從Configuration物件來構建SqlSessionFactory。
2. SqlSessionFactory就是建立SqlSession例項的工廠。通過openSession方法來獲取SqlSession物件。而且,SqlSessionFactory一旦被建立,那麼在整個應用程式期間都存在。
3. SqlSession是一個面向程式設計師的介面,它提供了面向資料庫執行sql命令所需的所有方法。SqlSession對應一次資料庫會話,它是執行緒不安全的。

封裝持久層

MyBatis開發DAO層有兩種方式:

  1. 原始dao方式
  2. 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方式存在很多問題:

  1. dao實現類中存在大量重複程式碼
  2. 呼叫sqlSession方法時,將statement的id硬編碼了
  3. 呼叫sqlSession方法時傳入的引數,由於sqlSession使用了泛型,所以即使傳入引數的資料型別錯誤,在編譯階段也不會報錯。

mapper代理方式

只需要mapper介面和mapper.xml對映檔案,Mybatis可以自動生成mapper介面實現類代理物件。編mapper介面需要遵循4個一致

  1. Mapper對映檔案的名字和mapper介面的名字一致
  2. Mapper對映檔案中statementId的值,與mapper介面中對應的方法名一致
  3. Mapper對映檔案中statement的輸入引數parameterType的型別,與mapper介面中對應方法的引數型別一致。
  4. Mapper對映檔案中statement的輸出引數resultType的型別,與mapper介面中對應方法的返回值型別一致

解釋一下:

  • 第一個一致:
    名字相同
    而且前面學習提到過,xml對映檔案的namespace屬性的取值問題,當使用原始dao開發時,可以隨意取值;使用mapper代理開發時,取值為mapper介面的全路徑
    namespace取值

  • 第二個一致:方法名一致
    image
    image

  • 第三個一致:輸入型別一致
    image
    image

  • 第四個一致:輸出型別一致
    image
    image

還要記得在SqLMapConfig中註冊對映檔案
image

  • 優化:
    如果對映檔案與mapper介面名稱一致,且處在同一個資料夾內,那麼就可以使用介面來批量載入對映檔案。
    image
    注意一個是“/”,一個是“.”
    在第四個一致中,xml檔案中的輸出型別寫的很長,也可以進行簡化,同樣在SqlMapConfig中
    image
    image

  • 程式碼:

對映檔案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語句中#{}的值可以任意
image

pojo型別

輸入引數為pojo型別,#{}中的值為pojo物件的屬性名
image

擴充套件的pojo屬性

需求查詢員工所在的部門名稱
image
image
問題提出:員工表emp中沒有部門名的欄位,需要聯合到部門表dept;即Employee實體類無法滿足需求
解決方法:

  1. 在Employee類中加入一個屬性dname,這樣是最簡單的,但是可能會出現問題。因為Employee類是Mybatis根據資料庫的欄位自動生成的,新增屬性不規範後再次生成的能會覆蓋掉手動新增的屬性。所以可以在現有的pojo上做出擴充套件
  2. 建立一個EmployeeVo繼承類,當中只宣告一個屬性dname,其餘的繼承Employee類
    image
    這個類與資料庫沒有對映關係可以隨便修改,稱之為擴充套件的pojo。
    sql
    在寫sql語句時可以直接用
    image
    測試:
    image
    記得保持四個一致,要在EmpMapper介面類中加入方法
    image

包裝的pojo

在包裝POJO類中,關聯多個POJO物件,比如按照上例可以把emp和dept聯合起來組成EmpDept類;
image
sql語句
寫sql語句時輸入型別也可以直接用:
image
注意#{}裡面的值,empdept裡沒有name屬性只有imageemp與dept,但是可以用emp與dept來訪問他們的name
測試
寫測試的時候會複雜一點
image
在介面中加入方法
image

輸出引數:resultType

簡單型別

如果resultType為簡單型別,查詢結果必須為一個值。一行一列。
image
四個一致:在介面中定義方法
image
測試方法
image

pojo

如果select語句使用投影查詢時。即查詢列表只定義表中部分欄位。
此時結果中只會封裝查詢列表中有的欄位,查詢列表中沒有的欄位,輸出引數pojo屬性值為預設值

Employee中的所有屬性

image
寫一個投影查詢
image
可以看出查詢語句並沒有把所有的屬性都查出來

介面定義方法:image
在Employee.java實體類中加入一個tostring方法,只選取部分資料庫中的欄位:
image
測試:
image
輸出結果:
image
同樣能對映成功。
思考:此時用的是表中欄位的名字與屬性對映還是用的查詢列表的名字與屬性對映?

驗證:
在查詢列表中起別名再測試
image
結果:
image
email為空。

說明輸出引數進行對映時,是使用的查詢列表的別名和pojo屬性名進行對映,如果欄位別名和pojo屬性名不對應,則會對映失敗

把所有的都起別名,只剩下id能對應上
image
結果:
image

結論:即使只有一個屬效能對應,也會建立pojo物件

如果所有的都對應不上呢?
image
結果:
image

不報錯,輸出為空。

結論:所有屬性都對應不上則不會建立pojo物件

如果必須要起別名來查詢,怎麼解決?
可以利用resultMap

輸出引數:resultMap

在statement語句中把resultType的位置寫為resultMap
image

  1. resultMap是一個標籤,每一個statement語句都會根據標籤的id去找他的對映型別,上例中的型別就是Employee,有不同的對應不上的自己定義。
  1. 主鍵屬性對映用<id>普通屬性對映用<result>

再次測試:
image

email對映成功

但是resultMap的主要作用是實現關聯對映,在後續會學到

動態sql

where和if

運用:條件查詢
在前幾天j學習aveweb時用servlet寫條件查詢時,寫了一個"1=1",再連線其他的條件
image

測試

  1. 沒有輸入時
    image
    image
  2. 給name賦值查詢:
    image
    image
  3. 再給email賦值查詢:
    image
    image
  4. 再給部門賦值查詢:
    image
    image
    說明SQL語句正確可以正常拼接條件,但是在sql語句中加一個"1=1"不得勁

優化:放在<where>標籤裡面,<if>語句會加上and
image
測試能成功,但是發現sql語句中沒有了and
image

解釋:where標籤會為sql語句新增where字句,同時會去掉第一個條件前面的and,所以放心的加and,不管哪條語句是第一個條件都會自動去掉前面的and。

把所有條件去掉
image
測試:
image
所有條件都不滿足where自然也就不起作用了

第二個sql語句:
image
在介面類中定義:
image
測試:
image
結果:
image
都沒什麼問題。

重點是兩個語句的where標籤中的語句是一模一樣的,就是說這段程式碼可以複用,那就可以封裝起來。

封裝sql程式碼

把重複程式碼寫在<sql>標籤內
image

在statement中用<include>來包含<sql>語句
image

注意:

  1. 程式碼片段中,封裝的sql語法儘量能夠多被複用。所以where不太適合在片段中
  2. 程式碼片段中儘量進行單表操作,為了提高複用性。能重複用到很多表的畢竟是少數。

改進:把sql片段的where刪除
image

<include>前加上<where>image

foreach

用於批量刪除。
輸入引數中,應該包含一個陣列(List)型別的資料,該資料包含要刪除的所有id。
此時輸入引數為pojo,即Employee。需要擴充套件屬性,新增一個int[] ids屬性

刪除語句
image

測試
image

結果
image

對比著理解sql的書寫:
image

引數的含義:
tem表示集合中每一個元素進行迭代時的別名
index指定一個名字,用於表示在迭代過程中,每次迭代到的位置
open表示該語句以什麼開始
separator表示在每次進行迭代之間以什麼符號作為分隔符
close表示以什麼結束

第二條的書寫
遇到and與or時,and的優先順序更高,所以可以在or的語句外加一個"()"
image

sql語句中,除了上面用到的幾個子標籤,還有其他的
image

choose:多條件判斷,按順序判斷其內部when標籤中的test條件出否成立,如果有一個成立,則 choose 結束。
當 choose 中所有 when 的條件都不滿則時,則執行 otherwise 中的sql。
類似於Java 的 switch 語句,choose 為 switch,when 為 case,otherwise 則為 default。

image

image

trim:插入資料,動態拼接insert語句.主要功能是可以在自己包含的內容前加上某些字首,也可以在其後加上某些字尾,與之對應的屬性是prefix和suffix
可以把包含內容的首部某些內容覆蓋,即忽略,也可以把尾部的某些內容覆蓋,對應的屬性是prefixOverrides和suffixOverrides;

image
image
幫助我們去除末尾的“,”並填上“()”。

逆向工程

(當前階段只是瞭解)
Mybatis提供了一個專案,可以通過資料庫中的表自動匯出對應的pojo、mapper.xml、mapper.java。逆向工程只適合單表操作。

建立

image

修改配置檔案

image

執行逆向工程

image

關聯對映

以下圖的電商業務為例,查詢名叫張三的人買過哪些東西,就需要多表查詢
image
資料庫表詳情:
image
image
image
image
image

需求:查詢訂單關聯的使用者

resultType:一對一關聯對映

image

測試
image

結果
image

沒什麼問題,但是當業務需求越來越多,需要的包裝的pojo就會越來越多,與資料庫無關的實體類越來越多,導致系統維護起來很麻煩

resultMap:一對一關聯對映

準備工作:在pojo的order類中加入user,即定義關聯欄位並提供get與set
image

查詢語句
image
resultMap進行對映
image

  1. resultMap的type屬性即為select語句的輸出型別,即為order(進行了打包,完整路徑為com.nenedu.pojo.Order)
  2. 先對order類中本身有的屬性進行對映,主鍵用<id>,普通屬性用<result>
  3. 再對order類中加入的User進行對映,一對一對映用<association>,其中的JavaType表示關聯屬性的型別,即為User(com.neuedu.pojo.User)
  4. 細心,一個屬性一個屬性的來。

介面方法
image

測試
image

結果與resultTyoe相同。
注意其結構型別:
image

resultMap:一對多關聯對映

需求:查詢使用者及其關聯的所有訂單

要在User類中關聯訂單order的型別,訂單有很多,採用list
image

查詢語句
image

resultMap進行對映
image

  1. 先對映User本身的屬性,再對映關聯屬性
  2. 一對多的關聯屬性用collection,使用ofType屬性,他表示集合中存放的物件的型別(泛型屬性)
  3. JavaType表示的是關聯屬性的型別,但在user中關聯的是list,我們需要的是list內部的物件的屬性

介面方法:
image

測試:
image

結果:image

resultMap:多對多關聯對映

需求:查詢劉備的資訊及其購買過的商品資訊

sql語句:
image
關聯了4個表,主表為User

在之前例子的基礎上再往order表中關聯訂單明細OrderDetail,一個訂單有多個明細用list;訂單明細表OrderDetail中關聯商品Item,一個明細只針對一個商品.
關聯環為:User->Order->OrderDetail->Item

查詢語句
image

介面方法:
image
叫劉備的可能有很多,用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),找好每個表的屬性,一步一步來就不容易錯

測試:
image

結果:
image

總結

使用resultMap實現關聯對映時:

  1. 使用association標籤完成多對一或一對一對映。
    a. association標籤:將關聯查詢資訊對映到一個po物件中。
    b. association標籤中的javaType屬性:表示該po物件的型別。
    c. association標籤中的select屬性:表示應用哪一個關聯查詢。
    d. association標籤中的column屬性:表示應用關聯查詢的條件。
  2. 使用collection標籤完成一對多,多對多對映。
    a. collection標籤:將關聯查詢資訊對映到一個list集合中。
    b. collection標籤的ofType屬性:表示該集合中的元素物件的型別。
    c. collection標籤中的select屬性:表示應用哪一個關聯查詢。
    d. collection標籤中的column屬性:表示應用關聯查詢的條件。

延遲載入

含義

延遲載入:執行查詢時,關聯查詢不會立即載入。只有在使用關聯資料時才會載入。

1. 優點:按需載入,提高效率。
2. 具有關聯關係的物件才會存在延遲載入
3. 例如:查詢訂單關聯使用者。預設當查詢訂單資訊時,會立即載入關聯的使用者資訊。如果程式中只需要使用訂單資訊,而不需要使用關聯的使用者資訊,則立即載入關聯的使用者資訊就沒有必要。可以對訂單關聯的使用者資訊實現延遲載入,即使用到使用者資訊是再載入,不用就不載入,提供系統的執行效率。
4. 在mybatis中,只有association、collection標籤具有延遲載入的功能。

配置

注意: 要使用關聯查詢的延遲載入,就必須要使用單獨查詢形式。並且,需要先啟用MyBatis的延遲載入配置(需要配置兩項):

  1. azyLoadingEnabled:延遲載入的全域性開關(預設為false)。
  2. 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檔案的查詢語句,用到的時候才執行。

進行斷點測試:
image

  1. 第一條語句執行的時候,user的值為空,第二條語句沒有載入執行;

image

  1. 執行到session.commit();傳送了資料,user中也有值

image

註釋掉一條語句測試:
image

結果:只傳送了一條資料,也只執行了一條語句
image

但是在配置檔案中將lazyLoadingEnabled的true改為false,即使註釋了語句也還是會載入
image

確認了mybatis預設是立即載入的。

註解開發

在上面的開發過程中我們用的都是對映檔案開發,就是把查詢語句寫在xml檔案當中,然後在介面類中呼叫。
MyBatis也支援使用註解來配置對映語句。直接把語句寫在介面類中。

記得把檔案改一下,已經沒有Employee.xml檔案了。
image

主要有四種註解來實現增刪改查:@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();
	}

測試一個:
image

成功。
其他的如用註解實現動態sql、懶載入等等在這個裡面寫的非常詳細了
參考文件:mybatis註解開發

查詢快取

一級快取

Mybatis的一級快取是SqlSession級別的快取,每個SqlSession使用獨立的一級快取空間。

當執行Mapper介面中方法時,獲得一個sqlSession的物件。sqlSession物件執行緒不安全
每次操作都會使用獨立的sqlSession物件。
當sqlSession物件被建立時,sqlSession物件的內部會開闢一個快取區域(HashMap)

image

工作原理

當通過同一個SqlSession物件進行查詢操作時。一級快取會起作用。
Mybatis預設是開啟一級快取的,而且不能關閉一級快取。
image

當一個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介面中的方法時,會使用到二級快取。
image

當使用一個sqlSession物件進行查詢操作時,到二級快取中查詢,如果沒有找到,則傳送sql語句到資料庫中查詢,並把查詢結果儲存到二級快取
在兩次查詢過程中,如果執行了commit操作,則清空二級快取。
當使用另外一個sqlSession物件進行查詢操作時,到二級快取中查詢,如果找到,則不傳送sql語句查詢資料庫。

預設mybatis是開啟二級快取的,但可以在sqlMapConfig.xml檔案中配置二級快取。
image

要在需要被快取的xxxMapper.xml檔案中配置,該Mapper使用二級快取。
image

測試
第一次查詢時,到二級快取中查詢是否存在物件
image
快取命中率。 在快取中查詢物件的命中率。
第二次查詢時,二級快取中存在物件,則命中率為0.5
使用二級快取要注意: 一些異常
image

被二級快取快取的物件,有可能會被執行序列化或反序列化操作,所以 被快取的物件所屬的類必須支援序列化, 要實現Sericlizable介面。

分散式快取外掛 ehcache

需求:分散式系統,系統存在兩個子系統,在一個系統中登入,在另外一個系統上是否也能夠使用登入資訊?

image

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 實現類
image

在系統中加入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快取。

相關文章