MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。
特点
属性
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="Object">
select * from student where id=#{id}
</select>
属性
<insert id="insert" parameterType="Object">
insert into student
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null"> NAME, </if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
<if test="name != null"> #{name}, </if>
</trim>
</insert>
属性
<delete id="deleteByPrimaryKey" parameterType="Object">
delete from student where id=#{id}
</delete>
属性
传统的使用JDBC的方法,相信大家在组合复杂的的SQL语句的时候,需要去拼接,稍不注意哪怕少了个空格,都会导致错误。MyBatis的动态SQL功能正是为了解决这种问题, 其通过 if、choose、when、otherwise、trim、where、set、foreach和bind等9种标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。
MyBatis动态SQL是做什么的?
动态SQL的执行原理
if 标签通常用于 WHERE 语句、UPDATE 语句、INSERT 语句中,通过判断参数值来决定是否使用某个查询条件、判断是否更新某一个字段、判断是否插入某个字段的值。
<if test="name != null and name != ''">
and NAME = #{name}
</if>
有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。MyBatis提供了 choose 元素,按顺序判断 when 中的条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when的条件都不满则时,则执行 otherwise 中的 sql。类似于 Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。
if 是与(and)的关系,而 choose 是或(or)的关系。
<select id="getStudentListChoose" parameterType="Student" resultMap="BaseResultMap">
SELECT * from STUDENT WHERE 1=1
<where>
<choose>
<when test="Name!=null and student!='' ">
AND name LIKE CONCAT(CONCAT('%', #{student}),'%')
</when>
<when test="hobby!= null and hobby!= '' ">
AND hobby = #{hobby}
</when>
<otherwise>
AND AGE = 15
</otherwise>
</choose>
</where>
</select>
参见3.2,choose、when、otherwise这三个标签需要组合在一起使用,类似于 Java 中的 switch、case、default。只有一个条件生效,也就是只执行满足的条件 when,没有满足的条件就执行 otherwise,表示默认条件。
参见3.2。
trim标记是一个格式化的标记,主要用于拼接sql的条件语句(前缀或后缀的添加或忽略),可以完成set或者是where标记的功能。
属性
<update id="updateByPrimaryKey" parameterType="Object">
update student set
<trim suffixOverrides=",">
<if test="name != null">
NAME=#{name},
</if>
<if test="hobby != null">
HOBBY=#{hobby},
</if>
</trim>
where id=#{id}
</update>
如果name和hobby的值都不为空的话,会执行如下语句:
update student set NAME='XX',HOBBY='XX' /*,*/ where id='XX'
会忽略最后一个“,” 。
<select id="selectByNameOrHobby" resultMap="BaseResultMap">
select * from student
<trim prefix="WHERE" prefixOverrides="AND | OR">
<if test="name != null and name.length()>0">
AND name=#{name}
</if>
<if test="hobby != null and hobby.length()>0">
AND hobby=#{hobby}
</if>
</trim>
</select>
如果name和hobby的值都不为空的话,会执行如下语句:
select*fromuserWHERE /*and*/ name = ‘xx’ and hobby= ‘xx’
会为片段添加 “WHERE” 前缀,并忽略第一个 “and” 。
当然,避免出现“WHERE AND”还有其他方法,如下:
<!--将where提取出来,并加上“1=1”的查询条件 -->
select * from student where 1=1
<trim suffixOverrides=",">
<if test="name != null and name != ''">
and NAME = #{name}
</if>
<if test="hobby != null and hobby != ''">
and HOBBY = #{hobby}
</if>
</trim>
<insert id="insert" parameterType="Object">
insert into student
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">
NAME,
</if>
<if test="hobby != null ">
HOBBY,
</if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
<if test="name != null ">
#{name},
</if>
<if test="hobby != null ">
#{hobby},
</if>
</trim>
</insert>
当 if 标签较多时,这样的组合可能会导致错误。 如下:
<select id="getStudentListWhere" parameterType="Object" resultMap="BaseResultMap">
SELECT * from STUDENT WHERE
<if test="name!=null and name!='' ">
NAME LIKE CONCAT(CONCAT('%', #{name}),'%')
</if>
<if test="hobby!= null and hobby!= '' ">
AND hobby = #{hobby}
</if>
</select>
当 name 值为 null 时,查询语句会出现 “WHERE AND” 的情况,解决该情况除了将"WHERE"改为“WHERE 1=1”之外,还可以利用 where标签。这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以 AND 或 OR 开头的,则它会剔除掉。
<select id="getStudentListWhere" parameterType="Object" resultMap="BaseResultMap">
SELECT * from STUDENT
<where>
<if test="name!=null and name!='' ">
NAME LIKE CONCAT(CONCAT('%', #{name}),'%')
</if>
<if test="hobby!= null and hobby!= '' ">
AND hobby = #{hobby}
</if>
</where>
</select>
没有使用 if 标签时,如果有一个参数为 null,都会导致错误。当在 update 语句中使用 if 标签时,如果最后的 if 没有执行,则或导致逗号多余错误。使用 set 标签可以将动态的配置 set 关键字,和剔除追加到条件末尾的任何不相关的逗号。
<update id="updateStudent" parameterType="Object">
UPDATE STUDENT
SET NAME = #{name},
MAJOR = #{major},
HOBBY = #{hobby}
WHERE ID = #{id};
</update>
<update id="updateStudent" parameterType="Object">
UPDATE STUDENT SET
<if test="name!=null and name!='' ">
NAME = #{name},
</if>
<if test="hobby!=null and hobby!='' ">
MAJOR = #{major},
</if>
<if test="hobby!=null and hobby!='' ">
HOBBY = #{hobby}
</if>
WHERE ID = #{id};
</update>
使用 set+if 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。
<update id="updateStudent" parameterType="Object">
UPDATE STUDENT
<set>
<if test="name!=null and name!='' ">
NAME = #{name},
</if>
<if test="hobby!=null and hobby!='' ">
MAJOR = #{major},
</if>
<if test="hobby!=null and hobby!='' ">
HOBBY = #{hobby}
</if>
</set>
WHERE ID = #{id};
</update>
foreach 标签主要用于构建 in 条件,可在 sql 中对集合进行迭代。也常用到批量删除、添加等操作中。
<!-- in查询所有,不分页 -->
<select id="selectIn" resultMap="BaseResultMap">
select name,hobby from student where id in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
属性
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
下面分别来看看上述三种情况的示例代码:
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
测试代码:
@Test
public void dynamicForeachTest() {
SqlSession session = Util.getSqlSessionFactory().openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(3);
ids.add(6);
List<Blog> blogs = blogMapper.dynamicForeachTest(ids);
for (Blog blog : blogs)
System.out.println(blog);
session.close();
}
上述collection的值为list,对应的Mapper是这样的:
public List<Blog> dynamicForeachTest(List<Integer> ids);
<select id="dynamicForeach2Test" resultType="Blog">
select * from t_blog where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
上述collection为array,对应的Mapper代码:
public List<Blog> dynamicForeach2Test(int[] ids);
测试代码:
@Test
public void dynamicForeach2Test() {
SqlSession session = Util.getSqlSessionFactory().openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
int[] ids = new int[] {1,3,6,9};
List<Blog> blogs = blogMapper.dynamicForeach2Test(ids);
for (Blog blog : blogs)
System.out.println(blog);
session.close();
}
<select id="dynamicForeach3Test" resultType="Blog">
select * from t_blog where title like "%"#{title}"%" and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
上述collection的值为ids,是传入的参数Map的key,对应的Mapper代码:
public List<Blog> dynamicForeach3Test(Map<String, Object> params);
测试代码:
@Test
public void dynamicForeach3Test() {
SqlSession session = Util.getSqlSessionFactory().openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
final List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
ids.add(6);
ids.add(7);
ids.add(9);
Map<String, Object> params = new HashMap<String, Object>();
params.put("ids", ids);
params.put("title", "中国");
List<Blog> blogs = blogMapper.dynamicForeach3Test(params);
for (Blog blog : blogs)
System.out.println(blog);
session.close();
}
标签可以使用ONGL(Object-Graph Navigation Language, 对象图形化导航语言)表达式创建一个变量并将其绑定到上下文中。如以下代码:
<if test="userName != null and userName != ''">
and user_name like concat('%', #{userName} ,'%')
</if>
由于不同的数据库,concat函数的用法不一样,Mysql的有3个参数,而oracle的只有2个。这就造成了这条SQL在不同环境中运行可能会出错。为了避免这种错误,可以用标签,如下:
<if test="userName != null and userName != ''">
<bind name="nameLike" value="'%' + userName + '%'"/>
and user_name like #{nameLike}
</if>
标签的两个属性都是必选的,name为绑定到上下文的变量名,value为OGNL表达式。创建了标签的变量后,就可以在下面直接使用了。如下例子,先写一个静态方法,然后在Mapper.xml中调用该方法设置默认值:
package ex.mybatis.rbac.mapper;
public class BindTest {
public static String setName() {
return "test";
}
}
Mapper.xml
<!-- 动态根据输入的用户名和id查询用户信息<choose>标签用法 -->
<select id="selectByIdOrName" resultMap="BaseResultMap">
<!-- 使用<bind>标签调用的方法拿出来的默认值 -->
<bind name="name" value="@ex.mybatis.rbac.mapper.BindTest@setName()"/>
select id, user_name, user_password, user_email, create_time, user_info, head_img
from sys_user
<!-- 加上1=1是为了防止2个查询条件都为空的时候,SQL不会报错。学了where标签后可以去掉 -->
where 1=1
<choose>
<when test="id != null">
and id = #{id}
</when>
<when test="userName != null and userName != ''">
and user_name like concat('%', #{userName} ,'%')
</when>
<otherwise>
<!-- 当所有条件都空时,使用<bind>标签调用的方法拿出来的默认值 -->
and user_name=#{name}
</otherwise>
</choose>
</select>
使用标签打印入参信息,如下:
先定义静态方法print
package ex.mybatis.rbac.mapper;
public class BindTest {
public static void print(Object param) {
System.out.println(param);
}
}
在Mapper.xml中使用标签,调用该方法
<!-- <foreach>标签实现更新,参数为map -->
<update id="updateByMap">
<bind name="print" value="@ex.mybatis.rbac.mapper.BindTest@print(_parameter)"/>
update sys_user
set
<foreach collection="_parameter" item="val" index="key" separator=",">
${key} = #{val}
</foreach>
where id = #{id}
</update>
updateByMap参数打印结果
{user_email=test2@mybatis.ex, user_password=1111333, id=1001}