MyBatis(2)——MyBatis 深刻學習

編寫日誌輸出環境配置文件

在開發過程當中,最重要的就是在控制檯查看程序輸出的日誌信息,在這裏咱們選擇使用 log4j 工具來輸出:html

  • 準備工做: 將【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)。java

  • 通常經常使用的日誌輸出級別分別爲 DEBUG、 INFO、 ERROR 以及 WARN,分別表示 「調試級別」、 「標準信息級別」、 「錯誤級別」、 「異常級別」。若是須要查看程序運行的詳細步驟信息,通常選擇 「DEBUG」 級別,由於該級別在程序運行期間,會在控制檯纔打印出底層的運行信息,以及在程序中使用 Log 對象打印出調試信息。
  • 若是是平常的運行,選擇 「INFO」 級別,該級別會在控制檯打印出程序運行的主要步驟信息。「ERROR」 和 「WARN」 級別分別表明 「不影響程序運行的錯誤事件」 和 「潛在的錯誤情形」。
  • 文件中 「stdout」 這段配置的意思就是將 DEBUG 的日誌信息輸出到 stdout 參數所指定的輸出載體中。

第二條配置語句 「log4j.appender.stdout=org.apache.log4j.ConsoleAppender」 的含義是,設置名爲 stdout 的輸出端載體是哪一種類型。git

  • 目前輸出載體有 ConsoleAppender(控制檯) FileAppender(文件) DailyRollingFileAppender(天天產生一個日誌文件) RollingFileAppender(文件大小到達指定大小時產生一個新的文件) WriterAppender(將日誌信息以流格式發送到任意指定的地方)
  • 這裏要將日誌打印到 IDEA 的控制檯,因此選擇 ConsoleAppender

第三條配置語句 「log4j.appender.stdout.layout=org.apache.log4j.PatternLayout」 的含義是,名爲 stdout 的輸出載體的 layout(即界面佈局)是哪一種類型。github

  • 目前輸出端的界面類型分爲 HTMLLayout(以 HTML 表格形式佈局) PatternLayout(能夠靈活地指定佈局模式) SimpleLayout(包含日誌信息的級別和信息字符串) TTCCLayout(包含日誌產生的時間、線程、類別等信息)
  • 這裏選擇靈活指定其佈局類型,即本身去配置佈局。

第四條配置語句 「log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n」 的含義是,若是 layout 界面佈局選擇了 PatternLayout 靈活佈局類型,要指定的打印信息的具體格式。web

  • 格式信息配置元素大體以下: %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 是如何處理多張數據庫表之間的關聯關係,其中包括:spring

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

一對一查詢

首先咱們先來創建一個數據模型(刪掉以前建立的 student 表):sql

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 來實現這個一對一查詢的實例。apache

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中的延遲加載 ](http://www.javashuo.com/article/p-qlkugbfa-ca.html)

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

相關文章
相關標籤/搜索