天天看點

Mybatis 實踐篇(三)Mybatis映射檔案詳解XML映射檔案insert | delete | updatesqlselect參數傳遞結果映射

在我們日常編碼中,mapper 檔案無疑使我們最重要的工作,因為他承載着我們所有業務中的增删改查所涉及到的 sql 語句編寫,那麼我們今天就來系統的學習下常用的sql 語句在mybatis 中應該如何編寫。

XML映射檔案

在開始編寫映射檔案之前我我們先來認識下幾個常用的标簽。

  • cache ——對給定命名空間的緩存配置
  • cache-ref ——對其他命名空間緩存配置的引用
  • resultMap ——結果集映射(功能十分強大)
  • sql —— 聲明sql片段,可以被其他語句引用
  • insert —— 插入語句
  • update ——更新語句
  • delete ——删除語句
  • select ——查詢語句

insert | delete | update

由于在 mybatis 中增删改的操作十分相似,這裡就統一說明。在開始之前,先介紹下insert | delete | update 标簽中的屬性

屬性 描述
id 命名空間中的唯一辨別符,我們用 mapper 接口的方法名與該id值對應
parameterType 參數類型,為類的全限定名或别名,此屬性為可選配置
flushCache 設定為 true 時,在執行sql時或重新整理本地緩存和耳機緩存,預設值為 true
timeout 逾時時間,預設為未設定
statementType STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。
useGeneratedKeys 通過jdbc擷取資料庫自增主鍵,預設值為 false
keyProperty mybatis 會将擷取到的自增主鍵的值設定的該屬性的值

insert

  • 簡單插入
<insert id="saveUser" parameterType="org.bmth.mybatis.entity.User">
    INSERT INTO `users`  (`username`, `password`, `nickname`, `avatar`,`age`, `sex`, `telephone`, `enable`)
    VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
     #{telephone}, #{enable});
</insert>
           
  • 插入并傳回自增主鍵
<insert id="saveUserAndTakeId" useGeneratedKeys="true" keyProperty="id" parameterType="org.bmth.mybatis.entity.User">
    INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`, `enable`)
    VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
    #{telephone}, #{enable});
</insert>
           

注: 這裡的自增主鍵的值将會指派給

User

實體的 id 屬性值。在代碼中可以通過

User

的執行個體擷取。

  • 批量插入(這裡通過

    <foreach>

    标簽實作)
<insert id="batchSave">
    INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`, `enable`) VALUES
    <foreach item="item" collection="users" separator=",">
      (#{item.username}, #{item.password}, #{item.nickname}, #{item.avatar}, #{item.age}, #{item.sex}, #{item.telephone}, #{item.enable});
    </foreach>
  </insert>
           

注: 這裡的

collection="users"

的 users 屬性 來自于 mapper 接口層 ,通過

@Param

标簽聲名。

delete

<delete id="deleteById" parameterType="java.lang.Integer">
    DELETE FROM `users` WHERE `id`= #{id};
</delete>
           

update

<update id="updateById" parameterType="org.bmth.mybatis.entity.User">
    UPDATE `users` SET `username`=#{username}, `password`=#{password}, `nickname`=#{nickname},
    `avatar`=#{avatar}, `age`=#{age}, `telephone`=#{telephone}, `enable`=#{enable} WHERE `id`=#{id};
  </update>
           

sql

這個元素可以被用來定義可重用的 SQL 代碼段,這些 SQL 代碼可以被包含在其他語句中。

<sql id="userColumns">
    `username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`,`address`, `enable`
</sql>
           

這個 SQL 片段可以通過

<include>

标簽被其他 SQL 語句引用

<select id="findAll" resultType="org.bmth.mybatis.entity.User">
    SELECT
    	<include refid="userColumns"/>
    FROM `users`;
</select>
           

當然,在

<sql>

标簽中我們也可以提前聲明占位符:

<sql id="userColumns">
    ${alias}.`username`, ${alias}.`password`, ${alias}.`nickname`,
	${alias}.`avatar`,${alias}.`age`,${alias}.`sex`,
    ${alias}.`telephone`,${alias}.`address`,${alias}.`enable`
</sql>
           

然後通過

<include>

标簽中進行占位符替換:

<select id="findAll" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns">
      <property name="alias" value="u"/>
    </include>
    FROM `users` u;
</select>
           

select

查詢絕對在大多數業務場景中都占據這主導地位,而mybatis最強大之處就在于sql語句的靈活性,以及強大的結果集映射。由于結果映射比較複雜,我們會在後面單獨來講。這裡先介紹下

<select>

标簽中的屬性。

屬性 描述
id 唯一辨別,我們用 mapper 接口的方法名與該id值對應
parameterType 參數類型,為類的全限定名或别名,此屬性為可選配置
resultType 傳回類型,為類的完全限定名或别名。 注意如果傳回的是集合,那應該設定為集合包含的類型,而不是集合本身
resultMap 結果集映射,對

<resultMap>

标簽命名的引用
flushCache 設定為 true 時,在執行sql時或重新整理本地緩存和耳機緩存,預設值為 false,這裡需要注意的是,在 insert | update | delete 标簽中預設值為 true,在 select 标簽中,預設值為 false
useCache 将其設定為 true 後,将會導緻本條語句的結果被二級緩存緩存起來,預設值:對 select 元素為 true。
timeout 逾時時間,預設為未設定
fetchSize 嘗試讓每次批量傳回的結果行數和這個設定值相等。 預設值為未設定
statementType STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。
resultOrdered 針對嵌套結果 select 語句适用:如果為 true,就是假設包含了嵌套結果集或是分組,這樣的話當傳回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。 這就使得在擷取嵌套的結果集的時候不至于導緻記憶體不夠用。預設值:

false

resultSets 這個設定僅對多結果集的情況适用。它将列出語句執行後傳回的結果集并給每個結果集一個名稱,名稱是逗号分隔的。

看見這麼多可設定的屬性,是不是感覺我太難了,不用擔心,簡單的查詢其實很簡單,比如,我們要根據id查詢一個使用者:

<select id="findOneById" parameterType="int" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `id` = #{id};
</select>
           

這個語句接受一個 int 類型的參數,傳回一個 user 類型的結果,這裡可能有同學會問,為什麼不是

parameterType="java.lang.Integer"

, 上面提到過,這裡接收的是一個類的全限定名或者别名,很顯然,這裡就是 mybatis 為我們預先提供好的一些别名,這裡在後面源碼分析篇中會很清晰的看到 mybatis為我們提供了很多的别名,來友善我們開發的中使用。

參數傳遞

我們已經見到過了參數傳遞的代碼,其實并不複雜,而且在我們平時的工作中,也很少用到太過于複雜的參數傳遞行為。比如在上面的示例中,就是我們常用的一種最簡單的參數傳遞方式:

<select id="findOneById" parameterType="int" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `id` = #{id};
</select>
           

這裡就是講一個 int 類型的 id 作為參數傳遞,當然這裡我們還可以傳遞一個對象類型,比如我們的使用者插入操作,就是将一個對象類型作為參數傳遞,而我們隻需要在

#{}

中添加我們想要擷取的對象屬性即可。

<insert id="saveUser" parameterType="org.bmth.mybatis.entity.User">
    INSERT INTO `users`  (`username`, `password`, `nickname`, `avatar`,`age`, `sex`, `telephone`, `enable`)
    VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
     #{telephone}, #{enable});
</insert>
           

當然,有些時候單一的參數類型無法滿足我們的日常使用,比如我們想要查詢 username = ‘小明’ 并且 age = 18 的資料,顯然這裡我們無法将兩個不同類型的參數利用 parameterType 聲明,是以,我們需要這樣利用

@Param

注解來實作這個功能。

//mapper 接口 
List<User> findAllByUsernameAndAge(@Param("username") String username,
                                   @Param("age") int age);
           
<!--映射檔案-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age};
</select>
           

可以看到

@Param

注解的值,和

#{}

是綁定的,在 mybatis 中,執行 SQL 語句時,會将 #{} 替換成 ?,這樣做可以有效的防止SQL注入,不過有時你就是想直接在 SQL 語句中插入一個不轉義的字元串。 比如,像 ORDER BY,你可以使用 ${} 來實作,還是上面的例子,我們做一下修改,可以實作了。

//mapper 接口 
List<User> findAllByUsernameAndAge(@Param("username") String username,
                                   @Param("age") int age,
                                   @Param("columnName") String columnName);
           
<!--映射檔案-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

總結:在mybatis中,

${}

會被直接替換,而

#{}

會被使用

?

預處理。

結果映射

可以說,ResultMap 是 MyBatis 中最強大的元素,而且對于簡單的映射關系,根本不需要顯示的聲明,比如我們前面已經見到了簡單的映射示例:

簡單映射

<!--映射檔案-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

這裡就是直接将我們所查詢的列,映射到了

User

類,那麼如果所查詢的列和我們所要映射的屬性不一緻怎麼辦?其實很簡單,我們隻需要用 SQL 的 AS 作為别名來比對即可,比如:

<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    `id`       AS      id,
    `username` AS      username,
    `password` AS      password
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

當然,這裡我們也可以顯示的聲明

ResultMap

來解決列名不比對的問題:

<!--resultMap-->
<resultMap id="userResultMap" type="org.bmth.mybatis.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username" />
    <result property="password" column="password" />
    <result property="age" column="age" />
</resultMap>

<select id="findAllByUsernameAndAge" resultMap="userResultMap">
    SELECT
    `id`, 
    `username`,
    `password`,
    `age`
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

複雜映射

當然,有很多時候我們需要構造出一個比較複雜的對象,此時

resultMap

的強大之處就得到了很好的展現,假如現在有三張表:使用者表 ,收獲位址表,以及位址标簽表

現在我們需要根據使用者來查詢到他所有的收貨位址,及位址的标簽,那麼需要構造一個對象如下:

@Data
public class Address {
    private Integer id;
    private String province;
    private String city;
    private String region;
    private String detailAddress;
    private String labelName;
    private String phoneNumber;
}
           
@Data
public class User{
    private Integer id;

    private String username;

    private String password;

    private String avatar;

    private String nickname;

    private Integer sex;

    private Integer age;

    private String telephone;

    private Boolean enable;

    private Date createTime;

    private List<Address> addressList;
}
           

現在我們就利用 resultMap 來映射

User

實體:

<resultMap id="userDetailResultMap" type="org.bmth.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username" />
        <collection property="addressList" ofType="org.bmth.mybatis.entity.Address">
            <id column="address_id" property="id" />
            <result column="province" property="province"/>
            <result column="city" property="city"/>
            <result column="region" property="region"/>
            <result column="detail_address" property="detailAddress"/>
            <result column="region" property="region"/>
            <result column="name" property="labelName"/>
            <result column="phone_number" property="phoneNumber"/>
        </collection>
    </resultMap>

<select id="getUserDetail" parameterType="int" resultMap="userDetailResultMap">
        SELECT
            u.id,
            u.username,
            ra.address_id,
            ra.phone_number,
            ra.province,
            ra.city,
            ra.region,
            ra.detail_address,
            al.name
        FROM
            users u
                INNER JOIN
            receive_address ra ON u.id = ra.user_id
                LEFT JOIN
            address_label al ON ra.label_id = al.label_id
        WHERE u.id = #{userId};
</select>
           

這樣就完成了一個比較複雜的映射,

resultMap

的強大之處不僅于此,這裡就不一一列舉,還是一起來看下

resultMap

的映射結構。

嵌套查詢

還是上面的需求,我們還有另一種方式來實作,就是在

resultMap

中做嵌套查詢:

<resultMap id="userDetailResultMap2" type="org.bmth.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username" />
        <collection property="addressList" column="id" 		       ofType="org.bmth.mybatis.entity.Address"
                    select="listAddressByUserId"/>
    </resultMap>
    
    <select id="getUserDetail2" parameterType="int" resultMap="userDetailResultMap2">
        SELECT
            u.id,
            u.username
        FROM
            users u
        WHERE u.id = #{userId};
    </select>

    <select id="listAddressByUserId" resultType="org.bmth.mybatis.entity.Address">
        SELECT
            ra.address_id,
            ra.phone_number,
            ra.province,
            ra.city,
            ra.region,
            ra.detail_address,
            al.name
        FROM
            receive_address ra
                LEFT JOIN
            address_label al ON ra.label_id = al.label_id
        WHERE ra.user_id = #{userId};
    </select>
           

resultMap映射

  • constructor

    - 用于在執行個體化類時,注入結果到構造方法中
    • idArg

      - ID 參數;标記出作為 ID 的結果可以幫助提高整體性能
    • arg

      - 将被注入到構造方法的一個普通結果
  • id

    – 一個 ID 結果;标記出作為 ID 的結果可以幫助提高整體性能
  • result

    – 注入到字段或 JavaBean 屬性的普通結果
  • association

    – 一個複雜類型的關聯;許多結果将包裝成這種類型
    • 嵌套結果映射 – 關聯本身可以是一個

      resultMap

      元素,或者從别處引用一個
  • collection

    – 一個複雜類型的集合
    • 嵌套結果映射 – 集合本身可以是一個

      resultMap

      元素,或者從别處引用一個
  • discriminator

    – 使用結果值來決定使用哪個

    resultMap

    • case

      – 基于某些值的結果映射
      • 嵌套結果映射 –

        case

        本身可以是一個

        resultMap

        元素,是以可以具有相同的結構和元素,或者從别處引用一個

        resultMap

        屬性清單
屬性 描述

id

目前命名空間中的一個唯一辨別,用于辨別一個結果映射。

type

類的完全限定名, 或者一個類型别名(關于内置的類型别名,可以參考上面的表格)。

autoMapping

如果設定這個屬性,MyBatis将會為本結果映射開啟或者關閉自動映射。 這個屬性會覆寫全局的屬性 autoMappingBehavior。預設值:未設定。

id & result

<id property="id" column="id"/>
 <result property="username" column="username" />
           

id 元素表示的結果将是對象的辨別屬性,這會在比較對象執行個體時用到。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接配接映射)的時候。

兩個元素都有一些屬性:

屬性 描述

property

映射到列結果的字段或屬性。如果用來比對的 JavaBean 存在給定名字的屬性,那麼它将會被使用

column

資料庫中的列名,或者是列的别名。

javaType

一個 Java 類的完全限定名,或一個類型别名

jdbcType

JDBC 類型

typeHandler

我們在前面讨論過預設的類型處理器。使用這個屬性,你可以覆寫預設的類型處理器。 這個屬性值是一個類型處理器實作類的完全限定名,或者是類型别名。

association

屬性 描述

property

映射到列結果的字段或屬性

javaType

一個 Java 類的完全限定名,或一個類型别名

jdbcType

JDBC 類型

typeHandler

類型處理器

collection

<collection property="addressList" ofType="org.bmth.mybatis.entity.Address">
    <id column="address_id" property="id" />
    <result column="province" property="province"/>
    <result column="city" property="city"/>
    <result column="region" property="region"/>
    <result column="detail_address" property="detailAddress"/>
    <result column="region" property="region"/>
    <result column="name" property="labelName"/>
    <result column="phone_number" property="phoneNumber"/>
</collection>
           

association

差不多,不在贅述。

discriminator

一個資料庫查詢可能會傳回多個不同的結果集(但總體上還是有一定的聯系的)。 鑒别器(discriminator)元素就是被設計來應對這種情況的,另外也能處理其它情況,例如類的繼承層次結構。 鑒别器的概念很好了解——它很像 Java 語言中的 switch 語句。