1. 程式人生 > 其它 >MyBatis詳細介紹

MyBatis詳細介紹

技術標籤:java框架資料庫javaspringmysqlssm

1 簡介

mybatis官網

1.1 什麼是Mybatis

  • MyBatis 是一款優秀的持久層框架
  • 它支援定製化 SQL、儲存過程以及高階對映。
  • MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。
  • MyBatis 可以使用簡單的 XML 或註解來配置和對映原生資訊,將介面和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java物件)對映成資料庫中的記錄。
  • MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了[google code](https://baike.baidu.com/item/google code/2346604),並且改名為MyBatis 。
  • 2013年11月遷移到Github

獲取Mybatis

  • maven倉庫

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    
  • github : https://github.com/mybatis/mybatis-3/releases

  • 中文文件:https://mybatis.org/mybatis-3/zh/index.html

1.2 特點

  • 簡單易學
    • 本身就很小且簡單。
    • 沒有任何第三方依賴,最簡單安裝只要兩個jar檔案+配置幾個sql對映檔案易於學習
    • 易於使用,通過文件和原始碼,可以比較完全的掌握它的設計思路和實現。
  • 靈活:
    • mybatis不會對應用程式或者資料庫的現有設計強加任何影響。
    • sql寫在xml裡,便於統一管理和優化。通過sql語句可以滿足操作資料庫的所有需求。
  • 鬆耦合
    • 解除sql與程式程式碼的耦合
    • 通過提供DAO層,將業務邏輯和資料訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。
    • sql和程式碼的分離,提高了可維護性。
  • 提供標籤
    • 提供對映標籤,支援物件與資料庫的orm欄位關係對映
    • 提供物件關係對映標籤,支援物件關係組建維護
    • 提供xml標籤,支援編寫動態sql。

1.3 持久層

持久化:將資料從瞬時狀態轉化為持久狀態。

之所以有持久層,就是將資料能斷電後儲存。一些其他原因,比如記憶體太貴

Dao層就是一個持久層,層的概念是為了解耦合。

MyBatis 是一款優秀的持久層框架

1.4 為什麼使用Mybatis

  • 非常方便將資料庫存入到資料庫中
  • 框架,簡化JDBC,自動化
  • 不使用Mybatis也能持久化,但是它更容易、更方便上手
  • 優點:簡單易學、靈活、sql與程式碼分離、可維護性、標籤
  • 最重要的一點:使用的人多

2 第一個Mybatis程式

pojos

package com.zl.pojos;

//對應資料庫的一張表結構
public class Students {
    private int id;
    private int age;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
...
}

DAO介面

public interface StudentsMapper {
    public List<Students> getStudents();
}

對映檔案

<?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 namespace="com.zl.dao.StudentsMapper">
    <select id="getStudents" resultType="com.zl.pojos.Students">
        select * from students
    </select>
</mapper>

核心配置檔案

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--環境,可以配置多個-->
<environments default="development">
    <environment id="development">
        <!--事務型別-->
        <transactionManager type="JDBC"/>
        <!--type:執行緒池-->
        <dataSource type="POOLED"> 
            <!--四個配置項-->
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>
<mappers>
    <!--繫結對映檔案-->
    <mapper resource="com/zl/dao/StudentsMapper.xml"/>
</mappers>
</configuration>

工具類

public class Mybatis_utils {
    //SqlSessionFactory建立單例,可以重複使用,建立SqlSession
    private static SqlSessionFactory sqlSessionFactory;

    static{
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //SqlSessionFactoryBuilder在建立sqlSessionFactory即可刪除
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

測試

public class Test {

    @org.junit.Test
    public void test1(){
        //SqlSession不是執行緒安全的,每次使用完之後應該關閉
        try(SqlSession session = Mybatis_utils.getSqlSession()){
            StudentsMapper students = session.getMapper(StudentsMapper.class);
            List<Students> list = students.getStudents();

            for(Students s: list){
                System.out.println(s);
                System.out.println("");
            }
        }
    }

}

3 CRUD

更新

<insert id="addStudents" parameterType="com.zl.pojos.Students" >
        insert into students (`name`,`age`)  values (#{name}, #{age})
</insert>

<update id="updateStudents" parameterType="com.zl.pojos.Students">
    update students set `name` = #{name},`age` = #{age} where `id` = #{id}
</update>

<delete id="deleteStudents" parameterType="com.zl.pojos.Students">
    delete from `students` where `id` = #{id}
</delete>

測試

public void updateTest(){
    try(SqlSession session = Mybatis_utils.getSqlSession()){
        StudentsMapper mapper = session.getMapper(StudentsMapper.class);
        Students students = new Students(0, 12, "Jane");

        int line = mapper.addStudents(students);

        System.out.println("line:" + line);

        students.setId(1);
        line = mapper.updateStudents(students);
        System.out.println("line:" + line);

        students.setId(2);
        line = mapper.deleteStudents(students);
        System.out.println("line:" + line);

        session.commit();

        List<Students> list = mapper.getStudents();

        for(Students s: list){
            System.out.println(s);
            System.out.println("");
        }

    }
}

parameterType擴充套件

  • map 使用鍵值對傳參
  • 單個傳參查詢,無需指定parameterType
  • 模糊查詢
<select id="getStudentsById"  resultType="com.zl.pojos.Students">
        select * from `students` where `id` = #{id}
</select>

<select id="getStudentsByName" parameterType="map" resultType="com.zl.pojos.Students">
    select * from `students` where `name` = #{sname}
</select>

<select id="getStudentsLikeName" resultType="com.zl.pojos.Students">
    select * from `students` where `name` like #{name}
</select>

測試

try(SqlSession session = Mybatis_utils.getSqlSession()){
    StudentsMapper students = session.getMapper(StudentsMapper.class);
    List<Students> list = students.getStudentsById(3);

    showList(list);

    Map<String, Object> map = new HashMap<>();
    map.put("sname", "Tom"); //通過鍵值對,可以多個
    list = students.getStudentsByName(map);

    showList(list);

    list = students.getStudentsLikeName("_o%");

    showList(list);
}

4 配置解析

研究核心配置檔案中的屬性配置

4.1 環境配置(environments)

MyBatis 可以配置成適應多種環境,比如開發環境、上線環境、測試環境等。

儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境。

<!--default選擇預設環境-->
<environments default="development">

事務管理器(transactionManager)

<transactionManager type=""[JDBC|MANAGED]"">
  • JDBC – 這個配置直接使用了 JDBC 的提交和回滾設施,它依賴從資料來源獲得的連線來管理事務作用域,常用

  • MANAGED - 讓容器來管理事務的整個生命週期(比如 JEE 應用伺服器的上下文)

    如果使用 Spring + MyBatis,則沒有必要配置事務管理器,因為 Spring 模組會使用自帶的管理器來覆蓋前面的配置。

資料來源(dataSource)

<dataSource type="[UNPOOLED|POOLED|JNDI]">
  • UNPOOLED– 這個資料來源的實現會每次請求時開啟和關閉連線。
  • POOLED常用型別選擇,這種資料來源的實現利用“池”的概念將 JDBC 連線物件組織起來,避免了建立新的連線例項時所必需的初始化和認證時間。
  • JNDI – 這個資料來源實現是為了能在如 EJB 或應用伺服器這類容器中使用。在EJB用,目前很少用

4.2 屬性(properties)

這些屬性可以在外部進行配置,並可以進行動態替換。

<properties resource="org/mybatis/example/config.properties">
  <!--以下的property也可以不寫,會自動載入配置檔案中的內容-->
  <property name="username" value="root"/>
  <property name="password" value="123456"/>
</properties>

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

配置檔案

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456

注意點:核心配置中,各個配置有嚴格的順序

The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".

4.3 設定(settings)

這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的執行時行為。

常用的setting配置

<settings>
    <!--全域性性地開啟或關閉所有對映器配置檔案中已配置的任何快取-->
    <setting name="cacheEnabled" value="true"/>
    <!--延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--允許 JDBC 支援自動生成主鍵,需要資料庫驅動支援-->
    <setting name="useGeneratedKeys" value="false"/>
    <!--是否開啟駝峰命名自動對映,即從經典資料庫列名 A_COLUMN 對映到經典 Java 屬性名 aColumn-->
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <!--指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢-->
    <setting name="logImpl" value="JDK_LOGGING "/>
</settings>

4.4 類型別名(typeAliases)

類型別名可為 Java 型別設定一個縮寫名字。 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫。

<typeAliases>
    <package name="com.zl.pojos"/>
</typeAliases>

<typeAliases>
    <typeAlias type="com.zl.pojos.Students" alias="stu"/>
</typeAliases>

有兩種方式:

  • 指定包名,使用時直接使用類名,Mybatis會在包下搜尋需要的Jave Bean
  • 直接為單一型別增加別名,使用時使用別名即可
<!--已指定包名,直接使用students,首字母也可以不用大寫-->
<select id="getStudents" resultType="students">
    select * from students
</select>

<!--使用stu-->
<select id="getStudents" resultType="stu">
    select * from students
</select>

4.5 mappers(對映器)

作用:告訴Mybatis到哪裡去找尋SQL語句。

方式一:

<!-- 使用相對於類路徑的資源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

方式二:因為是指定類名,所以資源名要與類名同名、且在同一目錄下

<!-- 使用對映器介面實現類的完全限定類名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

方式三:同方式二,因為是指定類名,所以資源名要與類名同名、且在同一目錄下

<!-- 將包內的對映器介面實現全部註冊為對映器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

4.6 其他配置

4.7 生命週期和作用域

不同作用域和生命週期類別是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題

  • SqlSessionFactoryBuilder

    • 一旦建立了 SqlSessionFactory,就不再需要它了
    • 最佳作用域是方法作用域
  • SqlSessionFactory

    • SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在
    • 沒有任何理由丟棄它或重新建立另一個例項
    • 它維護一個連線池,供SqlSession獲取連線
  • SqlSession

    • 每個執行緒都應該有它自己的 SqlSession 例項
    • SqlSession 的例項不是執行緒安全的,因此是不能被共享的
    • 最佳的作用域是請求或方法作用域,即使用完就關閉

4.8 結果對映

結果集可以使用一個map接收,也可以使用JavaBean或者POJO接收,之前的例子都是使用POJO接收。

使用map接收:

<select id="getStudents" resultType="map">
  select id, username, hashedPassword from students
</select>

使用Pojo接收:

<select id="getStudents" resultMap="students">
    select * from students
</select>

問題:使用Pojo接收時,資料庫列名與物件類的屬性名不一致怎麼辦?如下 “name”與“sName”

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xBVqw4pF-1613116023827)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208160905129.png)][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-8J0m73ly-1613116023829)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208160927986.png)]

解決:使用結果對映

<!--建立一個resultMap結果對映-->
<resultMap id="myMap" type="students">
    <!--colume代表資料庫列名,property代表要對映到的類的屬性名-->
    <result property="sName" column="name"/>
    <!--未明確標出的對映,會自動安裝列名與屬性名相同對映-->
</resultMap>

<select id="getStudents" resultMap="myMap">
    select * from students
</select>

結論:

  • resultMap 元素是 MyBatis 中最重要最強大的元素。
  • 它可以讓你從 90% 的 JDBC ResultSets 資料提取程式碼中解放出來
  • ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
  • 如果這個世界總是這麼簡單就好了。對於複雜的SQL語句,就沒那麼簡單了,在後面繼續介紹。

5 日誌

5.1 日誌工廠STDOUT_LOGGING

在核心配置檔案中配置setting即可,無需外載入jar包

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-1ZpbesY3-1613116023833)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208163732473.png)]

5.2 log4j

log4J是java常用日誌工具,全稱是log for java

介紹:

  • 通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器等
  • 可以控制每一條日誌的輸出格式
  • 通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。
  • 最重要的是,可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。

配置:

<settings>
	<setting name="logImpl" value="LOG4J"/>
</settings>

需要引入jar包,maven引入

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

控制日誌的配置檔案

#將等級為DEBUG的日誌資訊輸出到console和file這兩個目的地,console和file的定義在下面的程式碼
log4j.rootLogger=DEBUG,console,file

#控制檯輸出的相關設定
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#檔案輸出的相關設定
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/zl.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日誌輸出級別
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

MyBatis使用輸出示例:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Zl2jrHvV-1613116023835)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208170633107.png)]

自用使用示例:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zB3ZyCnO-1613116023837)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208171225481.png)]

6 分頁

為什麼使用分頁

  • 分頁有利於提取少量資料,提高響應速度
  • 減少系統負擔

6.1 使用Limit分頁

資料庫分頁的本質都是包含limit的SQL語句分頁。

mybatis分頁示例:

<select id="getStudentsLimit" parameterType="map" resultType="students">
    select * from `students` limit #{start},#{size}
</select>

測試:

@org.junit.Test
    public void testPage(){
    try (SqlSession session = Mybatis_utils.getSqlSession()){
        StudentsMapper mapper = session.getMapper(StudentsMapper.class);

        int start = 0;
        int size = 4;
        int page = 1;

        Map<String,Integer> map = new HashMap<>();
        map.put("start", start);
        map.put("size",size);

        List<Students> list = mapper.getStudentsLimit(map);
        while(list.size() > 0) {
            showList(list);
            System.out.println(page + " page");

            start = page * size;
            page++;

            map.put("start", start);
            list = mapper.getStudentsLimit(map);
        }

    }
}

注意點:如果使用傳兩個引數(基本型別和String)的方法,需要使用@Param註解,引數是列名。

import org.apache.ibatis.annotations.Param;
selectAlbum(@Param("userid") Integer userid, @Param("albumName") String albumName);

6.2 RowBonds分頁

以前的程式碼使用的比較多,目前較少使用,看到老程式碼不慌!

6.3 分頁外掛

比較流行的PageHelper

7 使用註解

提一句:面向介面程式設計,介面是定義一系列的行為,是物件與物件互動的協議,面向介面程式設計最大的好處就是解耦合,從而帶來可擴充套件性、可維護性、分層開發、分模組開發等。

7.1 註解示例

首先,在方法上定義sql語句的註解

public interface UserMapper {

    @Select("select * from user")
    List<User> getUsers();
}

因為沒有xml檔案了,所以在核心配置檔案中,不能通過對映mapper檔案來繫結,可以通過之前介紹的後兩種方法:指定類名和指定包

<mappers>
    <!--指定類名-->
    <mapper class="com.zl.dao.UserMapper"/>
</mappers>

測試

@Test
public void annotationTest(){
    try(SqlSession session = Mybatis_utils.getSqlSession()){
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.getUsers();

        for (User user : users) {
            System.out.println(user);
        }
    }
}

**特別說明:**使用註解來對映簡單語句會使程式碼顯得更加簡潔,但對於稍微複雜一點的語句,Java 註解不僅力不從心,還會讓你本就複雜的 SQL 語句更加混亂不堪。 因此,如果你需要做一些很複雜的操作,最好用 XML 來對映語句。

7.2 本質

  • 反射
  • 動態代理

7.3 流程解析

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-nIUtvB2w-1613116023838)(C:\Users\zl\Downloads\1.png)]

7.4 使用註解CRUD

@Select("select * from user")
List<User> getUsers();

@Insert("insert into `user` (`name`,`pwd`) values(#{name}, #{pwd})")
int addUsers(User user);

@Update("update `user` set `name`=#{name} where `id` = #{id}")
int updateUserName(@Param("id") int id,  @Param("name") String name);

@Delete("delete from `user` where `id` = #{id}")
int deleteUser(@Param("id") int id);

測試方法和用xml對映是一樣的。

#{} ${}的區別:

  • #{}是預編譯,${}是字元拼接
  • 使用#{}防止SQL注入,提高系統的安全性。

8 Lombok

Lombok是一種 Java實用工具,可用來幫助開發人員消除Java的冗長,尤其是對於簡單的Java物件(POJO), 它通過註釋實現這一目的。

安裝步驟:

  • IDEA中下載外掛
  • 匯入Jar包(maven倉庫)

Lombok註解

  • @Data:註解在類上,將類提供的所有屬性都新增get、set方法,並新增、equals、canEquals、hashCode、toString方法
  • @Setter:註解在類上,為所有屬性新增set方法、註解在屬性上為該屬性提供set方法
  • @Getter:註解在類上,為所有的屬性新增get方法、註解在屬性上為該屬性提供get方法
  • @NotNull:在引數中使用時,如果呼叫時傳了null值,就會丟擲空指標異常
  • @Synchronized 用於方法,可以鎖定指定的物件,如果不指定,則預設建立一個物件鎖定
  • @Log作用於類,建立一個log屬性
  • @Builder:使用builder模式建立物件
  • @NoArgsConstructor:建立一個無參建構函式
  • @AllArgsConstructor:建立一個全參建構函式
  • @ToString:建立一個toString方法
  • @Accessors(chain = true)使用鏈式設定屬性,set方法返回的是this物件。
  • @RequiredArgsConstructor:建立物件, 例: 在class上新增@RequiredArgsConstructor(staticName = “of”)會建立生成一個靜態方法
  • @UtilityClass:工具類
  • @ExtensionMethod:設定父類
  • @FieldDefaults:設定屬性的使用範圍,如private、public等,也可以設定屬性是否被final修飾。
  • @Cleanup: 關閉流、連線點。
  • @EqualsAndHashCode:重寫equals和hashcode方法。
  • @Cleanup: 用於流等可以不需要關閉使用流物件.

使用:

import lombok.Data;

//將類提供的所有屬性都新增get、set方法,並新增、equals、canEquals、hashCode、toString方法
@Data
public class User {
    private int id;
    private String name;
    private String pwd;
    private String phone;
}

Lombok的爭議:

Lombok為java程式碼的精簡提供了一種方式,但是**所有的原始碼很多時候是用來閱讀的,只有很少的時間是用來執行的。**Lombok會降低程式碼的可讀性,而且對於升級和擴充套件帶來了一定潛在難度。

9 關聯查詢

當一個表需要同時關聯另一個表查詢時,需要用到association關鍵字。

比如,查詢一個學生表,附帶其中個的老師資訊,學生表的屬性是(id,name,tid),tid指向老師表的id。

create table students
(
    id   int auto_increment comment 'ID' primary key,
    name varchar(100) not null comment '姓名',
    tid  int          not null
)

create table teacher
(
    id   int auto_increment primary key,
    name varchar(20) not null
)

學生表的POJO

import lombok.Data;

@Data
public class Student {
    private int id;
    private String name;
    private Teacher teacher; //Teacher類
}

有兩種方式查詢,一種是連線查詢,一種是子查詢

<!--連線查詢-->
<resultMap id="sMap" type="student">
    <!--property是student類屬性名,column代表查詢出的表的列名-->
    <result column="sname" property="name"/>
    <result column="sid" property="id"/>
    <!--javaType需要對映到的類-->
    <association property="teacher" javaType="Teacher">
        <!--這裡表示的是JavaType的對映-->
        <result column="tid" property="id"/>
        <result column="tname" property="name"/>
    </association>
</resultMap>

<select id="getStudents2" resultMap="sMap">
    select s.id sid, s.name sname, t.id tid, t.name tname
    from `students` as s
    left join `teacher` as t
    on s.tid = t.id
</select>

<!--子查詢-->
<resultMap id="stMap" type="student">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
     <!--select代表子查詢,查詢的結果對映到javaType中,column代表傳遞給子查詢的引數-->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
</resultMap>

<select id="getStudents" resultMap="stMap">
    select * from `students`
</select>

<select id="getTeacherById" resultType="Teacher">
    select * from `teacher` where `id` = #{id}
</select>

結果:

Student(id=3, name=李四, teacher=Teacher(id=1, name=Janey))
Student(id=5, name=Tom, teacher=Teacher(id=2, name=wen))
Student(id=24, name=zhangsan, teacher=Teacher(id=1, name=Janey))
Student(id=25, name=lisi, teacher=Teacher(id=2, name=wen))

10 集合查詢

當一個pojo型別中有一個集合時,需要通過collection關鍵字查詢。

比如,老師表中有一個學生集合

import lombok.Data;

import java.util.List;

@Data
public class Teacher {
    private int id;
    private String name;

    private List<Student> studentList; //學生集合
}

同樣,有兩種查詢方式,一種是連線查詢,一種是子查詢。相對來說,連線查詢時推薦的方式,更接近SQL查詢的思想,更易於理解和排除錯誤。

<!--連線查詢-->
<resultMap id="map1" type="Teacher">
    <!--id與result一樣,只是id用於標記出ID欄位,有利於效能提高-->
    <id property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--ofType代表集合中的元素型別-->
    <collection property="studentList" ofType="student">
        <!--這裡表示元素中的每一個物件-->
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
    </collection>
</resultMap>

<select id="getTeachers" resultMap="map1">
    select t.id tid, t.name tname, s.id sid, s.name sname
    from `teacher` as t
    left join `students` as s
    on t.id = s.tid
</select>

<!--子查詢-->
<resultMap id="map2" type="teacher">
    <!--通過select查詢獲取javaType型別,傳參是column-->
    <collection property="studentList" ofType="student" javaType="ArrayList" select="getSudentsbyTid" column="id"/>
</resultMap>

<select id="getTeachers2" resultMap="map2">
    select * from `teacher`
</select>

<select id="getSudentsbyTid" resultType="student">
    select * from `students` where `tid` = #{tid}
</select>

建議: 最好逐步建立結果對映。從最簡單的形態開始,逐步迭代。每次都使用單元測試保證每次迭代的正確性。

資料庫面試高頻:

  • MySQL引擎
  • InnoDB底層
  • 索引
  • 索引優化

11 動態SQL

動態 SQL 是 MyBatis 的強大特性之一。

動態SQL就是根據不同條件拼接SQL語句,MyBatis提供一套標籤在xml對映檔案中使用的條件判斷。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

11.1 if語句

使用動態 SQL 最常見情景是根據條件包含 where 子句的一部分。

<!--parameterType傳遞一個條件類-->
 <select id="getUsers" parameterType="UserCondition" resultType="user">
     select * from `user`
     <!--were標籤會根據需要去掉and或者or,或者去掉where-->
     <where>
         <if test="name != null">
             and `name` = #{name}
         </if>
         <if test="hasPhone == true">
             and ( `phone` is not null and `phone` != "")
         </if>
     </where>
</select>

以上可以匹配getUser的兩個過載方法:

public interface UserMapper {
    List<User> getUsers();
    List<User> getUsers(UserCondition user);
}

11.2 choose

choose類似於java中的switch語句,只有一個case能夠執行,配合when、otherwise標籤,每個when相當於一個case,otherwise相當於default。

<select id="getUsers2" parameterType="UserCondition" resultType="User">
    select * from `user`
    <choose>
        <when test="name != null">
            where `name` = #{name}
        </when>
        <when test="hasPhone == true">
            where ( `phone` is not null and `phone` != "")
        </when>
        <otherwise>

        </otherwise>
    </choose>
</select>

11.3 trim

trim可以用於自定義元素的功能,如內建的where,如下所示(注意:此例中的空格是必須的)。

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

比如if例子中,最後拼接的是 select * from user where and name = ?,此處就會把“and ”去掉,如果有的話。

內建的set是如下設定,此處是在結尾刪除多餘的“,”

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

更新語句示例:

<update id="updateUser" parameterType="UserCondition">
    update `user`
    <set>
        <if test="name != null">
            `name` = #{name},
        </if>
        <if test="phone != null">
            `phone` = #{phone}
        </if>
    </set>
    where `id` = #{id}
</update>

上面也可以寫成:

<update id="updateUser" parameterType="UserCondition">
    update `user`
    <!--如果trim內部條件都不滿足,什麼都不返回。
		整個trim以prefix開頭,以suffix結尾,中間拼接的部分的最後去掉“,”-->
    <trim prefix="set" suffix=" where id = #{id} " suffixOverrides=",">

        <if test="name != null"> `name` = #{name} , </if>

        <if test="phone != null"> `phone` = #{phone} , </if>

    </trim>
</update>

翻譯:如果trim內部條件都不滿足,什麼都不返回。如果存在滿足條件,整個trim以prefix開頭,以suffix結尾,中間拼接的部分的最後去掉“,”

11.4 SQL片段

可以通過sql標籤將sql程式碼片段提取出來,減少重複SQL語句,提高複用。

<sql id="if-name-phone">
    <if test="name != null"> `name` = #{name} , </if>
    <if test="phone != null"> `phone` = #{phone} , </if>
</sql>

<update id="updateUser" parameterType="UserCondition">
    update `user`
    <trim prefix="set" suffix=" where id = #{id} " suffixOverrides=",">
        <!--直接引用sql片段-->
        <include refid="if-name-phone"/>
    </trim>
</update>

一般只將簡單的if語句提取出來,連表查詢部分,複用性不高,一般不提取,where、set標籤等一般也不提取。

11.5 foreach

foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。

<!--拼接select * from `user` where id in (1, 2, 3, 4)-->
<select id="getUsersForeach" parameterType="ArrayList" resultType="User">
    select * from `user`
    <where>
        <if test="ids.size() > 0">
            <!--collection集合名、open代表拼接的開頭,separator為每一項之間的分隔符,close是結尾字元,item代表每一項的變數名-->
            <foreach collection="ids" open="id in (" separator="," close=")" item="id">
                #{id}
            </foreach>
        </if>
    </where>
</select>

11.6 總結

動態SQL的本質就是拼接字串,字串最後會形成一個SQL語句,所以在寫動態SQL時,首先應該寫出完整的SQL語句,並在資料庫中執行成功後,再寫Mybatis的拼接語句。

12 快取

12.1 簡介

what: 什麼是快取?

  • 存在快取中的資料
  • 資料庫一般是存在讀取速度較慢的磁碟中,而將經常查詢的資料放在記憶體中,從而提高查詢效率,解決高併發系統的效能問題。

why:為什麼使用快取?

  • 減少與資料庫的互動,提升訪問速度,提升系統效率。

when:什麼時候使用快取?

  • 當經常查詢的資料經常保持不變時,應該使用快取

12.2 主從複製、讀寫分離

簡單的來說,就是有兩個或兩個以上的資料庫,其中一個當做主資料庫,其他當做從資料庫,主資料庫負責更新資料,從資料庫負責查詢,當主資料庫更新時,會更新到所有從資料庫。就實現了主從複製、讀寫分離的設計。

優點:

  • 當其中一個數據庫出問題時,其他資料庫可以工作,保證資料不易丟失,且穩定工作
  • 寫操作更耗時,將讀寫分離,更新操作不會影響到查詢操作。

12.3 Mybatis快取

MyBatis 內建了一個強大的事務性查詢快取機制,它可以非常方便地配置和定製。

MyBatis定義了兩級快取:會話快取和二級快取

  • 預設情況下,只啟用了本地的會話快取,它僅僅對一個會話中的資料進行快取。(在一個SqlSession中快取,呼叫close即快取結束)
  • 二級快取需要手動開啟和配置,在對映檔案中使用標籤,作用域是所在namespace內。
  • 二級快取也可以擴充套件Cache介面定義

12.4 一級快取

  • 一級快取是預設開啟的
  • 作用域是獲取SQLSession到close關閉
  • 期間的查詢會被快取
  • 期間所有的更新操作(insert、delete、update)會清除快取
  • 預設採用LRU(最近最少使用演算法)
  • 採用讀/寫快取,即快取不是共享的,可以安全的被呼叫者修改。

通過Log4j日誌輸出,可以檢視到select語句只執行了一次,如果中間有更新操作,select會再次執行。

[com.zl.dao.UserMapper.getUsers]-==>  Preparing: select * from `user`
[com.zl.dao.UserMapper.getUsers]-==> Parameters: 
[com.zl.dao.UserMapper.getUsers]-<==      Total: 3
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
======================
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
======================
[com.zl.dao.UserMapper.updateUser]-==>  Preparing: update `user` set `phone` = ? where id = ?
[com.zl.dao.UserMapper.updateUser]-==> Parameters: 5698(String), 4(Integer)
[com.zl.dao.UserMapper.updateUser]-<==    Updates: 1
[com.zl.dao.UserMapper.getUsers]-==>  Preparing: select * from `user`
[com.zl.dao.UserMapper.getUsers]-==> Parameters: 
[com.zl.dao.UserMapper.getUsers]-<==      Total: 3
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=5698)
User(id=5, name=Sam, pwd=789, phone=5566)

12.5 二級快取

一級快取的作用域太小,誕生了二級快取,二級快取的作用域是基於namespace,只需要加入標籤即可,當然,在核心配置檔案中可以增加setting設定,顯視的表示開啟全域性快取,雖然預設是開啟的。

首先查詢儲存在一級快取中,當close關閉時,會將一級快取儲存到二級快取,使用了java物件序列化。在另一個連線查詢中,讀取快取時,會從一級快取和二級快取中讀取。

<mapper namespace="com.zl.dao.UserMapper">

    <!--開啟二級快取,標籤內也可以進行詳細配置-->
    <cache/>

    <select id="getUsers" parameterType="UserCondition" resultType="user">
        select * from `user`
        <where>
            <if test="name != null">
                and `name` = #{name}
            </if>
            <if test="hasPhone == true">
                and ( `phone` is not null and `phone` != "")
            </if>
        </where>
    </select>
</mapper>
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

注意:二級快取使用了序列化,所以類必須可以序列化

測試:

@Test
    public void testCached(){
        try(SqlSession session1 = Mybatis_utils.getSqlSession()){
            UserMapper mapper = session1.getMapper(UserMapper.class);
            List<User> users = mapper.getUsers();
            for (User user : users) {
                System.out.println(user);
            }

            System.out.println("=====================");
        }

        try(SqlSession session1 = Mybatis_utils.getSqlSession()){
            UserMapper mapper = session1.getMapper(UserMapper.class);
            List<User> users = mapper.getUsers();
            for (User user : users) {
                System.out.println(user);
            }

            System.out.println("=====================");
        }


    }

下面結果顯示,兩次獲取連線,只查詢了一次 select * from user

[com.zl.dao.UserMapper]-Cache Hit Ratio [com.zl.dao.UserMapper]: 0.0
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1912850431.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [[email protected]]
[com.zl.dao.UserMapper.getUsers]-==>  Preparing: select * from `user`
[com.zl.dao.UserMapper.getUsers]-==> Parameters: 
[com.zl.dao.UserMapper.getUsers]-<==      Total: 3
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
=====================
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [[email protected]]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [[email protected]]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1912850431 to pool.
[org.apache.ibatis.io.SerialFilterChecker]-As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
[com.zl.dao.UserMapper]-Cache Hit Ratio [com.zl.dao.UserMapper]: 0.5
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
=====================

12.6 快取原理

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9pXoP1Xr-1613116023840)(C:\Users\zl\Downloads\未命名檔案.png)]

12.7 自定義快取

可以在引用自定義快取。

步驟就是在java中繼承cache類,然後設定在標籤中。

<cache type="com.domain.something.MyCustomCache"/>

自定義cache需要實現的cache介面,不過目前有大量開源的資料庫快取框架,你確定能做的比他們好,再自定義快取吧,-_-||

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

也可以引入開源的快取框架,如ehcache(需要導包,具體檢視網上)

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

不過,一般會使用redies資料庫做快取