MyBatis进阶
一. 动态sql语句
2.1、静态sql的缺陷
-- 如果传入的Student中age没有赋值,那么会将原纪录中的age更新为NULL
UPDATE student set name=#{name},age=#{age} WHERE id=#{id}
-- 如果传入的Student中name没有赋值,查询条件会变为id=xx AND name=NULL
SELECT * FROM student where id=#{id} AND name=#{name}
https://mybatis.org/mybatis-3/zh/index.html 这里可自主学习
2.2、动态sql常用标签
- if
-- 原语句
SELECT * FROM student where id=#{id} AND name=#{name}
-- if改造
SELECT * FROM student
<where>
<if test="id != null">
AND id=#{id}
</if>
<if test="name != null">
AND name=#{name}
</if>
</where>
当查询条件id和username都存在时,控制台打印的sql语句如下:
… … …
//获得MyBatis框架生成的StudentMapper接口的实现类
StudentMapper mapper = sqlSession.getMapper( StudentMapper.class);
Student condition = new Student();
condition.setId(1);
condition.setUsername("lucy");
Student student = mapper.findByCondition(condition);
… … …
当查询条件只有id存在时,控制台打印的sql语句如下:
… … …
//获得MyBatis框架生成的UserMapper接口的实现类
StudentMapper mapper = sqlSession.getMapper( StudentMapper.class);
Student condition = new Student();
condition.setId(1);
Student student = mapper.findByCondition(condition);
… … …
注意1:if判断表达式内的变量为实体对象的成员变量,而非数据库字段
注意2:条件中SQL语句需要以and开头,第一个可以省略
- foreach
-- 原语句
SELECT * FROM student where id in (1,2,3)
-- foreach改造 (list代表集合,array代表数组)
SELECT * FROM student
<where>
<foreach collection="list" open="id in (" close=")" item="id" seperator=",">
#{id}
</foreach>
</where>
属性
collection为要被遍历的参数名。
数组默认值为array;集合默认为list。
也可以通过@Param("参数名")在接口形参位置指定参数名,statement中取值的时候就必须使用指定的参数名
open 遍历开始的时候拼接的字符串
close 遍历结束的时候拼接的字符串
item 相当于本次遍历的元素命名为id,所以下面去的时候写成#{id}
separator 拼接时元素间使用的分割符
- choose
-
2.3 SQL片段抽取
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的
注意:改动的时候一定要小心,因为会影响结果的封装。
- sql
-- 对重复sql进行提取,使用 <include refid=""> 引用
-- 原语句
SELECT * FROM student
<!--抽取sql片段简化编写-->
<sql id="selectStudent" select * from student</sql>
<select id="findById" parameterType="int" resultType="student">
<include refid="selectStudent"></include> where id=#{id}
</select>
<select id="findByIds" parameterType="list" resultType="student">
<include refid="selectStudent"></include>
<where>
<foreach collection="array" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
我们可以将一些重复性的 SQL 语句进行抽取,以达到复用的效果。
- <sql>:抽取 SQL 语句标签。
- <include>:引入 SQL 片段标签。
<sql id=“片段唯一标识”>抽取的 SQL 语句</sql> <include refid=“片段唯一标识”/>
2.5 知识小结
MyBatis映射文件配置:
<select>:查询
<insert>:插入
<update>:修改
<delete>:删除
<where>:where条件
<if>:if判断
<foreach>:循环
<sql>:sql片段抽取
二.分页插件
2.1 分页插件介绍
- 分页可以将很多条结果进行分页显示。
- 如果当前在第一页,则没有上一页。如果当前在最后一页,则没有下一页。
- 需要明确当前是第几页,这一页中显示多少条结果。
- MyBatis分页插件总结
- 在企业级开发中,分页也是一种常见的技术。而目前使用的 MyBatis 是不带分页功能的,如果想实现分页的 功能,需要我们手动编写 LIMIT 语句。但是不同的数据库实现分页的 SQL 语句也是不同的,所以手写分页 成本较高。这个时候就可以借助分页插件来帮助我们实现分页功能。
- PageHelper:第三方分页助手。将复杂的分页操作进行封装,从而让分页功能变得非常简单。
2.2 分页插件的使用
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
开发步骤:
①导入与PageHelper的jar包
②在mybatis核心配置文件中配置PageHelper插件
<!-- 注意:分页助手的插件 配置在通用mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
③测试分页数据获取
@Test
public void testPageHelper(){
//设置分页参数
PageHelper.startPage(1,2);
List<User> select = userMapper2.select(null);
for(User user : select){
System.out.println(user);
}
}
3.3 分页插件的参数获取
获得分页相关的其他参数:
//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
3.4 分页插件知识小结
分页:可以将很多条结果进行分页显示。
-
分页插件 jar 包: pagehelper-5.1.10.jar jsqlparser-3.1.jar
-
:集成插件标签。
-
分页助手相关 API
1.PageHelper:分页助手功能类。
- startPage():设置分页参数
- PageInfo:分页相关参数功能类。
- getTotal():获取总条数
- getPages():获取总页数
- getPageNum():获取当前页
- getPageSize():获取每页显示条数
- getPrePage():获取上一页
- getNextPage():获取下一页
- isIsFirstPage():获取是否是第一页
- isIsLastPage():获取是否是最后一页
三.缓存小知识
3.1概念
对于不经常发生变化的不是很重要的数据,我们可以从数据库查询出来之后存入内存;
之后使用时直接从内存中获取,而不需要再次查询数据库。
是一种一空间换时间的做法,提高整体响应速度和体验。
Mybatis缓存分为两类:一级缓存和二级缓存
一级缓存就是SQLSession级别(连接)的缓存,默认开启的。
二级缓存就是SQLSessionFactory级别(连接池)的缓存,需要手动开启。
一级缓存
Mybatis一级缓存默认开始,多次相同查询就不会响应数据库查询,而是直接从缓存中获取。
不会走一级缓存的情况
* 2.1 同一个方法,传递不同参数的时候
* 2.2 同一个方法传递相同参数,但是不在同一个连接(会话)的时候
* 2.3 同一个表中记录如有修改,该表所有的缓存都会被清除
* 2.4 在调用SQLSession对象的clearCache()方法后,该连接所有缓存会被清除
工作中用法
Redis - 支持分布式的缓存
二级缓存
二级缓存是SQLSessionFactory级别的缓存
缓存的作用范围越大,越要小心的使用。确保数据真的不会被修改,才能存入二级缓存。
所有需要手动开启。
1.修改配置文件mybatis-config.xml加入<setting name="cacheEnabled"value=“true”/>,全局配置参数,需要时再设置
cacheEnabled 介绍
描述 : cacheEnabled
允许值: 对在此配置文件下的所有cache 进行全局性开/关设置。
默认值 (true/false): true
2.在mapper.xml中开启二缓存,mapper.xml下的sql执行完成会存储到它的缓存区,如:
开启缓存后,第一次查询会执行sql,第二次及以后的查询都会从缓存中读取数据
四.MyBatis的多表操作(重点)
4.1 多表模型介绍
我们之前学习的都是基于单表操作的,而实际开发中,随着业务难度的加深,肯定需要多表操作的。
- 多表模型分类 一对一:在任意一方建立外键,关联对方的主键。
- 一对多:在多的一方建立外键,关联一的一方的主键。
- 多对多:借助中间表,中间表至少两个字段,分别关联两张表的主键。
4.2 多表模型一对一操作
-
一对一模型: 人和身份证,一个人只有一个身份证
-
代码实现
- 步骤一: sql语句准备
CREATE TABLE person(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT
);
INSERT INTO person VALUES (NULL,'张三',23);
INSERT INTO person VALUES (NULL,'李四',24);
INSERT INTO person VALUES (NULL,'王五',25);
CREATE TABLE card(
id INT PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(30),
pid INT,
CONSTRAINT cp_fk FOREIGN KEY (pid) REFERENCES person(id)
);
INSERT INTO card VALUES (NULL,'12345',1);
INSERT INTO card VALUES (NULL,'23456',2);
INSERT INTO card VALUES (NULL,'34567',3);
步骤二:配置文件
<?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.itheima.table01.OneToOneMapper">
<!--配置字段和实体对象属性的映射关系-->
<resultMap id="oneToOne" type="card">
<id column="cid" property="id" />
<result column="number" property="number" />
<!--
association:配置被包含对象的映射关系
property:被包含对象的变量名
javaType:被包含对象的数据类型
-->
<association property="p" javaType="person">
<id column="pid" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
</association>
</resultMap>
<select id="selectAll" resultMap="oneToOne">
SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id
</select>
</mapper>
步骤三:测试类
@Test
public void selectAll() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取OneToOneMapper接口的实现类对象
OneToOneMapper mapper = sqlSession.getMapper(OneToOneMapper.class);
//5.调用实现类的方法,接收结果
List<Card> list = mapper.selectAll();
//6.处理结果
for (Card c : list) {
System.out.println(c);
}
//7.释放资源
sqlSession.close();
is.close();
}
4.3 多表模型一对多操作
-
一对多模型: 一对多模型:班级和学生,一个班级可以有多个学生。
-
代码实现
-
步骤一: sql语句准备
CREATE TABLE classes( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20) ); INSERT INTO classes VALUES (NULL,'黑马一班'); INSERT INTO classes VALUES (NULL,'黑马二班'); CREATE TABLE student( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(30), age INT, cid INT, CONSTRAINT cs_fk FOREIGN KEY (cid) REFERENCES classes(id) ); INSERT INTO student VALUES (NULL,'张三',23,1); INSERT INTO student VALUES (NULL,'李四',24,1); INSERT INTO student VALUES (NULL,'王五',25,2); INSERT INTO student VALUES (NULL,'赵六',26,2);
-
-
步骤二:配置文件
<mapper namespace="com.itheima.table02.OneToManyMapper"> <resultMap id="oneToMany" type="classes"> <id column="cid" property="id"/> <result column="cname" property="name"/> <!-- collection:配置被包含的集合对象映射关系 property:被包含对象的变量名 ofType:被包含对象的实际数据类型 --> <collection property="students" ofType="student"> <id column="sid" property="id"/> <result column="sname" property="name"/> <result column="sage" property="age"/> </collection> </resultMap> <select id="selectAll" resultMap="oneToMany"> SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid </select> </mapper>
-
步骤三:测试类
@Test public void selectAll() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(true); //4.获取OneToManyMapper接口的实现类对象 OneToManyMapper mapper = sqlSession.getMapper(OneToManyMapper.class); //5.调用实现类的方法,接收结果 List<Classes> classes = mapper.selectAll(); //6.处理结果 for (Classes cls : classes) { System.out.println(cls.getId() + "," + cls.getName()); List<Student> students = cls.getStudents(); for (Student student : students) { System.out.println("\t" + student); } } //7.释放资源 sqlSession.close(); is.close(); }
3.一对多配置文件总结:
<resultMap>:配置字段和对象属性的映射关系标签。
id 属性:唯一标识
type 属性:实体对象类型
<id>:配置主键映射关系标签。
<result>:配置非主键映射关系标签。
column 属性:表中字段名称
property 属性: 实体对象变量名称
<collection>:配置被包含集合对象的映射关系标签。
property 属性:被包含集合对象的变量名
ofType 属性:集合中保存的对象数据类型
4.4 多表模型多对多操作
-
多对多模型:学生和课程,一个学生可以选择多门课程、一个课程也可以被多个学生所选择。
-
代码实现
-
步骤一: sql语句准备
CREATE TABLE course( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20) ); INSERT INTO course VALUES (NULL,'语文'); INSERT INTO course VALUES (NULL,'数学'); CREATE TABLE stu_cr( id INT PRIMARY KEY AUTO_INCREMENT, sid INT, cid INT, CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id), CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id) ); INSERT INTO stu_cr VALUES (NULL,1,1); INSERT INTO stu_cr VALUES (NULL,1,2); INSERT INTO stu_cr VALUES (NULL,2,1); INSERT INTO stu_cr VALUES (NULL,2,2);
-
-
步骤二:配置文件
<?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.itheima.table03.ManyToManyMapper"> <resultMap id="manyToMany" type="student"> <id column="sid" property="id"/> <result column="sname" property="name"/> <result column="sage" property="age"/> <collection property="courses" ofType="course"> <id column="cid" property="id"/> <result column="cname" property="name"/> </collection> </resultMap> <select id="selectAll" resultMap="manyToMany"> SELECT sc.sid,s.name sname,s.age sage,sc.cid,c.name cname FROM student s,course c,stu_cr sc WHERE sc.sid=s.id AND sc.cid=c.id </select> </mapper>
-
步骤三:测试类
@Test public void selectAll() throws Exception{ //1.加载核心配置文件 InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml"); //2.获取SqlSession工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //3.通过工厂对象获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(true); //4.获取ManyToManyMapper接口的实现类对象 ManyToManyMapper mapper = sqlSession.getMapper(ManyToManyMapper.class); //5.调用实现类的方法,接收结果 List<Student> students = mapper.selectAll(); //6.处理结果 for (Student student : students) { System.out.println(student.getId() + "," + student.getName() + "," + student.getAge()); List<Course> courses = student.getCourses(); for (Course cours : courses) { System.out.println("\t" + cours); } } //7.释放资源 sqlSession.close(); is.close(); }
3.多对多配置文件总结:
<resultMap>:配置字段和对象属性的映射关系标签。
id 属性:唯一标识
type 属性:实体对象类型
<id>:配置主键映射关系标签。
<result>:配置非主键映射关系标签。
column 属性:表中字段名称
property 属性: 实体对象变量名称
<collection>:配置被包含集合对象的映射关系标签。
property 属性:被包含集合对象的变量名
ofType 属性:集合中保存的对象数据类型
4.5多表操作总结
数据库中表关系及建表原则
一对一
1. 主键对应(两个表的主键保持一致)
2. 外键唯一(在任意一张表中添加一个外键,指向另一个表的主键,保证外键唯一)
一对多/多对一
在多的一方(学生)维护一个一的一方(班级)的主键作为外键
多对多
创建一个中间表,中间表中至少有两个外键字段,分别指向另外两张表的主键
Java实体中如何维护关系
通过属性引用确定两个实体之间的关系
人和身份证
在身份证对象中保存一个人类型的对象引用,以维护关系
在身份证列中定义一个人类型的成员变量
反之亦可
班级和学生
在一的一方(班级)中维护一个多(学员)的一方的集合引用
学生和课程
从学生角度来说:一个学生可以选多个课程,在学生类中定义一个课程的集合
从课程角度来说:一个课程可以被多个同学选,在课程类中定义一个学生的集合
将多对多拆分成多个一对多
思路:简化关系模型,方便处理
从任意一个角度拆分即可。拆出来多个一对多。
Mybatis中如何处理上述关系
处理结果集字段名和实体类中属性的映射关系
方式1:使用resultMap被引用对象的属性可以通过,对象名.属性来设置值
方式2:使用resultMap + association在resultMap中只负责映射外层对象的成员
association负责内层(被引用)对象成员的赋值
方式3:使用resultMap + Collection解决一对多关系的方式 结果集字段和属性名一致,会自动映射匹配,不需要手动匹配
一对一
从数据库查询出数据后封装到POJO实体对象中的方式:
方式一:写一个新POJO,包含结果集中所有字段
方式二:使用resultMap,配合user.usernname、user.id完成封装
方式三:使用resultMap,配合<association property javatype>
一对多
使用resultMap,配合<colleciont property oftype>
多对多
相当于多个一对多
多表查询SQL查询方式
建议使用左外连接查询,避免部分数据查询不到
查询出来的结果封装方式
student表中的字段名name与Student类中的username属性不对应
mybatis自动封装的原理是结果集字段和类中属性名字一样即可完成封装
解决方案
1、修改结果集中name为username,让mybatis完成自动封装。
2、使用resultMap,手动指定字段name和属性username映射关系
注意
POJO实体中的其他POJO的属性也可以使用resultMap的方式解决
resultMap并不是为了多表查询而产生的,但是可以解决多表查询中的数据封装问题
五.总结
动态sql
多表操作
数据库中三种关系
一对一:在任意一张表中添加一个外键,指向另一个表的主键
一对多/多对一:在多的一方维护一个一的一方的主键作为外键
多对多:创建中间表,至少包含两个外键字段,分别指向两张表的主键
Java实体中
通过属性引用确定两个实体之间的关系
一对一:任意一个对象保存对方类型的引用
一对多/多对多(本质多个一对多):在一的一方维护多的一方的集合引用
Mybatis中如何处理
处理结果集字段名和实体类中属性的映射关系
Mybatis中如何处理上述关系
处理结果集字段名和实体类中属性的映射关系
方式1:使用resultMap
被引用对象的属性可以通过,对象名.属性来设置值
方式2:使用resultMap + association
在resultMap中只负责映射外层对象的成员
association负责内层(被引用)对象成员的赋值
方式3:使用resultMap + Collection
解决一对多关系的方式
结果集字段和属性名一致,会自动映射匹配,不需要手动匹配
建议使用左外连接查询,避免部分数据查询不到