天天看點

MyBatis(2)——MyBatis 深入學習

編寫日志輸出環境配置檔案

在開發過程中,最重要的就是在控制台檢視程式輸出的日志資訊,在這裡我們選擇使用 log4j 工具來輸出:

  • 準備工作: 将【MyBatis】檔案夾下【lib】中的 log4j 開頭的 jar 包都導入工程并添加依賴。

    在【src】下建立一個檔案 log4j.properties 資源:

# Global logging configuration
# 在開發環境下日志級别要設定成 DEBUG ,生産環境設為 INFO 或 ERROR
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
           

其中,第一條配置語句 “

log4j.rootLogger=DEBUG, stdout

” 指的是日志輸出級别,一共有 7 個級别(OFF、 FATAL、 ERROR、 WARN、 INFO、 DEBUG、 ALL)。

  • 一般常用的日志輸出級别分别為 DEBUG、 INFO、 ERROR 以及 WARN,分别表示 “調試級别”、 “标準資訊級别”、 “錯誤級别”、 “異常級别”。如果需要檢視程式運作的詳細步驟資訊,一般選擇 “DEBUG” 級别,因為該級别在程式運作期間,會在控制台才列印出底層的運作資訊,以及在程式中使用 Log 對象列印出調試資訊。
  • 如果是日常的運作,選擇 “INFO” 級别,該級别會在控制台列印出程式運作的主要步驟資訊。“ERROR” 和 “WARN” 級别分别代表 “不影響程式運作的錯誤事件” 和 “潛在的錯誤情形”。
  • 檔案中 “stdout” 這段配置的意思就是将 DEBUG 的日志資訊輸出到 stdout 參數所指定的輸出載體中。

第二條配置語句 “

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

” 的含義是,設定名為 stdout 的輸出端載體是哪種類型。

  • 目前輸出載體有

    ConsoleAppender(控制台)

    FileAppender(檔案)

    DailyRollingFileAppender(每天産生一個日志檔案)

    RollingFileAppender(檔案大小到達指定大小時産生一個新的檔案)

    WriterAppender(将日志資訊以流格式發送到任意指定的地方)

  • 這裡要将日志列印到 IDEA 的控制台,是以選擇 ConsoleAppender

第三條配置語句 “

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

” 的含義是,名為 stdout 的輸出載體的 layout(即界面布局)是哪種類型。

  • 目前輸出端的界面類型分為

    HTMLLayout(以 HTML 表格形式布局)

    PatternLayout(可以靈活地指定布局模式)

    SimpleLayout(包含日志資訊的級别和資訊字元串)

    TTCCLayout(包含日志産生的時間、線程、類别等資訊)

  • 這裡選擇靈活指定其布局類型,即自己去配置布局。

第四條配置語句 “

log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

” 的含義是,如果 layout 界面布局選擇了 PatternLayout 靈活布局類型,要指定的列印資訊的具體格式。

  • 格式資訊配置元素大緻如下:

    %m 輸出代碼中的指定的資訊

    %p 輸出優先級,即 DEBUG、 INFO、 WARN、 ERROR 和 FATAL

    %r 輸出自應用啟動到輸出該 log 資訊耗費的毫秒數

    %c 輸出所屬的類目,通常就是所在類的全名

    %t 輸出産生該日志事件的線程名

    %n 輸出一個回車換行符,Windows 平台為 “

    rn

    ”,UNIX 平台為 “

    n

    %d 輸出日志時的時間或日期,預設個事為 ISO8601,也可以在其後指定格式,比如 %d{yyy MMM dd HH:mm:ss},輸出類似:2018 年 4 月18 日 10:32:00

    %l 輸出日志事件的發生位置,包括類目名、發生的線程,以及在代碼中的行數

MyBatis 進階映射

在上一篇文章中,我們講解了一個 MyBatis 的入門程式的開發,了解了 MyBatis 開發的基本内容。今天我們先來了解一下 MyBatis 是如何處理多張資料庫表之間的關聯關系,其中包括:

  • 一對一的查詢
  • 一對多查詢
  • 多對多查詢
  • 延遲加載

一對一查詢

首先我們先來建立一個資料模型(删掉之前建立的 student 表):

use mybatis;
CREATE TABLE student (
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) DEFAULT NULL,
  card_id int(11) NOT NULL,
  PRIMARY KEY (id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE card (
  id int(11) NOT NULL AUTO_INCREMENT,
  number int(11)  NOT NULL,
  PRIMARY KEY (id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO student VALUES (1,'student1',1);
INSERT INTO student VALUES (2,'student2',2);

INSERT INTO card VALUES (1,1111);
INSERT INTO card VALUES (2,2222);
           
  • 注意: 這裡并沒有在資料庫中設定外鍵,而是讓 MyBatis 去處理多表之間的關系。事實上,外鍵隻是用來保證資料一緻性,在某些特殊的情況下(例如高并發秒殺系統中),會專門設定不适用外鍵,因為存在一定的性能損耗。

然後我們要來确認我們查詢的 SQL 語句,我們或許可以簡單的寫成下面這樣:

SELECT
    student.*,
    card.*
FROM
    student,card
WHERE student.card_id = card.id AND card.number = #{value}
           
  • 提示: 在日常開發中,總是先确定業務的具體 SQL ,再将此 SQL 配置在 Mapper 檔案中

确定了主要的查詢 SQL 後,接下來我們分别使用 resultType 和 resultMap 來實作這個一對一查詢的執行個體。

1. 使用 resultType 實作

首先建立學生 student 表所對應的 Java 實體類 Student,其中封裝的屬性資訊為響應資料庫中的字段:

package pojo;

public class Student {

	int id;
	String name;
	int card_id;

	/* getter and setter */
}
           

最終我們執行查詢(上述的 SQL 語句)的結果如下:

由于最終的查詢的結果是由 resultType 指定的,也就是隻能映射一個确定的 Java 包裝類,上面的 Stuent 類隻包含了學生的基本資訊,并沒有包含 Card 的資訊,是以我們要建立一個最終映射類,以 Student 類為父類,然後追加 Card 的資訊:

package pojo;

public class StudentAndCard extends Student {
	private int number;

	/* getter and setter /*
}
           

然後在 Student.xml 映射檔案中定義

<select>

類型的查詢語句 SQL 配置,将之前設計好的 SQL 語句配置進去,然後指定輸出參數屬性為 resultType,類型為 StudentAndCard 這個 Java 包裝類:

<select id="findStudentByCard" parameterType="_int" resultType="Student">
  SELECT
    student.*,
    card.*
  FROM
    student,card
  WHERE student.card_id = card.id AND card.number = #{value}
</select>
           

然後在測試類中編寫測試方法:

@Test
public void test() throws IOException {

	// 根據 mybatis-config.xml 配置的資訊得到 sqlSessionFactory
	String resource = "mybatis-config.xml";
	InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	// 然後根據 sqlSessionFactory 得到 session
	SqlSession session = sqlSessionFactory.openSession();

    // 找到身份證身份證号碼為 1111 的學生
	StudentAndCard student = session.selectOne("findStudentByCard",1111);
    // 獲得其姓名并輸出
	System.out.println(student.getName());
}
           

獲得正确結果:

2. 使用 resultMap 實作

使用 resultMap 可以将資料字段映射到名稱不一樣的響應實體類屬性上,重要的是,可以映射實體類中包裹的其他實體類。

首先我們來建立一個封裝了 Card 号碼和 Student 實體類的 StudentWithCard 類:

package pojo;

public class StudentWithCard {
	
	Student student;
	int number;
	int id;

	/* getter and setter */
}
           

SQL 語句依然沒有變化,但是使用的輸出映射屬性改為了 resultMap ,其中的映射類型是 id 為 StudentInfoMap 的 resultMap 配置:

<select id="findStudentByCard" parameterType="_int" resultMap="StudentInfoMap">
  SELECT
    student.*,
    card.*
  FROM
    student,card
  WHERE student.card_id = card.id AND card.number = #{value}
</select>

<resultMap id="StudentInfoMap" type="pojo.StudentWithCard">
    <!-- id 标簽表示對應的主鍵
         column 對應查詢結果的列值
         property 對應封裝類中的屬性名稱
         -->
    <id column="id" property="id"/>
    <result column="number" property="number"/>
    <!-- association 表示關聯的嵌套結果,
         可以簡單了解就是為封裝類指定的标簽 
         -->
    <association property="student" javaType="pojo.Student">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="card_id" property="card_id"/>
    </association>
</resultMap>
           

稍微修改一下測試類,測試使用 resultMap 實作的一對一查詢映射:

@Test
public void test() throws IOException {

	// 根據 mybatis-config.xml 配置的資訊得到 sqlSessionFactory
	String resource = "mybatis-config.xml";
	InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	// 然後根據 sqlSessionFactory 得到 session
	SqlSession session = sqlSessionFactory.openSession();

    // 找到身份證身份證号碼為 1111 的學生
	StudentWithCard student = session.selectOne("findStudentByCard", 1111);
    // 獲得其姓名并輸出
	System.out.println(student.getStudent().getName());
}
           

測試仍然能得到正确的結果:

還是先來建立資料模型,删掉之前的:

use mybatis;
CREATE TABLE student (
  student_id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) DEFAULT NULL,
  PRIMARY KEY (student_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE class (
  class_id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) NOT NULL,
  student_id int(11)  NOT NULL,
  PRIMARY KEY (class_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO student VALUES (1,'student1');
INSERT INTO student VALUES (2,'student2');

INSERT INTO class VALUES (1,'Java課',1);
INSERT INTO class VALUES (2,'Java課',2);
           
  • 其中 class 的

    name

    字段表示課程的名稱。

然後我們來編寫我們的 SQL 語句:

SELECT 
  student.*
FROM
  student, class
WHERE student.student_id = class.student_id AND class.class_id = #{value}
           

我們執行的結果如下:

我們再來建立對應的實體類:

public class Student {

	private int id;
	private String name;

	/* getter and setter */
}

public class Class {

	private int id;
	private String name;
	private List<Student> students;

	/* getter and setter */
}
           

在 Package【pojo】下建立一個【class.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">

<mapper namespace="class">
    <resultMap id="Students" type="pojo.Student">
        <id column="student_id" property="id"/>
        <result column="name" property="name"/>
    </resultMap>
    <select id="listStudentByClassName" parameterType="String" resultMap="Students">
        SELECT
          student.*
        FROM
          student, class
        WHERE student.student_id = class.student_id AND class.name= #{value}
    </select>
</mapper>
           

編寫測試類:

@Test
public void test() throws IOException {

	// 根據 mybatis-config.xml 配置的資訊得到 sqlSessionFactory
	String resource = "mybatis-config.xml";
	InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	// 然後根據 sqlSessionFactory 得到 session
	SqlSession session = sqlSessionFactory.openSession();

	// 查詢上Java課的全部學生
	List<Student> students = session.selectList("listStudentByClassName", "Java課");
	for (Student student : students) {
		System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());
	}
}
           

運作測試結果,成功:

建立資料模型:

use mybatis;
CREATE TABLE students (
  student_id int(11) NOT NULL AUTO_INCREMENT,
  student_name varchar(255) DEFAULT NULL,
  PRIMARY KEY (student_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE courses (
  course_id int(11) NOT NULL AUTO_INCREMENT,
  course_name varchar(255) NOT NULL,
  PRIMARY KEY (course_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE student_select_course(
  s_id int(11) NOT NULL,
  c_id int(11) NOT NULL,
  PRIMARY KEY(s_id,c_id)
) DEFAULT CHARSET=utf8;

INSERT INTO students VALUES (1,'student1');
INSERT INTO students VALUES (2,'student2');

INSERT INTO courses VALUES (1,'Java課');
INSERT INTO courses VALUES (2,'Java Web課');

INSERT INTO student_select_course VALUES(1,1);
INSERT INTO student_select_course VALUES(1,2);
INSERT INTO student_select_course VALUES(2,1);
INSERT INTO student_select_course VALUES(2,2);
           

根據要求我們來設計一下 SQL 語言:

SELECT
    s.student_id,s.student_name
FROM
    students s,student_select_course ssc,courses c
WHERE s.student_id = ssc.s_id 
AND ssc.c_id = c.course_id 
AND c.course_name = #{value}
           

執行 SQL 結果如下:

實體類雷同,就不再贅述,我們直接來配置映射檔案【Student.xml】:

<resultMap id="Students" type="pojo.Student">
    <id property="id" column="student_id"/>
    <result column="student_name" property="name"/>
</resultMap>

<select id="findStudentsByCourseName" parameterType="String" resultMap="Students">
    SELECT
      s.student_id,s.student_name
    FROM
      students s,student_select_course ssc,courses c
    WHERE s.student_id = ssc.s_id
    AND ssc.c_id = c.course_id
    AND c.course_name = #{value}
</select>
           

測試類也雷同,隻需要修改一下調用的 id (改為findStudentsByCourseName)就好了,直接上測試結果:

相反也是一樣的,重要的是 SQL 語句和映射。

總結:

  • 自己寫的 SQL 語句看着雖然沒有很惡心(至少思路清晰),但感覺很爛!
  • 結合 SQL 語言和映射檔案,能夠很友善的操作資料庫
  • 資料庫還是建立外鍵得好....(啪啪打臉,根據《阿裡Java開發手冊》裡提到,最好不要建外鍵,而讓程式的Service層去做判斷)

什麼是延遲加載?從字面上了解,就是對某一類資訊的加載之前需要延遲一會兒。在 MyBatis 中,通常會進行多表聯合查詢,但是有的時候不會立即用到所有的聯合查詢結果,這時候就可以采用延遲加載的功能。

  • 功能: 延遲加載可以做到,先從單表查詢,需要時再從關聯表關聯查詢,這樣就大大提高了資料庫的性能,因為查詢單表要比關聯查詢多張表速度快。
  • 執行個體: 如果查詢訂單并且關聯查詢使用者資訊。如果先查詢訂單資訊即可滿足要求,當我們需要查詢使用者資訊時再查詢使用者資訊。把對使用者資訊的按需去查詢就是延遲加載。
關聯查詢:
SELECT 
    orders.*, user.username 
FROM orders, user
WHERE orders.user_id = user.id
延遲加載相當于:
SELECT 
    orders.*,
    (SELECT username FROM USER WHERE orders.user_id = user.id)
    username 
FROM orders
           

是以這就比較直覺了,也就是說,我把關聯查詢分兩次來做,而不是一次性查出所有的。第一步隻查詢單表orders,必然會查出orders中的一個user_id字段,然後我再根據這個user_id查user表,也是單表查詢。

參考文章:[ 【MyBatis學習11】MyBatis中的延遲加載

](https://blog.csdn.net/eson_15/article/details/51668523)

Mapper 映射配置編寫

首先在 Mapper 映射檔案中定義隻查詢所有訂單資訊的 SQL :

<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap">
    SELECT * FROM orders
</select>
           

上面的 SQL 語句查詢所有的訂單資訊,而每個訂單資訊中會關聯查詢使用者,但由于希望延遲加載使用者資訊,是以會在 id 為 "

OrdersUserLazyLoadingResultMap

" 的 resultMap 對應的結果集配置中進行配置:

最後配置延遲加載要執行的擷取使用者資訊的 SQL:

<select id="findUserById" parameterType="int" resultType="user">
    select * from user where id = #{id}
</select>
           

上面的配置會被用來延遲加載的 resultMap 中的 association 調用,輸入參數就是 association 中 column 中定義的字段資訊。

在編寫測試方法之前,首先需要開啟延遲加載功能(這在 MyBatis 中預設是禁用掉的)。這需要在 MyBatis 的全局配置檔案 mybatis-config.xml 中配置 setting 屬性,将延遲加載(lazyLoadingEnable)的開關設定成 “

ture

” ,并且由于是按需加載,是以還需要将積極加載改為消極加載:

<settings>
    <!-- 打開延遲加載的開關 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 将積極加載改為消極加載,即延遲加載 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
           
  • 注意: 在 configuration 中配置是有一定順序的,具體可以按住【Ctrl】不放點選 configuration 屬性,能看到如下資訊(即定義的順序):
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
           

Mapper 動态代理

什麼是 Mapper 動态代理?一般建立 Web 工程時,從資料庫取資料的邏輯會放置在 DAO 層(Date Access Object,資料通路對象)。使用 MyBatis 開發 Web 工程時,通過 Mapper 動态代理機制,可以隻編寫資料互動的接口及方法定義,和對應的 Mapper 映射檔案,具體的互動方法實作由 MyBatis 來完成。這樣大大節省了開發 DAO 層的時間。

實作 Mapper 代理的方法并不難,隻需要遵循一定的開發規範即可。

Mapper 代理執行個體編寫

我們編寫一個使用 Mapper 代理查詢學生資訊的示例,首先還是在【pojo】下建立一個名為 StudentMapper.xml 的 Mapper 配置檔案,其中包含了對 Student 的增删改查的 SQL 配置:

<?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">

<mapper namespace="mapper.StudentMapper">
    <!-- 查詢學生 -->
    <select id="findStudentById" parameterType="_int" resultType="pojo.Student">
        SELECT * FROM student WHERE student_id = #{id}
    </select>
    <!-- 增加使用者 -->
    <insert id="insertStudent" parameterType="pojo.Student">
        INSERT INTO student(student_id, name) VALUES(#{id}, #{name})
    </insert>
    <!-- 删除使用者 -->
    <delete id="deleteStudent" parameterType="_int">
        DELETE FROM student WHERE student_id = #{id}
    </delete>
    <!-- 修改使用者 -->
    <update id="updateStudent" parameterType="pojo.Student">
        UPDATE student SET name = #{name} WHERE student_id = #{id}
    </update>
</mapper>
           

如果需要使用 StudentMapper.xml 的 Mapper 代理,首先需要定義一個接口,名為 StudentMapper。然後在裡面建立四個方法定義,分别對應 StudentMapper.xml 中的 Student 的增删改查的 SQL 配置,然後将 StudentMapper 中的 namespace 改為 StudentMapper 接口定義的地方(也就是 mapper 包下的 StudentMapper),這樣就可以在業務類中使用 Mapper 代理了,接口代碼如下:

package mapper;

import pojo.Student;

public interface StudentMapper {

	// 根據 id 查詢學生資訊
	public Student findStudentById(int id) throws Exception;

	// 添加學生資訊
	public void insertStudent(Student student) throws Exception;

	// 删除學生資訊
	public void deleteStudent(int id) throws Exception;

	// 修改學生資訊
	public void updateStudent(Student student) throws Exception;
}
           
  • 注意: 别忘了在 mybatis-config.xml 中配置一下 Mapper 映射檔案

測試動态代理

在測試方法中,使用 SqlSession 類的 getMapper 方法,并将要加載的 Mapper 代理的接口類傳遞進去,就可以獲得相關的 Mapper 代理對象,使用 Mapper 代理對象去對學生資訊進行增删改查:

@Test
public void test() throws Exception {

	// 根據 mybatis-config.xml 配置的資訊得到 sqlSessionFactory
	String resource = "mybatis-config.xml";
	InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	// 然後根據 sqlSessionFactory 得到 session
	SqlSession session = sqlSessionFactory.openSession();
	// 擷取 Mapper 代理
	StudentMapper studentMapper = session.getMapper(StudentMapper.class);
	// 執行 Mapper 代理獨享的查詢方法
	Student student = studentMapper.findStudentById(1);
	System.out.println("學生的姓名為:" + student.getName());
	session.close();
}
           

運作測試方法,看到正确的結果:

使用 Mapper 代理可以讓開發更加簡潔,使查詢結構更加清晰,工程結構更加規範。

使用注解開發 MyBatis

在上面的例子中,我們已經有了友善的 Mapper 代理對象,我們可以進一步省掉 XML 的配置資訊,進而使用友善的注解來開發 MyBatis ,讓我們實際來操練一下:

第一步:為 Mapper 增加注解

我們把 StudentMapper.xml 下配置的 SQL 語句通過注解的方式原封不動的配置在 StudentMapper 接口中:

public interface StudentMapper {

	// 根據 id 查詢學生資訊
	@Select("SELECT * FROM student WHERE student_id = #{id}")
	public Student findStudentById(int id) throws Exception;

	// 添加學生資訊
	@Insert("INSERT INTO student(student_id, name) VALUES(#{id}, #{name})")
	public void insertStudent(Student student) throws Exception;

	// 删除學生資訊
	@Delete("DELETE FROM student WHERE student_id = #{id}")
	public void deleteStudent(int id) throws Exception;

	// 修改學生資訊
	@Update("UPDATE student SET name = #{name} WHERE student_id = #{id}")
	public void updateStudent(Student student) throws Exception;
}
           

第二步:修改 mybatis-config.xml

将之前配置的映射注釋掉,建立一條:

<!-- 映射檔案 -->
<mappers>
    <!--<mapper resource="pojo/StudentMapper.xml"/>-->
    <mapper class="mapper.StudentMapper"/>
</mappers>
           
  • 注意: 這次映射的并不是檔案(使用

    resource

    屬性),而是類(使用

    class

    屬性)

第三步:運作測試代碼

上面的測試代碼不用修改,直接運作,也能得到正确結果:

更多的注解:戳這裡

MyBatis 緩存結構

在 Web 系統中,最重要的操作就是查詢資料庫中的資料。但是有些時候查詢資料的頻率非常高,這是很耗費資料庫資源的,往往會導緻資料庫查詢效率極低,影響客戶的操作體驗。于是我們可以将一些變動不大且通路頻率高的資料,放置在一個緩存容器中,使用者下一次查詢時就從緩存容器中擷取結果。

  • MyBatis 擁有自己的緩存結構,可以用來緩解資料庫壓力,加快查詢速度。
  • mybatis一級緩存是一個SqlSession級别,sqlsession隻能通路自己的一級緩存的資料
  • 二級緩存是跨sqlSession,是mapper級别的緩存,對于mapper級别的緩存不同的sqlsession是可以共享的。

一級查詢緩存

一級查詢存在于每一個 SqlSession 類的執行個體對象中,當第一次查詢某一個資料時,SqlSession 類的執行個體對象會将該資料存入一級緩存區域,在沒有收到改變該資料的請求之前,使用者再次查詢該資料,都會從緩存中擷取該資料,而不是再次連接配接資料庫進行查詢。

  • MyBatis 的一級緩存原理:

第一次發出一個查詢 sql,sql 查詢結果寫入 sqlsession 的一級緩存中,緩存使用的資料結構是一個 map

  • key:hashcode+sql+sql輸入參數+輸出參數(sql的唯一辨別)
  • value:使用者資訊

同一個 sqlsession 再次發出相同的 sql,就從緩存中取不走資料庫。如果兩次中間出現 commit 操作(修改、添加、删除),本 sqlsession 中的一級緩存區域全部清空,下次再去緩存中查詢不到是以要從資料庫查詢,從資料庫查詢到再寫入緩存。

一級緩存示例

  • 我們在同一個 session 中查詢兩次 id = 1 的 Category 對象試一試:
public static void main(String[] args) throws IOException {
	String resource = "mybatis-config.xml";
	InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	SqlSession session1 = sqlSessionFactory.openSession();

	Category c1 = session1.selectOne("getCategory", 1);
	System.out.println(c1);
	Category c2 = session1.selectOne("getCategory", 1);
	System.out.println(c2);

	session1.commit();
	session1.close();

}
           

運作,可以看到第一次會去資料庫中取資料,但是第二次就不會通路資料庫了,而是直接從session中取出來:

  • 我們再來測試一下在不同 session 裡查詢相同的 id 資料
public static void main(String[] args) throws IOException {
	String resource = "mybatis-config.xml";
	InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	SqlSession session1 = sqlSessionFactory.openSession();

	Category c1 = session1.selectOne("getCategory", 1);
	System.out.println(c1);
	Category c2 = session1.selectOne("getCategory", 1);
	System.out.println(c2);

	session1.commit();
	session1.close();

	SqlSession session2 = sqlSessionFactory.openSession();
	Category c3 = session2.selectOne("getCategory", 1);
	System.out.println(c3);
	session2.commit();
	session2.close();

}
           

這一次,另外打開一個 session , 取同樣 id 的資料,就會發現需要執行 sql 語句,證明了一級緩存是在 session 裡的:

MyBatis 一級緩存值得注意的地方:

  • MyBatis 預設就是支援一級緩存的,并不需要我們配置.
  • MyBatis 和 spring 整合後進行 mapper 代理開發,不支援一級緩存,mybatis和 spring 整合,spring 按照 mapper 的模闆去生成 mapper 代理對象,模闆中在最後統一關閉 sqlsession。

二級查詢緩存

  • 問題: 有些時候,在 Web 工程中會将執行查詢操作的方法封裝在某個 Service 方法中,當查詢完一次後,Service 方法結束,此時 SqlSession 類的執行個體對象就會關閉,一級緩存就會被清空。
  • 二級緩存原理:

二級緩存的範圍是 mapper 級别(mapper即同一個命名空間),mapper 以命名空間為機關建立緩存資料結構,結構是 map。

要開啟二級緩存,需要進行兩步操作。

第一步:在 MyBatis 的全局配置檔案 mybatis-config.xml 中配置 setting 屬性,設定名為 “

cacheEnable

” 的屬性值為 “

true

” 即可:

<settings>
    <!-- 開啟二級緩存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>
           
  • 注意: settings 配置的位置一定是在 properties 後面,typeAliases前面!

第二步:然後由于二級緩存是 Mapper 級别的,還要在需要開啟二級緩存的具體 mapper.xml 檔案中開啟二級緩存,隻需要在相應的 mapper.xml 中添加一個 cache 标簽即可:

<!-- 開啟本 Mapper 的 namespace 下的二級緩存 -->
<cache />
           

開啟二級緩存之後,我們需要為查詢結果映射的 POJO 類實作

java.io.serializable

接口,二級緩存可以将記憶體的資料寫到磁盤,存在對象的序列化和反序列化,是以要實作java.io.serializable接口。

二級緩存示例

我們在同一個 SessionFactory 下查詢 id = 1 的資料,隻有第一次需要執行 SQL 語句,從後都是從緩存中取出來的:

參考資料:how2j.cn-MyBatis教程、Java3y-Mybatis【緩存、代理、逆向工程】

參考資料:

  • 《Java EE 網際網路輕量級架構整合開發》
  • 《Spring MVC + MyBatis開發從入門到項目實戰》
  • How2j-MyBatis 系列教程
  • 全能的百度和萬能的大腦

歡迎轉載,轉載請注明出處!

簡書ID:@我沒有三顆心髒

github:wmyskxz

歡迎關注公衆微信号:wmyskxz_javaweb

分享自己的Java Web學習之路以及各種Java學習資料

繼續閱讀