动态SQL是MyBatis的强大特性之一。在JDBC或其它类似的框架中,通常需要开发人员手动拼接SQL语句。根据不同的条件拼接不同的SQL语句是一件及其麻烦的事情,例如:拼接是要确保添加了必要的空格,还要注意去掉列表最后一个列名的逗号。
动态SQL一般是根据用户输入或外部条件动态组合的SQL语句块。通过一些条件的判断,可以实现在不同情况下执行不同的SQL语句,避免了手动拼接SQL的麻烦,不过动态SQL有时候在执行性能上面不如动态SQL,而且使用不恰当,往往会在安全方面存在隐患。如SQL注入的攻击。
MyBatis动态SQL可以让我们在Xml映射文件内,以标签的形式编写动态SQL,完成逻辑判断和动态拼接SQL的功能。
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose | 相当于Java中的Switch case语句 | 多条件分支判断 |
trim,where | 辅助元素 | 用于处理一些SQL拼装问题 |
foreach | 循环语句 | 在in语句等列举条件中较常用 |
bind | 辅助元素 | 拼接参数 |
sql | 定义一个通用功能 | 常用include结合使用 |
原理:使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。
在使用标签前,先做一些准备工作。
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`
(
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(45) DEFAULT NULL COMMENT '名称',
`gender` tinyint(1) DEFAULT NULL COMMENT '性别:1男性,0女性',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`grade_class` varchar(45) DEFAULT NULL COMMENT '年级-班级',
`birthday` datetime DEFAULT NULL COMMENT '出生日期',
`flag` tinyint(1) NOT NULL DEFAULT 1 COMMENT '有效标志:1有效,0无效',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Data
public class Student {
private Integer id;
private String name;
private boolean gender;
private Integer age;
private String gradeClass;
private Date birthday;
private String description;
}
if标签与java中的if语句类似,用于条件判断,使用if标签可以根据条件不同,自动拼接不同的SQL。下面是一个例子。
现在需实现下面的功能,当传入的参数中值不为空时,则将其作为查询条件,若为空,则不作为查询条件。
// 通过if条件查询学生信息
List<Student> selectStudentsByIf(@Param("name") String name, @Param("gender") boolean gender,@Param("age") Integer age);
<resultMap id="studentMap" type="com.tick.tack.manager.entity.Student">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="gender" property="gender" jdbcType="TINYINT"/>
<result column="age" property="age" jdbcType="INTEGER"/>
<result column="grade_class" property="gradeClass" jdbcType="VARCHAR"/>
<result column="birthday" property="birthday" jdbcType="DATE"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
resultMap>
<select id="selectStudentsByIf" resultMap="studentMap">
select * from `student`
<where>
<if test="name!=null and name != ''">
`name`like concat('%',#{name},'%')
if>
<if test="gender!=null">
AND `gender`=#{gender}
if>
<if test="age!=null">
AND `age`=#{age}
if>
where>
select>
set标签可以用来修改
// 通过set修改学生信息
void updateStudentBySet(Student student);
<update id="updateStudentBySet" parameterType="com.tick.tack.manager.entity.Student">
update `student`
<set>
<if test="name != null and name !=''">`name`=#{name},if>
<if test="gender!=null">`gender`=#{gender},if>
<if test="age!=null">`age`=#{age},if>
<if test="gradeClass!=null and gradeClass!=''">`grade_class`=#{gradeClass},if>
<if test="birthday != null">`birthday`=#{birthday},if>
<if test="description!=null and description!=''">`description`=#{description}if>
set>
where `id`=#{id}
update>
有时候,我们不想用到所有的查询条件,只想选择其中一个,查询条件有一个满足即可,使用choose标签可以解决此类问题,类似于Java的Switch语句
// 通过choose查询学生信息
List<Student> selectStudentByChoose(@Param("name") String name, @Param("gender") boolean gender,@Param("age") Integer age);
<select id="selectStudentByChoose" resultMap="studentMap">
select * from `student`
<where>
<choose>
<when test="name!=null and name != ''">
`name` like concat('%',#{name},'%')
when>
<when test="gender!=null">
AND `gender`=#{gender}
when>
<when test="age!=null">
AND `age`>=#{age}
when>
<otherwise>
AND 1=1
otherwise>
choose>
where>
select>
trim标记是一个格式化的标记,可以完成set或者是where标记的功能。
通过trim改写if,where语句
// 通过if和trim条件查询学生信息
List<Student> selectStudentByTrimIf(@Param("name") String name, @Param("gender") boolean gender,@Param("age") Integer age);
<select id="selectStudentByTrimIf" resultMap="studentMap">
select * from `student`
<trim prefix="where" prefixOverrides="and | or">
<if test="name!=null and name !=''">
`name` like concat('%',#{name},'%')
if>
<if test="gender!=null">
AND `gender`=#{gender}
if>
<if test="age!=null">
AND `age`=#{age}
if>
trim>
select>
通过trim改写if,set语句
// 通过trim来修改学生信息,可替换标签
void updateStudentByTrimSet(Student student);
<update id="updateStudentByTrimSet" parameterType="com.tick.tack.manager.entity.Student">
update `student`
<trim prefix="set" suffixOverrides=",">
<if test="name != null and name !=''">`name`=#{name},</if>
<if test="gender!=null">`gender`=#{gender},</if>
<if test="age!=null">`age`=#{age},</if>
<if test="gradeClass!=null and gradeClass!=''">`grade_class`=#{gradeClass},</if>
<if test="birthday != null">`birthday`=#{birthday},</if>
<if test="description!=null and description!=''">`description`=#{description}</if>
</trim>
where `id`=#{id}
</update>
通过if,trim可以实现添加功能
// 通过trim插入学生信息
void insertStudentByTrim(Student student);
<insert id="insertStudentByTrim" parameterType="com.tick.tack.manager.entity.Student">
insert into `student`
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null and name !=''">`name`,if>
<if test="gender!=null">`gender`,if>
<if test="age!=null">`age`,if>
<if test="gradeClass!=null and gradeClass!=''">`grade_class`,if>
<if test="birthday != null">`birthday`=#{birthday},if>
<if test="description!=null and description!=''">`description`if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null and name !=''">#{name},if>
<if test="gender!=null">#{gender},if>
<if test="age!=null">#{age},if>
<if test="gradeClass!=null and gradeClass!=''">#{gradeClass},if>
<if test="birthday != null">#{birthday},if>
<if test="description!=null and description!=''">#{description}if>
trim>
insert>
foreach是用来对集合进行遍历,和java中的foreach功能类似。
我们可以将任何可迭代对象(如List,Set等),Map对象或者数组对象作为集合参数传递给foreach。当使用迭代对象或者数组时,index是当前迭代的序号,item的值是本次迭代获取到的元素。当使用Map对象(或者Map.Entry对象的集合)时,index是键,item是值。
通过foreach查询id在集合中的数据。
// 通过foreach批量查询
List<Student> selectStudentByIds(@Param("ids") List<Integer> ids);
<select id="selectStudentByIds" resultMap="studentMap">
select * from `student`
<if test="ids != null and ids.size() > 0">
where `id` in
<foreach collection="ids" item="item" index="index" open="(" close=")" separator=",">
#{item}
foreach>
if>
select>
通过foreach实现批量插入
// 通过foreach批量插入学生信息
void insertStudentBatch(@Param("list") List<Student> students);
<insert id="insertStudentBatch">
insert into `student`(`name`,`gender`,`age`,`grade_class`,`birthday`,`description`)
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.name},#{item.gender},#{item.age},#{item.gradeClass},#{item.birthday},#{item.description})
foreach>
insert>
在开发过程中会遇到许多相同的SQL,以查询功能为例,各功能要查询的参数都是一样,但是筛选条件不同,那么我们便可以将相同的部分抽取出来,用得时候直接引用即可,不用再写重复代码。
<sql id="selectStudent">
select * from `student`
sql>
<select id="selectStudentById" resultType="com.tick.tack.manager.entity.Student">
<include refid="com.tick.tack.manager.dao.selectStudent"/>
where 1=1
<if test="id!=null">
and `id`=#{id}
if>
select>
MyBatis Plus有提供saveOrUpdata方法,MyBatis未提供,我们自己可自己实现该功能。
// 插入或更新接口
void saveOrUpdate(@Param("student") Student student);
// 插入或更新接口-方便事务控制
void saveOrUpdateDuplicate(Student student);
<insert id="saveOrUpdate" useGeneratedKeys="true"
keyProperty="id">
<selectKey keyProperty="count" resultType="int" order="BEFORE">
select count(*) from `student` where `id`=#{student.id}
</selectKey>
<if test="count>0">
update `student`
set
`name`=#{student.name},
`gender`=#{student.gender},
`age`=#{student.age},`grade_class`=#{student.gradeClass},`birthday`=#{student.birthday},
`description`=#{student.description}
where `id`=#{student.id}
</if>
<if test="count==0">
insert into `student`(`name`,`gender`,`age`,`grade_class`,`birthday`,`description`)
values (#{student.name},#{student.gender},#{student.age},
#{student.gradeClass},#{student.birthday},#{student.description})
</if>
</insert>
<insert id="saveOrUpdateDuplicate" parameterType="com.tick.tack.manager.entity.Student" useGeneratedKeys="true"
keyProperty="id">
insert into `student`(`id`,`name`, `gender`, `age`, `grade_class`, `birthday`, `description`)
values (#{id},#{name}, #{gender}, #{age}, #{gradeClass}, #{birthday}, #{description})
on duplicate key update
<if test="name != null and name !=''">`name`=#{name},</if>
<if test="gender!=null">`gender`=#{gender},</if>
<if test="age!=null">`age`=#{age},</if>
<if test="gradeClass!=null and gradeClass!=''">`grade_class`=#{gradeClass},</if>
<if test="birthday != null">`birthday`=#{birthday},</if>
<if test="description!=null and description!=''">`description`=#{description}</if>
</insert>