动态SQl是MyBatis的强大特性之一,可以完成对SQL语句的动态组装。
比如说传入一个User对象,要根据这个User中的数据查询用户的完整信息:
- 如果User对象中只有name属性有值,sql语句是:select * from user_tb where name=#{name}
- 如果User对象中只有tel属性有值,sql语句是:select * from user_tb where tel=#{tel}
- 如果User对象的name、tel都有值,sql语句是:select * from user_tb where name=#{name} and tel=#{tel}
有时候sql语句不是一成不变的,要根据传入的数据动态生成要执行的sql语句,动态sql就适合这种情况。
MyBatis中的动态sql元素
- <if> 相当于java中的if,用于单分支的条件判断
- <choose>、<when>、<otherwise> 相当于java中的switch...case...default,用于多分支的条件判断,从多个选项中选择一个
- <foreach> 循环,常和sql的in语句搭配使用
- <where>、<trim>、<set> 辅助元素,用于一些处理sql拼装、特殊字符的问题
- <bind> 从OGNL表达式中创建一个变量,并将其绑定到上下文,常用于模糊查询
<where>、<if>的使用
<select id="queryUser" parameterType="user" resultType="user"> SELECT * FROM user_tb <where> <if test="name!=null and name!=''"> name=#{name} </if> <if test="tel!=null and tel!=''"> AND tel=#{tel} </if> </where> </select>
<where>相当于sql语句中的关键字WHERE。
<if>中的test表示条件,条件成立就把元素体中的字符串拼接到sql语句中,否则不拼接。
tel不进行数学运算,使用字符串类型。
<where>中有<if>成立时,<where>才会在sql语句中添加WHERE关键字,否则不添加。
<where>会自动剔除元素体中多余的AND、OR。
比如传入的User对象只设置了tel,拼接的sql语句是:SELECT * FROM user_tb WHERE AND tel=#{tel},<where>会自动剔除多余的AND。
不使用<where>的写法:
<select id="queryUser" parameterType="user" resultType="user"> SELECT * FROM user_tb WHERE 1=1 <if test="name!=null and name!=''"> AND name=#{name} </if> <if test="tel!=null and tel!=''"> AND tel=#{tel} </if> </select>
<where>会自动去掉多余的AND,但不会自动加上缺少的AND,我们通常在每个<if>中都加上AND,防止把AND写掉了。
<trim>的使用
<trim>可以定制需要的功能,比如使用<trim>达到<where>的效果:
<select id="queryUser" parameterType="user" resultType="user"> SELECT * FROM user_tb <trim prefix="WHERE" prefixOverrides="AND"> <if test="name!=null and name!=''"> name=#{name} </if> <if test="tel!=null and tel!=''"> AND tel=#{tel} </if> </trim> </select>
prefix会在这段字符串之前加上指定的前缀,如果里面的<if>都不满足条件,则不加前缀。
prefixOverrides指定要去除的多余的字符串。
<choose>、<when>、<otherwise>的使用
有时候我们只需要从多个选项中选择一个,比如登录教务系统,让你选择角色:数据库管理员、教职工、学生:
<select id="queryPwd" parameterType="user" resultType="string"> SELECT password FROM user_tb WHERE name=#{name} <choose> <when test="role=='admin'"> AND role='admin' </when> <when test="role=='teacher'"> AND role='teacher' </when> <when test="role=='student'"> AND role='student' </when> </choose> </select>
传入的值是admin、teacher、student,数据库中的role字段也是这几个值,这种传入的值和数据库中存储的值一致的可以直接这样写:
<select id="queryPwd" parameterType="user" resultType="string"> SELECT password FROM user_tb WHERE name=#{name} AND role=#{role} </select>
如果不一致,比如性别,传入的是male、female,数据库中存储的是0、1,就需要使用<choose>转换一下。
<choose> <when test=""> </when> <when test=""> </when> <otherwise> </otherwise> </choose>
<choose>相当于switch,<when>相当于case,<otherwise>相当于default。
<set>的使用
更新操作传入一个pojo类的对象,但我们并不知道哪些属性是有值的(需要更新的),不能更新全部字段,因为有的字段没有手动赋值,不能用JVM赋的null、0去覆盖数据表中原来的值。
<set>可解决此问题:
<select id="updateUser" parameterType="user"> UPDATE user_tb <set> <if test="name!=null and name!=''"> name=#{name}, </if> <if test="tel!=null and tel!=''"> tel=#{tel}, </if> <if test="address!=null and address!=''"> name=#{name}, </if> </set> where id=#{id} </select>
<set>用于传入pojo类型,更新数据表的多个字段。先判断字段是否有值,有值才更新该字段。
<set>用于更新操作,会自动在这段字符串前面加sql关键字“SET”(里面有<if>为真),并自动去除多余的逗号(一般自己写第一个<if>,然后copy下来改,最后面往往会多一个逗号)。
如果里面的<if>都为假,即没有要更新的字段,不会自动在前面加”SET”,此时这个update语句有语法错误,会报错,所以要保证至少有一个字段需要更新。
test属性的注意点
- 相等判断:==,!=
- 逻辑与:只能用and,不能用&&
- 逻辑或:or、||均可
<foreach>的使用
<foreach>用于迭代集合、数组,常和in搭配使用。
比如按照手机号随机抽取3位幸运观众(根据手机号码查询用户信息):
<select id="queryUser" parameterType="list" resultType="user"> SELECT * FROM user_tb WHERE tel IN <foreach collection="list" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </select>
ArrayList<String> telList = new ArrayList<>(); telList.add("110"); telList.add("119"); telList.add("120"); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.queryUser(telList); System.out.println(userList);
- collection 要迭代的数据类型,数组=>array,List=>list,Map=>Map的key的数据类型。使用数组容易出错,尽量用List代替数组。
- index 本次迭代的下标,指定一个临时变量表示下标
- item 本次迭代的元素,指定一个临时变量表示本次迭代的元素
- open 拼接这段字符串时以什么开头
- close 拼接这段字符串以什么结尾
- separator 迭代的元素之间用什么连接(分隔)
collection是必需的,其余均可选。
更严谨的写法:
<select id="queryUser" parameterType="list" resultType="user"> SELECT * FROM user_tb WHERE tel IN <if test="list!=null and list.size>0"> <foreach collection="list" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </if> </select>
如果传入的是List类型,要使用List对象本身时,约定使用list表示List对象本身。
<bind>的使用
模糊查询时可以使用${}连接字符串,但不能防止sql注入。
mysql可以使用concat()连接字符串,oracle可以使用||连接字符串,但只能针对特定的数据库使用,不利于项目移植。
mybatis的<bind>元素可以解决以上问题。
<select id="queryUser" parameterType="string" resultType="user"> <bind name="pattern_name" value="'%'+name+'%'"/> SELECT * FROM user_tb WHERE name LIKE #{pattern_name} </select>
<bind>定义一个变量,拼接好字符串。
在sql语句中引用该变量的值。