【MyBatis系列3】收藏!最全MyBatis中XML映射文件(Mapper)標簽分析及示例

前言

MyBatis的強大之處就在于它的映射器文件,而這也正是MyBatis的魔力所在,對于任何MyBatis的使用者來說,MyBatis的映射文件是必須要掌握的。

Mapper文件標簽

Mapper中一個提供了9個頂層標簽,除了1個已經過期的我們不需要去了解,另外8個都是必須要掌握的,只要熟練掌握了標簽的使用,使用MyBatis才能如魚得水。接下來我們就一個個來分析一下這些標簽的使用。

select

select用來映射查詢語句,是我們使用最多的一種標簽,也是最復雜的一種標簽。比如下面就是一個簡單的select標簽的使用:

<select id="listUserByUserName" parameterType="String" resultType="lwUser">
        select user_id,user_name from lw_user where user_name=#{userName}
</select>

select標簽內,提供了一些二級標簽,下面就列舉出了全部的二級標簽:

<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY"
databaseId="mysql"
resultOrdered="false"
resultSets="xxx,xxx"
lang="">

id

必選標簽。同一個命名空間里面的唯一標識符,如果需要被外部接口調用,則需要和Mapper接口中的方法名保持一致。

parameterType

可選標簽。參數類的完全限定名或別名,上面示例中的表示我們傳入的參數是一個String類型(關于別名如果不清楚的可以點擊這里)。如果不寫這個屬性的話,MyBatis在解析xml文件的時候會默認設為unset,然后根據TypeHandler推斷出參數類型。如果有多個參數的情況下建議還是不寫這個參數,否則可能會出現參數類型轉換錯誤

parameterMap

這是一個過期的屬性,我們不做討論。

resultType

非必選標簽。注意這里的非選是因為resultType和resultMap不能并存,兩者能且只能選擇一個。主要是用來定義一個返回結果集對象的全限定名或者別名。如果接收參數是一個集合,那么這里定義的就是集合中可以包含的類型,而并不是集合本身。
比如示例中的寫法,那么對應Mapper接口中,適用于以下兩種語句:

LwUser listUserByUserName(@Param("userName") String userName);
List<LwUser> listUserByUserName(@Param("userName") String userName);

resultMap

非必選標簽。注意這里的非選是因為resultType和resultMap不能并存,兩者能且只能選擇一個。resultMap類型的結果集映射,是MyBatis最強大的特性,在這里我們不展開,過兩天會有一篇單獨介紹MyBatis一對一和一對多等復雜查詢時候會單獨介紹該屬性。感興趣的可以先關注我,留意后面的文章。

flushCache

可選標簽。設置為 true時,任何時候只要語句被調用,都會導致本地緩存和二級緩存都會被清空,默認值:false。

useCache

可選標簽。設置為 true時將會導致本條語句的結果被二級緩存,對 select 標簽語句默認值為true,對insert,delete,update等語句默認是false。

timeout

可選標簽。這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為 unset(依賴驅動)。

fetchSize

可選標簽。這是嘗試影響驅動程序每次批量返回的結果行數和這個設置值相等。默認值為 unset(依賴驅動)。注意這個只是嘗試,假如把fetchSize設置為10萬,而數據庫驅動最高只支持到5w,那么也會只能返回5w數據

statementType

可選標簽。可以選擇:STATEMENT,PREPARED 或 CALLABLE 中的一個,這會讓 MyBatis 分別使用Statement,PreparedStatement 或 CallableStatement,默認值是PREPARED,也就是使用預編譯PreparedStatement 語句。

resultSetType

可選標簽。可以選擇以下三種類型中的一個,默認為unset(依賴驅動)。

  • FORWARD_ONLY:只允許游標向前訪問
  • SCROLL_SENSITIVE:允許游標雙向滾動,但不會及時更新數據,也就是說如果數據庫中的數據被修改過,并不會在resultSet中體現出來
  • SCROLL_INSENSITIVE:許游標雙向滾動,如果數據庫中的數據被修改過,會及時更新到resultSet

上面的解釋可能有些人還是看不明白,我們先來看一段JDBC讀取結果集的操作:


image.png

而MyBatis只是把這些操作封裝了,底層實際上還是這個操作,rs.next()游標向前滾,其實還有一個rs.previous()表示游標可以向后滾。
所以FORWARD_ONLY只允許向前滾,訪問過的數據就會釋放內存,在某些場景中可以提升性能。
后面那兩個都是允許雙向滾動,所以即使訪問過得數據,內存也不能釋放。這兩個的區別就是一個對數據敏感,一個對數據不敏感。

  • 對數據不敏感 就是說當我們查詢出結果之后,會將整個結果集都緩存在內存中,假如有一條數據還沒讀取到(還在while循環中)這時候有另外一個線程修改了這條數據,那么當我們后面讀取這條數據的時候,還是讀取到修改之前的。
  • 對數據敏感 就是說當我們查詢出結果之后,只會緩存一個rowid,而并不會緩存整條數據,假如有一條數據還沒讀取到(還在while循環中)這時候有另外一個線程修改了這條數據,那么當我們后面讀取這條數據的時候,會根據rowid去查詢數據,查詢到的就是最新的數據。不過需要注意的是,因為delete的時候數據其實還在,只是打了個標記,所以如果一條數據被刪除了,是體現不出來的。同理,insert也不影響,因為查詢出來的數據不包含insert數據的rowid。

如果對于MySQL的InnoDB引擎的MVCC機制那么數據肯定是不會敏感的,因為其他事務改了當前事務也看不到。

databaseId

可選標簽

resultOrdered

可選標簽。這個設置僅針對嵌套結果 select 語句適用。如果為 true,就是假設包含了嵌套結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。這就使得在獲取嵌套的結果集的時候不至于導致內存不夠用。默認值: false 。

resultSets

可選標簽。這個設置僅對多結果集的情況適用,它將列出語句執行后返回的結果集并每個結果集給一個名稱,名稱是逗號分隔的。

lang

自定義語言,這個我也沒用過,所以就不介紹了

insert

insert用來映射插入語句。以下就是一個insert標簽的全部二級標簽:

<insert
id="insertLwUser"
parameterType="lwUser"
parameterMap="deprecated"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20"
databaseId="mysql"
lang="">

有一些標簽和select語句是重復的就不在重復介紹,主要來關注一下其他標簽。

useGeneratedKeys

可選標簽。配置為true時,MyBatis會使用JDBC的getGeneratedKeys方法來取出由數據庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系數據庫管理系統的自動遞增字段),默認值為false。

keyProperty

可選標簽。唯一標記一個屬性,MyBatis會將通過getGeneratedKeys 的返回值或者通過insert 語句的selectKey 子元素設置它的鍵值,默認值是unset 。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表

keyColumn

通過生成的鍵值設置表中的列名,這個設置僅在某些數據庫(像PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設置。如果希望得到多個生成的列,也可以是
逗號分隔的屬性名稱列表

獲取自增主鍵

獲取自增主鍵,可以通過keyProperty來映射
定義一個實體類:

package com.lonelyWolf.mybatis.model;

public class UserAddress {
    private int id;
    private String address;

    public int getId() {
        return id;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

定義一個UserAddressMapper.java接口:

package com.lonelyWolf.mybatis.mapper;

import com.lonelyWolf.mybatis.model.UserAddress;
import org.apache.ibatis.annotations.Param;

public interface UserAddressMapper {
    int insert(UserAddress userAddress);
}

注意:這里參數如果直接只有一個的話可以不適用@Param注解,這樣在xml文件中可以直接使用JavaBean內的屬性名。如果使用了@Param注解,如下:

int insert(@Param("userAddress") UserAddress userAddress);

那么xml文件中就可以使用#{userAddress.屬性名}來獲取屬性JavaBean內的屬性

定義一個UserAddressMapper.xml映射文件(keyProperty="id"表示把主鍵的值設置到參數UserAddress類中的屬性id):

<?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="com.lonelyWolf.mybatis.mapper.UserAddressMapper">

   <insert id="insert" parameterType="com.lonelyWolf.mybatis.model.UserAddress" useGeneratedKeys="true" keyProperty="id">
       insert into lw_user_address (address) values (#{address})
   </insert>
</mapper>

mybatis-config.xml中要新增mapper映射文件配置:

<mappers>
        <mapper resource="com/lonelyWolf/mybatis/mapping/UserAddressMapper.xml"/>
    </mappers>

然后寫一個測試類:

package com.lonelyWolf.mybatis;

import com.lonelyWolf.mybatis.mapper.UserAddressMapper;
import com.lonelyWolf.mybatis.model.UserAddress;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class TestMyBatisInsert {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        //讀取mybatis-config配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //創建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //創建SqlSession對象
        SqlSession session = sqlSessionFactory.openSession();

        try {
            UserAddress userAddress = new UserAddress();
            userAddress.setAddress("廣東深圳");
            UserAddressMapper userAddressMapper = session.getMapper(UserAddressMapper.class);
            int i = userAddressMapper.insert(userAddress);
            session.commit();
            System.out.println("插入成功數:" + i);
            System.out.println("插入數據的主鍵為:" + userAddress.getId());
        }finally {
            session.close();
        }
    }
}

輸出結果(成功獲取到了插入數據的主鍵):

插入成功數:1
插入數據的主鍵為:1

通過selectKey獲取自定義列

假如有些數據庫不支持自增主鍵,或者說我們想插入自定義的主鍵,而又不想在業務代碼中編寫邏輯,那么就可以通過MyBatis的selectKey來獲取。
UserAddressMapper.java中新建一個方法:

int insert2(UserAddress userAddress);

然后在UserAddressMapper.xml中對應新增一個insert2語句:

<insert id="insert2"  useGeneratedKeys="true" keyProperty="address">
    <selectKey keyProperty="address" resultType="String" order="BEFORE">
        select uuid() from lw_user_address
    </selectKey>
        insert into lw_user_address (address) values (#{address})
    </insert>

然后修改測試類:

try {
            UserAddress userAddress = new UserAddress();
            UserAddressMapper userAddressMapper = session.getMapper(UserAddressMapper.class);
            int i = userAddressMapper.insert2(userAddress);
            session.commit();
            System.out.println("插入成功數:" + i);
            System.out.println("插入數據的address為:" + userAddress.getAddress());
        }finally {
            session.close();
        }

輸出結果如下,成功獲得了我們自定義的address:

插入成功數:1
插入數據的address為:097dfc8b-f043-11ea-97c4-00163e12524a

selectKey中的order屬性有2個選擇:BEFORE和AFTER。

  • BEFORE:表示先執行selectKey的語句,然后將查詢到的值設置到JavaBean對應屬性上,然后再執行insert語句。
  • AFTER:表示先執行AFTER語句,然后再執行selectKey語句,并將selectKey得到的值設置到JavaBean中的屬性。上面示例中如果改成AFTER,那么插入的address就會是空值,但是返回的JavaBean屬性內會有值。

PS:selectKey中返回的值只能有一條數據,如果滿足條件的數據有多條會報錯,所以一般都是用于生成主鍵,確保唯一,或者在selectKey后面的語句加上條件,確保唯一

update

insert用來映射更新語句。以下就是一個undate標簽的全部二級標簽:

<update
id="UpdateLwUser"
parameterType="lwUser"
parameterMap="deprecated"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20"
databaseId="mysql"
lang="">

這個標簽和insert基本一致,就不重復解釋了。

delete

delete用來映射刪除語句。以下就是一個delete標簽的全部二級標簽:

<delete
id="insertLwUser"
parameterType="lwUser"
parameterMap="deprecated"
flushCache="true"
statementType="PREPARED"
timeout="20"
databaseId="mysql"
lang="">

這里的標簽除了少了useGeneratedKeys,keyProperty和keyColumn三個標簽之外,其余的和insert,update一樣。

sql

這個元素可以被用來定義可重用的 SQL 代碼段,可以包含在其他語句中。
如下是一個最簡單的例子:

  <select id="selectUserAddress" resultType="com.lonelyWolf.mybatis.model.UserAddress">
        select <include refid="myCloumn"></include> from lw_user_address
    </select>

    <sql id="myCloumn" >
        id,address
    </sql>

如果說我們現在需要定義一個關聯語句,列來自于兩張不同的表,又該如何實現呢?這時候就可以通過制定參數的方式來實現了:

 <select id="selectUserAddress" resultType="com.lonelyWolf.mybatis.model.UserAddress">
        select
        <include refid="myCloumn1">
            <property name="prefix1" value="u"/>
            <property name="prefix2" value="j"/>
        </include>
        from lw_user u inner join  lw_user_job j on u.user_id=j.user_id
    </select>

    <sql id="myCloumn1" >
        ${prefix1}.user_id,${prefix2}.id
    </sql>

這時候打印出來的sql語句如下:

select u.user_id,j.id from lw_user u inner join lw_user_job j on u.user_id=j.user_id

cache

MyBatis 包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定制。但是默認情況下只開啟了一級緩存,即局部的session緩存,如果想要開啟二級緩存。那么就需要使用到cache標簽

<cache
type="com.lonelyWolf.xxx"
eviction="FIFO"
flushInterval="60000"
readOnly="true"
size="512"/>

PS:這些屬性都是有默認值的,所以一般情況下可以直接使用:

<cache/>

關于默認值是多少,請繼續往下看。。。。

type

如果說我們自己自定義了緩存,那么這里可以配置自定義緩存類的全限定名或者別名,如果沒有自定義緩存,則不需要配置type屬性。

關于緩存相關原理以及如何自定義緩存,后面會有一篇文章專門介紹緩存,本文主要還是介紹一下標簽的使用,不會過多涉及底層原理性問題。

eviction

緩存回收策略,MyBatis中more提供了以下策略可以選擇:

  • LRU:最近最少使用算法(默認算法)。移除最長時間不被使用的對象
  • FIFO:先進先出算法。按對象進入緩存的順序來移除它們
  • SOFT:[軟引用]移除基于垃圾回收器狀態和軟引用規則的對象。
  • WEAK:[弱引用]更積極地移除基于垃圾收集器狀態和弱引用規則的對象

flushInterval

刷新間隔時間(單位是毫秒)。可以被設置為任意的正整數。默認情況是不設置,也就是不會主動刷新緩存(只有等待sql被執行的時候才會被刷新)

readOnly

是否只讀。屬性可以被設置為 true 或 false。如果設置為true,那么只讀的緩存會給所有調用者返回緩存對象的相同示例,因為緩存無法被修改。這在一定程度上可以提升性能。
默認是false,也就是可以修改緩存,那么當讀取緩存的時候會通過序列化的方式返回緩存對象的拷貝,雖然這么做會慢一點,但是安全,因此默認才會設置為false,允許修改緩存。

size

引用數目。通俗點就是可以緩存的個數,默認值是1024。超過了設置值的時候,就會采用上面的算法進行覆蓋

cache-ref

假如我們在其中一個Mapper中已經配置好了緩存,然后在其他Mapper想要共用,那么在這樣的情況下就可以使用cache-ref元素來引用另外一個緩存,從而不需要重復配置。如:

<cache-ref namespace="com.lonelyWolf.mybatis.mapper.UserAddressMapper"/>

這樣當前Mapper就可以共用UserMapper文件中的相同緩存了。

需要注意的是,被引用的namespace自己本身要開啟了二級緩存,否則會報錯:


image.png

resultMap

上面介紹select標簽的時候提到,select標簽的返回結果可以使用resultMap,但是一旦我們使用了resultMap時,我們就必須要自己定義一個resultMap。
如下,我們定義了一個resultMap:

 <resultMap id="JobResultMap" type="lwUser">
        <result column="user_id" property="userId" jdbcType="VARCHAR" />
        <result column="user_name" property="userName" jdbcType="VARCHAR" />
    </resultMap>

這時候select語句就可以引用:

<select id="selectUserAndJob" resultMap="JobResultMap">
        select * from lw_user
    </select>

可以看到,resultMap可以自由定義,所以可以接受非常復雜的查詢返回結果集

parameterMap

這個參數已過期,不再討論,可以忘掉有這個參數

總結

本文主要介紹了MyBatis中映射文件Mapper.xml文件一些標簽的使用,可以算是最全MyBatis中XML映射文件標簽分析了,當然這其中并不涉及到如何完成動態sql語句的拼寫。動態sql也是MyBatis的一個非常重要的功能點,但是綜合考慮如果單純通過一篇文章來書寫sql語句動態標簽的使用,會顯得非常枯燥,所以動態sql的映射就不準備通過單獨的文章來書寫了,雖然如此,但是依然會通過一些相關知識點來介紹動態標簽的使用,比如下一篇介紹MyBatis中一對多和多對多的結果集如何返回時,就會涉及到一些標簽,如where,if,set,choose等,后面還會介紹批量操作等,也會涉及到foreach等一些標簽的使用。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。