在我們日常編碼中,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 | 結果集映射,對 标簽命名的引用 |
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,就是假設包含了嵌套結果集或是分組,這樣的話當傳回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。 這就使得在擷取嵌套的結果集的時候不至于導緻記憶體不夠用。預設值: 。 |
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
-
- ID 參數;标記出作為 ID 的結果可以幫助提高整體性能idArg
-
- 将被注入到構造方法的一個普通結果arg
-
-
– 一個 ID 結果;标記出作為 ID 的結果可以幫助提高整體性能id
-
– 注入到字段或 JavaBean 屬性的普通結果result
-
– 一個複雜類型的關聯;許多結果将包裝成這種類型association
- 嵌套結果映射 – 關聯本身可以是一個
元素,或者從别處引用一個resultMap
- 嵌套結果映射 – 關聯本身可以是一個
-
– 一個複雜類型的集合collection
- 嵌套結果映射 – 集合本身可以是一個
元素,或者從别處引用一個resultMap
- 嵌套結果映射 – 集合本身可以是一個
-
– 使用結果值來決定使用哪個discriminator
resultMap
-
– 基于某些值的結果映射case
- 嵌套結果映射 –
本身可以是一個case
resultMap
元素,是以可以具有相同的結構和元素,或者從别處引用一個
屬性清單resultMap
- 嵌套結果映射 –
-
屬性 | 描述 |
---|---|
| 目前命名空間中的一個唯一辨別,用于辨別一個結果映射。 |
| 類的完全限定名, 或者一個類型别名(關于内置的類型别名,可以參考上面的表格)。 |
| 如果設定這個屬性,MyBatis将會為本結果映射開啟或者關閉自動映射。 這個屬性會覆寫全局的屬性 autoMappingBehavior。預設值:未設定。 |
id & result
<id property="id" column="id"/>
<result property="username" column="username" />
id 元素表示的結果将是對象的辨別屬性,這會在比較對象執行個體時用到。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接配接映射)的時候。
兩個元素都有一些屬性:
屬性 | 描述 |
---|---|
| 映射到列結果的字段或屬性。如果用來比對的 JavaBean 存在給定名字的屬性,那麼它将會被使用 |
| 資料庫中的列名,或者是列的别名。 |
| 一個 Java 類的完全限定名,或一個類型别名 |
| JDBC 類型 |
| 我們在前面讨論過預設的類型處理器。使用這個屬性,你可以覆寫預設的類型處理器。 這個屬性值是一個類型處理器實作類的完全限定名,或者是類型别名。 |
association
屬性 | 描述 |
---|---|
| 映射到列結果的字段或屬性 |
| 一個 Java 類的完全限定名,或一個類型别名 |
| JDBC 類型 |
| 類型處理器 |
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 語句。