mybatis快速入門

王延領發表於2021-08-26

1.Mybatis

MyBatis 原本是apache的一個開源專案iBatis,2010年這個專案由Apache Software Foundation遷移到了Google Code,並且改名為MyBatis,2013年11月遷移到GitHub。它是一款優秀的持久層框架,它支援自定義 SQL、儲存過程以及高階對映(ORM),支援XML或者註解來配置和對映原生型別、介面和java的POJO(Plain Old Java Objects,普通老式的Java物件)資料庫中的記錄。。

2.下載MyBatis

2.1.github

https://github.com/mybatis/mybatis-3/releases

image-20210816153745328

2.2Maven倉庫

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

3.入門

3.1驅動與依賴

採用mavenhttps://search.maven.org/ 查詢相關的驅動。

image-20210816171241868

<dependencies>
    <!--dependency>
      <groupId>org.wyl</groupId>
      <artifactId>[the artifact id of the block to be mounted]</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency-->
    <!-- mysql驅動 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.18</version>
    </dependency>
    <!-- mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.5</version>
    </dependency>
    <!-- 整合log4j -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.6.4</version>
    </dependency>
      <!-- 測試junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

右擊pom.xml->meave 重新載入專案。包下載完即可。

將 mybatis-x.x.x.jar 檔案置於 classpath 中即可。

3.2.MyBatis的功能架構

3.2.1.架構

img

API介面層:提供給外部使用的介面API,開發人員通過這些本地API來操縱資料庫。介面層一接收到呼叫請求就會呼叫資料處理層來完成具體的資料處理。
資料處理層:負責具體的SQL查詢、SQL解析、SQL執行和執行結果對映處理等。它主要的目的是根據呼叫的請求完成一次資料庫操作。

  • 引數對映(引數解析和引數繫結):獲取並解析對映檔案中的 Statement標籤及其屬性(parameterType)。也就是解析SQL語句併為 SQL語句準備需要進行繫結的引數
  • SQL解析:對 Statement【 <select><update><delete><insert>】標籤中的內容進行解析、拼接、封裝,最後得到一個完整的帶有佔位符的 SQL語句。也就是 JDBC中的準備 SQL語句的過程
  • 結果對映(結果集解析和結果集處理):獲取配置檔案中的結果集型別,並進行型別轉換,將ResultSet進行結果對映。也就是 JDBC中處理結果集的步驟。

基礎支撐層:負責最基礎的功能支撐,包括連線管理、事務管理、配置載入和快取處理,這些都是共用的東西,將他們抽取出來作為最基礎的元件。為上層的資料處理層提供最基礎的支撐。

  • 管理 Mybatis與資料庫的連線方式

  • 管理 Mybatis的事務

  • 載入 配置檔案

  • Mybatis 查詢快取

3.2.2.全域性配置檔案 mybatis-config

<?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標籤 => 宣告MyBatis核心配置 -->
<configuration>
    <!-- environments標籤 => 設定MyBatis選用的環境資訊 -->
    <environments default="development">
        <environment id="development">
            <!-- transactionManager標籤 => 事務管理 -->
            <transactionManager type="JDBC"/>
            <!-- dataSource標籤 => 配置資料來源屬性 -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--將mapper對映檔案註冊到全域性配置檔案中-->
    <mappers>
        <mapper resource="org/mybatis/example/WylMapper.xml"/>
    </mappers>
</configuration>
<?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標籤 => 宣告MyBatis核心配置 -->
<configuration>
    <!-- environments標籤 => 設定MyBatis選用的環境資訊 -->
    <environments default="development">
        <environment id="development">
            <!-- transactionManager標籤 => 事務管理 -->
            <transactionManager type="JDBC"/>
            <!-- dataSource標籤 => 配置資料來源屬性 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/wyl?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=CST"/>
                <property name="username" value="nps"/>
                <property name="password" value="123.nps@zst"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mappers/userMapper.xml"/>
    </mappers>

</configuration>

3.2.3.SqlSessionFactory 會話工廠

  • 通過Mybatis的配置資訊,使用 SqlSessionFactoryBuilder構建器,來構建會話工廠物件

  • SqlSessionFactory 建立了 Configuration物件,使用 Configuration物件來構建SqlSession會話工廠

  • SqlSessionFactoryBuilder構建器使用了 Builder構建者設計模式

  • SqlSessionFactory 會話工廠,使用了工廠設計模式

    public class MyBatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            try {
                // 定義XML核心配置檔案路徑資訊
                String resource = "mybatis-config.xml";
                // 讀取XML核心配置檔案路徑資訊
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 獲得例項化SQLSessionFactory
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    	//呼叫SqlSessionFactory.openSession()方法,返回SqlSession物件
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
    

3.2.4.SqlSession介面方法

程式通過SqlSession來運算元據庫

  • SqlSession對外提供了一整套的增刪改查的api,通過api來運算元據庫
  • SqlSession還能夠獲取動態的Mapper介面
  • SqlSession的作用域是方法級別的,也就是從建立到銷燬必須保證在方法內完成,注意一定要在方法內部銷燬sqlSession,千萬不要忘記
  • sqlSession在使用完後一定要及時關閉,尤其是在Service層,會在一個方法中同時使用多個sqlSession,每個sqlSession在用完後最好在下一行程式碼就關閉,避免影響其他sqlSession。
//從 SqlSessionFactory 中獲取 SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UsersDao mapper = sqlSession.getMapper(UsersDao.class);
List<Users> usersInfo = mapper.getUsersInfo();

也可以

try (SqlSession session = sqlSessionFactory.openSession()) {
  User user = (User) session.selectOne("org.mybatis.example.WylMapper.getUsersById",12);
  }

3.2.5.pojo層

對應的資料庫表的實體類

package com.wyl.mybatis.pojo;

/**
 * @建立人 王延領
 * @建立時間 2021/8/16
 * 描述
 **/
public class Users {
    private int id;
    private String username;
    private String password;
    private String email;
    private int gender;

    public Users() {
    }

    public Users(int id, String username, String password, String email, int gender) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                '}';
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }
}

快速生成建構函式get set 等的方法,空白處右擊生成即可

image-20210823153814340

3.2.6. dao層

與資料庫互動相關程式碼,對應Mybatis的mapper介面

package com.wyl.mybatis.dao;
import com.wyl.mybatis.pojo.Users;
import java.util.List;
import java.util.Map;
/**
 * @建立人 王延領
 * @建立時間 2021/8/18
 * 描述
 **/
public interface UserDao {
    // 【select】所有使用者資訊
    List<Users> getUsersInfo();

    // 【select】指定使用者資訊
    Users getUserInfoById(int id);

    // 【update】指定使用者資訊
    int updateUseInfoById(Users user);

    // 【insert】指定使用者資訊
    int insertUser(Users user);

    // 【delete】指定使用者資訊
    int deleteUserById(int id);

    // 【insert】 批量使用者資訊
    int insertManyUseList(List<Users> users);

    // 【select】 模糊查詢
    List<Users> getUsersInfoByPhantomSelect(String username);
}

3.2.7.mapper

image-20210823155011994

userMapper.xml配置對應的資料庫操作對映,在mybatis-config進行註冊。

<!--如圖所示配置-->
<mappers>
    <mapper resource="mappers/usersMapper.xml"/>
</mappers>

mapper如下。

<?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】: 指定dao層,繫結Dao -->
<mapper namespace="com.wyl.mybatis.dao.UserDao">
    <!-- select sql: 繫結getUsersInfo方法,返回所有使用者資訊【id】: 繫結Dao中的方法名
				【resultType】: 指定對應【類的形式】返回結果集的型別 -->
    <select id="getUsersInfo" resultType="com.wyl.mybatis.pojo.Users">
        select * from users
    </select>
  </mapper>

測試:

 @Test
    public void getUsersInfo() {
        // 呼叫MyBatisUtils.getSqlSession()方法,獲取SqlSession物件
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        // 呼叫獲取到的SQLSession物件中的getMapper物件
        // 反射Dao介面,動態代理Dao介面中的方法,並將這些方法存在物件【mapper】中
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        // 呼叫mapper中對應方法,並設定對應的物件來接收其返回結果
        // 以下為測試方法getUsersInfo() => 獲取所有Users表中資訊,並用對應類接收
        List<Users> usersInfo = mapper.getUsersInfo();
        // for迴圈遍歷輸出List集合
        for (Users users : usersInfo) {
            System.out.println(users);
        }
        // 關閉sqlSession
        sqlSession.close();
    }

3.3.作用域與生命週期

image-20210823173846319

3.3.1.SqlSessionFactoryBuilder

這個類可以被例項化、使用和丟棄,一旦建立了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 例項的最佳作用域是方法作用域(也就是區域性方法變數)。 你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 例項,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。

3.3.2.SqlSessionFactory

SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。類似於資料庫連線池。 使用 SqlSessionFactory 的最佳實踐是在應用執行期間不要重複建立多次,多次重建 SqlSessionFactory 被視為一種程式碼“壞習慣”。因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式

3.3.3.SqlSession

每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求方法作用域。 絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行。 也絕不能將 SqlSession 例項的引用放在任何型別的託管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以開啟一個 SqlSession返回一個響應後,就關閉它。 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。

3.3.4.Mapper

是一種建立的用於繫結對映語句的介面,Mapper 介面的例項是用 SqlSession 來獲得的。同樣,從技術上來說,最廣泛的 Mapper 例項作用域像 SqlSession 一樣,使用請求作用域。確切地說,在方法被呼叫的時候呼叫 Mapper 例項,然後使用後,就自動銷燬掉不需要使用明確的登出。當一個請求執行正確無誤的時候,像 SqlSession 一樣,你可以輕而易舉地操控這一切。保持簡單性,保持 Mapper 在方法體作用域內。

4.xml 配置解析

MyBatis 的配置檔案包含了會深深影響 MyBatis 行為的設定和屬性資訊。 配置文件的頂層結構如下:

<?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>
    <!-- 屬性:定義配置外在化 -->
    <properties></properties>
    <!-- 設定:定義mybatis的一些全域性性設定 -->
    <settings>
       <!-- 具體的引數名和引數值 -->
       <setting name="" value=""/> 
    </settings>
    <!-- 型別名稱:為一些類定義別名 -->
    <typeAliases></typeAliases>
    <!-- 型別處理器:定義Java型別與資料庫中的資料型別之間的轉換關係 -->
    <typeHandlers></typeHandlers>
    <!-- 物件工廠 -->
    <objectFactory type=""></objectFactory>
    <!-- 外掛:mybatis的外掛,外掛可以修改mybatis的內部執行規則 -->
    <plugins>
       <plugin interceptor=""></plugin>
    </plugins>
    <!-- 環境:配置mybatis的環境 -->
    <environments default="">
       <!-- 環境變數:可以配置多個環境變數,比如使用多資料來源時,就需要配置多個環境變數 -->
       <environment id="">
          <!-- 事務管理器 -->
          <transactionManager type=""></transactionManager>
          <!-- 資料來源 -->
          <dataSource type=""></dataSource>
       </environment> 
    </environments>
    <!-- 資料庫廠商標識 -->
    <databaseIdProvider type=""></databaseIdProvider>
    <!-- 對映器:指定對映檔案或者對映類 -->
    <mappers></mappers>
</configuration>

image-20210823161021441

​ 圖片來自官網

4.1.屬性(properties)

這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性檔案中配置這些屬性,也可以在 properties 元素的子元素中設定.

比如第三章的配置檔案我們可以如下寫法

<!-- properties標籤 => 讀取外部properties檔案 -->
    <properties resource="dataSource.properties">
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </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>

這個例子中的username和password將會由properties元素中設定的相應值來替換。driver和url屬性將會由dataSource.properties檔案中對應的值來替換。這樣就為配置提供了諸多靈活選擇。

image-20210823162924181

屬性也可以被傳遞到SqlSessionBuilder.build()方法中。

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, props);

但是,這也就涉及到了優先順序的問題,如果屬性不只在一個地方配置,那麼mybatis將會按照下面的順序來載入:
1.在properties元素體內指定的屬性首先被讀取。

  1. 然後根據properties元素中的resource屬性讀取類路徑下屬性檔案或根據url屬性指定的路徑讀取屬性檔案,並覆蓋已讀取的同名屬性。
  2. 最後讀取作為方法引數傳遞的屬性,並覆蓋已讀取的同名屬性。

通過方法引數傳遞的屬性具有最高優先順序,resource/url 屬性中指定的配置檔案次之,最低優先順序的則是 properties 元素中指定的屬性

從 MyBatis 3.4.2 開始,你可以為佔位符指定一個預設值。例如:

<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${username:ut_user}"/> <!-- 如果屬性 'username' 沒有被配置,'username' 屬性的值將為 'ut_user' -->
</dataSource>

這個特性預設是關閉的。要啟用這個特性,需要新增一個特定的屬性來開啟這個特性。例如:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 啟用預設值特性 -->
</properties>

4.2.設定(settings)

一個配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的執行時行為。 下表描述了設定中各項設定的含義、預設值等。

設定名 描述 有效值 預設值
cacheEnabled 全域性性地開啟或關閉所有對映器配置檔案中已配置的任何快取。 true | false true
lazyLoadingEnabled 延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入。 特定關聯關係中可通過設定 fetchType 屬性來覆蓋該項的開關狀態。 true | false false
aggressiveLazyLoading 開啟時,任一方法的呼叫都會載入該物件的所有延遲載入屬性。 否則,每個延遲載入屬性會按需載入(參考 lazyLoadTriggerMethods)。 true | false false (在 3.4.1 及之前的版本中預設為 true)
multipleResultSetsEnabled 是否允許單個語句返回多結果集(需要資料庫驅動支援)。 true | false true
useColumnLabel 使用列標籤代替列名。實際表現依賴於資料庫驅動,具體可參考資料庫驅動的相關文件,或通過對比測試來觀察。 true | false true
useGeneratedKeys 允許 JDBC 支援自動生成主鍵,需要資料庫驅動支援。如果設定為 true,將強制使用自動生成主鍵。儘管一些資料庫驅動不支援此特性,但仍可正常工作(如 Derby)。 true | false False
autoMappingBehavior 指定 MyBatis 應如何自動對映列到欄位或屬性。 NONE 表示關閉自動對映;PARTIAL 只會自動對映沒有定義巢狀結果對映的欄位。 FULL 會自動對映任何複雜的結果集(無論是否巢狀)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior 指定發現自動對映目標未知列(或未知屬性型別)的行為。NONE: 不做任何反應WARNING: 輸出警告日誌('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日誌等級必須設定為 WARNFAILING: 對映失敗 (丟擲 SqlSessionException) NONE, WARNING, FAILING NONE
defaultExecutorType 配置預設的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); BATCH 執行器不僅重用語句還會執行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 設定超時時間,它決定資料庫驅動等待資料庫響應的秒數。 任意正整數 未設定 (null)
defaultFetchSize 為驅動的結果集獲取數量(fetchSize)設定一個建議值。此引數只可以在查詢設定中被覆蓋。 任意正整數 未設定 (null)
defaultResultSetType 指定語句預設的滾動策略。(新增於 3.5.2) FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同於未設定) 未設定 (null)
safeRowBoundsEnabled 是否允許在巢狀語句中使用分頁(RowBounds)。如果允許使用則設定為 false。 true | false False
safeResultHandlerEnabled 是否允許在巢狀語句中使用結果處理器(ResultHandler)。如果允許使用則設定為 false。 true | false True
mapUnderscoreToCamelCase 是否開啟駝峰命名自動對映,即從經典資料庫列名 A_COLUMN 對映到經典 Java 屬性名 aColumn。 true | false False
localCacheScope MyBatis 利用本地快取機制(Local Cache)防止迴圈引用和加速重複的巢狀查詢。 預設值為 SESSION,會快取一個會話中執行的所有查詢。 若設定值為 STATEMENT,本地快取將僅用於執行語句,對相同 SqlSession 的不同查詢將不會進行快取。 SESSION | STATEMENT SESSION
jdbcTypeForNull 當沒有為引數指定特定的 JDBC 型別時,空值的預設 JDBC 型別。 某些資料庫驅動需要指定列的 JDBC 型別,多數情況直接用一般型別即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 OTHER
lazyLoadTriggerMethods 指定物件的哪些方法觸發一次延遲載入。 用逗號分隔的方法列表。 equals,clone,hashCode,toString
defaultScriptingLanguage 指定動態 SQL 生成使用的預設指令碼語言。 一個型別別名或全限定類名。 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler 指定 Enum 使用的預設 TypeHandler 。(新增於 3.4.5) 一個型別別名或全限定類名。 org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls 指定當結果集中值為 null 的時候是否呼叫對映物件的 setter(map 物件時為 put)方法,這在依賴於 Map.keySet() 或 null 值進行初始化時比較有用。注意基本型別(int、boolean 等)是不能設定成 null 的。 true | false false
returnInstanceForEmptyRow 當返回行的所有列都是空時,MyBatis預設返回 null。 當開啟這個設定時,MyBatis會返回一個空例項。 請注意,它也適用於巢狀的結果集(如集合或關聯)。(新增於 3.4.2) true | false false
logPrefix 指定 MyBatis 增加到日誌名稱的字首。 任何字串 未設定
logImpl 指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未設定
proxyFactory 指定 Mybatis 建立可延遲載入物件所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 以上)
vfsImpl 指定 VFS 的實現 自定義 VFS 的實現的類全限定名,以逗號分隔。 未設定
useActualParamName 允許使用方法簽名中的名稱作為語句引數名稱。 為了使用該特性,你的專案必須採用 Java 8 編譯,並且加上 -parameters 選項。(新增於 3.4.1) true | false true
configurationFactory 指定一個提供 Configuration 例項的類。 這個被返回的 Configuration 例項用來載入被反序列化物件的延遲載入屬性值。 這個類必須包含一個簽名為static Configuration getConfiguration() 的方法。(新增於 3.2.3) 一個型別別名或完全限定類名。 未設定
shrinkWhitespacesInSql 從SQL中刪除多餘的空格字元。請注意,這也會影響SQL中的文字字串。 (新增於 3.5.5) true | false false
defaultSqlProviderType Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted. A type alias or fully qualified class name Not set

4.3.型別別名(typeAliases)

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

 <typeAlias alias="user" type="com.wyl.mybatis.pojo.Users"></typeAlias>

​ 當這樣配置時,user 可以用在任何使用 com.wyl.mybatis.pojo.Users 的地方。

在使用註解的時候也可以指定一個包名,再使用其類的時候直接小寫類名即可。.比如:

<!--除非使用註解,否則不支援自定義別名-->
<package name="com.wyl.mybatis.dao"/>

4.4.對映器(mappers)

定義SQL對映語句,指定MyBatis尋找SQL語句。

    1. 使用相對於類路徑的資源引用 【推薦】:

      <mappers>
         <mapper resource="mappers/userMapper.xml"/>
      </mappers>
      
    2. 使用對映器介面實現類的完全限定類名

      <mappers>
        <mapper class="com.wyl.mybatis.dao.UsersDao"/>
      </mappers>
      
    3. 將包內的對映器介面實現全部註冊為對映器(注意相對位置)

      <mappers>
        <package name="com.wyl.mybatis.dao"/>
      </mappers>
      
    4. 使用完全限定資源定位符(URL) 【不推薦使用】

      <mappers>
        <mapper url="file:///var/mappers/usersMapper.xml"/>
      </mappers>
      

4.5.環境配置(environments)

MyBatis 可以配置成適應多種環境,這種機制有助於將 SQL 對映應用於多種資料庫之中, 現實情況下有多種理由需要這麼做。例如,開發、測試和生產環境需要有不同的配置;或者想在具有相同 Schema 的多個生產資料庫中使用相同的 SQL 對映。還有許多類似的使用場景。

不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境。

所以,如果你想連線兩個資料庫,就需要建立兩個 SqlSessionFactory 例項,每個資料庫對應一個。

 <environments default="oracle">
        <environment id="mysql">
         <!--mysql 配置-->
        </environment>
        <environment id="oracle">
             <!--oracle 配置-->
        </environment>
    </environments>

4.6.事務管理器(transactionManager)

在 MyBatis 中有兩種型別的事務管理器(也就是 type="[JDBC|MANAGED]"):

在 MyBatis 中有兩種型別的事務管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 這個配置直接使用了 JDBC 的提交和回滾設施,它依賴從資料來源獲得的連線來管理事務作用域。

  • MANAGED – 這個配置幾乎沒做什麼。它從不提交或回滾一個連線,而是讓容器來管理事務的整個生命週期(比如 JEE 應用伺服器的上下文)。 預設情況下它會關閉連線。然而一些容器並不希望連線被關閉,因此需要將 closeConnection 屬性設定為 false 來阻止預設的關閉行為。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

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

這兩種事務管理器型別都不需要設定任何屬性。它們其實是型別別名,換句話說,你可以用 TransactionFactory 介面實現類的全限定名或型別別名代替它們。

public interface TransactionFactory {
  default void setProperties(Properties props) { // 從 3.5.2 開始,該方法為預設方法
    // 空實現
  }
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

在事務管理器例項化後,所有在 XML 中配置的屬性將會被傳遞給 setProperties() 方法。你的實現還需要建立一個 Transaction 介面的實現類,這個介面也很簡單:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

使用這兩個介面,你可以完全自定義 MyBatis 對事務的處理。

4.7.資料來源(dataSource)

  • 【官方宣告】:dataSource 元素使用標準的 JDBC 資料來源介面來配置 JDBC 連線物件的資源。

  • 【官方宣告】:大多數 MyBatis 應用程式會按示例中的例子來配置資料來源。雖然資料來源配置是可選的,但如果要啟用延遲載入特性,就必須配置資料來源。

  • 【官方宣告】:有三種內建的資料來源型別(也就是 type="[UNPOOLED|POOLED|JNDI]")

    • UNPOOLED– 這個資料來源的實現會每次請求時開啟和關閉連線。雖然有點慢,但對那些資料庫連線可用性要求不高的簡單應用程式來說,是一個很好的選擇。 效能表現則依賴於使用的資料庫,對某些資料庫來說,使用連線池並不重要,這個配置就很適合這種情形。
    • POOLED– 這種資料來源的實現利用“池”的概念將 JDBC 連線物件組織起來,避免了建立新的連線例項時所必需的初始化和認證時間。 這種處理方式很流行,能使併發 Web 應用快速響應請求。
    • JNDI – 這個資料來源實現是為了能在如 EJB 或應用伺服器這類容器中使用,容器可以集中或在外部配置資料來源,然後放置一個 JNDI 上下文的資料來源引用。
  • MyBatis預設資料來源型別 => 【POOLED】

  • 資料來源型別: dbcp c3p0 druid hikari

4.8.物件工廠(objectFactory)

MyBatis 每次建立結果物件的新例項時,它都會使用一個物件工廠(ObjectFactory)例項來完成。 預設的物件工廠需要做的僅僅是例項化目標類,要麼通過預設構造方法,要麼在引數對映存在的時候通過引數構造方法來例項化。 如果想覆蓋物件工廠的預設行為,則可以通過建立自己的物件工廠來實現.
自定義物件工廠
ObjectFactory是個介面類,其預設實現類是DefaultObjectFactory。在 MyBatis 中,預設的DefaultObjectFactory要做的就是例項化查詢結果對應的目標類,有兩種方式可以將查詢結果的值對映到對應的目標類:一種是通過目標類的預設構造方法,另外一種就是通過目標類的有參構造方法。

4.8.1.自定義物件工廠

MyBatis允許註冊自定義的ObjectFactory,只需要實現介面 org.apache.ibatis.reflection.factory.ObjectFactory即可。但是在大部分的情況下,我們都不需要自定義ObjectFactory物件工廠,只需要繼承系統已經實現好的 DefaultObjectFactory ,通過一定的改寫來完成我們所需要的工作。

有時候在新建一個新物件(構造方法或者有參構造方法),在得到物件之前需要處理一些邏輯,或者在執行該類的有參構造方法時,在傳入引數之前,要對引數進行一些處理,這時就可以建立自己的 ObjectFactory 來載入該型別的物件。如下所示,增加了日誌列印功能:

public class MyObjectFactory extends DefaultObjectFactory
{
    private static final long serialVersionUID = 1L;
    Logger log = Logger.getLogger(MyObjectFactory.class);
    private Object temp = null;
    @Override
    public void setProperties(Properties properties)
    {
        super.setProperties(properties);
    }
    @Override
    public <T> T create(Class<T> type)
    {
        T result = super.create(type);
        log.info("建立物件:" + result.toString());
        return result;
    }
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
    {
        T result = super.create(type, constructorArgTypes, constructorArgs);
        log.info("建立物件:" + result.toString());
        return result;
    }
    @Override
    public <T> boolean isCollection(Class<T> type)
    {
        return super.isCollection(type);
    }

然後,需要在 SqlMapConfig.xml 全域性配置檔案中配置該自定義物件工廠即可,程式碼如下:

<objectFactory type="cn.mybatis.mydemo.MyObjectFactory">
    <property name="key" value="value" />
</objectFactory>

這樣 MyBatis 就會採用配置的 MyObjectFactory 來生成結果集物件,採用下面的程式碼進行測試。

public class MyBatisDemo
{
    public static void main(String[] args) throws IOException
    {
        Logger log = Logger.getLogger(MyBatisDemo.class);
        InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
        SqlSession session = factory .openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.getUser(1L);  
    }
}

4.9.外掛

4.9.1.外掛的分類

第一類:外掛是對系統的一種補充,例如在分散式系統中,可以使用外掛的方式,實現記憶體外掛、磁碟外掛、線性網路外掛、Paxos外掛等。此類外掛等同於元件。

第二類:外掛是對系統預設功能的自定義修改,例如mybatis裡面自定義外掛,它實現的攔截器的功能。此類外掛等同於攔截器。

4.9.2.MyBatis攔截器外掛

MyBatis允許使用者在已對映語句執行過程中的某一點進行攔截呼叫。MyBatis使用外掛來攔截的方法呼叫,故此MyBatis外掛通常稱為:Mybatis攔截器。預設情況下,MyBatis允許使用外掛來攔截的物件包括下面的四大金剛:

  • Executor:MyBatis的執行器,用於執行增刪改查操作
  • ParameterHandler:處理SQL的引數物件
  • ResultSetHandler:處理SQL的返回結果集
  • StatementHandler:資料庫的處理物件,用於執行SQL語句

在Java裡面,我們想攔截某個物件,只需要把這個物件包裝一下,用程式碼行話來說,就是重新生成一個代理物件。

4.9.2.註解例項

@Intercepts({
    @Signature(
            type=Executor.class,
            method="query",
            args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
    )
})
public class MyInterceptor implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }
 
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }
 
    public void setProperties(Properties arg0) {}
 

@Intercepts 攔截器註解,此註解宣告此類是一個外掛類。其中可以宣告多個 @Signature 簽名資訊註解,type 為攔截的方法所屬的介面型別,method 為攔截的方法名稱,args 是引數資訊。

intercept 方法是一個對目標方法進行攔截的抽象方法,而 plugin 方法的作用是將攔截器插入目標物件。

setProperties 方法的作用是將全域性配置檔案中的引數注入外掛類中。

MyBatis 全域性配置檔案中配置該外掛即可

<plugins>
    <plugin interceptor="com.wyl.mybatis.unit.MyInterceptor"></plugin>
</plugins>

4.10.typeHandlers(型別處理器)

在JDBC中,需要在PreparedStatement物件中設定那些已經預編譯過的SQlL語句的引數。執行SQL後,會通過ResultSet物件獲取得到資料庫的資料,而這些在Mybatis是根據資料的型別通過typeHandler來實現的。

在typeHandler中,分為jdbcType和javatype,其中jdbcType用於定義資料庫型別,而javaType用於定義Java型別,那麼typeHandler的作用就是承擔jdbcTypr和javaType之間的相互轉換。

image-20210824103050041

和別名一樣,在 MyBatis 中存在系統定義 typeHandler 和自定義 typeHandler。MyBatis 會根據 javaType 和資料庫的 jdbcType 來決定採用哪個 typeHandler 處理這些轉換規則。系統提供的 typeHandler 能覆蓋大部分場景的要求,但是有些情況下是不夠的,比如我們有特殊的轉換規則,列舉類就是這樣。

4.10.1.系統定義的typeHandler

image

image-20210824104028080

這些就是MyBatis 系統已經建立好的typeHandler。在大部分的情況下無須顯式地宣告jdbcType 和javaType ,或者用typeHandler 去指定對應的typeHandler 來實現資料型別轉換,因為MyBatis 系統會自己探測。有時候需要修改一些轉換規則,比如列舉類往往需要自己去編寫規則。

public interface TypeHandler<T> {
  // 使用typeHandler通過PreparedStatement物件進行設定SQL引數的時候使用的具體方法。其中:ps是PreparedStatement物件;i是引數在SQL語句中的下標;jdbcType是資料庫型別
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  //JDBC結果集中獲取資料進行轉換,使用列名(columnName)或下標(columeIdex)獲取資料庫的資料
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
   //儲存過程專用
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

4.10.2.自定義TypeHandler

從系統定義的typeHandler中可以知道,要實現typeHandler就需要去實現介面Typehandler,或者繼承BaseTypeHandler(實際上,BaseTypehandler實現了typeHandler介面)

實現TypeHandler

public class MyTypeHandler implements TypeHandler<String>{
	//定義一個日誌
	Logger log = Logger.getLogger(MyTypeHandler.class);

	@Override
	public String getResult(ResultSet rs, String columnName) throws SQLException {
		String result = rs.getString(columnName);
		log.info("讀取string引數1【"+result+"】");
		return result;
	}

	@Override
	public String getResult(ResultSet rs, int columnIdex) throws SQLException {
		String result = rs.getString(columnIdex);
		log.info("讀取string引數2【"+result+"】");
		return result;
	}

	@Override
	public String getResult(CallableStatement cs, int columnIdex) throws SQLException {
		String result = cs.getString(columnIdex);
		log.info("讀取string引數3【"+result+"】");
		return result;
	}

	@Override
	public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
		log.info("設定string引數【"+parameter+"】");
		ps.setString(i, parameter);
	}
}

在程式碼中使用自定義typeHandler

<!-- 型別轉換器 -->
	<typeHandlers>
		<typeHandler handler="com.wyl.mybatis.utils.MyTypeHandler" jdbcType="VARCHAR" javaType="string"/>
	</typeHandlers>

or

<resultMap id="roleMapper" type="role">
    <result property="id" column="id"/>
    <result property="roleName" column="role_name" jdbcType="VARCHAR" javaType="string"/>
    <result property="note" column="note" typeHandler="com.wyl.mybatis.utils.MyTypeHandler"/>
</resultMap>

<select id="getRole" parameterType="long" resultMap="roleMapper">
    select id,role_name,note from t_role where id = #{id}
</select>

有時候配置的typeHandler太多,也可以使用包掃描的方式

<typeHandlers>      
    <package name="com.wyl.mybatis.unit.typeHandler"/>
</typeHandlers>
@MappedJdbcTypes(JdbcType.VARCHAR)   // 表示把資料庫中的varchar型別轉成java的String型別時使用該轉換器
@MappedTypes(String.class)
public class MyTypeHandler implements TypeHandler<String>{
     
    Logger logger = Logger.getLogger(MyTypeHandler.class);
 
    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        logger.info("讀取string引數1【" + result +"】");
        return result;
    }
 
    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("讀取string引數2【" + result +"】");
        return result;
    }
 
    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("讀取string引數3【" + result +"】");
        return result;
    }
 
    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        logger.info("設定string引數【" + parameter + "】");
        ps.setString(i, parameter);
    }
}

4.10.3.列舉TypeHandler

在絕大多數情況下,typeHandler 因為列舉而使用,MyBatis 已經定義了兩個類作為列舉型別的支援,這兩個類分別是:

  • EnumOrdinalTypeHandler。使用整數下標作為引數傳遞的(列舉型別的預設轉換類)

        <resultMap id="userMapper" type="user">
            <result property="id" column="id" />
            <result property="userName" column="user_name" />
            <result property="password" column="passsword" />
            <result property="sex" column="sex"
                typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" />
        </resultMap>
        <select id="getUser" resultMap="userMapper" parameterType="long">
            select id,user_name,password,sex,mobile,tel,email,note from myUser
            where id=#{id}
        </select>
    
    
  • EnumTypeHandler。使用列舉字串名稱作為引數傳遞的

    <resultMap id="userMapper" type="com.mybatis.po.User">
            <result property="id" column="id" />
            <result property="userName" column="user_name" />
            <result property="password" column="passsword" />
            <result property="sex" column="sex"
                typeHandler="org.apache.ibatis.type.EnumTypeHandler" />
        </resultMap>
        <select id="getUser" resultMap="userMapper" parameterType="long">
            select id,user_name,password,sex,mobile,tel,email,note from myUser
            where id=#{id}
        </select>
    

4.10.4.BlobTypeHandler讀取Blob欄位

MyBatis 對資料庫的 Blob 欄位也進行了支援,它提供了一個 BlobTypeHandler,為了應付更多的場景,它還提供了 ByteArrayTypeHandler,只是它不太常用.

create table file(
    id int(12) not null auto_increment,
    content blob not null,
    primary key(id)
);
//pojo
public class TestFile{
    long id;
    byte[] content;
    /** setter and getter **/
}
 <resultMap type="com.ssm.chapter5.pojo.TestFile" id="file">
        <id column="id" property="id"/>
        <id column="content" property="content" typeHandler="org.apache.ibatis.type.BlobTypeHandler"/>
    </resultMap>

4.11.資料庫廠商標識(databaseIdProvider)

MyBatis 可以根據不同的資料庫廠商執行不同的語句,這種多廠商的支援是基於對映語句中的 databaseId 屬性。 MyBatis 會載入帶有匹配當前資料庫 databaseId 屬性和所有不帶 databaseId 屬性的語句。 如果同時找到帶有 databaseId 和不帶 databaseId 的相同語句,則後者會被捨棄。 為支援多廠商特性,只要像下面這樣在 mybatis-config.xml 檔案中加入 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />

databaseIdProvider 對應的 DB_VENDOR 實現會將 databaseId 設定為 DatabaseMetaData#getDatabaseProductName() 返回的字串。 由於通常情況下這些字串都非常長,而且相同產品的不同版本會返回不同的值,你可能想通過設定屬性別名來使其變短:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

在提供了屬性別名時,databaseIdProvider 的 DB_VENDOR 實現會將 databaseId 設定為資料庫產品名與屬性中的名稱第一個相匹配的值,如果沒有匹配的屬性,將會設定為 “null”。 在這個例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 將被設定為“oracle”。

你可以通過實現介面 org.apache.ibatis.mapping.DatabaseIdProvider 並在 mybatis-config.xml 中註冊來構建自己的 DatabaseIdProvider:

public interface DatabaseIdProvider {
  default void setProperties(Properties p) { // 從 3.5.2 開始,該方法為預設方法
    // 空實現
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

5.對映器

5.1.Select

編寫介面dao
User getUserById(int id);

編寫對應的mapper中的sql語句,注意要寫在對應的mapper下

<!--
id:對應的dao介面
resultType:sql語句執行的返回值
parameterType : 引數型別
User:為別名
-->
<select id="getUserById" parameterType="int" resultType="user">
        select * from mybatis.user where id=#{id}
</select>
@Test
    public void getUserInfoById(){
        // 呼叫MyBatisUtils.getSqlSession()方法,獲取SqlSession物件
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        // 呼叫獲取到的SQLSession物件中的getMapper物件
        // 反射Dao介面,動態代理Dao介面中的方法,並將這些方法存在物件【mapper】中
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        Users user = mapper.getUserInfoById(2);
        System.out.println(user);
        // 關閉sqlSession
        sqlSession.close();
    }

查詢所有

 // 【select】所有使用者資訊
    List<Users> getUsersInfo();
<!-- select sql: 繫結getUsersInfo方法,返回所有使用者資訊 -->
    <select id="getUsersInfo" resultType="com.camemax.com.camemax.pojo.Users">
        select * from school.users
    </select>
 @Test
    public void getUsersInfo(){

        // 呼叫MyBatisUtils.getSqlSession()方法,獲取SqlSession物件
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        // 呼叫獲取到的SQLSession物件中的getMapper物件
        // 反射Dao介面,動態代理Dao介面中的方法,並將這些方法存在物件【mapper】中
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);

        // 呼叫mapper中對應方法,並設定對應的物件來接收其返回結果
        // 以下為測試方法getUsersInfo() => 獲取所有Users表中資訊,並用對應類接收
        List<Users> usersInfo = mapper.getUsersInfo();
        // for迴圈遍歷輸出List集合
        for (Users users : usersInfo) {
            System.out.println(users);
        }
        // 關閉sqlSession
        sqlSession.close();

    }

5.2.insert

插入單條細膩

  // 【insert】指定使用者資訊
    int insertUser(Users user);
 <!-- insert sql: 繫結insertUser方法,插入單個使用者資訊-->
    <insert id="insertUser" parameterType="user" >
        insert into users
        values (#{id},#{username},#{password},#{email},#{gender})
    </insert>
 @Test
    public void insertUsers(){
        // 呼叫MyBatisUtils.getSqlSession()方法,獲取SqlSession物件
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        // 呼叫獲取到的SQLSession物件中的getMapper物件
        // 反射Dao介面,動態代理Dao介面中的方法,並將這些方法存在物件【mapper】中
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        int i = mapper.insertUser(
                new Users(2, "wyl", "123456", "171@qq.com", 0)         
        );
        //提交事務
        sqlSession.commit();
        if ( i > 0 ){
            System.out.println("Insert 成功!");
        }
        // 關閉sqlSession
        sqlSession.close();
    }

插入多條資料

    // 【insert】 批量使用者資訊
    int insertManyUseList(List<Users> users);
<!-- insert sql: 繫結insertManyUseMap,批量插入 -->
    <insert id="insertManyUseList" >
        insert into users values
        /* foreach 標籤:
            -【item】屬性: 表示集合中每一個元素進行迭代時的別名
            - 【collection】屬性: 引數型別是一個List的時候,collection屬性值為list
            - 【separator】屬性: 表示在每次進行迭代之間以什麼符號作為分隔符。
        */
        <foreach  item="user" collection="list" separator=",">
            (#{user.id},#{user.username},#{user.password},#{user.email},#{user.gender})
        </foreach>
    </insert>
 @Test
    public void insertManyUseList(){

        List<Users> users = new ArrayList<Users>();
        users.add(new Users(2, "wyl", "123", "123@qq.com", 20));
        users.add(new Users(3, "wjm", "123456", "123456@qq.com", 30));
        users.add(new Users(4, "王延領", "1123456", "wjm@qq.com", 41));
        users.add(new Users(5, "王經墨", "223", "wjm@qq.com", 51));
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        int i = mapper.insertManyUseList(users);
        if ( i > 0 ){
            System.out.println("插入成功過!");
        }
        sqlSession.commit();

        sqlSession.close();
    }

5.3.1.主鍵回填

如果你的資料庫支援自動生成主鍵的欄位(比如 MySQL 和 SQL Server),那麼你可以設定 useGeneratedKeys=”true”,然後再把 keyProperty 設定為目標屬性就 OK 了

 <insert id="insertUser" useGeneratedKeys="true"  keyProperty="id">
        insert into users
        values (#{id},#{username},#{password},#{email},#{gender})
    </insert>

如果你的資料庫還支援多行插入, 你也可以傳入一個 Author 陣列或集合,並返回自動生成的主鍵。

5.3.2.自定義主鍵

首先會執行 selectKey 元素中的語句,並設定 Users的 id,然後才會呼叫插入語句。這樣就實現了資料庫自動生成主鍵類似的行為,同時保持了 Java 程式碼的簡潔。

<insert id="insertUser" useGeneratedKeys="true"  keyProperty="id">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
    select if(max(id)==null,1,max(id)+2) from users
  </selectKey>
        insert into users
        values (#{id},#{username},#{password},#{email},#{gender})
    </insert>

5.3.update

// 【update】指定使用者資訊
    int updateUseInfoById(Users user);
 <!-- update sql: 繫結updateUser方法,更新指定使用者資訊 -->
    <update id="updateUseInfoById" parameterType="users">
        update users
        set username = #{username},
            password = #{password},
            email = #{email},
            gender = #{gender}
        where id = #{id}
    </update>
@Test
    public void updateUseInfoById(){

        SqlSession session = MyBatisUtils.getSqlSession();
        UsersDao mapper = session.getMapper(UsersDao.class);
        int i = mapper.updateUseInfoById(new Users(1, "王延領", "123456", "171@qq.com", 1));
        if ( i > 0 ){
            System.out.println(mapper.getUserInfoById(1).getUsername() + " 修改成了!");
        }
        session.commit();
        session.close();
    }

5.4.delete

// 【delete】指定使用者資訊
    int deleteUserById(int id);
 <!-- delete sql: 繫結deleteUserById方法,刪除指定使用者資訊 -->
    <delete id="deleteUserById" parameterType="int">
        delete from users
        where id = #{id}
    </delete>
@Test
    public void deleteUserInfoById(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        String willDeleteUsername = mapper.getUserInfoById(2).getUsername();
        int i = mapper.deleteUserById(2);

        if (i > 0){
            System.out.println(willDeleteUsername + " 已刪除!");
        }
        sqlSession.commit();
        sqlSession.close();
    }

5.5.模糊查詢like

// 【select】 模糊查詢
    List<Users> getUsersInfoByPhantomSelect(String username);
<!-- select sql: 繫結getUsersInfoByPhantomSelect,模糊查詢 -->
    <select id="getUsersInfoByPhantomSelect" resultType="Users">
        select * from users where username like #{username}
    </select>
 @Test
    public void getUsersInfoByPhantomSelect(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        List<Users> users = mapper.getUsersInfoByPhantomSelect("%e%");
        for (Users user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

5.6.sql

這個元素可以用來定義可重用的 SQL 程式碼片段,以便在其它語句中使用。

<sql id='userCols'> user_name,pwd</sql>
 <select id="getUsersInfoByPhantomSelect" resultType="Users">
        select <include refid='userCols' from school.users where username like #{username}
    </select>

5.7.對映結果(resultMap)

resultMap 元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 資料提取程式碼中解放出來,並在一些情形下允許你進行一些 JDBC 不支援的操作。實際上,在為一些比如連線的複雜語句編寫對映程式碼的時候,一份 resultMap 能夠代替實現同等功能的數千行程式碼。ResultMap的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。

5.7.1結構

<!--元素的 type 屬性表示需要的 POJO,id 屬性是 resultMap 的唯一標識-->
<resultMap id="" type="">
    <constructor><!-- 類再例項化時用來注入結果到構造方法 -->
        <idArg/><!-- ID引數,結果為ID -->
        <arg/><!-- 注入到構造方法的一個普通結果 -->  
    </constructor>
    <id/><!-- 用於表示哪個列是主鍵 -->
    <result/><!-- 注入到欄位或JavaBean屬性的普通結果 -->
    <association property=""/><!-- 用於一對一關聯 -->
    <collection property=""/><!-- 用於一對多、多對多關聯 -->
    <discriminator javaType=""><!-- 使用結果值來決定使用哪個結果對映 -->
        <case value=""/><!-- 基於某些值的結果對映 -->
    </discriminator>
</resultMap>

5.7.2.使用 Map 儲存結果集

任何 select 語句都可以使用 Map 儲存結果,示例程式碼如下:

<!-- 查詢所有使用者資訊存到Map中 -->
<select id="selectAllUserMap" resultType="map">
    select * from user
</select>
@Test
// 查詢所有使用者資訊存到Map中
List<Map<String, Object>> lmp = userDao.selectAllUserMap();
for (Map<String, Object> map : lmp) {
    System.out.println(map);
}

上述 Map 的 key 是 select 語句查詢的欄位名(必須完全一樣),而 Map 的 value 是查詢返回結果中欄位對應的值

5.7.3.使用POJO儲存結果集

Map 用起來很方便,但可讀性稍差,有的開發者不太喜歡使用 Map,更多時候喜歡使用 POJO 的方式。

package com.wyl.mybatis.pojo;
public class User {
    private Integer m_uid;
    private String m_uname;
    private String m_usex;
    // 此處省略setter和getter方法
    @Override
    public String toString() {
        return "User[uid=" + m_uid + ",uname=" + m_uname + ",usex=" + m_usex
                + "]";
    }
}
 <!-- 查詢指定使用者資訊 -->
    <resultMap id="com.wyl.mybatis.pojo.User" type="users">
        <!-- 類屬性【userId】對映為資料庫中的【id】欄位 -->
        <id property="userId" column="id"/>
        <!-- 類屬性【userName】對映為資料庫中的【name】欄位 -->
        <result property="userName" column="name" />
        <!-- 類屬性【userPasswd】對映為資料庫中的【password】欄位 -->
        <result property="userPasswd" column="password" />
    </resultMap>

 <!-- 【resultMap】屬性指向<resultMap>標籤 -->
    <select id="getUsersInfo" resultType="MyBatisAliasUsers" >
        select * from users
    </select>
@Test
// 使用resultMap對映結果集
List<User> listResultMap = userDao.selectResultMap();
for (User myUser : listResultMap) {
    System.out.println(myUser);
}

5.8.分頁

5.8.1.使用limit分頁

//分頁
List<User> getUserByLimit(Map<String,Integer> map);
<!--分頁
語法:
SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3;  #[0,3)
-->
<select id="getUserByLimit" parameterType="map" resultMap="User">
    select * from user limit #{startIndex},#{pageSize}
 </select>
//分頁
@Test
public void getUserByLimit(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper =sqlSession.getMapper(UserMapper.class);
    Map<String,Integer> map=new HashMap<String, Integer>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> userList=mapper.getUserByLimit(map);
    for (User user: userList) {
        System.out.println(user);
     }
    sqlSession.close();
}

5.8.2.RowBounds分頁

//分頁2
List<User> getUserByRowBounds();
<select id="getUserByRowBounds" resultMap="User">
    select * from user
</select>
//分頁2
@Test
public void getUserByRowBounds(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    //RowBounds
    RowBounds rowBounds = new RowBounds(1, 2);
    List<User> userList = sqlSession.selectList("com.wyl.mybatis.dao.UserMapper.getUserByRowBounds",null,rowBounds);
    for (User user: userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

5.8.3.使用分頁外掛

官網地址:https://pagehelper.github.io/

image-20210824093737129

5.9.級聯查詢

級聯關係是一個資料庫實體的概念,有 3 種級聯關係,分別是一對一級聯、一對多級聯以及多對多級聯

5.9.1.一對一關聯查詢

在 MyBatis 中,通過 元素的子元素 處理這種一對一級聯關係。

元素中通常使用以下屬性。

  • property:指定對映到實體類的物件屬性。
  • column:指定表中對應的欄位(即查詢返回的列名)。
  • javaType:指定對映到實體物件屬性的型別。
  • select:指定引入巢狀查詢的子 SQL 語句,該屬性用於關聯對映中的巢狀查詢。
public class Teacher {
    private int tid;
    private String tname;
    private Classes classes;
     
    public int getTid() {
        return tid;
    }
    public void setTid(int tid) {
        this.tid = tid;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }
    public Classes getClasses() {
        return classes;
    }
    public void setClasses(Classes classes) {
        this.classes = classes;
    }
    @Override
    public String toString() {
        return "Teacher [tid=" + tid + ", tname=" + tname + ", classes=" + classes + "]";
    }
     
     
}
public class Classes {
    private int cid;
    private String cname;
    private Teacher teacher;
     
    public int getCid() {
        return cid;
    }
    public void setCid(int cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Classes [cid=" + cid + ", cname=" + cname + ", teacher=" + teacher + "]";
    }
<?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="one.to.one.classesMapper">
    <!--
         方式一:巢狀結果:使用巢狀結果對映來處理重複的聯合結果的子集
                 封裝聯表查詢的資料(去除重複的資料)
         select * from classes c, teacher t where c.tid=t.tid and c.tid=#{tid}
     -->
    <select id="getClasses" resultMap="getClassesMap" parameterType="int">
        select * from classes c ,teacher t
            where c.tid=t.tid and c.tid=#{tid}
    </select>
    <resultMap type="one.to.one.Classes" id="getClassesMap">
        <id column="cid" property="cid"/>
        <result column="cname" property="cname"/>
        <association property="teacher" javaType="one.to.one.Teacher">
            <id column="tid" property="tid"></id>
            <result column="tname" property="tname"/>
        </association>
    </resultMap>
    <!--
         方式一:巢狀結果:使用巢狀結果對映來處理重複的聯合結果的子集
                 封裝聯表查詢的資料(去除重複的資料)
         select * from teacher t,classes c where t.cid = c.cid and t.cid=#{cid}
     -->
    <select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
        select * from teacher t,classes c
            where t.cid = c.cid and t.cid=#{cid}
    </select>
    <resultMap type="one.to.one.Teacher" id="getTeacherMap">
        <id column="tid" property="tid"/>
        <result column="tname" property="tname"/>
        <association property="classes" javaType="one.to.one.Classes">
            <id column="cid" property="cid"/>
            <result column="cname" property="cname"/>
        </association>
    </resultMap>
     
     
    <!--
         方式二:巢狀查詢:通過執行另外一個SQL對映語句來返回預期的複雜型別
         SELECT * FROM classes WHERE cid=1;
         SELECT * FROM teacher WHERE tid=1   //1 是上一個查詢得到的tid的值
         property:別名(屬性名)    column:列名 -->
          <!-- 把teacher的欄位設定進去 -->
    <select id="getClasses2" resultMap="getClassesMap2">
        select * from classes c where c.cid = #{cid}
    </select>
    <resultMap type="one.to.one.Classes" id="getClassesMap2">
        <id column="cid" property="cid"/>
        <result column="cname" property="cname"/>
        <collection property="teacher" column="tid" select="getTeacherCollection">
        </collection>
    </resultMap>
    <select id="getTeacherCollection" resultType="one.to.one.Teacher">
        select tid tid,tname tname from teacher where tid=#{tid}
    </select>
   
</mapper>

我們這裡一對一的關聯操作,有兩種方式:

    1、使用巢狀結果對映來處理重複的聯合結果的子集

    2、通過執行另外一個SQL對映語句來返回預期的複雜型別

 //一對一巢狀結果方式:根據教師id查詢班級資訊
    @Test
    public void testGetClasses(){
        String statement = "one.to.one.classesMapper.getClasses";
        Classes c = session.selectOne(statement, 1);
        System.out.println(c);
    }
     
    //一對一巢狀結果方式:根據班級id查詢教師資訊
    @Test
    public void testGetTeacher(){
        String statement = "one.to.one.classesMapper.getTeacher";
        Teacher t = session.selectOne(statement, 1);
        System.out.println(t);
    }
     
    //一對一巢狀查詢方式:根據教師id查詢班級資訊
    @Test
    public void testGetClasses2(){
        String statement = "one.to.one.classesMapper.getClasses2";
        Classes c = session.selectOne(statement, 1);
        System.out.println(c);
    }

5.9.2. 多對一查詢

  • SQL返回的值需要使用到類時的處理方式

  • 模擬測試:多個學生對應一個老師

    1. MySQL測試表【Teachers】、【Students】
    2. 測試實體類【Teachers】、【Students】
    3. dao層【TeachersMapper】、【StudentsMapper】
    4. XML對映檔案【teachersMapper.xml】、【studentsMapper.xml】
    5. 核心配置檔案=>【mybatis-config.xml】繫結dao介面、註冊XML對映檔案
    6. 輸出測試
  • 整體目錄結構

    image-20200829123326705

5.9.2.1 環境搭建

MySQL建立測試資料

use school;

#教師表
DROP TABLE IF exists teachers;
create table teachers(
	`tid` int(10),
	`tname` varchar(20) DEFAULT NULL,
	PRIMARY KEY (`tid`)
	)ENGINE=INNODB DEFAULT CHARSET=utf8;

#學生表
DROP TABLE IF exists students;
create table students(
	`id` int(10) ,
	`name` varchar(20) DEFAULT NULL,
	`tid` int(10) DEFAULT NULL,
	PRIMARY KEY (`id`),
	CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teachers` (`tid`)	
	)ENGINE=INNODB DEFAULT CHARSET=utf8;
	
	insert into teachers (`tid`,`tname`) values (1,'卡梅克斯');
	
	insert into students (`id`,`name`,`tid`) values (1,'小紅',1);
	insert into students (`id`,`name`,`tid`) values (2,'小黃',1);
	insert into students (`id`,`name`,`tid`) values (3,'小黑',1);
	insert into students (`id`,`name`,`tid`) values (4,'小白',1);
	insert into students (`id`,`name`,`tid`) values (5,'小紫',1);

5.9.2.2 實體類與介面

  • 學生相關

    • 【Students】實體類

      package com.camemax.pojo;
      import org.apache.ibatis.type.Alias;
      
      @Alias("students")
      public class Students {
          private int sid;
          private String sname;
          // 新增【Teachers】類屬性
          private Teachers teacher;
      
          public Students() {};
      
          public Students(int sid, String sname, Teachers teacher) {
              this.sid = sid;
              this.sname = sname;
              this.teacher = teacher;
          }
      
          public int getSid() {
              return sid;
          }
      
          public void setSid(int sid) {
              this.sid = sid;
          }
      
          public String getSname() {
              return sname;
          }
      
          public void setSname(String sname) {
              this.sname = sname;
          }
      
          public Teachers getTeacher() {
              return teacher;
          }
      
          public void setTeacher(Teachers teacher) {
              this.teacher = teacher;
          }
      
          @Override
          public String toString() {
              return "Students{" +
                      "sid=" + sid +
                      ", sname='" + sname + '\'' +
                      ", teacher=" + teacher +
                      '}';
          }
      }
      
    • 【StudentsMapper】介面

      package com.camemax.dao;
      
      import com.camemax.pojo.Students;
      import java.util.List;
      
      public interface StudentsMapper {
      	//查詢所有學生資訊,同時輸出教師資訊
          List<Students> getStudentsInfo();
      }
      
      
  • 教師相關

    • 【Teachers】實體類

      package com.camemax.pojo;
      
      import org.apache.ibatis.type.Alias;
      
      @Alias("teachers")
      public class Teachers {
          private int tid;
          private String tname;
      
          public Teachers() {};
      
          public Teachers(int tid, String tname) {
              this.tid = tid;
              this.tname = tname;
          }
      
          public int getTid() {
              return tid;
          }
      
          public void setTid(int tid) {
              this.tid = tid;
          }
      
          public String getTname() {
              return tname;
          }
      
          public void setTname(String tname) {
              this.tname = tname;
          }
      
          @Override
          public String toString() {
              return "Teachers{" +
                      "tid=" + tid +
                      ", tname='" + tname + '\'' +
                      '}';
          }
      }
      
    • 【TeachersMapper】介面

      package com.camemax.dao;
      
      public interface TeachersMapper {
      }
      

5.9.2.3 Mapper對映器

  • mybatis-config.xml

    <configuration>
        <properties resource="db.properties"/>
    
        <settings>
            <setting name="logImpl" value="LOG4J"/>
        </settings>
    
        <typeAliases>
            <package name="com.camemax.pojo"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${propDriver}"/>
                    <property name="url" value="${propUrl}"/>
                    <property name="username" value="${propUsername}"/>
                    <property name="password" value="${propPassword}"/>
                </dataSource>
            </environment>
        </environments>
        <!-- 註冊Mapper-->
        <mappers>
            <mapper resource="mapper/studentsMapper.xml"/>
            <mapper resource="mapper/teachersMapper.xml"/>
        </mappers>
    </configuration>
    

5.9.2.4. 按查詢巢狀處理【子查詢】

  • studentsMapper.xml

     <!-- 按查詢巢狀處理 -->
    <select resultMap="StudentsInfoMapBySelect" id="getStudentsInfo">
        select * from school.students
    </select>
    <resultMap id="StudentsInfoMapBySelect" type="students">
        <id property="sid" column="id"/>
        <result property="sname" column="name"/>
    
        <!-- 複雜型別: Teachers類
                【association】: 物件
                    - 【property】: 設定獲取到的結果集欄位 => private Teachers teacher
                    - 【column】: 設定對映對應的資料庫欄位 => tid
                    - 【javaType】: 設定返回型別 => Teachers
                    - 【select】: 子查詢繫結。通過其他<select>標籤中的值,指向其他select語句 => <select id="TeachersInfo">
                【collection】: 集合
            -->
        <association property="teacher" column="tid" javaType="Teachers" select="TeachersInfo"/>
    </resultMap>
    
    <!-- 查詢指定教師資訊 -->
    <select id="TeachersInfo" resultType="teachers">
        select * from school.teachers where tid = #{tid}
    </select>
    
    
    
  • teachersMapper.xml

    <mapper namespace="com.wyl.mybatis.dao.TeachersMapper">
    </mapper>
    

5.9.2.5 按結果巢狀處理【關聯】

  • studentsMapper.xml

    <!-- 按結果巢狀處理 -->
    <select id="getStudentsInfo" resultMap="getStudentsInfoByResult">
        select s.id studentId,
        s.name studentName,
        t.tname teacherName
        from students s,teachers t
        where s.tid = t.tid;
    </select>
    <resultMap id="getStudentsInfoByResult" type="students">
        <id property="sid" column="studentId"/>
        <result property="sname" column="studentName"/>
        <association property="teacher" javaType="Teachers">
            <result property="tname" column="teacherName"/>
        </association>
    </resultMap>
    
  • teachersMapper.xml

    <mapper namespace="com.wyl.mybatis.dao.TeachersMapper">
    </mapper>
    

5.9.3. 一對多查詢

  • 模擬測試:一名老師有多名學生 => 【面向教師】
  • 本質:使用<collection>標籤完成一對多的輸出

5.9.3.1. 基於[12.1環境搭建](#12.1 環境搭建)做出的修改

  1. dao層 => 【TeachersDao】

    package com.camemax.dao;
    
    import com.camemax.pojo.Students;
    import com.camemax.pojo.Teachers;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    public interface TeachersMapper {
    	// 傳入指定教師編號,返回其下學生資訊
        List<Students> getTeacherByIdHasStudents(@Param("tid") int tid);
    }
    
    
  2. 實現類 => 【Teachers】

    package com.camemax.pojo;
    
    import org.apache.ibatis.type.Alias;
    
    import java.util.List;
    
    @Alias("teachers")
    public class Teachers {
        private int tid;
        private String tname;
        // 新增屬性 : 教師擁有的學生
        private List<Students> teacherHasStudents;
    
        public List<Students> getTeacherHasStudents() {
            return teacherHasStudents;
        }
    
        public void setTeacherHasStudents(List<Students> teacherHasStudents) {
            this.teacherHasStudents = teacherHasStudents;
        }
    
        public Teachers(int tid, String tname, List<Students> teacherHasStudents) {
            this.tid = tid;
            this.tname = tname;
            this.teacherHasStudents = teacherHasStudents;
        }
    
        public Teachers() {};
    
        public int getTid() {
            return tid;
        }
    
        public void setTid(int tid) {
            this.tid = tid;
        }
    
        public String getTname() {
            return tname;
        }
    
        public void setTname(String tname) {
            this.tname = tname;
        }
    
        @Override
        public String toString() {
            return "Teachers{" +
                    "tid=" + tid +
                    ", tname='" + tname + '\'' +
                    ", teacherHasStudents=" + teacherHasStudents +
                    '}';
        }
    }
    
  3. 實體類 => 【Students】

    package com.camemax.pojo;
    
    import org.apache.ibatis.type.Alias;
    
    @Alias("students")
    public class Students {
        private int sid;
        private String sname;
        private int tid;
    
        public Students(){};
        
        public Students(int sid, String sname, int tid) {
            this.sid = sid;
            this.sname = sname;
            this.tid = tid;
        }
    
        @Override
        public String toString() {
            return "Students{" +
                    "sid=" + sid +
                    ", sname='" + sname + '\'' +
                    ", tid=" + tid +
                    '}';
        }
    
        public int getSid() {
            return sid;
        }
    
        public void setSid(int sid) {
            this.sid = sid;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        public int getTid() {
            return tid;
        }
    
        public void setTid(int tid) {
            this.tid = tid;
        }
    }
    
  4. 測試實現類 => 【DaoTest】

    @Test
    public void getStudentsByTid(){
        MyBatisUtils mybatis = new MyBatisUtils();
        SqlSession sqlSession = mybatis.getSqlSession();
        TeachersMapper mapper = sqlSession.getMapper(TeachersDao.class);
        
        System.out.println(mapper.getStudentsByTid(1));
      	sqlSession.close();
    }
    

5.9.3.2. 按查詢巢狀處理 【子查詢】

  1. XML對映檔案 => teachersMapper.xml

    <select id="getStudentsByTid" resultMap="getStudentsByTidMapUseSelect">
        select * from school.teachers where tid = #{tid}
    </select>
    
    <!-- 建立【getStudentsByTidMapUseSelect】對映結果集,實現一對多結果返回。 
    	注意:Teachers類 使用了 @Alias("teachers") 
    -->
    <resultMap id="getStudentsByTidMapUseSelect" type="teachers">
        <id property="tid" column="tid" />
        <result property="tname" column="name" />
        <!-- Teachers類中新增List<Students> teacherHasStudents屬性欄位 
    			javaType: 指定在java中的欄位型別屬性
    			ofType: 指定型別所屬類
    			select: 使resultMap繫結指定<select>標籤
    			column: 使resultMap傳遞指定的屬性欄位
    	-->
        <collection property="teacherHasStudents" javaType="ArrayList" ofType="students" select="getStudentsByTid" column="tid"/> 
    </resultMap>
    
    <!-- 子查詢:學生資訊 -->
    <select id="getStudentsByTid" resultMap="studentsMap">
    	select * from school.students where tid = #{tid}
    </select>
    
    <!-- 建立【studentsMap】,對映Students類中,與Teachers表欄位不一致的屬性欄位 -->
    <resultMap id="studentsMap" type="students">
        <id property="sid" column="id" />
        <result property="sname" column="name"/>
     	<!-- 不加會導致欄位【tid】結果為0 -->   
        <result property="tid" column="tid" />
    </resultMap>
    
  2. 輸出結果

    // 按查詢巢狀處理 => 子查詢 結果:
    [Teachers{tid=1, tname='卡梅克斯', teacherHasStudents=[Students{sid=1, sname='小紅', tid=1}, Students{sid=2, sname='小黃', tid=1}, Students{sid=3, sname='小黑', tid=1}, Students{sid=4, sname='小白', tid=1}, Students{sid=5, sname='小紫', tid=1}]}]
    

5.9.3.3. 按結果巢狀處理 【關聯查詢】

  1. XML對映檔案 => teachersMapper.xml

    <select id="getTeacherByIdHasStudents" resultMap="teacherGetStudentsByResult">
        select s.id studentId,s.name studentName,s.tid,t.tname teacherName,t.tid
        from students s,teachers t
        where s.tid = t.tid
        and t.tid = #{tid}
    </select>
    <resultMap id="teacherGetStudentsByResult" type="teachers">
        <id property="tid" column="tid"/>
        <result property="tname" column="teacherName"/>
        <collection property="teacherHasStudents" ofType="students">
            <id property="sid" column="studentId"/>
            <result property="sname" column="studentName"/>
            <result property="tid" column="tid" />
        </collection>
    </resultMap>
    
  2. 測試結果

    // 按結果巢狀處理 => 關聯查詢 結果:
    [Teachers{tid=1, tname='卡梅克斯', teacherHasStudents=[Students{sid=1, sname='小紅', tid=1}, Students{sid=2, sname='小黃', tid=1}, Students{sid=3, sname='小黑', tid=1}, Students{sid=4, sname='小白', tid=1}, Students{sid=5, sname='小紫', tid=1}]}]
    

5.10.快取

如果每次查詢都連線資料庫 ,耗資源!一次查詢的結果,給他暫存在一個可以直接取到的地方!–> 記憶體 : 快取
我們再次查詢相同資料的時候,直接走快取,就不用走資料庫了

所以經常查詢又不常改變的資料可以使用快取,減少和資料庫的互動次數,減少系統開銷,提高系統效率.

  • MyBatis包含一個非常強大的查詢快取特性,它可以非常方便地定製和配置快取。快取可以極大的提升查詢效率。

  • MyBatis系統中預設定義了兩級快取:一級快取二級快取.

    • 預設情況下,只有一級快取開啟。(SqlSession級別的快取,也稱為本地快取)

    • 二級快取需要手動開啟和配置,他是基於namespace級別的快取。

    • 為了提高擴充套件性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來自定義二級快取

5.10.1.一級快取

也叫本地快取: SqlSession.

  1. 一級快取預設是開啟的,只在一次SqlSession中有效,也就是拿到連線到關閉連線這個區間段!

  2. 一級快取就是一個Map

  3. 在同一個 SqlSession 中, Mybatis 會把執行的方法和引數通過演算法生成快取的鍵值, 將鍵值和結果存放在一個 Map 中, 如果後續的鍵值一樣, 則直接從 Map 中獲取資料;

  4. 不同的 SqlSession 之間的快取是相互隔離的;

  5. 用一個 SqlSession, 可以通過配置使得在查詢前清空快取;

  6. 任何的 UPDATE, INSERT, DELETE 語句都會清空快取。

5.10.2.二級快取

  1. 級快取也叫全域性快取,一級快取作用域太低了,所以誕生了二級快取
  2. 基於namespace級別的快取,一個名稱空間,對應一個二級快取;
  3. 工作機制
    3.1.一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中;
    3.2.如果當前會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中;
    3.3.新的會話查詢資訊,就可以從二級快取中獲取內容;
    3.4.不同的mapper查出的資料會放在自己對應的快取(map)中;

步驟:

開啟全域性快取

<!--顯示的開啟全域性快取-->
<setting name="cacheEnabled" value="true"/>

在要使用二級快取的Mapper中開啟

<!--在當前Mapper.xml中使用二級快取-->
<cache/>

也可以自定義引數

<!--在當前Mapper.xml中使用二級快取-->
<cache  eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

小結:

  • 只要開啟了二級快取,在同一個Mapper下就有效
  • 所有的資料都會先放在一級快取中;
  • 只有當會話提交,或者關閉的時候,才會提交到二級緩衝中

6.動態sql

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

MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。

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

6.1.if

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

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

6.2.choose、when、otherwise

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

6.3.trim、where、set

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

6.3.foreach

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

6.4.script

 @Update({"<script>",
      "update Author",
      "  <set>",
      "    <if test='username != null'>username=#{username},</if>",
      "    <if test='password != null'>password=#{password},</if>",
      "    <if test='email != null'>email=#{email},</if>",
      "    <if test='bio != null'>bio=#{bio}</if>",
      "  </set>",
      "where id=#{id}",
      "</script>"})
    void updateAuthorValues(Author author);

6.5.bind

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

6.6.trim

<select id="queryBlogsByTrim" parameterType="blogs">
	select * from test.blog
    <trim prefix="WHERE" prefixOverride="AND |OR ">
        <if test="titleMap != null"> AND title = #{titleMap}</if>
        <if test="authorMap != null"> OR author = #{authorMap}</if>
    </trim>
</select>
<update id="updateBlogInfoByTrim" parameterType="map">
	update test.blog
    <trim prefix="SET" suffixOverride=",">
        <if test="titleMap != null"> title = #{titleMap},</if>
        <if test="authorMap != null"> author = #{authorMap},</if>
    </trim>
    where id = #{idMap}
</update>

6.7.多資料庫支援

如果配置了 databaseIdProvider,你就可以在動態程式碼中使用名為 “_databaseId” 的變數來為不同的資料庫構建特定的語句。比如下面的例子:

<insert id="insert">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
      select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
      select nextval for seq_users from sysibm.sysdummy1"
    </if>
  </selectKey>
  insert into users values (#{id}, #{name})
</insert>

6.8.動態 SQL 中的插入指令碼語言

MyBatis 從 3.2 版本開始支援插入指令碼語言,這允許你插入一種語言驅動,並基於這種語言來編寫動態 SQL 查詢語句。

可以通過實現以下介面來插入一種語言:

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

實現自定義語言驅動後,你就可以在 mybatis-config.xml 檔案中將它設定為預設語言:

<typeAliases>
  <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
  <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

或者,你也可以使用 lang 屬性為特定的語句指定語言:

<select id="selectBlog" lang="myLanguage">
  SELECT * FROM BLOG
</select>

或者,在你的 mapper 介面上新增 @Lang 註解:

public interface Mapper {
  @Lang(MyLanguageDriver.class)
  @Select("SELECT * FROM BLOG")
  List<Blog> selectBlog();
}

7.註解

image-20210824160741551

優缺點:

  • 優點:省去複雜的mapper對映器中的sql程式碼相關配置
  • 缺點:無法執行復雜的SQL,例如:存在欄位異常不匹配時,使用註解執行SQL容易出現找不到值的情況(查詢結果為'null')
 @Select("select * from school.users where id = #{userId}")
    Users getUserInfoById(@Param("userId") int id);

    @Insert("insert into school.users value(#{id},#{username},#{password},#{email},#{gender})")
    int insertUserInfo(@Param("userId") int id
            ,@Param("userName") String username
            ,@Param("userPassword") String password
            ,@Param("userEmail") String email
            ,@Param("userGender") int gender
    );

    @Delete("delete from school.users where id = #{userId}")
    int deleteUserInfoById(@Param("userId") int id);

    @Update("update school.users set username = #{userName} , password = #{userPassword} , email = #{userEmail} , gender = #{userGender} where id = #{userId}")
    int updateUserInfoById(
            @Param("userId") int id
            ,@Param("userName") String username
            ,@Param("userPassword") String password
            ,@Param("userEmail") String email
            ,@Param("userGender") int gender
    );

8.日誌

Mybatis 通過使用內建的日誌工廠提供日誌功能。內建日誌工廠將會把日誌工作委託給下面的實現之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

在mybatis-config.xml中我們已經設定了日誌的預設值為STDOUT_LOGGING標準日誌輸出

 <!--配日誌-SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

測試輸出

image-20210824182641931

8.1.Log4J

  • Log4j是Apache的一個開源專案,通過使用Log4j,可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIX Syslog守護程式等;
  • 控制每一條日誌的輸出格式;通過定義每一條日誌資訊的級別,能夠更加細緻地控制日誌的生成過程。
  • 通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。
  1. 匯入Maven依賴

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. 對映器開啟日誌功能

    <configuration>
      <settings>
        ...
        <setting name="logImpl" value="LOG4J"/>
        ...
      </settings>
    </configuration>
    
  3. log4j.properties

#將等級為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/MyBatis.txt
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

8.2.日誌類使用

匯入Apache-Log4J包

import org.apache.log4j.Logger;

使用反射當前物件來建立當前Logger物件

// 建立靜態變數Logger物件 => logger
// 使用當前類.class反射建立logger物件
static Logger logger = logger.getLogger(UsersDaoTest.class)
@Test
public void log4jTest(){
    logger.info("info: 日誌輸出等級【Info】");
    logger.debug("debug: 日誌輸出等級【DEBUG】");
    logger.error("error: 日誌輸出等級【ERROR】");
}

image-20210824183517453

相關文章