虽然动态sql 和 映射xml的关系很密切,但是这里还是把它分出来把。
文章目录
- if
- choose、when、otherwise
- trim、where、set
- where
- update语句的实际例子
- foreach遍历list
- foreach遍历数组
- date日期处理(CDATA的使用)
- 日期格式化的兼容处理
- resultType的使用
- resultMap和resultType 报错对比
- update的时候一定要当心
- mybatis中入参日期,数据库字段是date的写法
- if的判断
- Integer的写法
- Boolean类型的写法
if
demo1:
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员挑选的 Blog)。
<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>
trim、where、set
where
where标签可以解决一个臭名昭著的问题,就是如果后面条件都不成立,where就是多余的了:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
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>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
update语句的实际例子
<update id="updateUser" parameterType="UserBean">
update t_user set
<trim prefix="set" suffixOverrides=",">
<if test="user.name != null">
user.name = #{user.name},
</if>
<if test="user.sex != null">
user.sex = #{user.sex},
</if>
<if test="user.age != null">
user.age = #{user.age},
</if>
</trim>
WHERE id=#{id}
</update>
foreach
foreach遍历list
例如list为invoiceList,多条件并列,可以这样写。 但是有点需要注意,就是如果list.size>0 ,但是item的数据不合规,会多出一个and,所以要用代码控制item数据的合规。
<if test="invoiceList != null and invoiceList.size() > 0">
and
<foreach collection="invoiceList" index="index" item="item" open="(" close=")" separator=" or ">
<if test="item.invoiceCode != null and item.invoiceCode !='' and item.invoiceNumber != null and item.invoiceNumber !='' ">
(info.INVOICE_CODE=#{item.invoiceCode} and info.INVOICE_NUMBER =#{item.invoiceNumber})
</if>
</foreach>
</if>
foreach遍历数组
要注意数组是length,list是size()。
而且不要少了and。
<if test="types!= null and types.length >0 ">
and type in
<foreach collection="types" open="(" separator="," close=")" item="type">
#{type}
</foreach>
</if>
注:使用foreach的时候,如果list为空,最好不执行这个mapper,因为很容易报错。
script
这个感觉不如在xml中写着舒服。
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
bind
这个用的比较少,我就没用过,耳目一新啊。
bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
date日期处理(CDATA的使用)
日期范围:
一种是直接写死,代码量最少。
create_date between sysdate-1 and sysdate
另一种是写为可配置的,那么需要dto中增加startDate和endDate这2个字段,并在查询前设置值,代码中这么写。
<if test="startDate!=null>
<![CDATA[ and create_date>=#{startDate} ]]>
</if>
<if test="excEndTime!=null>
<![CDATA[ and create_date<=#{endDate} ]]>
</if>
当然了,如果日期格式的,也可能需要格式化下:
这里是伪代码:
startDate >= #{startDate} 00:00:00
endDate <= #{endDate} 23:59:59
动态sql官网文档地址:
https://mybatis.org/mybatis-3/zh/dynamic-sql.html
日期格式化的兼容处理
例如,有时会报错:
ORA-01830: date format picture ends before converting entire input string
SELECT to_date('2022-07-20 00:00:00','yyyy-mm-dd') FROM dual;
这是会报错的。
解决方案:
之前的写法:
<if test="createDate != null">
CREATE_DATE = to_date(#{createDate,jdbcType=VARCHAR},'yyyy-mm-dd'),
</if>
兼容性写法:
<if test="createDate != null">
<choose>
<when test="19==createDate.length">
CREATE_DATE = to_date(#{createDate,jdbcType=VARCHAR},'yyyy-mm-dd hh24:mi:ss'),
</when>
<otherwise>
CREATE_DATE = to_date(#{createDate,jdbcType=VARCHAR},'yyyy-mm-dd'),
</otherwise>
</choose>
</if>
## 其他
### resultMap和resultType
结果映射的2大元素。
都是非常常用的。
#### resultMap的使用
机制:在<select>标签中根据id来查抄对应resultMap。
1、用<resultMap>标签中定义。
2、在<select>标签中根据id来查找对应resultMap。
例子:
```xml
定义:
<resultMap type="com.test.Product" id="productBaseMap">
<id column="id" property="id" />
<result column="product_name" property="productName" />
</resultMap>
引用:
<select id="queryProduct" resultMap="productBaseMap">
</select>
resultType的使用
机制: 直接查找对应的类
这个不需要在mapper.xml中定义,只需要引用的类型存在即可。
自定义类型:
<select id="queryProduct" resultType="com.test.Product"></select>
基本类型:
<select id="queryTotal" resultType="java.lang.Integer"></select>
注:
resultType的基本类型虽然可以简写,但是建议写全称。因为全称肯定是通用的,而且不容易出错。
resultMap和resultType 报错对比
如果resultType找不到,报错例子:
Result Maps collection does not contain value for java.lang.Integer
如果resultMap找不到,报错例子:
Cannot find class: UserBaseMap。
其他
update的时候一定要当心
先来段错误的代码:
<update id="updateByProductId">
update T_PRODUCT
<set>
<if test="totalAmount != null and totalAmount!=''">
TOTAL_AMOUNT = #{totalAmount},
</if>
</set>
<where>
<if test="productId != null and productId!=''">
and PRODUCT_ID = #{productId}
</if>
</where>
</update>
发现问题在哪了吗?
update的where条件竟然用了<if>
。如果一不小心,入参对象中没有设置productId,那么相当于update整张表,想想就知道有多恐怖了。
所以update的判定条件不要用if标签,应该直接写固定了:
<update id="updateByProductId">
update T_PRODUCT
<set>
<if test="totalAmount != null and totalAmount!=''">
TOTAL_AMOUNT = #{totalAmount},
</if>
</set>
where PRODUCT_ID = #{productId}
</update>
这样如果productId为空,语句报错,不会有update整个表的风险。
mybatis中入参日期,数据库字段是date的写法
<if test="startDate != null and startDate != ''">
AND CREATE_DATE ">>= to_date(#{startDate},'yyyy-MM-dd HH24:MI:SS')
</if>
<if test="endDate != null and endDate != ''">
AND CREATE_DATE <= to_date(concat(#{endDate},'23:59:59'),'yyyy-MM-dd HH24:MI:SS')
</if>
if的判断
string的写法
这个最常见,最简单,没什么坑:
<if test="username != null and username!=''">
and username = #{username,jdbcType=VARCHAR}
</if>
Integer的写法
Integer是有坑的。
错误写法(因为如果传入的是0,会当做''来处理,也就是判断不会成功):
<if test="times != null and times !=''">
and times=#{times,jdbcType=INTEGER}
</if>
那么如果要传入0,怎么办呢? 正确写法:
<if test="times != null and times !='' or times==0">
and times=#{times,jdbcType=INTEGER}
</if>
Boolean类型的写法
特殊查询
有些特殊查询,可以额外的添加字段来实现。例如字段后面加上后缀,然后进行特殊处理。
NotEq
NotIn
IsNull
IsNotNull
需要手动维护,有一定工作量。 但是至少解决了特殊查询不好实现的问题。
isNull的特殊查询
java代码要用Boolean类。
private Boolean codeIsNull;
<if test="codeIsNull != null and codeIsNull==true >
and CODE is null
</if>
其他
sql语句中不要有分号
这是很基础的知识了,因为每个语句都是单独的,所以根本不用加分号。如果加了,会认为是语句的一部分。报错:
Cause: java.sql.SQLSyntaxErrorException: ORA-00911: invalid character