Mybatis框架

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

MyBatis核心接口和類

1. SqlSessionFactoryBuilder負責構建SqlSessionFactory,而且提供了多個build()方法的重載。也就是說:此對象能夠從xml配置文件,或從Configuration對象來構建SqlSessionFactory。
2. SqlSessionFactory就是建立SqlSession實例的工廠。經過openSession方法來獲取SqlSession對象。並且,SqlSessionFactory一旦被建立,那麼在整個應用程序期間都存在。
3. SqlSession是一個面向程序員的接口,它提供了面向數據庫執行sql命令所需的全部方法。SqlSession對應一次數據庫會話,它是線程不安全的。
html

封裝持久層

MyBatis開發DAO層有兩種方式:java

  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接口:web

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的實現類:sql

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方式存在不少問題:apache

  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緩存。

相關文章
相關標籤/搜索