MyBatis篇4-MyBatis的动态SQL

370 阅读9分钟

问题:为什么需要动态SQL

INSERT INTO student (id,name,sex,age) VALUES (#{id},#{name},#{sex},#{age})

假设在测试程序中输入没有name属性的值,那他就会因缺少值而报错
 
因此,是否可以让上面的SQL语句在缺少name属性时,自动变成 
    INSERT INTO student (id,sex,age) VALUES (#{id},#{sex},#{age})

这种想要达到随着参数的变化,让SQL也变化,就需要使用动态SQL

动态SQL

动态if标签:符合if条件的分段SQL可以被加入的整体的SQL中去执行

 	例如<if test="name != null">,name</if>
 	如果name是null,这个if标签的文本就不会被拼接到SQL中,SQL语句就会变成 INSERT INTO student (id,sex,age) VALUES (#{id},#{sex},#{age})

    注意:动态if条件可以使用在增删改查任意一个标签中,只要是需要if判断



动态Where条件,一般需要与if结合使用,使用Where 标签,可以省略了where 1=1

    where标签,假设被where标签包含的部分没有SQL段,我们的SQL就不会拼接where,只剩select * from student 
    
    如果被where标签包含的部分存在SQL段,mybatis会自动地在主题的SQL后面先拼接where,然后去除紧跟where后的第一个and字符串
    
    例如:select * from student where and id = #{id} and name = #{name},
    就会去掉第一个and,变成select * from student where id = #{id} and name = #{name}
    
    注意:动态where条件可以使用在删改查任意一个标签中,只要这个标签有where条件,就可以使用



choose(when,otherwise)条件 ,类似if()else if()else()
    
    查询student表,根据年龄,如果年龄小于15,只查询姓名字段
    如果年龄大于20,查询姓名和性别字段
    如果不是以上的两个条件,查询姓名、性别、班级字段
    在mybatis中, <(小于)使用lt, >(大于)使用gt, <=(小于等于)使用lte, >=(大于等于)使用gte

    注意: choose(when,otherwise)条件可以使用在增删改查任意一个标签中,只要需要if()else if()else() 的逻辑,就可以使用
    
    
    
set标签,在更新SQL中,对于set部分的语句拼接时,可以自动去掉多余的逗号

	在update中,如果有的值是null,就不要去set值。因为可能会出现以下的情况
        	update student set 
        	name = #{name},
        	age = #{age},
        	where id = #{id}
	这个SQL多了一个逗号。处理这种问题,mybatis使用set标签解决,用set标签替代set,把需要改的字段括起来,这样就可以自动去掉多余的逗号
	
	
	
forEach标签,迭代数组或集合类型的参数,把所有元素组装成一个字符串

	要传递一个id数组,根据id数组查询student,使用foreach标签
	collection=“array或list”,array用来对应参数为数组,list对应参数为集合, 
	item是为当前元素的变量名称,separator是以什么符号来分隔元素,
	open和close分别是以什么符号来作为整体字符串的开头和结尾。
 
    注意:forEach标签可以使用在增删改查任意一个标签中,只要需要迭代数组或集合类型的参数,就可以使用



 include标签,可以事先使用SQL标签预置SQL段,在需要的地方用include标签引用这些SQL

	sql标签中,我们写SQL段时候,可以使用之前学习的set,where,if,choose(when),trim,foreach
	
	使用sql标签预置SQL段,使用include标签引用SQL,一般是在经常重复的SQL段常用
	
	可以把经常会用到的SQL段提取出来,放到SQL标签里,其他的映射器需要使用的时候直接使用include标签引用

主配置文件

    <!-- 数据源环境配置 -->
<environments default="development">
	<environment id="development">
		<transactionManager type="JDBC" />
		<dataSource type="POOLED">
			<property name="driver" value="com.mysql.jdbc.Driver" />
			<property name="url" value="jdbc:mysql://localhost:3306/demo?useUnicode=true&amp;characterEncoding=UTF-8" />
			<property name="username" value="root" />
			<property name="password" value="root" />
        </dataSource>
	</environment>
</environments>

	<!-- 主配置文件引入映射器 -->
<mappers>
	<mapper resource="com/test/day39/DynamicSqlMapper.xml" />
</mappers>

xml映射器-if

<!-- 动态if标签在增删改上的应用 -->
<insert id="insert" parameterType="Student">
	<!-- 对比原例:insert into student(id, name, sex, age) values (#{id},#{name},#{sex},#{age}); -->
	INSERT INTO student 
	(
	id
		<if test="name != null">,name</if>
		<if test="sex != null">,sex</if>
		<if test="age != null">,age</if>
	) 
	VALUES 
	(
	#{id}
		<if test="name != null">,#{name}</if>
		<if test="sex != null">,#{sex}</if>
		<if test="age != null">,#{age}</if>
	)
</insert>

<!-- 动态if在查询上的应用:根据传递的字符串判断到底执行哪一段SQL。str指的就是测试程序输入进来的值 -->
<select id="select" resultType="Map" parameterType="String">
	<if test="str == 'abc'">
		select * from student
	</if>
	<if test="str == 'ss'">
		select * from user
	</if>
</select>

<!-- 注意:关于 1=1 的说明 -->
<select id="select" resultType="Map" parameterType="String">
	select * from dept where 1 = 1 
	<if test="deptId != null">
		and dept_id=#{deptId} 
	</if>
	<if test="deptName != null">
		and dept_name=#{deptName} 
	</if>
</select>

为什么上面where后面要存在一个 1=1 的恒等式?
上面的式子,将判断横放就是如下式子
select * from dept where 1 = 1
<if test="deptId != null">and dept_id=#{deptId} </if>
<if test="deptName != null">and dept_name=#{deptName} </if>

假设deptId是null,SQL语句就是select * from dept where and dept_name=#{deptName}
这样会造成的一个问题就是,where 后面直接跟着一个 and,从SQL语法上来说,这个语句就是错的,
因此,我们需要在where后面紧跟一个 and,用来避免语法上的错误
select * from dept where 1 = 1 and dept_id=#{deptId} and dept_name=#{deptName}

xml映射器-where、choose、set、forEach、include

 <?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">

<!-- 接口与xml映射器混合使用 -->
<mapper namespace="com.test.day39.DynamicSqlMapper">
    <!-- 动态where标签的应用 -->
    	<select id="selectStu" resultType="Map" parameterType="Student">
		select * from student 
		<where>
			<if test="id != null">and id = #{id} </if>
			<if test="name != null">and name = #{name}</if>
		</where>
	</select>
 
    <!-- 动态choose标签的应用 -->
 	<select id="selectStuByAge" resultType="Map" parameterType="Integer">
		select
		 <choose>
		 	<when test="age lt 15">name</when>
		 	<when test="age gt 20">name,sex</when>
		 	<otherwise>name,sex,class</otherwise>
		 </choose>
		 from student where age = #{age}
	</select>
 
    <!-- 动态set标签的应用 --> 
 	<update id="updateStu" parameterType="Student">
		update student 
		<set>
			<if test="name != null">name = #{name},</if>
			<if test="age != null">age = #{age},</if>
			<if test="sex != null">sex = #{sex}</if>
		</set> 
		where id = #{id}
	</update>
 
    <!-- 动态forEach标签的应用:下面的语句等于select * from student where id in (1,2,3) --> 
 	<select id="selectStuByIds" resultType="Map" parameterType="Integer[]">
		select * from student where id in 
		<foreach collection="array" item="id" open="(" separator="," close=")">
			#{id}
		</foreach>
	</select>
 
 	<!-- foreach标签可以完成批量操作。比如批量插入,下面的语句等于insert into student (id,name) values (1,''),(2,''),(3,'') -->
	<insert id="insertStus" parameterType="Student">
		insert into student (id,name) values 
		<foreach collection="list" item="stu" open="" separator="," close="">
			(#{stu.id},#{stu.name})
		</foreach>
	</insert>
 
    <!-- 动态include标签的应用-1 --> 
 	 <sql id="columns">
	 	id,name,sex,class
	 </sql>
	 
	 <select id="selectStuById" resultType="Map" parameterType="Integer">
		select
		 <include refid="columns"/>
		 from student where id = #{id}
	</select>

    <!-- 动态include标签的应用-2 --> 
	<sql id="updateVal">
		<!-- trim suffixOverrides=","	这样可以去掉被trim标签所包含的最后一个逗号 -->
		<trim suffixOverrides=",">
			<if test="name != null">name = #{name},</if>
			<if test="age != null">age = #{age},</if>
			<if test="sex != null">sex = #{sex},</if>
		</trim>
	</sql>
	
	<update id="updateStu1" parameterType="Student">
		update student set
		<include refid="updateVal"/>
		where id = #{id}
	</update>
 
</mapper>

接口- if

 package com.test.day38;

//接口和映射器混合使用:方法名与xml映射器中的id值对应
public interface DynamicSqlMapper {
    	public int insert(Student stu);//动态if
	
    	public List<Map> select(String s);//动态if
}

接口- where、choose、set、forEach、include

package com.test.day38;

//接口和映射器混合使用:方法名与xml映射器中的id值对应
public interface DynamicSqlMapper {
    	public List<Map> selectStu(Student stu);//动态where
	
    	public List<Map> selectStuByAge(Integer age);//动态choose
	
	public int updateStu(Student stu);//动态set
	
	public List<Map> selectStuByIds(Integer[] ids);//动态foreach
	
	public int insertStus(List<Student> stus);//动态foreach
	
	public Map selectStuById(Integer id);//动态include
	
	public int updateStu1(Student stu);//动态include
}

调用程序:接口和映射器混合使用

public class XmlAndInMapperTest {
    	public static void main(String[] args) {
    		Reader reader = null;
    		SqlSessionFactory sqlSessionFactory = null;
    		SqlSession session = null;
    		try{
    			//加载主配置文件,主配置文件中有数据库连接相关信息,还有映射器的相关信息
    			//参数是主配置文件的路径,我们的主配置文件是在跟路径下,所以直接写文件名就可以
			reader = Resources.getResourceAsReader("myBatis-config.xml");
			
			//创建SqlSessionFactory工厂
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
			
			//得到SqlSession对象,这个对象是操作映射器的对象
			session = sqlSessionFactory.openSession();
            
                        //操作接口与xml混合映射器:getMapper(接口的class).调用的方法
                       Student stu = new Student();
			stu.setId(43);
//			stu.setName("测试动态SQL");
//			stu.setAge(16);
//			stu.setSex("男");
    	        	int res = session.getMapper(DynamicSqlMapper.class).insert(stu);//动态if的测试
			System.out.println(res);
			
			System.out.println(session.getMapper(DynamicSqlMapper.class).select("ss"));//动态if的测试
			
//			Student stu1 = new Student();
//			stu1.setId(1);
//			stu1.setName("张三");
//			System.out.println(session.getMapper(DynamicSqlMapper.class).selectStu(stu1));//动态where
			
//			System.out.println(session.getMapper(DynamicSqlMapper.class).selectStuByAge(21));//动态choose
			
//			Student stu2 = new Student();
//			stu2.setId(11);
//			stu2.setName("测试set标签");
//			stu2.setAge(55);
//			int res = session.getMapper(DynamicSqlMapper.class).updateStu(stu2);//动态set
//			System.out.println(res);
			
//			Integer[] ids = new Integer[]{1,2,3};
//			System.out.println(session.getMapper(DynamicSqlMapper.class).selectStuByIds(ids));//动态foreach
			
//			List<Student> list = new ArrayList<Student>();
//			Student s1 = new Student();
//			s1.setId(44);
//			s1.setName("批量操作1");
//			Student s2 = new Student();
//			s2.setId(45);
//			s2.setName("批量操作2");
//			list.add(s1);
//			list.add(s2);
//			System.out.println(session.getMapper(DynamicSqlMapper.class).insertStus(list));//动态foreach
			
//			System.out.println(session.getMapper(DynamicSqlMapper.class).selectStuById(1));//动态include

//			Student stu2 = new Student();
//			stu2.setId(41);
//			stu2.setName("测试include标签");
//			stu2.setAge(55);
//			int res = session.getMapper(DynamicSqlMapper.class).updateStu1(stu2);//动态include
//			System.out.println(res);

			
			//提交session
			session.commit();
		}catch(Exception e){
			//如果发生异常回滚session
			session.rollback();
		}finally{
			//关闭资源
			try {
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			session.close();
		}
	}
}

测试用的实体类

package com.test.day38;

import java.io.Serializable;

public class Student implements Serializable{
    	private static final long serialVersionUID = 1L;
	
    	public Integer id;
	public String name;
	public Integer age;
	public String sex;
	public String classes;
	public Integer servlet;
	public Integer jsp;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public String getClasses() {
		return classes;
	}
	public void setClasses(String classes) {
		this.classes = classes;
	}
	public Integer getServlet() {
		return servlet;
	}
	public void setServlet(Integer servlet) {
		this.servlet = servlet;
	}
	public Integer getJsp() {
		return jsp;
	}
	public void setJsp(Integer jsp) {
		this.jsp = jsp;
	}
	
	@Override
	public String toString() {//打印的时候用,System.out.println(stus)等于System.out.println(stus.toString())
		return "[id=" + id + ",name=" + name + ",age=" + age + ",sex=" + sex + ",classes="
				+ classes + ",servlet=" + servlet + ",jsp=" + jsp + "]";
	}
}