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:

在这里插入图片描述

在执行插入 语句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 一对一关联

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

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

type:为返回的类型

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

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

该位置的teacher_id

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

2.2.4测试结果

方法一:

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

在这里插入图片描述

方法二:

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

在这里插入图片描述

2.3 一对多关联

2.3.1需求

在这里插入图片描述

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

2.3.2 配置bean
存有集合的一方,里面的集合也可以是Set
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 多对多关联

存有集合的一方,里面的集合也可以是Set

2.4.1 需求

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

在这里插入图片描述

在这里插入图片描述

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的结果集如下:

在这里插入图片描述

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

三 动态sql

3.1 动态SQL简介

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

在这里插入图片描述

     通常使用动态 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);
		}
	}

四 Mybatis性能优化

4.1什么是延迟加载(按需加载)

resultMap中的**association(has a)**和**collection(has some**)标签具有延迟加载的功能。

延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

  • 设置延迟加载

    Mybatis默认是没开启延迟加载功能的,我们需要手动开启。

    需要在SqlMapConfig.xml文件中,在标签中开启延迟加载功能。

    lazyLoadingEnabled、aggressiveLazyLoading

在这里插入图片描述

在最新官方MyBatis文档里,有上面这2个属性,一个是延迟加载,一个是分层加载。

lazyLoadingEnabled 默认值为false,那么在有级联关系的resultMap里,查询后会加载出所有的级联关系,当然有时候我们并不需要这些所有的时候,我们就可以应用到延迟加载给我们带来的好处了。

aggressiveLazyLoading默认值是true,这里我称之为分层加载,大概意思是如果它为true,那么当我使用了延迟加载,要么所有级联都不加载,要么如果我加载一个,其他都得加载。**

aggressiveLazyLoading值是false 那么就是按需加载,如果是true表示只要使用一个级联对象,就全部加载!

  • 全局配置

在这里插入图片描述

fetchType是可以注明在association 和 collection里的,选值为eager和lazy,就是为了方便我们结合aggressiveLazyLoading(false)来配合使用的,让延迟加载发挥到极致,即只加载我需要的!

  • 局部配置

在这里插入图片描述

4.2延迟加载

在这里插入图片描述

  • 以下是resultMap对应的POJO

    Student:

    public class Student {
    
    	private Integer id;
    	private String studentName;
    	private String studentAge;
    	private List studentHealthCards;
    	private ParentOfStudent parentOfStudent;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public String getStudentName() {
    		return studentName;
    	}
    
    	public void setStudentName(String studentName) {
    		this.studentName = studentName;
    	}
    
    	public String getStudentAge() {
    		return studentAge;
    	}
    
    	public void setStudentAge(String studentAge) {
    		this.studentAge = studentAge;
    	}
    
    	public List getStudentHealthCards() {
    		return studentHealthCards;
    	}
    
    	public void setStudentHealthCards(List studentHealthCards) {
    		this.studentHealthCards = studentHealthCards;
    	}
    
    	public ParentOfStudent getParentOfStudent() {
    		return parentOfStudent;
    	}
    
    	public void setParentOfStudent(ParentOfStudent parentOfStudent) {
    		this.parentOfStudent = parentOfStudent;
    	}
    
    	@Override
    	public String toString() {
    		return "Student [id=" + id + ", studentName=" + studentName + ", studentAge=" + studentAge + "]";
    	}
    
    }
    
    

    StudentHealthCard:

    public class StudentHealthCard {
    
    	private Integer id;
    	private Integer stu_id;
    	private String name;
    	private String message;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public Integer getStu_id() {
    		return stu_id;
    	}
    
    	public void setStu_id(Integer stu_id) {
    		this.stu_id = stu_id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getMessage() {
    		return message;
    	}
    
    	public void setMessage(String message) {
    		this.message = message;
    	}
    
    	@Override
    	public String toString() {
    		return "StudentHealthCard [id=" + id + ", stu_id=" + stu_id + ", name=" + name + ", message=" + message + "]";
    	}
    }
    
    

    ParentOfStudent:

    public class ParentOfStudent {
    
    	private Integer id;
    	private Integer stu_id;
    	private String name;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public Integer getStu_id() {
    		return stu_id;
    	}
    
    	public void setStu_id(Integer stu_id) {
    		this.stu_id = stu_id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		return "ParentOfStudent [id=" + id + ", stu_id=" + stu_id + ", name=" + name + "]";
    	}
    }
    
    
    • Mapper文件
      <?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.mybatis.mapper.StudentMapper">
      
      	<resultMap type="Student" id="stu">
      		<id property="id" column="id" />
      		<result property="studentName" column="studentName" />
      		<result property="studentAge" column="studentAge" />
      		<association property="parentOfStudent" column="id"
      			select="selectOneParentOfStudent" fetchType="eager">
      		</association>
      		<collection property="studentHealthCards" column="id"
      			ofType="Model.StudentHealthCard" 
      			select="selectOneStudentHealthCard" fetchType="eager">
      		</collection>
      	</resultMap>
      	
      	<select id="selectOneStudent" resultMap="stu"> 
      	    select * from student
      		where id = #{id}		  
      	</select>
      	
      	<select id="selectOneParentOfStudent" resultType="ParentOfStudent"> 
      	    select * from
      		parentofstudent where stu_id = #{id}		  
      	</select>
      	
      	<select id="selectOneStudentHealthCard" resultType="StudentHealthCard"> 
      	    select *
      		from studenthealthcard where stu_id = #{id}		  
      	</select>
      	
      </mapper>
        
        
      
      
    
    • 测试
    情况1:开启延迟加载,默认分层加载,不开启局部加载

    执行语句 Student student = sm.selectOneStudent(1);

    以下是运行结果:

在这里插入图片描述

	执行语句:Student student = sm.selectOneStudent(1);

	student.getParentOfStudent();

在这里插入图片描述

		这就是默认分层加载的后果,好的那么现在我把分层加载设置为false

即情况2:开启延迟加载,分层加载false,不适用局部加载
	执行语句 Student student = sm.selectOneStudent(1);

	以下是运行结果:

在这里插入图片描述

	执行语句:Student student = sm.selectOneStudent(1);

	student.getParentOfStudent();
	好了 3条sql变成了2条


在这里插入图片描述

情况3:就是使用fetchType的情况下,可以指明即使在延迟加载情况下也可以立即加载某个级联关系!

4.3 MyBatis缓存

4.3.1MyBatis缓存分析

mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。

在这里插入图片描述

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

Mybatis的缓存,包括一级缓存和二级缓存

一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。

二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是二级缓存区域。二级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。二级缓存中的value,就是查询出的结果对象。
一级缓存是默认使用的。
二级缓存需要手动开启。

Map<String,Object> key 缓存标志 Value 缓存的数据

4.3.2 一级缓存

在这里插入图片描述

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。

 如果sqlSession去执行**commit****操作(执行插入、更新、删除)**,清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。         

 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

 Mybatis默认支持一级缓存。

- 测试1

java
  	@Test
  	public void test1(){
  		Student s1 = mapper.selectOneStudent(1);
  		Student s2 = mapper.selectOneStudent(1);
  		System.out.println(s1==s2);
  	}


- 测试2

java
  	@Test
  	public void test1(){
  		Student s1 = mapper.selectOneStudent(1);
  		
  		//session.commit();
  		//session.clearCache();
  		
  		Student s2 = mapper.selectOneStudent(1);
  		System.out.println(s1==s2);
  	}
  
  • 应用

    正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
    一个service方法中包括很多mapper方法调用。
    service{
    //开始执行时,开启事务,创建SqlSession对象

      //第一次调用mapper的方法findUserById(1)
      //第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    
      //方法结束,sqlSession关闭
    

    }
    如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。

4.3.3 二级缓存

下图是多个sqlSession请求UserMapper的二级缓存图解
在这里插入图片描述
二级缓存是mapper级别的。

第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。

第二次调用相同namespace下的mapper映射文件(xml)中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。

如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。

  • 开启二级缓存

Mybatis默认是没有开启二级缓存

 1.在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):

在settings标签中添加以下内容:

在这里插入图片描述

2.在StudentMapper映射文件中,加入以下内容,开启二级缓存:

在这里插入图片描述

3.实现序列化

由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。缓存默认是存入内存中,但是如果需要把缓存对象存入硬盘那么久需要序列化(实体类要实现)

在这里插入图片描述
如果该类存在父类,那么父类也要实现序列化。

  • 测试1
  	@Test
  	public void test2(){
  		SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
  		SqlSession session1 = factory.openSession();
  		StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
  		Student s1 = mapper1.selectOneStudent(1);
  		System.out.println(s1);
  		session1.close();
  		
  		SqlSession session2 = factory.openSession();
  		StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
  		Student s2 = mapper2.selectOneStudent(1);
  		System.out.println(s2);
  	}
  
  • 测试2
  	@Test
  	public void test2(){
  		SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
  		
  		SqlSession session1 = factory.openSession();
  		StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
  		Student s1 = mapper1.selectOneStudent(1);
  		System.out.println(s1);
  		session1.close();
  		
  		SqlSession session2 = factory.openSession();
  		StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
  		s1.setStudentName("王二小");
  		mapper2.updateStudent(s1);
  		session2.commit();
  		session2.close();
  		
  		SqlSession session3= factory.openSession();
  		StudentMapper mapper3 = session3.getMapper(StudentMapper.class);
  		Student s2 = mapper3.selectOneStudent(1);
  		System.out.println(s2);
  	}
  

根据SQL分析,确实是清空了二级缓存了。

  • 禁用二级缓存

    该statement中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。

在这里插入图片描述

  • 刷新二级缓存

    该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。

    如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。

    如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DragonnAi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值