文章目录
1. if
通过title
和author
两个参数进行可选搜索。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<if test="title != null and title != '' ">
AND title like #{title}
</if>
<if test="author != null and author.name != null ">
AND author_name like #{author.name}
</if>
</select>
条件判断注意事项:
非String
类型使用obj != null
即可,String
通常要加一个空串的判断obj != null and obj != ''
。
如果入参是一个数字类型,如Long
,但是在判空时使用obj != null and obj != ''
,你会发现,当Long
值传0
的时候,obj != null and obj != ''
条件是不成立的,这就导致了错误的结果。
MyBatis
的处理方式:当发现你用一个数字类型的对象和字符串比较的时候,它会尝试将字符串转换成数字类型,MyBatis
会将空串转换成数字0
。最后的结论就是,数字类型的对象不要使用空串判断表达式obj != ''
。
条件中==
和!=
调用的是org.apache.ibatis.ognl.ASTEq
和org.apache.ibatis.ognl.ASTNotEq
中的getValueBody
方法。都调用了org.apache.ibatis.ognl.OgnlOps
的equal
方法,源码太多,就不贴出来了,感兴趣的可以从下面的方法开始去跟踪一下:
public static boolean equal(Object v1, Object v2) {
if (v1 == null) {
return v2 == null;
} else if (v1 != v2 && !isEqual(v1, v2)) { //逻辑在这里面
if (v1 instanceof Number && v2 instanceof Number) {
return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
} else {
return false;
}
} else {
return true;
}
}
2. choose
有时我们不想应用到所有的条件语句,而只想选择其中一项。针对这种情况,MyBatis
提供了choose
元素,它有点像Java
中的switch
语句。搭配when
和otherwise
使用。(我觉得更类似于Java
中的if...else if...else
语句,而switch
语句会有switch
穿透的情况。)
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
提供了title
就按title
查找,提供了author
就按author
查找,若两者都没有提供,就返回所有符合条件的BLOG
(实际情况可能是由管理员按一定策略选出BLOG
列表,而不是返回大量无意义的随机结果)。
3. where
where
元素只会在至少有一个子元素的条件返回SQL
子句的情况下才去插入WHERE
子句。而且,若紧跟WHERE
后面的语句开头为AND
或OR
,where
元素会将它们自动去除。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
AND state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
这里注意如果所有条件都不满足的话会返回所有的记录,实际使用中应该进行相应处理。上面SQL
如果所有条件都不满足会变成:
SELECT * FROM BLOG
如果Mapper
层接口使用的是一个对象进行查询结果接收,如果查询到的记录条数超过一条,则会报出org.apache.ibatis.exceptions.TooManyResultsException
异常。如果使用的是List
进行接收,则没有问题。
如果where
元素没有按正常套路出牌,我们可以通过自定义trim
元素来定制where
元素的功能。比如,和where
元素等价的自定义trim
元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides
属性会忽略通过管道分隔的文本序列(注意示例中的空格也是必要的)。它的作用是移除所有在prefixOverrides
属性中指定的内容(出现在语句开头的),并且插入prefix
属性中指定的内容。
4. set
set
元素可以用于动态包含需要更新的列。set
元素只会在至少有一个子元素的条件返回SQL
子句的情况下才去插入SET
子句。而且,若紧临WHERE
前面的语句结尾为逗号,set
元素会将多余的逗号自动去除。
<update id="updateAuthorIfNecessary">
UPDATE Author
<set>
<if test="username != null">
username=#{username},
</if>
<if test="password != null">
password=#{password},
</if>
</set>
WHERE id=#{id}
</update>
这里注意如果所有条件都不满足的话会返回错误的SQL
(没有更新字段的UPDATE
语句),实际使用中应该进行相应处理。上面SQL
如果所有条件都不满足会变成:
UPDATE Author WHERE id=1
这很明显是错误的SQL
语句,会报java.sql.SQLSyntaxErrorException
异常。
和set
元素等价的自定义trim
元素:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
suffixOverrides
的作用是移除所有在suffixOverrides
属性中指定的内容(出现在语句结尾的),并且插入prefix
属性中指定的内容。
5. foreach
5.1. 集合遍历
动态SQL
的另外一个常见的操作是对一个集合进行遍历,通常是在构建IN
条件语句和批量插入的时候。比如:
<select id="selectPostIn" resultType="Post">
SELECT * FROM POST P WHERE id IN
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
INSERT INTO Author (username, password, email, bio) VALUES
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
</foreach>
</insert>
foreach
元素的功能非常强大,它允许你指定一个集合(collection
),声明可以在元素体内使用的集合中元素(item
)和索引(index
)。它也允许你指定开头(open
)与结尾(close
)的字符串以及在迭代结果之间放置分隔符(separator
)。这个元素是很智能的,因此它不会附加多余的分隔符。
可以将任何可迭代对象(如List
、Set
等)、数组、Map
对象传递给foreach
作为集合参数。当使用可迭代对象或者数组时,index
是当前迭代的次数,item
是本次迭代获取的元素。当使用Map
对象(或者Map.Entry
对象的集合)时,index
是key
,item
是value
。
对于collection
的值,直接写上@Param
注解指定的参数名即可。如果没有注解的话,List
类型参数就写list
;Set
类型参数就写collection
;数组类型参数就写array
;Map
类型参数就写_parameter
。
List<Author> getListByList(List<Long> ids);
<select id="getListByList" resultType="com.example.demo.model.Author">
select * from Author where id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
List<Author> getListBySet(Set<Long> ids);
<select id="getListBySet" resultType="com.example.demo.model.Author">
select * from Author where id IN
<foreach collection="collection" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
List<Author> getListByArray(Long[] ids);
<select id="getListByArray" resultType="com.example.demo.model.Author">
select * from Author where id IN
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
Map
的迭代,如果参数注解为@Param("map")
,map
等价于没注解时的_parameter
,有以下三种遍历场景:
- 如果
collection
为map
,则index
是key
,item
是value
; - 如果
collection
为map.keys
,则index
是当前迭代的次数,item
是本次迭代获取的key
。主要用于迭代所有的key
。此时可通过#{map[${key}]}
获取到对应的value
。 - 如果
collection
为map.values
,则index
是当前迭代的次数,item
是本次迭代获取的value
。主要用于迭代所有的value
。
List<Author> getListByMap(@Param("map") Map<String, Long> ids);
<select id="getListByMap" resultType="com.example.demo.model.Author">
select * from Author where id IN
<foreach collection="map" index="key" item="value" open="(" separator="," close=")">
#{value}
</foreach>
</select>
<select id="getListByMap" resultType="com.example.demo.model.Author">
select * from Author where id IN
<foreach collection="map.keys" item="key" open="(" separator="," close=")">
#{map[${key}]}
</foreach>
</select>
<select id="getListByMap" resultType="com.example.demo.model.Author">
select * from Author where id IN
<foreach collection="map.values" item="value" open="(" separator="," close=")">
#{value}
</foreach>
</select>
5.2. 批量执行
这个也属于集合遍历。特殊在批量SQL
操作。
5.2.1. MySQL
MySQL
的批量操作是要我们主动去设置的, 在数据库的连接url
上加上&allowMultiQueries=true
即可。如果不进行设置,将不能进行批量SQL
操作,会报语法错误。数据库连接示例:
jdbc.jdbcUrl=jdbc:mysql://127.0.0.1:3306/database?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
<update id="updateAllAvailable">
<foreach collection="skuOptionList" item="item" index="index" open="" close="" separator=";">
UPDATE t_xxx SET old_id = #{item.oldId} WHERE id = #{item.id}
</foreach>
</update>
5.2.2. Oracle
实际的业务系统里面Oracle
数据库也用的非常的多,当然,Oracle
数据库不需要做特殊的配置,但是相应的sql
写法要做些变化。
<update id="updateAllAvailable">
<foreach collection="skuOptionList" item="item" index="index" open="begin" close="end;" separator=";">
UPDATE t_xxx SET old_id = #{item.oldId} WHERE id = #{item.id}
</foreach>
</update>
6. 特殊符号
使用MyBatis
在XML
中编写SQL
时如果需要输入大于小于等特殊符号时,需要写成如下形式,否则会报错,原因是XML
中不允许出现一些特殊字符。以下是MyBatis
中特殊符号的两种写法。
6.1. 转义字符替换
lt
即英文less then
小于的意思,gt
即英文great then
大于的意思。实体符号必须以符号"&"
开头,以符号";"
结尾。虽然只有"<"
字符和"&"
字符对于XML
来说是严格禁止使用的。剩下的都是合法的,为了减少出错,使用实体符号是一个好习惯。
6.2. CDATA 部件包含
在CDATA
部件内部的所有内容都会被解析器忽略。如果文本包含了很多的"<"
字符和"&"
字符,那么最好把他们都放到CDATA
部件中。一个CDATA
部件以<![CDATA[
标记开始,以]]>
标记结束。
大于等于 <![CDATA[ >= ]]>
小于等于 <![CDATA[ <= ]]>
注意事项:
CDATA
部件之间不能再包含CDATA
部件(不能嵌套)。如果CDATA
部件包含了字符]]>
或者<![CDATA[
,将很有可能出错。- 同样要注意在字符串
<![CDATA[
和]]>
中不能有空格或者换行符。