第三章. 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", "test@mybatis.tk");
		map.put("user_password", "12345678");
		//更新数据
		userMapper.updateByMap(map);
		//根据当前 id 查询修改后的数据
		SysUser user = userMapper.selectById(1L);
		Assert.assertEquals("test@mybatis.tk", 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
  • @class@method(args)调用类的静态方法
  • @class@field调用类的静态字段值

表达式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="@tk.mybatis.util.StringUtil@isNotEmpty(userName)">
    and user_name like concat('%', #{userName}, '%')
</if>

假设只是在测试的时候想知道映射XML中方法执行的参数,可以在XML方法标签中添加如下方法:

<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
	<bind name="print" value="@tk.mybatis.util.StringUtil@print(_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="@tk.mybatis.util.StringUtil@isNotEmpty(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);
	}
	
}