MyBatis深入


typora-copy-images-to: mybatis
typora-root-url: mybatis

MyBatis深入

一 Mybatis主键映射

1.1主键映射作用

     当数据插入操作不关心插入后数据的主键(唯一标识),那么建议使用 不返回自增主键值 的方式来配置插入语	句,这样可以避免额外的SQL开销.

    当执行插入操作后需要立即获取插入的**自增主键值**,比如一次操作中保存一对多这种关系的数据,那么就要使用插入后获取自增主键值的方式配置.

    mybatis进行插入操作时,如果表的主键是自增的,针对不同的数据库相应的操作也不同。基本上经常会遇到的就是**Oracle Sequece** 和 **Mysql** **自增主键**,解释如下。

1.2自动递增返回主键

一对多的那种表结构,在插入多端数据时,需要获取刚刚保存了的一端的主键。那么这个时候,上述的配置就无法满足需要了。为此我们需要使用mybatis提供的<selectKey />来单独配置针对自增逐渐的处理。
  • Oracle Sequence配置
	<sql id='TABLE_NAME'>TEST_USER</sql>
	
	<sql id='TABLE_SEQUENCE'>SEQ_TEST_USER_ID.nextval</sql>
	
	<!-- 注意这里需要先查询自增主键值 -->
	<insert id="insertOrcle" parameterType="User">
		<selectKey keyProperty="id" resultType="int" order="BEFORE">
			select
			<include refid="TABLE_SEQUENCE" />
			from dual
		</selectKey>
		insert into
		<include refid="TABLE_NAME" />
		(ID,NAME,AGE) values ( #{id}, #{name}, #{age} )
	</insert>

当使用了后,在实际的插入操作时,mybatis会执行以下两句SQL:

MyBatis深入

在执行插入 语句2 之前,会先执行
语句1 以获取当前的ID值,然后mybatis使用反射调用User对象的setId方法,将 语句1 查询出的值保存在User对象中,然后才执行
语句2 这样就保证了执行完插入后

@Test
	public void testInsertOracle(){
		User u=new User("小毛", 18);
		int count = mapper.insertOrcle(u);
		System.out.println(count>0?"新增成功":"新增失败");
		System.out.println("新增主键是:"+u.getId());
	}

user.id 是有值的

  • Mysql自增主键配置

针对于Mysql这种自己维护主键的数据库,可以直接使用以下配置在插入后获取插入主键

	<sql id='TABLE_NAME'>TEST_USER</sql>
	
	<insert id="insertMySql" useGeneratedKeys="true" keyProperty="id" parameterType="User">
		insert into
		<include refid="TABLE_NAME" />
		( NAME, AGE ) values ( #{name}, #{age} )
	</insert>

当然,由于Mysql的自增主键可以通过SQL语句

select LAST_INSERT_ID();

来获取的。因此针对Mysql,Mybatis也可配置如下:

<sql id='TABLE_NAME'>TEST_USER</sql>
	
	<!-- 注意这里需要先查询自增主键值 -->
	<insert id="insertMySql" parameterType="User">
		<selectKey keyProperty="id" resultType="int" order="AFTER"> 
		  SELECT LAST_INSERT_ID()
		</selectKey>
		insert into
		<include refid="TABLE_NAME" />
		(ID,NAME,AGE) values ( #{id}, #{name}, #{age} )
	</insert>

只不过该中配置需要额外的一条查询SQL!

二 关联映射

2.1关联映射作用

在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存在着一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表。那么Mybatis中是如何实现这种多表关系的映射呢?
  • 查询结果集ResultMap

       resultMap元素是 MyBatis中最重要最强大的元素。它就是让你远离90%的需要从结果 集中取出数据的JDBC代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事 情。  
    

有朋友会问,之前的示例中我们没有用到结果集,不是也可以正确地将数据表中的数据映射到Java对象的属性中吗?是的。这正是resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。

  • resultMap元素中,允许有以下直接子元素:
  • constructor - 类在实例化时,用来注入结果到构造方法中(本文中暂不讲解)
  • id - 作用与result相同,同时可以标识出用这个字段值可以区分其他对象实例。可以理解为数据表中的主键,可以定位数据表中唯一一笔记录
  • result - 将数据表中的字段注入到Java对象属性中
  • association - 关联,简单的讲,就是“有一个”关系,如“用户”有一个“帐号”
  • collection - 集合,顾名思议,就是“有很多”关系,如“客户”有很多“订单”
  • discriminator(鉴别器) - 使用结果集决定使用哪个结果映射(暂不涉及)

每个元素的用法及属性我会在下面结合使用进行讲解。

数据库sql语句如下

/*
Navicat MySQL Data Transfer

Source Server         : mysql
Source Server Version : 50624
Source Host           : localhost:3306
Source Database       : mybatis1105

Target Server Type    : MYSQL
Target Server Version : 50624
File Encoding         : 65001

Date: 2018-11-05 14:39:43
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `class`
-- ----------------------------
DROP TABLE IF EXISTS `class`;
CREATE TABLE `class` (
  `c_id` int(11) NOT NULL AUTO_INCREMENT,
  `c_name` varchar(20) DEFAULT NULL,
  `teacher_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`c_id`),
  UNIQUE KEY `uq_teacher_Id` (`teacher_id`),
  CONSTRAINT `fk_teacher_id` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of class
-- ----------------------------
INSERT INTO `class` VALUES ('1', 'Java班', '1');
INSERT INTO `class` VALUES ('2', 'H5班', '2');

-- ----------------------------
-- Table structure for `course`
-- ----------------------------
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
  `course_id` int(11) NOT NULL AUTO_INCREMENT,
  `course_name` varchar(20) DEFAULT NULL,
  `course_time` int(11) DEFAULT NULL,
  PRIMARY KEY (`course_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of course
-- ----------------------------
INSERT INTO `course` VALUES ('1', 'Java', '12');
INSERT INTO `course` VALUES ('2', 'SQL', '18');
INSERT INTO `course` VALUES ('3', 'HTML', '14');

-- ----------------------------
-- Table structure for `course_student`
-- ----------------------------
DROP TABLE IF EXISTS `course_student`;
CREATE TABLE `course_student` (
  `cs_id` int(11) NOT NULL AUTO_INCREMENT,
  `c_id` int(11) DEFAULT NULL,
  `s_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`cs_id`),
  KEY `FK_C_ID` (`c_id`),
  KEY `FK_S_ID` (`s_id`),
  CONSTRAINT `FK_C_ID` FOREIGN KEY (`c_id`) REFERENCES `course` (`course_id`),
  CONSTRAINT `FK_S_ID` FOREIGN KEY (`s_id`) REFERENCES `student` (`s_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of course_student
-- ----------------------------
INSERT INTO `course_student` VALUES ('1', '1', '1');
INSERT INTO `course_student` VALUES ('2', '1', '2');
INSERT INTO `course_student` VALUES ('3', '2', '1');
INSERT INTO `course_student` VALUES ('5', '2', '3');
INSERT INTO `course_student` VALUES ('6', '1', '4');

-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `s_id` int(11) NOT NULL AUTO_INCREMENT,
  `s_name` varchar(20) DEFAULT NULL,
  `class_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`s_id`),
  KEY `FK_class_id` (`class_id`),
  CONSTRAINT `FK_class_id` FOREIGN KEY (`class_id`) REFERENCES `class` (`c_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', 'student_A', '1');
INSERT INTO `student` VALUES ('2', 'student_B', '1');
INSERT INTO `student` VALUES ('3', 'student_C', '1');
INSERT INTO `student` VALUES ('4', 'student_D', '2');
INSERT INTO `student` VALUES ('5', 'student_E', '2');
INSERT INTO `student` VALUES ('6', 'student_F', '2');

-- ----------------------------
-- Table structure for `teacher`
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `t_id` int(11) NOT NULL AUTO_INCREMENT,
  `t_name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('1', 'teacher1');
INSERT INTO `teacher` VALUES ('2', 'teacher2');

2.2 一对一关联

一个班级有一个班主任,一个班主任对应一个班级。现在有如下数据库联系,一对一关联其实和一对都很像,只是将外键设一个唯一约束

MyBatis深入
MyBatis深入

2.2.1 配置bean

public class Classess {
	private Integer cid;
	private String cname;
	private Teacher teacher;
	
	public Integer getCid() {
		return cid;
	}
	public void setCid(Integer cid) {
		this.cid = cid;
	}
	public String getCname() {
		return cname;
	}
	public void setCname(String cname) {
		this.cname = cname;
	}
	public Teacher getTeacher() {
		return teacher;
	}
	public void setTeacher(Teacher teacher) {
		this.teacher = teacher;
	}

}	

public class Teacher {
	private Integer tid;
	private String tname;
	public Integer getTid() {
		return tid;
	}
	public void setTid(Integer tid) {
		this.tid = tid;
	}
	public String getTname() {
		return tname;
	}
	public void setTname(String tname) {
		this.tname = tname;
	}
	@Override
	public String toString() {
		return "Teacher [tid=" + tid + ", tname=" + tname + "]";
	}
}

2.2.2 配置核心配置文件 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
  <configuration>
  <!-- 1.配置dataSource配置文件 -->
  	<properties resource="jdbc.properties">
  	</properties>
    <!-- 设置别名 -->
  	<typeAliases>
		<package name="com.dream.bean"/>
  	</typeAliases>
  <!-- 2.配置连接环境 -->
  	<environments default="development">
  		<environment id="development">
  			<transactionManager type="JDBC"></transactionManager>
  			<dataSource type="POOLED">
  				<property name="driver" value="${driver}"/>
  				<property name="url" value="${url}"/>
  				<property name="username" value="${username}"/>
  				<property name="password" value="${password}"/>
  				<property name="driver.encoding" value="${encoding}"/>
  			</dataSource>
  		</environment>
  	</environments>
  	<!-- 3.配置mapper -->
  	<mappers>
  		<package name="com.dream.mapper"/>
  	</mappers>
  </configuration>

jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis1105?useUnicode=true&characterEncoding=utf-8
username=root
password=as501226107A.
encoding=UTF8

2.2.3配置mapper

classmapper.java

public interface ClassesMapper {
	//1.通过id获得班级,并获取教该班机的教师
	public Classess getClassesById(Integer id);
	//1.通过id获得班级,并获取教该班机的教师(嵌套查询)
	public Classess getClassesById1(Integer id);
}

classesMapper.xml

<?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.dream.mapper.ClassesMapper">
      
       <!-- 
                    根据班级id查询班级信息(带老师的信息)
        ##1. 联表查询
        SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1;
        
        ##2. 执行两次查询
        SELECT * FROM class WHERE c_id=1;  //teacher_id=1
        SELECT * FROM teacher WHERE t_id=1;//使用上面得到的teacher_id
     -->

      
  	<!-- 方法1 -->
  	<!-- 设置返回Map -->
  	<resultMap type="Classess" id="classesMap1">
  		<id column="c_id" property="cid"/>
  		<result column="c_name" property="cname"/>
  		<association property="teacher" javaType="Teacher">
  			<id column="t_id" property="tid"/>
  			<result column="t_name" property="tname"/>
  		</association>
  	</resultMap>
  	
  	<select id="getClassesById" resultMap="classesMap1">
  		select * from teacher t,class c where c.teacher_id =t.t_id
  		and c.c_id=#{id}
  	</select>
  	<!-- ======================================================== -->
  	<!-- 方法2 -->
  	<resultMap type="Classess" id="classesMap2">
  		<id column="c_id" property="cid"/>
  		<result column="c_name" property="cname"/>
  		<association property="teacher" column="teacher_id" select="getTeacherById"/>
  	</resultMap>
  	<select id="getClassesById1" resultMap="classesMap2">
  		select * from class c where c.c_id=#{id}
  	</select>
  	<select id="getTeacherById" resultType="Teacher">
  		<!-- 注意设置别名让查出的结果列明与bean属性对应 -->
  		select t_id tid,t_name tname from teacher where t_id=#{id}
  	</select>

  </mapper>

方法一:使用一个sql语句完成查询,我们需要给select设置resultMap

MyBatis深入

type:为返回的类型

: 表示”一个“,property表示Teacher对应的属性名;javaType表示属性的类型

在association中将bean的属性和查询出来的结果集的列进行映射

MyBatis深入

方法二:首先根据id查出对应的班级,在根据班级中的teacher_id去查询对应的teacher

MyBatis深入

**这里我们需要注意的是,<association>标签不需要设置类型,我们需要加入column,然后需要一个新的select语句,这个select语句需要teacherid  ,该参数就在第一个查询结果集中,我们需要找到对应的列(即class表中的外键),所以我们设置一个column,传递到**

MyBatis深入

该位置的teacher_id

并且,我们需要给查询的列设置别名和我们的bean属性对应

2.2.4测试结果

方法一:

@Test
	public void test1() {
		//测试一个语句返回结果
		Classess classesById = c.getClassesById2(1);
		System.out.println(classesById.getStudents());
	}

MyBatis深入

方法二:

@Test
	public void test2() {
		//测试两条语句返回结果(嵌套查询)
		Classess classesById = c.getClassesById3(1);
		System.out.println(classesById.getStudents());
	}

MyBatis深入

2.3 一对多关联

2.3.1需求

MyBatis深入

一个学生对应一个班级,一个班级有多个学生,这里我们重点配置1的一方,因为它存在集合

2.3.2 配置bean

public class Student {
	private Integer sid;
	private String sname;
	public Integer getSid() {
		return sid;
	}
	public void setSid(Integer sid) {
		this.sid = sid;
	}
	public String getSname() {
		return sname;
	}
	public void setSname(String sname) {
		this.sname = sname;
	}
	@Override
	public String toString() {
		return "Student [sid=" + sid + ", sname=" + sname + "]";
	}

}

public class Classess {
	private Integer cid;
	private String cname;
	private Teacher teacher;
	private List<Student> students;
	public Integer getCid() {
		return cid;
	}
	public void setCid(Integer cid) {
		this.cid = cid;
	}
	public String getCname() {
		return cname;
	}
	public void setCname(String cname) {
		this.cname = cname;
	}
	public Teacher getTeacher() {
		return teacher;
	}
	public void setTeacher(Teacher teacher) {
		this.teacher = teacher;
	}
	public List<Student> getStudents() {
		return students;
	}
	public void setStudents(List<Student> students) {
		this.students = students;
	}
}	

2.3.3 配置mapper

	<!-- 根据classId查询对应的班级信息,包括学生,老师 -->
	<!-- 方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集 SELECT * FROM class c, teacher t,student 
		s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND c.c_id=1 -->
	<select id="getClass3" parameterType="int" resultMap="ClassResultMap3">
		select *
		from class c, teacher t,student s where c.teacher_id=t.t_id and
		c.C_id=s.class_id and c.c_id=#{id}
	</select>
	<resultMap type="Classes" id="ClassResultMap3">
		<id property="id" column="c_id" />
		<result property="name" column="c_name" />
		<association property="teacher" column="teacher_id"
			javaType="Teacher">
			<id property="id" column="t_id" />
			<result property="name" column="t_name" />
		</association>
		<!-- ofType指定students集合中的对象类型 -->
		<collection property="students" ofType="Student">
			<id property="id" column="s_id" />
			<result property="name" column="s_name" />
		</collection>
	</resultMap>

	<!-- 方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型 SELECT * FROM class WHERE c_id=1; 
		SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的teacher_id的值 SELECT * FROM 
		student WHERE class_id=1 //1是第一个查询得到的c_id字段的值 -->
		
	<select id="getClass4" parameterType="int" resultMap="ClassResultMap4">
		select * from class where c_id=#{id}
	</select>
	
	<resultMap type="Classes" id="ClassResultMap4">
		<id property="id" column="c_id" />
		<result property="name" column="c_name" />
		<association property="teacher" column="teacher_id" javaType="Teacher" select="getTeacher2"></association>
		<collection property="students" ofType="Student" column="c_id" select="getStudent"></collection>
	</resultMap>

	<select id="getTeacher2" parameterType="int" resultType="Teacher">
		SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
	</select>

	<select id="getStudent" parameterType="int" resultType="Student">
		SELECT s_id id, s_name name FROM student WHERE class_id=#{id}
	</select>

2.3.4编写单元测试类

	@Test
	public void test3(){
		Classes c1 = mapper.getClass3(1);
		System.out.println(c1);
	}
	
	@Test
	public void test4(){
		Classes c1 = mapper.getClass4(1);
		System.out.println(c1);
	}

2.3.5 MyBatis一对多关联查询总结

 MyBatis中使用collection标签来解决一对多的关联查询,**ofType****属性指定集合中元素的对象类型**。

2.4

2.4 多对多关联

2.4.1 需求

一个课程可以被多个学生选择,一个学生可以选择多门课程,我们想查看该学生的所有选择课程

MyBatis深入

MyBatis深入

2.4.2 创建bean

public class Student {
	private Integer sid;
	private String sname;
	private List<Course> courses;
	public Integer getSid() {
		return sid;
	}
	public void setSid(Integer sid) {
		this.sid = sid;
	}
	public String getSname() {
		return sname;
	}
	public void setSname(String sname) {
		this.sname = sname;
	}
	@Override
	public String toString() {
		return "Student [sid=" + sid + ", sname=" + sname + "]";
	}
	public List<Course> getCourses() {
		return courses;
	}
	public void setCourses(List<Course> courses) {
		this.courses = courses;
	}
}

2.4.3 编写sql映射文件CourseMapper.xml

添加如下的SQL映射信息

<?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.dream.mapper.StudentMapper">
  	<select id="getStudentById" parameterType="int" resultMap="getStudentMap1">
		select * from student s,
		course c,
		course_student cs 
		where s.s_id=cs.s_id 
		and c.course_id=cs.c_id 
		and s.s_id=#{id}
  	</select>
  	<resultMap type="Student" id="getStudentMap1">
  		<id property="sid" column="s_id"/>
  		<result property="sname" column="s_name"/>
  		<collection property="courses" ofType="Course">
  			<id property="cid" column="course_id"/>
  			<result property="cname" column="course_name"/>
  			<result property="ctime" column="course_time"/>
  		</collection>
  	</resultMap>
  </mapper>

加入学生id为1,该sql的结果集如下:

MyBatis深入

我们根据sql的结果集来配resultMap

三 动态sql

3.1 动态SQL简介

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,**你就能体会到根据不同条件拼接** **SQL** **语句有多么痛苦**。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

MyBatis深入

     通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。

     动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 **OGNL**(Struts2语法) 的表达式来消除其他元素。

     mybatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:
  • if 语句 (简单的条件判断)
  • choose (when,otherwise) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似.
  • trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)
  • where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)
  • set (主要用于更新时)
  • foreach (在实现 mybatis in 语句查询时特别有用)

3.2 分支判断

3.2.1 if元素

根据 username 和 sex 来查询数据。如果username为空,那么将只根据sex来查询;反之只根据username来查询

首先不使用 动态SQL 来书写

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="User">
		select * from user where username=#{username} and sex=#{sex}
	</select>

上面的查询语句,我们可以发现,如果 #{username} 为空,那么查询结果也是空,如何解决这个问题呢?使用 if 来判断

	<select id="selectUserByUsernameAndSex" resultType="user"
		parameterType="User">
		select * from user where
		<if test="username != null">
			username=#{username}
		</if>
		<if test="sex!= null">
			and sex=#{sex}
		</if>
	</select>

这样写我们可以看到,如果 sex 等于 null,那么查询语句为 select * from user where username=#{username},但是如果usename 为空呢?那么查询语句为 select * from user where and sex=#{sex},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句

3.2.2 动态SQL:if+where 语句

<select id="selectUserByUsernameAndSex" resultType="User"
		parameterType="User">
		select * from user
		<where>
			<if test="username != null">
				username=#{username}
			</if>
			<if test="sex != null">
				and sex=#{sex}
			</if>
		</where>
	</select>

**这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。**

3.2.3 动态SQL:if+set 语句

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
<!-- 根据 id 更新 user 表的数据 -->
	<update id="updateUserById" parameterType="User">
		update user u
		<set>
			<if test="username != null and username != ''">
				u.username = #{username},
			</if>
			<if test="sex != null and sex != ''">
				u.sex = #{sex},
			</if>
		</set>
		where id=#{id}
	</update>

这样写,如果第一个条件 username 为空,那么 sql 语句为:update user u set u.sex=? where id=?

如果第一个条件不为空,那么 sql 语句为:update user u set u.username = ? ,u.sex = ? where id=?

如果出现多余逗号 会自动去掉!!

3.2.4 动态SQL:choose(when,otherwise) 语句

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
	<select id="selectUserByChoose" resultType="User" parameterType="User">
		select * from user
		<where>
			<choose>
				<when test="id !='' and id != null">
					id=#{id}
				</when>
				<when test="username !='' and username != null">
					and username=#{username}
				</when>
				<otherwise>
					and sex=#{sex}
				</otherwise>
			</choose>
		</where>
	</select>

也就是说,这里我们有三个条件,id,username,sex,只能选择一个作为查询条件

如果 id 不为空,那么查询语句为:select * from user where id=?

如果 id 为空,那么看username 是否为空,如果不为空,那么语句为 select * from user where username=?;

如果 username 为空,那么查询语句为 select * from user where sex=?

3.2.5 动态SQL:trim 语句 了解

trim标记是一个格式化的标记,可以完成set或者是where标记的功能

①、用 trim 改写上面第二点的 if+where 语句

	<select id="selectUserByUsernameAndSex" resultType="user" parameterType="User">
		select * from user
		<trim prefix="where" prefixOverrides="and | or">
			<if test="username != null">
				and username=#{username}
			</if>
			<if test="sex != null">
				and sex=#{sex}
			</if>
		</trim>
	</select>

prefix:前缀

prefixoverride:去掉第一个and或者是or

②、用 trim 改写上面第三点的 if+set 语句

	<!-- 根据 id 更新 user 表的数据 -->
	<update id="updateUserById" parameterType="User">
		update user u
		<trim prefix="set" suffixOverrides=",">
			<if test="username != null and username != ''">
				u.username = #{username},
			</if>
			<if test="sex != null and sex != ''">
				u.sex = #{sex},
			</if>
		</trim>
		where id=#{id}
	</update>

3.2.6动态SQL: SQL 片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的**重用性**,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

比如:假如我们需要经常根据用户名和性别来进行联合查询,那么我们就把这个代码抽取出来,如下:

	<!-- 定义 sql 片段 -->
	<sql id="selectUserByUserNameAndSexSQL">
		<if test="username != null and username != ''">
			AND username = #{username}
		</if>
		<if test="sex != null and sex != ''">
			AND sex = #{sex}
		</if>
	</sql>

引用 sql 片段

<select id="selectUserByUsernameAndSex" resultType="user"
		parameterType="User">
		select * from user
		<trim prefix="where" prefixOverrides="and | or">
			<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
			<include refid="selectUserByUserNameAndSexSQL"></include>
			<!-- 在这里还可以引用其他的 sql 片段 -->
		</trim>
	</select>

注意:①、最好基于 单表来定义 sql 片段,提高片段的可重用性

②、在 sql 片段中不要包括 where

5.2.7动态SQL: foreach 语句

需求:我们需要查询 user 表中 id 分别为1,2,3的用户

sql语句:select * from user where id=1 or id=2 or id=3

select * from user where id in (1,2,3)

①、建立一个 UserVo 类,里面封装一个 List ids 的属性

public class UserVo {

	//封装多个用户的id
    private List<Integer> ids;
 
    public List<Integer> getIds() {
        return ids;
    }
 
    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}

②、我们用 foreach 来改写 select * from user where id=1 or id=2 or id=3


	<select id="selectUserByListId" parameterType="UserVo"
		resultType="User">
		select * from user
		<where>
			<!-- collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 
				separator:遍历对象之间需要拼接的字符串 select * from user where 1=1 and (id=1 or id=2 or 
				id=3) -->
			<foreach collection="ids" item="id" open="and (" close=")"
				separator="or">
				id=#{id}
			</foreach>
		</where>
	</select>

测试

	//根据id集合查询user表数据
	@Test
	public void testSelectUserByListId(){
	    UserVo uv = new UserVo();
	    List<Integer> ids = new ArrayList<Integer>();
	    ids.add(1);
	    ids.add(2);
	    ids.add(3);
	    uv.setIds(ids);
	    List<User> listUser = mapper.selectUserByListId(uv);
	    for(User u : listUser){
	        System.out.println(u);
	    }
	    session.close();
	}

  • 我们用 foreach 来改写 select * from user where id in (1,2,3)
	<select id="selectUserByListId" parameterType="User" resultType="User">
        select * from user
        <where>
            <!--
                collection:指定输入对象中的集合属性
                item:每次遍历生成的对象
                open:开始遍历时的拼接字符串
                close:结束时拼接的字符串
                separator:遍历对象之间需要拼接的字符串
                select * from user where 1=1 and id in (1,2,3)
              -->
            <foreach collection="ids" item="id" open="and id in (" close=") " separator=",">
                #{id}
            </foreach>
        </where>
    </select>

  • 单参数List的类型
	<select id="dynamicForeachTest" resultType="User">
		select * from user where id in
		<foreach collection="list" index="index" item="item" open="("
			separator="," close=")">
			#{item}
		</foreach>
	</select>

测试

 public List<User> dynamicForeachTest(List<Integer> ids);

	@Test
	public void testSelectUserByListId1(){
	    List<Integer> ids = new ArrayList<Integer>();
	    ids.add(1);
	    ids.add(2);
	    ids.add(3);
	    List<User> listUser = mapper.dynamicForeachTest(ids);
	    for(User u : listUser){
	        System.out.println(u);
	    }
	}

  • 单参数array数组的类型
	<select id="dynamicForeach2Test" resultType="User">
		select * from user where id in
		<foreach collection="array" index="index" item="item" open="("
			separator="," close=")">
			#{item}
		</foreach>
	</select>

public List<User> dynamicForeach2Test(Integer[] ids);
	@Test
	public void dynamicForeach2Test(){
	    Integer[] ids={1,2,3};
		List<User> listUser = mapper.dynamicForeach2Test(ids);
	    for(User u : listUser){
	        System.out.println(u);
	    }
	}

  • 将参数封装成Map的类型
<select id="dynamicForeach3Test" resultType="User">
		select * from user where username like "%"#{username}"%" and id in
		<foreach collection="ids" index="index" item="item" open="("
			separator="," close=")">
			#{item}
		</foreach>
	</select>

public List<User> dynamicForeach3Test(Map params);
	@Test
	public void dynamicForeach3Test() {
		List ids = new ArrayList();
		ids.add(28);
		ids.add(29);
		ids.add(30);
		
		Map params = new HashMap();
		params.put("ids", ids);
		params.put("username", "张");
		
		List<User> listUser = mapper.dynamicForeach3Test(params);
		for (User u : listUser) {
			System.out.println(u);
		}
	}