Mybatis搞四下(动态SQL,一对多多对多,延迟加载)
上一篇主要讲了Mybatis的各种强大的参数,标签等,本篇不再赘述标签,主要讲讲动态SQL和复杂的一对多,多对一,多对多查询,还有一个优化性能的延迟加载问题。
动态SQL
动态sql其实就是sql的内容是变化的,根据条件获得不同的sql语句。
(其实主要是where部分变化)
if
choose(when、otherwise)
trim(where、set)
foreach
搭建一个简单的例子,后面的动态SQL实操基于本表
blog.sql
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Blog.java实体类
public class Blog {
private int id;
private String title;
private String author;
private Date createTime;
private int views;
//getter,setter,等略
}
<if>标签
这里的标签主要是判断条件
语法上
<if test = "Java对象属性值的判断条件">
<!--sql语句-->
</if>
if标签实操
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<!--满足if条件才会把里面的内容拼接到sql-->
<if test="author != null">
and author = #{author}
</if>
</select>
注:
这里为什么要加1=1?因为如果第一个if不满足单第二个满足,那sql语句就变成了
select * from mybatis.blog where and author = #{author}
显然会报错,所以一定要加上where 1=1
<choose>、<when>、</otherwise>标签
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是举例来理解
<select id="queryBlogChoose" resultType="blog" parameterType="map">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = 6666
</otherwise>
</choose>
</where>
</select>
这里可以看到when里有两个参数,title和author,如果传了title就按title来,如果传了author就按author来,如果两者都不传,就按照otherwise里咱指定的返回
<where>标签
<where>标签主要是用来包含多个<if>标签的,当多个if成立,<where>会自动增加一个where关键字并且去掉多余if中and、or等
where实操
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<!-- <include refid="if-title-author"></include>-->
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
如果传两个参数(title和author),则sql语句是这样
如果只传一个参数(author),则
<set>标签
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
上代码!
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
可以看到这里我是想更新指定id的title和author吧
title和author都指定的情况:
title和author只指定一个呢?
咋样?不会这还看不懂吧
<trim>标签
这个标签主要是在你觉得where不好用的时候,通过自定义trim来定制where 的功能,同样,set也可以用它来指定。也可以单独拿出来用,可以自定义字符串截取规则,举几个简单的例子
<trim prefix="1=1" suffix="" suffixOverrides="AND | OR" prefixOverrides=""></trim>
prefix:在trim标签内sql语句加上前缀。
suffix:在trim标签内sql语句加上后缀。
prefixOverrides:指定去除多余的前缀内容
suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。
<foreach>标签
foreach:循环Java中的数组,list集合的,主要用在sql语句的in语句中
foreach实操
<!--
select * from mybatis.blog where 1=1 and (id=1 or id = 2 or id=3)
-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
可以看到这个foreach标签里面元素很多,这里把测试类代码块一并附上,一起讲解
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
foreach参数
- collection:表示接口中方法参数的类型,数组就是Array,集合就是list
- item:自定义,表示数组或者集合的成员变量
- open:循环开始时的字符
- close:循环结束时的字符
- separator:分隔符
SQL片段拼接–<include>
有时候我们可能会碰到sql重复的地方,复制太多也没意思,这个时候是不是考虑一下把代码块提出来复用?确实有这个功能(拿上面where的例子直接改)
步骤:先使用<sql id=“自定义唯一名称”>sql语句</sql>
在使用<include refid=“自定义唯一名称”/>调用
1、抽取SQL中公共部分
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
2、在需要调用的地方使用<include>标签
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
注意事项:
- 最好基于单表来定义SQL片段!
- 不要存在where标签
一对多、多对一、多对多、一对一索性全讲讲看吧
一对一查询
我们现在手上有两张表是一对一的关系,并且我们想同时查出来,一般我们会怎么做?把两张表的属性都放在一个类里?也不是不可以,但是那样的代码冗余度就上去了,不太好。现在举个例子,如果我们有一个员工表和一个部门表,一个员工对应一个部门。显然要把department包含在employee里,来做做看。
SQL
CREATE TABLE `employee` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`lastName` varchar(50) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`gender` int(10) DEFAULT NULL,
`birth` datetime DEFAULT NULL,
`department` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `did` (`department`),
CONSTRAINT `did` FOREIGN KEY (`department`) REFERENCES `department` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=utf8
CREATE TABLE `department` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=106 DEFAULT CHARSET=utf8
先在Department实体类里整活
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
没有lombok的乖乖alt+insert哦
Employee里
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private Integer departmentId;
private Department departmentinfo;
}
在这里面最后一行直接把department信息导入。
接口里来一行就行
接下来比较复杂的东西来了,在EmployeeMapper里
<select id="listEmployeeAndDept" resultMap="EmployeeAndDeptMap">
select e.id,e.lastName,d.id,d.departmentName from employee e inner join department d on e.department = d.id
</select>
<resultMap id="EmployeeAndDeptMap" type="Employee">
<result column="id" property="id"/>
<result column="lastName" property="lastName"/>
<association property="departmentinfo" javaType="Department">
<result column="id" property="id"/>
<result column="departmentName" property="departmentName"/>
</association>
</resultMap>
写一个测试类执行以后得到这个
这里一对一就拿下
多对一查询
多对多我们用老师和学生的关系,其实跟一对一有类似之处,大家细看
SQL
CREATE TABLE `student` (
`id` int(10) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`tid` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `teacher` (
`id` int(10) NOT NULL,
`name` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
数据自己插一下哈
实体类Stu、Ter
@Data
public class Student {
private int id;
private String name;
//学生关联一个老师
private Teacher teacher;
}
@Data
public class Teacher {
private int id;
private String name;
}
StudentMapper接口
public interface StudentMapper {
//查询所有学生的信息,根据查询出来的学生tid,寻找出对应的老师
public List<Student> getStudent();
public List<Student> getStudent2();
}
Mapper主要配Student,Teacher不用搞实质内容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.feng.dao.StudentMapper">
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher" >
<result property="name" column="tname"/>
<result property="id" column="tid"/>
</association>
</resultMap>
<select id="getStudent" resultMap="StudentTeacher">
select * from student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="is"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
</mapper>
按照查询嵌套处理
<!--
思路:
1. 查询所有的学生信息
2. 根据查询出来的学生的tid,寻找对应的老师! 子查询
-->
<select id="getStudent" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂的属性,我们需要单独处理 对象: association 集合: collection -->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
按照结果嵌套处理
<!--按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
一对多查询
一对多就是多对一反过来,就是咱老师对学生呗~
还用上面的例子
实体类
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
按照结果嵌套处理
<!--按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname, t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--复杂的属性,我们需要单独处理 对象: association 集合: collection
javaType="" 指定属性的类型!
集合中的泛型信息,我们使用ofType获取
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
按照查询嵌套处理
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from mybatis.student where tid = #{tid}
</select>
多对多查询
多对多其实和一对多差不多的原理,都是利用collection标签,就是在collection标签里面再嵌套collection标签就可以实现多对多的查询,在这里就不在举例了。
多多一一小结
- 关联 - association 【多对一】
- 集合 - collection 【一对多】
- javaType & ofType
- JavaType 用来指定实体类中属性的类型
- ofType 用来指定映射到List或者集合中的 pojo类型,泛型中的约束类型!
注意点:
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中,属性名和字段的问题!
- 如果问题不好排查错误,可以使用日志 , 建议使用 Log4j
延迟加载
这里上面多对一里面的Collection里,我们外部查询触发了内部查询(使用association也有这个问题),在性能要求高的情况下显然不行,非常浪费资源,MyBatis官方也不建议我们使用这种方式。
我们拿刚才的连表查询中,来测试懒加载
select * from student
select * from teacher where id = #{id}
未使用懒加载时,
使用延迟加载(懒加载)
这个功能默认关闭的,首先我们要在mybatis-config.xml里打开懒加载的开关~
<setting name="lazyLoadingEnabled" value="true"/>
然后在association或者collection标签中加入fetchType
此时我们再打断点一步一步看,
再进入下一层循环
其实到这一步,不难发现懒加载的功能了
懒加载小结
懒加载其实就是按需加载,当我们需要什么信息的时候再去查,而不是一次性把所有的东西全查了,将复杂的关联查询分解成单表查询,然后通过单表查询的结果去关联查询