-
建议1:对于绝大数查询,我们是返回统一字段,所以可以使用
<sql />标签,定义SQL段。对于性能或者查询字段比较大的查询,按需要的字段查询。 -
建议2:对于数据库的关键字,使用大写。例如说,
SELECT、WHERE等等。 -
建议 3 :基本是每“块”数据库关键字占用一行
-
示例
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "<http://mybatis.org/dtd/mybatis-3-mapper.dtd>"> <mapper namespace="cn.iocoder.springboot.lab12.mybatis.mapper.UserMapper"> <sql id="FIELDS"> id, username, password, create_time </sql> <insert id="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users ( username, password, create_time ) VALUES ( #{username}, #{password}, #{createTime} ) </insert> <update id="updateById" parameterType="UserDO"> UPDATE users <set> <if test="username != null"> , username = #{username} </if> <if test="password != null"> , password = #{password} </if> </set> WHERE id = #{id} </update> <delete id="deleteById" parameterType="Integer"> DELETE FROM users WHERE id = #{id} </delete> <select id="selectById" parameterType="Integer" resultType="UserDO"> SELECT <include refid="FIELDS" /> FROM users WHERE id = #{id} </select> <select id="selectByUsername" parameterType="String" resultType="UserDO"> SELECT <include refid="FIELDS" /> FROM users WHERE username = #{username} LIMIT 1 </select> <select id="selectByIds" resultType="UserDO"> SELECT <include refid="FIELDS" /> FROM users WHERE id IN <foreach item="id" collection="ids" separator="," open="(" close=")" index=""> #{id} </foreach> </select> </mapper>
if标签
- if 标签 : 内嵌于 select / delete / update / insert 标签,如果满足 test 属性的条件,则执行代码块
- test 属性 :作为 if 标签的属性,用于条件判断,使用 OGNL 表达式。
<select id="findUser">
select * from User where 1=1
<if test=" age != null ">
and age > #{age}
</if>
<if test=" name != null ">
and name like concat(#{name},'%')
</if>
</select>
choose标签、when标签、otherwise标签
- choose 标签:顶层的多分支标签,单独使用无意义
- when 标签:内嵌于 choose 标签之中,当满足某个 when 条件时,执行对应的代码块,并终止跳出 choose 标签,choose 中必须至少存在一个 when 标签,否则无意义
- otherwise 标签:内嵌于 choose 标签之中,当不满足所有 when 条件时,则执行 otherwise 代码块,choose 中 至多 存在一个 otherwise 标签,可以不存在该标签
- test 属性 :作为 when 与 otherwise 标签的属性,作为条件判断,使用 OGNL 表达式
<select id="findUser">
select * from User where 1=1
<choose>
<when test=" age != null ">
and age > #{age}
</when>
<when test=" name != null ">
and name like concat(#{name},'%')
</when>
<otherwise>
and sex = '男'
</otherwise>
</choose>
</select>
很明显,choose 标签作为多分支条件判断,提供了更多灵活的流程控制,同时 otherwise 的出现也为程序流程控制兜底,有时能够避免部分系统风险、过滤部分条件、避免当程序没有匹配到条件时,把整个数据库资源全部查询或更新。
foreach标签
没错,确实 Mybatis 提供了 foreach 标签来处理这几类需要遍历集合的场景,foreach 标签作为一个循环语句,他能够很好的支持数组、Map、或实现了 Iterable 接口(List、Set)等,尤其是在构建 in 条件语句的时候,我们常规的用法都是 id in (1,2,3,4,5 ... 100) ,理论上我们可以在程序代码中拼接字符串然后通过 ${ ids } 方式来传值获取,但是这种方式不能防止 SQL 注入风险,同时也特别容易拼接错误,所以我们此时就需要使用 #{} + foreach 标签来配合使用,以满足我们实际的业务需求。譬如我们传入一个 List 列表查询 id 在 1 ~ 100 的用户记录:
<select id="findAll">
select * from user where ids in
<foreach collection="list"
item="item" index="index"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
最终拼接完整的语句就变成:
select * from user where ids in (1,2,3,...,100);
当然你也可以这样编写:
<select id="findAll">
select * from user where
<foreach collection="list"
item="item" index="index"
open=" " separator=" or " close=" ">
id = #{item}
</foreach>
</select>
最终拼接完整的语句就变成:
select * from user where id =1 or id =2 or id =3 ... or id = 100;
- foreach 标签:顶层的遍历标签,单独使用无意义
- collection 属性:必填,Map 或者数组或者列表的属性名(不同类型的值获取下面会讲解)
- item 属性:变量名,值为遍历的每一个值(可以是对象或基础类型),如果是对象那么依旧是 OGNL 表达式取值即可,例如 #{item.id} 、#{ user.name } 等
- index 属性:索引的属性名,在遍历列表或数组时为当前索引值,当迭代的对象时 Map 类型时,该值为 Map 的键值(key)
- open 属性:循环内容开头拼接的字符串,可以是空字符串
- close 属性:循环内容结尾拼接的字符串,可以是空字符串
- separator 属性:每次循环的分隔符
第一,当传入的参数为 List 对象时,系统会默认添加一个 key 为 'list' 的值,把列表内容放到这个 key 为 list 的集合当中,在 foreach 标签中可以直接通过 collection="list" 获取到 List 对象,无论你传入时使用 kkk 或者 aaa ,都无所谓,系统都会默认添加一个 key 为 list 的值,并且 item 指定遍历的对象值,index 指定遍历索引值。
// java 代码
List kkk = new ArrayList();
kkk.add(1);
kkk.add(2);
...
kkk.add(100);
sqlSession.selectList("findAll",kkk);
<!-- xml 配置 -->
<select id="findAll">
select * from user where ids in
<foreach collection="list"
item="item" index="index"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
第二,当传入的参数为数组时,系统会默认添加一个 key 为 'array' 的值,把列表内容放到这个 key 为 array 的集合当中,在 foreach 标签中可以直接通过 collection="array" 获取到数组对象,无论你传入时使用 ids 或者 aaa ,都无所谓,系统都会默认添加一个 key 为 array 的值,并且 item 指定遍历的对象值,index 指定遍历索引值。
// java 代码
String [] ids = new String[3];
ids[0] = "1";
ids[1] = "2";
ids[2] = "3";
sqlSession.selectList("findAll",ids);
<!-- xml 配置 -->
<select id="findAll">
select * from user where ids in
<foreach collection="array"
item="item" index="index"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
第三,当传入的参数为 Map 对象时,系统并 不会 默认添加一个 key 值,需要手工传入,例如传入 key 值为 map2 的集合对象,在 foreach 标签中可以直接通过 collection="map2" 获取到 Map 对象,并且 item 代表每次迭代的的 value 值,index 代表每次迭代的 key 值。其中 item 和 index 的值名词可以随意定义,例如 item = "value111",index ="key111"。
<!-- xml 配置 -->
<select id="findAll">
select * from user where
<foreach collection="map2"
item="value111" index="key111"
open=" " separator=" or " close=" ">
id = #{value111}
</foreach>
</select>
可能你会觉得 Map 受到不公平对待,为何 map 不能像 List 或者 Array 一样,在框架默认设置一个 'map' 的 key 值呢?但其实不是不公平,而是我们在 Mybatis 框架中,所有传入的任何参数都会供上下文使用,于是参数会被统一放到一个内置参数池子里面,这个内置参数池子的数据结构是一个 map 集合,而这个 map 集合可以通过使用 “_parameter” 来获取,所有 key 都会存储在 _parameter 集合中,因此:
- 当你传入的参数是一个 list 类型时,那么这个参数池子需要有一个 key 值,以供上下文获取这个 list 类型的对象,所以默认设置了一个 'list' 字符串作为 key 值,获取时通过使用 _parameter.list 来获取,一般使用 list 即可。
- 同样的,当你传入的参数是一个 array 数组时,那么这个参数池子也会默认设置了一个 'array' 字符串作为 key 值,以供上下文获取这个 array 数组的对象值,获取时通过使用 _parameter.array 来获取,一般使用 array 即可。
- 但是!当你传入的参数是一个 map 集合类型时,那么这个参数池就没必要为你添加默认 key 值了,因为 map 集合类型本身就会有很多 key 值,例如你想获取 map 参数的某个 key 值,你可以直接使用 _parameter.name 或者 _parameter.age 即可,就没必要还用 _parameter.map.name 或者 _parameter.map.age ,所以这就是 map 参数类型无需再构建一个 'map' 字符串作为 key 的原因,对象类型也是如此,例如你传入一个 User 对象。
因此,如果是 Map 集合,你可以这么使用:
// java 代码
Map map2 = new HashMap<>();
map2.put("k1",1);
map2.put("k2",2);
map2.put("k3",3);
sqlSession.selectList("findAll",map2);
直接使用 collection="_parameter",你会发现神奇的 key 和 value 都能通过 _parameter 遍历在 index 与 item 之中。
<!-- xml 配置 --><select id="findAll">
select * from user where
<foreach collection="_parameter"
item="value111" index="key111"
open=" " separator=" or " close=" ">
id = #{value111}
</foreach>
</select>
foreach标签的insert、update、delete用法
-
insert
批量插入 100 条用户记录:
<insert id="insertUser" parameterType="java.util.List"> insert into user(id,username) values <foreach collection="list" item="user" index="index" separator="," close=";" > (#{user.id},#{user.username}) </foreach> </insert> -
update
更新 500 个用户的姓名:
<update id="updateUser" parameterType="java.util.List"> update user set username = '潘潘' where id in <foreach collection="list" item="user" index="index" separator="," open="(" close=")" > #{user.id} </foreach> </update> -
delete
删除 10 条用户记录:
<delete id="deleteUser" parameterType="java.util.List"> delete from user where id in <foreach collection="list" item="user" index="index" separator="," open="(" close=")" > #{user.id} </foreach> </delete>
where标签、set标签
-
where
之前我们使用1=1的写法,有了where标签之后,我们只需把 where 关键词以及 1=1 改为 < where > 标签即可,另外还有一个特殊的处理能力,就是 where 标签能够智能的去除(忽略)首个满足条件语句的前缀,例如以上条件如果 age 和 name 都满足,那么 age 前缀 and 会被智能去除掉,无论你是使用 and 运算符或是 or 运算符,Mybatis 框架都会帮你智能处理。
<select id="findUser"> select * from User <where> <if test=" age != null "> and age > #{age} </if> <if test=" name != null "> and name like concat(#{name},'%') </if> </where> </select>用法特别简单,我们用官术总结一下:
where 标签:顶层的遍历标签,需要配合 if 标签使用,单独使用无意义,并且只会在子元素(如 if 标签)返回任何内容的情况下才插入 WHERE 子句。另外,若子句的开头为 “AND” 或 “OR”,where 标签也会将它替换去除。
<select id="findUser"> select * from User <where> <if test=" age != null "> and age > #{age} </if> <if test=" name != null "> and name like concat(#{name},'%') </if> </where> </select>如果 age 传入有效值 10 ,满足 age != null 的条件之后,那么就会返回 where 标签并去除首个子句运算符 and,最终的 SQL 语句会变成:
select * from User where age > 10; -- and 巧妙的不见了- 值得注意的是,where 标签 只会 智能的去除(忽略)首个满足条件语句的前缀,所以就建议我们在使用 where 标签的时候,每个语句都最好写上 and 前缀或者 or 前缀
- 另外还有一个值得注意的点,我们使用 XML 方式配置 SQL 时,如果在 where 标签之后添加了注释,那么当有子元素满足条件时,除了 < !-- --> 注释会被 where 忽略解析以外,其它注释例如 // 或 / / 或 -- 等都会被 where 当成首个子句元素处理,导致后续真正的首个 AND 子句元素或 OR 子句元素没能被成功替换掉前缀,从而引起语法错误!**
-
set
- 用法与where标签与元素相似
- set 标签:顶层的遍历标签,需要配合 if 标签使用,单独使用无意义,并且只会在子元素(如 if 标签)返回任何内容的情况下才插入 set 子句。另外,若子句的 开头或结尾 都存在逗号 “,” 则 set 标签都会将它替换去除。
- 另外需要注意,set 标签下需要保证至少有一个条件满足,否则依然会产生语法错误;同时每个条件子句都建议在句末添加逗号,最后一个条件语句可加可不加。或者每个条件子句都在句首添加逗号,第一个条件语句可加可不加;
- 与 where 标签相同,我们使用 XML 方式配置 SQL 时,如果在 set 标签子句末尾添加了注释,那么当有子元素满足条件时,除了 < !-- --> 注释会被 set 忽略解析以外,其它注释例如 // 或 /**/ 或 -- 等都会被 set 标签当成末尾子句元素处理,导致后续真正的末尾子句元素的逗号没能被成功替换掉后缀,从而引起语法错误!
trim标签
- prefix :前缀,当 trim 元素内存在内容时,会给内容插入指定前缀
- suffix :后缀,当 trim 元素内存在内容时,会给内容插入指定后缀
- prefixesToOverride :前缀去除,支持多个,当 trim 元素内存在内容时,会把内容中匹配的前缀字符串去除。
- suffixesToOverride :后缀去除,支持多个,当 trim 元素内存在内容时,会把内容中匹配的后缀字符串去除。
bind标签
简单来说,这个标签就是可以创建一个变量,并绑定到上下文,即供上下文使用,就是这样,我把官网的例子直接拷贝过来:
<select id="selecUser">
<bind name="myName" value="'%' + _parameter.getName() + '%'" />
SELECT * FROM user
WHERE name LIKE #{myName}
</select>
sql标签+include标签
sql 标签与 include 标签组合使用,用于 SQL 语句的复用,日常高频或公用使用的语句块可以抽取出来进行复用
简单的复用代码块可以是:
<!-- 可复用的字段语句块 -->
<sql id="userColumns">
id,username,password
</sql>
查询或插入时简单复用:
<!-- 查询时简单复用 -->
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"></include>
from user
</select>
<!-- 插入时简单复用 -->
<insert id="insertUser" resultType="map">
insert into user(
<include refid="userColumns"></include>
)values(
#{id},#{username},#{password}
)
</insert>