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}

我們執行的結果如下:

圖片.png

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

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學習資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM