1. 程式人生 > >《Mybatis從入門到精通》讀書筆記(二)

《Mybatis從入門到精通》讀書筆記(二)

第三章. Mybatis 註解方式的基本用法

表字段和Java屬性欄位對映的方式:

1. SQL語句中列取別名

2. 設定mapUnderscoreToCamelCase = true(下劃線轉駝峰)

3. resultMap對映

註解方式應用場景不多,不做過多介紹,具體可以參考原書或mybatis官方文件。

第四章. Mybatis 動態SQL

以下是Mybatis動態SQL在XML中支援的幾種標籤:

  • if
  • choose(when、otherwise)
  • trim(where、set)
  • foreach
  • bind

4.1. if

判斷條件property != null或property == null:適用於任何型別的欄位,用於判斷屬性值是否為空。

判斷條件property != ' '或property == ' ':僅適用於String型別的欄位,用於判斷屬性值是否為空字串。

// 在OGNL表示式中,這兩個判斷的順序不會影響判斷的結果,也不會有空指標異常
<if test="userName != null and userName != ''">

4.2. choose

用來實現 if .. .else if ... else ... 的邏輯

<select id="selectByIdOrUserName" resultType="tk.mybatis.simple.model.SysUser">
    select id, 
    	user_name userName, 
        user_password userPassword,
        user_email userEmail,
    from sys_user
    where 1 = 1
	<choose>
		<when test="id != null">
		and id = #{id}
		</when>
		<when test="userName != null and userName != ''">
		and user_name = #{userName}
		</when>
		<otherwise>
		limit 0
		</otherwise>
	</choose>
</select>

4.3. where、set、trim

where標籤的作用:如果該標籤包含的元素中有返回值,就插入一個where;如果where後面的字串是以AND或OR開頭的,就將它們剔除。

<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
    select 
        id, 
    	user_name userName, 
        user_password userPassword,
        user_email userEmail,
    from sys_user
    <where>
	    <if test="userName != '' and userName != null">
	       and user_name like concat('%', #{userName}, '%')
	    </if>
	    <if test="userEmail != '' and userEmail != null">
	       and user_email = #{userEmail}
	    </if>
    </where>
</select>

set標籤的作用:如果該標籤包含的元素中有返回值,就插入一個set;如果set後面的字串是以逗號結尾的,就將這個逗號剔除。

<update id="updateByIdSelective">
	update sys_user 
	<set>
		<if test="userName != null and userName != ''">
		user_name = #{userName},
		</if>
		<if test="userPassword != null and userPassword != ''">
		user_password = #{userPassword},
		</if>
		<if test="userEmail != null and userEmail != ''">
		user_email = #{userEmail},
		</if>
		<if test="userInfo != null and userInfo != ''">
		user_info = #{userInfo},
		</if>
		<if test="headImg != null">
		head_img = #{headImg, jdbcType=BLOB},
		</if>
		<if test="createTime != null">
		create_time = #{createTime, jdbcType=TIMESTAMP},
		</if>
		id = #{id},
	</set>
	where id = #{id}
</update>	

where和set標籤的功能都可以用trim標籤來實現,並且在底層就是通過TrimSqlNode實現的。

where標籤對應trim的實現如下:

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

這裡的AND和OR後面的空格不能省略,為了避免匹配到andes、orders等單詞。

實際的prefixOverrides包含“AND”、“OR”、“AND\n”、“OR\n”、“AND\r”、“OR\r”、“AND\t”、“OR\t”,不僅僅是上面提到的兩個帶空格的字首。

set標籤對應trim的實現如下:

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

trim標籤有如下屬性:

prefix:當trim元素內包含內容時,會給內容增加prefix指定的字首。

prefixOverrides:當trim元素內包含內容時,會把內容中匹配的字首字串去掉。

suffix:當trim元素內包含內容時,會給內容增加suffix指定的字尾。

suffixOverrides:當trim元素內包含內容時,會把內容中匹配的字尾字串去掉。

4.4. foreach

SQL語句中有時會使用IN關鍵字,例如id in (1, 2, 3)。可以使用${id}方式直接獲取值,但這種寫法不能防止SQL注入,想避免SQL注入就需要用#{}的寫法,這時就要配合foreach標籤來滿足需求。

foreach可以對陣列、Map或實現了Iterable介面(如List、Set)的物件進行遍歷。陣列在處理時會轉換為List物件,因此foreach遍歷的物件可以分為兩大類:Iterable型別和Map型別。

4.4.1. foreach實現in集合

List<SysUser> selectByIdList(List<Long> idList);
<select id="selectByIdList" resultType="tk.mybatis.simple.model.SysUser">
    select id, 
    	user_name userName, 
        user_password userPassword,
        user_email userEmail,
        user_info userInfo,
        head_img headImg,
        create_time createTime
    from sys_user
	where id in
	<foreach collection="list" open="(" close=")" separator="," item="id" index="i">
		#{id}
	</foreach>
</select>

foreach包含以下屬性:

collection:必填,值為要迭代迴圈的屬性名。這個屬性值的情況有很多。
index:索引的屬性名,在集合、陣列情況下值為當前索引值,當迭代迴圈的物件是Map型別時,這個值為Map的key(鍵值)
item:變數名,值為從迭代物件中取出的每一個值。
open:整個迴圈內容開頭的字串。
close:整個迴圈內容結尾的字串。
separator:每次迴圈的分隔符。

collection的屬性設定分為以下幾種情況:

1. 只有一個數組引數或集合引數

以下程式碼是DefaultSQLSession中的方法,也是預設情況下的處理邏輯。

private Object wrapCollection(Object object) {
    DefaultSqlSession.StrictMap map;
    if (object instanceof Collection) {
        map = new DefaultSqlSession.StrictMap();
        map.put("collection", object);
        if (object instanceof List) {
            map.put("list", object);
        }
        return map;
    } else if (object != null && object.getClass().isArray()) {
        map = new DefaultSqlSession.StrictMap();
        map.put("array", object);
        return map;
    } else {
        return object;
    }
}

總結下來就是:

當傳入引數為集合collection的子類(Set、List):collection = "collection"

當傳入引數為List:collection = "collection" 或 collection = "list"

當傳入引數為Array:collection = "array" 

這裡是傳入陣列或集合型別引數的預設名字。推薦使用@Param來指定引數的名字,這時collection就設定為通過@Param註解指定的名字。

2. 有多個引數

當有多個引數的時候,要使用@Param註解給每個引數指定一個名字,否則在SQL中使用引數時就會不方便,因此將collection設定為@Param註解指定的名字即可。

3. 引數是Map型別

使用Map和使用@Param註解方式類似,將collection指定為對應Map中的key即可。

如果要迴圈所傳入的Map,推薦使用@Param註解指定名字,此時可將collection設定為指定的名字,如果不想指定名字,就使用預設值_parameter。

4. 引數是一個物件

這種情況下指定為物件的屬性名即可。當使用物件內多層巢狀的物件時,使用屬性.屬性(集合和陣列使用下標取值)的方式可以指定深層的屬性值。

4.4.2. foreach實現批量插入

如果資料庫支援批量插入,就可以通過foreach來實現。批量插入是SQL-92新增的特性,目前支援的資料庫有DB2、SQL Server 2008及以上版本、PostgreSQL 8.2及以上版本、MySQL、SQLite 3.7.11及以上版本、H2。批量插入的語法如下:

INSERT INTO tablename (column-a, [column-b, ...])

VALUES ('values-1a',['value-1b',... ]),

 ('values-2a',['value-2b',... ]),

...
<insert id="insertList">
	insert into sys_user(
		user_name, user_password,user_email,
		user_info, head_img, create_time)
	values
	<foreach collection="list" item="user" separator=",">
		(
		#{user.userName}, #{user.userPassword},#{user.userEmail},
		#{user.userInfo}, #{user.headImg, jdbcType=BLOB}, #{user.createTime, jdbcType=TIMESTAMP})
	</foreach>
</insert>

從Mybatis 3.3.1版本開始,Mybatis開始支援批量新增回寫主鍵值的功能,到目前為止,可以完美支援該功能的僅有MySQL資料庫。要在MySQL中實現批量插入返回自增主鍵值,只需要在原來基礎上進行如下修改:

<insert id="insertList" useGeneratedKeys="true" keyProperty="id">

4.4.3. foreach實現動態UPDATE

<update id="updateByMap">
	update sys_user 
	set 
	<foreach collection="_parameter" item="val" index="key" separator=",">
		${key} = #{val}
	</foreach>
	where id = #{id}
</update>
@Test
public void testUpdateByMap(){
	SqlSession sqlSession = getSqlSession();
	try {
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		//從資料庫查詢 1 個 user 物件
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("id", 1L);
		map.put("user_email", "[email protected]");
		map.put("user_password", "12345678");
		//更新資料
		userMapper.updateByMap(map);
		//根據當前 id 查詢修改後的資料
		SysUser user = userMapper.selectById(1L);
		Assert.assertEquals("[email protected]", user.getUserEmail());
	} finally {
		//為了不影響資料庫中的資料導致其他測試失敗,這裡選擇回滾
		sqlSession.rollback();
		//不要忘記關閉 sqlSession
		sqlSession.close();
	}
}

4.4.4. OGNL用法

  • e1 or e2
  • e1 and e2
  • e1 == e2,e1 eq e2
  • e1 != e2,e1 neq e2
  • e1 lt e2:小於
  • e1 lte e2:小於等於,其他gt(大於),gte(大於等於)
  • e1 in e2
  • e1 not in e2
  • e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
  • !e,not e:非,求反
  • e.method(args)呼叫物件方法
  • e.property物件屬性值
  • e1[ e2 ]按索引取值,List,陣列和Map
  • @[email protected](args)呼叫類的靜態方法
  • @[email protected]呼叫類的靜態欄位值

表示式1~4是最常用的4中情況。另外有時候需要判斷一個集合是否為空時,可能會出現如下判斷。

<if test="list != null and list.size() > 0">
	<!--其他-->
</if>

在這種用法中,list.size()是呼叫物件的方法,> 0是和數字進行比較。

表示式10、11兩種情況也特別常見,而且可以多層巢狀使用。假設User型別的屬性user中有一個Address型別的屬性名為addr,在Address中還有一個屬性zipcode,可以通過user.addr.zipcode直接使用zipcode的值。假設Map型別的屬性為map,我們可以通過map['userName']或map.userName來獲取map中的key為userName的值,這裡一定要注意,不管userName的值是不是null,必須保證userName這個key存在,否則就會報錯。

表示式12通常用於簡化一些校驗,或者進行更特殊的校驗,例如if中常見的判斷可以寫成如下這樣。

<if test="@[email protected](userName)">
    and user_name like concat('%', #{userName}, '%')
</if>

假設只是在測試的時候想知道對映XML中方法執行的引數,可以在XML方法標籤中新增如下方法:

<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
	<bind name="print" value="@[email protected](_parameter)"/>
    select id, 
    	user_name userName, 
        user_password userPassword,
        user_email userEmail,
        user_info userInfo,
        head_img headImg,
        create_time createTime
    from sys_user
    <where>
	    <if test="@[email protected](userName)">
		    and user_name like concat('%', #{userName}, '%')
	    </if>
	    <if test="userEmail != '' and userEmail != null">
	    	and user_email = #{userEmail}
	    </if>
    </where>
</select>

其中StringUtil類如下:

package tk.mybatis.util;

public class StringUtil {
	
	public static boolean isEmpty(String str){
		return str == null || str.length() == 0;
	}
	
	public static boolean isNotEmpty(String str){
		return !isEmpty(str);
	}
	
	public static void print(Object parameter){
		System.out.println(parameter);
	}
	
}