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,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。