动态SQL
动态SQL能够根据输入的参数而确定最终提交给数据库的SQL语句。当传入的参数满足一定的条件时,MyBatis就会相应地拼接对应的语句,且能够自动地处理空格、逗号和语句头部的连词。
动态SQL需要用到if、where、foreach等标签来实现。
if标签
if标签时动态SQL的灵魂,分支条件全都要if标签的test属性来实现。如果test当中的语句结果为真,就将这个if标签中的sql语句拼接到总的sql语句中:
<select id="queryPerosnByNameOrAgeWithSQLTag" parameterType="person" resultType="person"> select id,name,age from person where <if test="name!=null and name!=''"> name=#{name} </if> <if test="age!=null and age!=0"> and age=#{age} </if> </select>
Person person=new Person();
person.setName("刘咯咯");
person.setAge(19);
Person pers=personMapper.queryPerosnByNameOrAgeWithSQLTag(person);
where标签
if标签是按照代码的顺序执行的,如果第一个条件不成立,且有第二个条件成立的话,由于第二个if标签中的sql语句首部必然有连接词and或or,那么将其与原sql语句拼接之后必然会导致sql语句解析错误。
可以巧妙地在where后面加一句1=1,就可以暂时解决这个问题了:
<select id="queryPerosnByNameOrAgeWithSQLTag" parameterType="person" resultType="person"> select id,name,age from person where 1=1 <if test="name!=null and name!=''"> and name=#{name} </if> <if test="age!=null and age!=0"> and age=#{age} </if> </select>
但是还是有一个问题,1=1对于or来讲就是恒真了,会屏蔽所有or后i按的语句,你说加一句1<>1吧,又防不了and语句,所以MyBatis推出了更加灵活的where标签来处理这问题:
<select id="queryPerosnByNameOrAgeWithSQLTag" parameterType="person" resultType="person">
select id,name,age from person
<where>
<if test="name!=null and name!=''">
and name=#{name}
</if>
<if test="age!=null and age!=0">
and age=#{age}
</if>
</where>
</select>
将所有的if标签都写入where标签内,where语句会处理第一个成立的if标签中的and(或者or)。这个时候哪怕第一个if中写上了and或test不通过,也能够拼接成正确的语句。不过where只会处理第一个成立的if标签,如果之后的标签中缺少连接词的话研究会报错。
foreach标签
foreach标签是为了让动态SQL能够处理in语句中的内容,例如查询编号为1、2、13的人的信息:
select id, name from person where id in(1,2,13)
我们想让in后面的那个集合能用动态SQL的方式动态传入。显然多个元素的话需要遍历每一个元素来查询,如果企图直接通过读取属性的方式是行不通的,例如定义一个Country类,其中有一个成员变量list为ids,在下面的语句中,会报无法识别ids的错误:
<select id="queryPersonWithIdInCountry" parameterType="Country" resultType="person">
select * from person
<where>
<if test="ids!=null and ids.size>0">
and id in(ids)
</if>
</where>
</select>
foreach看名字就知道是实现迭代的功能。foreach可以迭代的类型有:属性、数组(包括对象数组)、集合等。
属性
还是以上面的那个country中的属性ids为例,想要用foreach处理它,实现起来就和java中的增强for差不多:
<select id="queryPersonWithIdInCountry" parameterType="Country" resultType="person">
select * from person
<where>
<if test="ids!=null and ids.size>0">
<foreach collection="ids" open=" and id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
collection中写属性名,item中写入每一次遍历取出的值(相当于for (String name: [names])中的name);由于一个sql语句中不止要遍历的集合,还包括语句中的其它部分,所以用open写入要迭代的元素的前面的内容,用close写入后面的内容。最后还需要定义分隔符,用separator来写。
foreach标签内写入展位符,表示要迭代的部分的取值。所以整个sql语句就被拆成了这样:
select * from person【open】and id in(【item】#{id}【separator】,【item】#{id}【close】)
简单类型的数组
当传入的是简单类型的数组时,不仅输入参数的类型要改为对应的数组类型,而且在使用时需要用array来代替所有的数组名。原因时数组名时作为形参传入进来的,无法写成映射。
<select id="queryPersonWithArray" parameterType="int[]" resultType="person">
select * from person
<where>
<if test="array!=null and array.length>0">
<foreach collection="array" open=" and id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
集合类型(list)
和传入简单类型的数组差不多,将传入的参数写为list,且将所有集合名写为list:
<select id="queryPersonWithList" parameterType="list" resultType="person">
select * from person
<where>
<if test="list!=null and list.size>0">
<foreach collection="list" open=" and id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
对象数组
假设要传入一个person类的数组:
Person per1=new Person();
per1.setId(1);
Person per2=new Person();
per2.setId(2);
Person per13=new Person();
per13.setId(13);
Person persons[]=new Person[]{per1,per2,per13};
现在传入的类型并不能写Person[]了,会报错无法识别这个别名,只能传入Object[](我觉得挺迷的,为什么parameterType写单个Person类就可以写Person数组就不行了)。
<select id="queryPersonWithObjectArray" parameterType="Object[]" resultType="person"> select * from person <where> <if test="array!=null and array.length>0"> <foreach collection="array" open=" and id in(" close=")" item="person" separator=","> #{person.id} </foreach> </if> </where> </select>
现在每一次迭代取出的都是一个person实例,真正要用的其实时person实例中的id变量,所以在占位符中要写item中的临时变量(person)点id。
SQL片段
对于需要重复出现很多次的代码,在java中可以将其写入一个方法中,在SQL里可以写入存储过程或存储函数中。那么在MyBatis中,可以使用SQL片段处理可能需要多次出现的SQL语句。
第一步要提取相似代码。 在Mapper.xml文件中的namespace标签下写sql标签(与增删改查的标签同级),在sql标签中写入需要提取的代码:
<sql id="queryWithObjectArray">
<where>
<if test="array!=null and array.length>0">
<foreach collection="array" open=" and id in(" close=")" item="person" separator=",">
#{person.id}
</foreach>
</if>
</where>
</sql>
sql标签的id属性作用与增删改查的id相同,唯一标识这个SQL片段。
第二步引用。在需要引用的地方写入refield标签,补充完id值之后就可以引用完成了:
<select id="queryPersonWithObjectArray" parameterType="Object[]" resultType="person">
select * from person
<include refid="queryWithObjectArray"></include>
</select>
如果引用的地方不在SQL片段同一个文件,需要在引用时加上namespace。