MyBatis 的强大特性之一便是它的动态 SQL。通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。
动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。
if
动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </select>
这条语句提供了一个可选的文本查找类型的功能。如果没有传入“title”、”author.name”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”、”author.name”,那么就会把模糊查找“title”、”author.name 内容的BLOG结果返回。
choose, when, otherwise
有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 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
trim, where, set
<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> </select>
这段代码会出现一个臭名昭著的问题。如
SELECT * FROM BLOG WHERE SELECT * FROM BLOG WHERE AND title like ‘someTitle’
解决方法如下:
<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 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <trim prefix="WHERE" prefixOverrides="AND |OR "> <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> </trim> </select>
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>
<update id="updateAuthorIfNecessary"> update Author <trim prefix="SET" suffixOverrides=","> <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> </trim> where id=#{id} </update>
foreach
动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select>
foreach 元素可指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。
bind
bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。
<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
Multi-db vendor support
一个配置了“_databaseId”变量的 databaseIdProvider 对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句。
<insert id="insert"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> <if test="_databaseId == 'oracle'"> select seq_users.nextval from dual </if> <if test="_databaseId == 'db2'"> select nextval for seq_users from sysibm.sysdummy1" </if> </selectKey> insert into users values (#{id}, #{name}) </insert>
#和$的区别
- #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.
- 将传入的数据直接显示生成在sql中。如:orderby user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
- #方式能够很大程度防止sql注入。
- $方式无法防止Sql注入。
- $方式一般用于传入数据库对象,例如传入表名.
- 一般能用#的就别用$.
- MyBatis排序时使用order by 动态参数时需要注意,用$而不是#
- 默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置安全的值(比如?)。这样做很安全,很迅速也是首选做法,有时你只是想直接在SQL语句中插入一个不改变的字符串。比如,像ORDER BY,你可以这样来使用:ORDER BY ${columnName} 这里MyBatis不会修改或转义字符串。
完整示例
< ?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="com.heqing.ssm.dao.ClassDao">
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
<resultMap id="classResult" type="com.heqing.ssm.entity.Class" >
<id column="class_id" property="id" />
<result column="class_name" property="name" />
<association column="class_headTeacherId" property="headTeacher" javaType="Teacher"
select="com.heqing.ssm.dao.TeacherDao.getById" />
<association column="class_classDirectorId" property="classDirector" javaType="Teacher"
select="com.heqing.ssm.dao.TeacherDao.getById" />
<collection column="class_id" property="teachers" ofType="Teacher"
select="com.heqing.ssm.dao.TeacherDao.getTeacherByClassId" />
</resultMap>
<sql id="Base_Column">
class._id class_id, class._name class_name, class._headTeacherId class_headTeacherId, class._classDirectorId class_classDirectorId
</sql>
<insert id="save" keyColumn="id" keyProperty="id" useGeneratedKeys="true">
insert into t_Class (_id, _name, _headTeacherId, _classDirectorId) values (#{id}, #{name}, #{headTeacher.id}, #{classDirector.id})
</insert>
<update id="update" parameterType="class">
update t_Class
set _name = #{name}, _headTeacherId = #{headTeacher.id}, _classDirectorId = #{classDirector.id}
where _id = #{id}
</update>
<delete id="delete" parameterType="Long">
delete from t_Class where _id = #{id}
</delete>
<select id="getById" parameterType="Long" resultMap="classResult">
select
<include refid="Base_Column"/>
from t_Class class
where class._id = #{id}
</select>
<select id="getByIds" parameterType="Long[]" resultMap="classResult">
select
<include refid="Base_Column"/>
from t_Class class where class._id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<select id="findAll" resultMap="classResult">
select
<include refid="Base_Column"/>
from t_Class class
<where>
<if test="name != null">
class._name = #{name}
</if>
<if test="headTeacher != null and headTeacher.id != null">
AND class._headTeacherId = #{headTeacher.id}
</if>
<if test="classDirector != null and classDirector.id != null">
AND class._classDirectorId = #{classDirector.id}
</if>
</where>
</select>
<select id="getPageBean" resultMap="classResult">
select
<include refid="Base_Column"/>
from t_Class class
<where>
<if test="name != null">
class._name = #{name}
</if>
<if test="headTeacher != null and headTeacher.id != null">
AND class._headTeacherId = #{headTeacher.id}
</if>
<if test="classDirector != null and classDirector.id != null">
AND class._classDirectorId = #{classDirector.id}
</if>
</where>
<if test="pageNum != 0 or pageSize != 0">
limit #{pageNum} , #{pageSize}
</if>
</select>
<select id="getClassListByTeacher" resultMap="classResult" >
select
<include refid="Base_Column"/>
from t_Class class
where class._classDirectorId = #{teacherId}
</select>
<select id="getClassByTeacherId" parameterType="Long" resultMap="classResult">
select
<include refid="Base_Column"/>
from t_Class class , t_teacher_class tc
where tc._classId = class._id and tc._teacherId = #{teacherId}
</select>
<insert id="saveTeacherClass" keyColumn="id" keyProperty="id" useGeneratedKeys="true">
insert into t_teacher_class (_classId, _teacherId) values (#{classId}, #{teacherId})
</insert>
<delete id="deleteTeacherClass" >
delete from t_teacher_class
<where>
<if test="classId != 0">
_classId = #{classId}
</if>
<if test="teacherId != 0">
AND _teacherId = #{teacherId}
</if>
</where>
</delete>
</mapper>