文章目录
- 一、简介
- 二、insert、update和delete标签
- 2.1 关于增删改
- 2.2 获取自增主键的值
- 三、参数处理
- 3.1 获取不同形式的参数
- 3.1.1 获取单个参数
- 3.1.2 获取多个参数
- 3.2 #{Key}
- 3.2.1 #{}🆚${}
- 3.2.2 #{}中设置参数规则
- 四、select标签
- 4.1 select标签的主要属性
- 4.2 关于返回值类型
- 4.2.1 返回值类型为List集合
- 4.2.2 返回值类型为Map集合
- 4.3 嵌套结果集查询
- 4.3.1 resultMap属性和resultMap标签
- 4.3.2 级联查询
- 4.3.3 association标签
- 4.3.4 collection标签
- 4.3.5 association标签🆚collection标签
学习地址🔗
- https://www.bilibili.com/video/BV1mW411M737
- https://www.bilibili.com/video/BV1NE411Q7Nx
- 官网文档
一、简介
💬概述:SQL映射文件是MyBatis的核心文件,它指导MyBatis对数据库进行CRUD操作,有着重要意义
🔑SQL映射文件中的重要标签
| 标签名 | 解释 |
|---|---|
| cache | 命名空间的二级缓存配置 |
| cache-ref | 其他命名空间缓存配置的引用 |
| resultMap | 自定义结果集映射 |
| sql | 抽取可重用的语句块 |
| insert | 映射插入(INSERT)语句 |
| update | 映射更新(UPDATE)语句 |
| delete | 映射删除(DELETE)语句 |
| select | 映射查询(SELECT)语句 |
💡 还有一个parameterMap标签,但已经废弃
🔑根标签<mapper>
- 概述:SQL映射文件的根标签是
<mapper>——映射,表示映射文件中每一条SQL语句都需要与对应的持久层接口方法建立映射关系,不然没有意义 - 唯一属性——
namespace:名称空间,<mapper>的唯一属性,也是必须要设置的属性,因为它是SQL映射文件的唯一标识,属性值为对应的持久层接口的全类名,将映射文件与持久层接口建立映射关系
二、insert、update和delete标签
2.1 关于增删改
💬概述:在SQl映射文件中使用<insert>、<update>和<delete>三个SQL语句标签可以实现增删改操作
🔑增删改标签的主要属性
| 属性名 | 解释 |
|---|---|
| id | 标签的唯一标识,对应持久层接口的方法名,必须添加 |
| parameterType | 参数类型,对应该SQL语句标签对应的持久层接口方法中的形参类型,只适用于单个形参,可以不添加 |
| resultType | 返回值类型,对应该SQL语句标签对应的持久层接口方法中的形参类型,可以不添加 |
🔑实现增删改操作的注意事项
- 在持久层接口中创建对应增删改方法时,方法返回值类型可以是void、boolean、int、long以及它们对应的包装类
- 实现增删改操作时,需要修改数据库表数据,因此需要对当前事务进行提交,而MyBatis中通过
SqlSessionFactory.openSession()获取sqlSession对象时,默认不会自动提交,实现提交有两种方式
① 设置自动提交:在openSession()方法中,带上true参数,即openSession(true),此时获取的sqlSession对象在操作完增删改后就回自动提交事务,不用手动提交
② 手动提交:使用openSession()方法直接获取sqlSession对象,在实现增删改操作之后,再通过sqlSession.commit()方法手动提交事务
🔑测试
-
持久层接口方法
public interface EmployeeDao { /** * 添加一条员工记录 * @param newEmployee 新的员工对象 */ void addEmp(Employee newEmployee); /** * 更新员工信息 * @param updatedEmp 被更新的员工对象 */ void updateEmpInfo(Employee updatedEmp); /** * 根据id删除员工信息 * @param empId 员工id */ void deleteEmpById(Integer empId); } -
对应映射文件中的SQL语句
<?xml version="1.0" encoding="utf8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.key.mybatis.dao.EmployeeDao"> <!-- 添加一条员工记录 --> <insert id="addEmp" parameterType="com.key.mybatis.entity.Employee"> insert into `employee`(`emp_name`, `gender`, `email`, `dept_id`) values(#{empName}, #{gender}, #{email}, 1); </insert> <!-- 更新员工信息 --> <update id="updateEmpInfo" parameterType="com.key.mybatis.entity.Employee"> update `employee` set `emp_name` = #{empName}, `gender` = #{gender}, `email` = #{email} where emp_id = #{empId}; </update> <!-- 根据id删除员工信息 --> <delete id="deleteEmpById" parameterType="int"> delete from `employee` where emp_id = #{empId}; </delete> </mapper> -
测试类
public class EmployeeTest { /** * 测试插入 */ @Test public void testInsert() { // 获取SqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 根据sqlSession对象获取mapper对象 EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); // 创建一个新的Employee对象 Employee newEmp = new Employee(null, "周星驰", "男", "zhouxingchi@qq.com"); // 调用接口方法,传入新的员工对象 mapper.addEmp(newEmp); // 提交事务,一定要提交事务,不然插入操作失败 sqlSession.commit(); // 最后关闭sqlSession对象 sqlSession.close(); } /** * 测试更新 */ @Test public void testUpdate() { // 获取SqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 根据sqlSession对象获取mapper对象 EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); // 创建需要更新的员工对象 Employee updatedEmp = new Employee(2, "周润发", "男", "zhourunfa@gmail.com"); // 调用接口方法,传入更新的员工对象 mapper.updateEmpInfo(updatedEmp); // 提交事务 sqlSession.commit(); // 关闭sqlSession sqlSession.close(); } /** * 测试删除 */ @Test public void testDelete() { // 获取SqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 根据sqlSession对象获取mapper对象 EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); // 调用接口方法 mapper.deleteEmpById(4); // 提交事务 sqlSession.commit(); // 关闭sqlSession sqlSession.close(); } }
2.2 获取自增主键的值
💬概述:在实现添加操作时,如果数据库表中的主键字段是自增的,则在创建对应的JavaBean时,会将主键成员变量设置null后再插入到数据表中,但插入后JavaBean中的主键成员变量还是null,此时就需要在<insert>标签中设置相关属性获取自增主键的值
🔑关于获取自增主键值的几个相关属性
| 属性名 | 属性值 | 解释 |
|---|---|---|
| useGeneratedKeys | true/false,默认是false | 设置使用主键策略,即获取自增主键值;设置为true表示使用主键策略,必须添加 |
| keyProperty | JavaBean中自增主键对应的成员变量名 | 将获取的自增主键值赋值给JavaBean中对应的成员变量,必须添加 |
| keyColumn | 数据库表中对应的自增主键字段名 | 标识出数据库表中对应的自增主键字段,可以不添加 |
💡 MyBatis底层中获取自增主键的值使用的也是原生JDBC中
statement对象,即通过statement.getGeneratedKeys()方法获取
🔑获取自增主键值方式:直接在<insert>标签中添加useGeneratedKeys="true"和keyProperty="empId"两个属性即可
<!-- 添加一条员工记录 -->
<insert id="addEmp" parameterType="com.key.mybatis.entity.Employee"
useGeneratedKeys="true" keyProperty="empId" keyColumn="emp_id">
insert into `employee`(`emp_name`, `gender`, `email`, `dept_id`)
values(#{empName}, #{gender}, #{email}, 1);
</insert>
三、参数处理
3.1 获取不同形式的参数
💡 在持久层接口的方法中可以设置多个多种不同类型的形参,因此在SQL映射文件中获取不同参数时有不同的处理方式
3.1.1 获取单个参数
🔑传入的形参是普通类型:对于单个普通类型的形参,如String name,MyBatis不会对其做特殊处理,因此在映射文件中通过#{Key}获取对应参数值时,#{Key}中的键Key可以是任意字符串,不一定与方法形参名一致
🔑测试
// 持久层接口方法
Employee getEmpById(Integer empId);
<!--
* 对应映射文件中的SQL标签
- #{empId}用于获取形参的参数值时
- #{empId}中的键Key可以任意,#{id}、#{aaa}都可
-->
<select id="getEmpById" resultType="com.key.mybatis.entity.Employee" parameterType="int">
select *
from `employee`
where emp_id = #{empId};
</select>
3.1.2 获取多个参数
🔑传入的形参都是普通类型:持久层接口中传入多个普通类型的形参,MyBatis会将所有形参封装成一个Map集合,Map集合中的键Key就是#{Key}中的Key,值Value就是参数值,因此通过#{Key}获取每一个参数值时,#{Key}中的键Key就不能是任意的,键Key有下列两种情况
-
没有对每一个形参进行命名
-
键的选取:只能使用MyBatis为每一个形参设置的默认键
-
默认键:[arg0…argN-1] 或 [param1…paramN](N表示有N个形参)
-
测试
<!-- * 没有对形参命名时,通过默认键获取参数 - 获取第一个参数:#{arg0}或#{param1} - 获取第二个参数:#{arg1}或#{param2} --> <select id="getEmpByIdAndName" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `emp_id` = #{arg0} and `emp_name` = #{param2}; </select>
-
-
对每一个形参进行命名⭐
-
键的选取:可以使用默认的[param1…paramN](不能使用[arg0…argN-1]),也可以使用自定义的参数名
-
对形参进行命名的方式:在持久层接口对应方法中的每一个形参上添加
@Param("自定义参数名")注解,属性值就是为对应参数自定义的名称(一般命名为与形参名一样) -
测试
// 根据id和姓名获取员工信息,并为第一个形参进行命名 Employee getEmpByIdAndName(@Param("empId") Integer empId, String empName);<!-- * 对形参命名时,可以通过[paramN]获取参数,也可以通过自定义参数名获取 - 获取第一个参数:#{param1}或#{empId} - 获取第二个参数:#{arg1}或#{param2} --> <select id="getEmpByIdAndName" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `emp_id` = #{empId} and `emp_name` = #{param2}; </select>
-
🔑传入单个特殊类型形参
-
将需要获取的参数封装后传入:在持久层接口方法中可以将多个需要获取的参数封装后再传入(封装后就变为单个特殊类型的形参)
-
一般封装后的形参类型下列三种情况
形参类型 使用方式 获取参数值时的键Key 适用场景 POJO(普通JavaBean) 直接在持久层接口对应方法上传入JavaBean类型的形参 参数对应的JavaBean属性名 获取的多个参数正好与JavaBean中成员变量一一对应 Map<String,Object>类型 持久层接口方法形参设置为 Map<String,Object>类型,传入实参时,需要先创建一个Map<String,Object>集合,在集合中添加参数和参数值(键值对形式),然后传入方法中自定义 Map集合中的键Key多个参数与JavaBean不是一一对应,且不经常使用 DTO(数据传输对象) 自定义一个DTO类型的对象,作为持久层接口方法的形参类型 参数对应的DTO对象的属性名 多个参数与JavaBean不是一一对应,且经常使用 ❓ 关于DTO:Data Transfer Object,数据传输对象,也是一个POJO,与数据库表中的每一个字段不是一一对应关系,一般会有增加或删减
❓ 关于对象类型的形参
- 不对形参进行命名:如果对于单个POJO或DTO形参,不对其自定义参数名时,键Key直接就是对象的属性名,不能使用【对象.属性名】的形式
- 对形参进行命名:如果对POJO或DTO形参进行命名,则键Key就不能是简单的对象属性名,需要使用【对象.属性名】的形式,而【对象.属性名】中的【对象】可以是【paramN】,也可以是自定义的形参名
-
测试
① 测试POJO
// 持久层接口方法,不对形参进行命名 Employee getEmpByEmpPo(Employee empPo);<!-- 将多个参数封装成POJO后传入,获取参数时通过POJO的属性名作为键Key获取即可 --> <select id="getEmpByEmpPo" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `emp_id` = #{empId} and `emp_name` = #{empName} and `gender` = #{gender} and `email` = #{email}; </select>② 测试Map<String,Object>集合
// 根据Map集合获取参数 Employee getEmpByMap(Map<String, Object> map);<!-- 传入形参是Map集合类型,则获取参数的键Key就是Map集合的键 --> <select id="getEmpByMap" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `emp_id` = #{id} and `emp_name` = #{name}; </select>
-
-
传入形参是集合或数组类型:持久层接口方法中可以传入单个集合或数组类型的形参,即
Collection类型或Array类型,包括List和Set,MyBatis也会将集合类型或数组的形参封装成一个Map集合-
Map集合的键Key选值如下形参类型 获取参数值的键Key Collection(包括List、Set) collection[i]( Collection类型小写)List collection[i]或list[i]( List类型小写)Array(数组) array[i]( Array类型小写)💡 集合或数组类型获取参数时,需要根据参数在集合中的索引值
i来获取,不能只写collection -
测试
// 传入形参是一个id集合 Employee getEmpByIdList(List<Integer> idList);<!-- 传入形参是List集合类型,获取的键Key为collection[i]或list[i] --> <select id="getEmpByIdList" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `emp_id` = #{list[0]}; </select>💡 如果集合中元素类型为对象类型,则获取集合中某个元素的属性时,可以通过【对象.属性】形式获取,只是这里的【对象】需要先根据集合索引来获取,如获取第二个元素(对象)的
name属性值——list[1].name
-
🔑传入形参中既有普通类型又有对象类型:在持久层接口方法中可以添加不同类型的形参,当添加一个普通类型形参和一个对象类型形参,在SQL标签中要获取的参数需要从对象类型的形参中获取,此时MyBatis同样会将两个形参封装成一个Map集合
-
集合的键Key有下列情况
- 对于普通类型的形参:普通类型的形参适用于MyBatis设置的默认的键Key——[arg0…argN-1] 或 [param1…paramN];如果不使用默认的键Key,同样可以对每一个普通类型形参进行命名,此时键Key既可以时默认的(不能使用[arg0…argN-1]),也可以是自定的参数名
- 对于对象类型的形参:需要获取的参数得从对象形参的属性中获取,因此键Key必须是【对象.属性名】的形式,而【对象.属性名】中的对象相当于一个普通类型的形参,既可以是默认的[param1…paramN](不能使用[arg0…argN-1]),也可以使用自己命名的参数名(命名方式同普通类形参)
-
测试
// 传入的形参既有普通类型,又有对象类型,没有对形参进行命名 Employee getEmpByNameAndUserId(String empName, User user);<!-- * 传入形参既有普通类型,又有对象类型,获取两种形式的参数时键Key有所不同(没有命名形参的情况下) - 获取普通类型的键Key:#{arg0}或#{param1} - 获取对象类型封装的参数时的键Key:#{arg1.userid}或#{param2.userid} --> <select id="getEmpByNameAndUserId" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `emp_name` = #{param1} and `emp_id` = #{arg1.userid}; </select>
3.2 #{Key}
3.2.1 #{}🆚${}
🔑共同点
- 都可以获取由持久层接口方法传过来的参数值
- 获取参数值的方式一样,都是在
{}中添加参数值对应的键Key即可
🔑区别
| 获取参数两种方式 | 在SQL语句中设置参数值的方式 | 适用场景 | 适用场景例子 |
|---|---|---|---|
| #{Key} | #{Key}通过预编译的方式将参数值设置到SQL语句中,而SQL字符串中对应参数值的位置用占位符?来拼接,而不是直接将参数值拼接到SQL中,相当于JDBC中的PreparedStatement对象,可以防止SQL注入 | 原生JDBC支持使用占位符?的地方,一般都使用#{Key} | 在WHERE语句中获取参数值 |
| ${Key} | ${Key}是直接将获取的参数值拼接到SQL语句中,不进行预编译,因此会有SQL注入的安全问题 | 不能使用#{Key}获取参数的情况,即JDBC不支持占位符地方就可以使用${Key}获取参数值 | ①获取数据库表名;②获取排序规则的ASE和DESE; |
3.2.2 #{}中设置参数规则
💬概述:#{}中除了可以添加键Key来获取对应参数值之外,还可以设置获取参数时的其他规则
🔑可以设置的主要规则
- javaType:设置JavaBean的属性类型
- jdbcType:设置数据库表中字段的类型
- mode:设置存储过程
- resultMap:设置结果集
- jdbcName:设置数据库表字段名
- typeHandler:设置类型处理器
四、select标签
4.1 select标签的主要属性
| 属性名 | 解释 | 是否必须设置 |
|---|---|---|
| id | <select>标签的唯一标识,对应持久层接口方法名,与方法进行绑定 | 是 |
| resultType | 设置返回值类型 | 是 |
| parameterType | 设置参数类型(仅适用单个形参) | 否 |
| resultMap | 设置结果集中数据库表的字段名和JavaBea中属性名之间的映射规则,属性值对应<resultMap>的唯一标识id,不能与resultType一起使用 | 否 |
4.2 关于返回值类型
4.2.1 返回值类型为List集合
💬概述:在持久层接口方法中,可以设置返回值类型为List<T>,此时<select>标签中的resultType的属性值为集合中泛型T对应的全类名,而不是集合List的全类名
🔑测试
// 持久层接口方法
List<Employee> getAllEmployees();
<!-- 返回值类型设置为List中的泛型 -->
<select id="getAllEmployees" resultType="com.key.mybatis.entity.Employee">
select *
from `employee`;
</select>
4.2.2 返回值类型为Map集合
💬概述:持久层接口方法的返回值类型可以设置为Map<K,V>,此时<select>标签中的resultType的属性值就为Map类型的全类名,但MyBatis已经对常用的Java类型设置过别名,因此属性名直接写类型小写map即可
🔑对于查询一条与多条记录,返回Map集合时的封装规则不同
-
查询返回一条记录:返回一条记录时,相当于返回一个结果集对应的JavaBean,此时返回值
Map<K,V>中的键值对分别对应JavaBean的属性名和属性值,一般泛型设置为Map<String,Object>// 持久层接口方法 Map<String, Object> getEmpReturnMap(Integer empId);<!-- 返回值类型设置为map --> <select id="getEmpReturnMap" resultType="map"> select * from `employee` where `emp_id` = #{empId}; </select> -
查询返回多条记录:返回多条记录时,相当于返回多个JavaBean,此时需要将每一个JavaBean作为
Map集合的值Value来返回,而键Key需要自定义为JavaBean的某一个属性名(如Map<String, Employee>),将该属性与JavaBean一一对应起来-
自定义键Key的方式:在持久层接口对应的方法上添加
@MapKey("JavaBean中某一个属性名")注解,注解中添加的属性值就是自定义的JavaBean的某一个属性名 -
测试
// 持久层接口方法,添加@MapKey注解,设置键Key为员工id @MapKey("empId") Map<Integer, Employee> getAllEesReturnMap();<!-- 返回值类型设置为map --> <select id="getAllEesReturnMap" resultType="map"> select * from `employee`; </select>
-
4.3 嵌套结果集查询
4.3.1 resultMap属性和resultMap标签
🔑resultMap属性
- 作用:
<select>标签的属性,用来标识结果集所使用的映射规则,而具体的映射规则需要使用<resultMap>标签来设置 - 属性值:
resultMap的属性值对应<resultMap>标签的唯一标识id,表示将<resultMap>标签中设置的映射规则应用到<select>标签的结果集中
🔑<resultMap>标签
-
作用:设置查询结果集与JavaBean的映射规则
❓ 关于结果集的映射规则
- 使用
<resultMap>设置的映射规则是作用于查询出来的结果集(一张不存在的表)与某一个JavaBean之间,不是将数据库中某张表与某一个JavaBean建立映射关系 - 查询出来的结果集与该JavaBean可以是没有关系的,也可以是相对应的,如在不取别名的情况下查询某一个员工的信息,则查询结果集中每一列与员工表每一列都相等,因此员工表对应的JavaBean就与结果集相对应
❓ 解决表字段名与JavaBean字段名不一致的方法
① 在SQL语句中为每一个字段设置别名,别名与JavaBean的属性名保持一致(比较麻烦)
② 在全局配置文件中通过<setting>标签开启驼峰命名映射(只能将带下划线的_表字段名转成对应的使用驼峰命名JavaBean属性名)
③ 使用<resultMap>标签自定义映射规则,适用于任何情况 - 使用
-
<resultMap>标签的两个属性① id:标签的唯一标识,属性值对应
<select>标签的resultMap属性值
② type:标识映射规则中的JavaBean,属性值为JavaBean的全类名 -
主要的子标签
标签名 作用 主要属性 是否必须添加 <id>设置主键列名对应JavaBean属性的映射规则 ①column:对应结果集中的主键列名; ②property:对应JavaBean中的属性名 否 <result>设置普通列名与对应JavaBean属性的映射规则 同上 否 ❓ 关于两个子标签
<id>和<result>的作用其实是一样的,只是使用<id>标签时,MyBatis底层会对相应的列做特殊处理- 两个子标签都不是必须要添加的,但不添加时就不能保证结果集与JavaBean的映射关系
-
测试
<!-- 设置映射规则 --> <resultMap id="myEmp" type="com.key.mybatis.entity.Employee"> <!-- 设置主键列名的映射规则 --> <id column="emp_id" property="empId"/> <!-- 设置其他列名映射规则 --> <result column="emp_name" property="empName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> </resultMap> <!-- 使用自定义的映射规则处理结果集 --> <select id="getEmpById" resultMap="myEmp"> select * from `employee` where `emp_id` = #{emp}; </select>
4.3.2 级联查询
🔑创建既有级联关系的表及其对应的JavaBean
-
创建部门表department,员工表中添加部门id字段,作为外键约束
/*创建部门表*/ drop table if exists `department`; create table `department` ( `dept_id` int unsigned not null unique auto_increment comment '部门id - 主键', `dept_name` varchar(20) not null comment '部门名字', primary key(`dept_id`) ) engine = innodb default character = utf8 comment = '部门表'; /*员工表中添加部门id*/ alter table `employee` add column `dept_id` int unsigned not null comment '员工所在部门id - 外键'; /*部门id设置为外键约束*/ alter table `employee` add constraint `fk_emp_dept` foreign key(`dept_id`) references `department`(`dept_id`); -
创建部门表对应JavaBean——
Departmentpublic class Department { private Integer deptId; private String deptName; // code... } -
在
Employee类中添加对象类型属性(级联属性)——Department myDept(注意添加的不是部门的id,是部门对象)public class Employee { private Integer empId; private String empName; private String gender; private String email; // myDept为级联属性 private Department myDept; // code... }
🔑设置级联查询的结果集映射规则:在级联查询中,需要使用<resultMap>标签设置级联查询结果集的映射规则(封装规则),保证级联查询的结果,在子标签<result>中通过【对象.属性】的形式可以对级联属性进行赋值,此时【对象.属性】中的【对象】为JavaBean(员工类)中的对象类型属性名(部门属性)
<!-- 设置映射规则 -->
<resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee">
<!-- 设置主键映射规则 -->
<id column="emp_id" property="empId"/>
<!-- 设置其他属性的映射规则(这里省略) -->
<!-- 设置级联属性的映射规则1.0
- myDept是Employee类中的属性名
- deptId和deptName都是Department类中的属性名
-->
<result column="dept_id" property="myDept.deptId"/>
<result column="dept_name" property="myDept.deptName"/>
</resultMap>
❓ 为什么不使用
resultType:级联查询时,在<select>中只能使用resultMap属性设置结果集,不能简单使用resultType设置返回值类型,因为resultType属性只能设置查询结果集的类型,不能设置结果集中的对象类型属性,因此调用接口方法返回的对象中,其对象类型的属性值肯定是null
🔑测试:根据员工id查询某一位员工的全部信息及其所在部门的id和部门名(连接查询)
-
SQL语句
select e.`emp_id`, e.`emp_name`, e.`gender`, e.`email`, e.`dept_id`, d.`dept_name` from `employee` e, `department` d where e.`dept_id` = d.`dept_id` and e.`emp_id` = #{empId}; -
员工持久层接口中的查询方法
Employee getEmpAndDeptById(Integer empId); -
员工映射文件
<!-- 设置映射规则 --> <resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee"> <!-- 设置主键映射规则 --> <id column="emp_id" property="empId"/> <!-- 设置其他属性的映射规则 --> <result column="emp_name" property="empName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <!-- 设置级联属性的映射规则1.0 --> <result column="dept_id" property="myDept.deptId"/> <result column="dept_name" property="myDept.deptName"/> </resultMap> <!-- 级联查询 --> <select id="getEmpAndDeptById" resultMap="cascadeQuery"> select e.`emp_id`, e.`emp_name`, e.`gender`, e.`email`, e.`dept_id`, d.`dept_name` from `employee` e, `department` d where e.`dept_id` = d.`dept_id` and e.`emp_id` = #{empId}; </select> -
测试方法
@Test public void testCascadeQuery() { // 获取SqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 获取mapper对象 EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); // 调用方法 Employee emp = mapper.getEmpAndDeptById(2); System.out.println("员工对象 --> " + emp); System.out.println("员工部门 --> " + emp.getMyDept()); // 关闭 sqlSession.close(); } -
打印结果

4.3.3 association标签
💬概述:<resultMap>还有一个重要的子标签——<association>,它的使用与<resultMap>类似
🔑作用:可以设置结果集与被关联的JavaBean(部门类)的映射规则
🔑主要属性
① property:属性值为<resultMap>中设置的JavaBean(员工类)中的对象类型的属性名(部门属性),必须设置
② javaType:属性值为对象类型属性的类型全类名(部门类),即被关联的JavaBean全类名,必须设置
🔑<association>的子标签:<association>中的子标签中也有<id>和<result>,用于设置结果集中的列名与被关联JavaBean的属性名的映射规则,用法与<resultMap>的一样
🔑使用1.0——简单的级联查询:在<resultMap>标签中添加<association>,然后添加以上两个属性和子标签
<!-- 设置映射规则 -->
<resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee">
<!-- 设置主键映射规则 -->
<id column="emp_id" property="empId"/>
<!-- 设置其他属性的映射规则(这里省略) -->
<!-- 设置级联属性的映射规则2.0
- myDept是员工类中的属性名
- javaType属性值为被关联的JavaBean(部门类)的全类名
-->
<association property="myDept" javaType="com.key.mybatis.entity.Department">
<!-- 设置被关联的JavaBean的映射规则 -->
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
❓ 关于主键映射规则设置
- 使用
<association>设置级联属性映射规则时,也能通过<id>标签设置主键,而外部的父标签<resultMap>中也有一个<id>设置主键,两个<id>中的column属性值不能一样- 如果员工表和部门表的主键字段名一样,都是id,则在查询时需要通过取别名的方式区分两个主键,保证两个
<id>标签中的column属性值不一样
🔑使用2.0——分步查询
-
❓什么是分步查询
- 将级联查询分步进行,以员工部门为例,分步查询就是先根据员工emp_id在员工表中查询出对应的员工信息,查询结果中会有一列是员工所在部门的dept_id,根据这个查询出来的dept_id,再去部门表查询对应的部门信息,此时就能将emp_id所对应的员工信息以及员工所在部门信息都查询出来
- 使用
<association>实现分步查询时也是遵循以上原理,先根据传入的empId查出对应的员工对象,再根据查询的结果中的deptId查询对应的部门对象,因此会执行两个持久层接口方法(两个SQL标签/两条SQL语句)
-
在
<association>标签中关于分步查询的三个重要属性属性名 属性值 解释 是否必须添加 property <resultMap>中设置的JavaBean(员工类)中的对象类型的属性名(部门属性)与级联查询的使用方式一样,都是用来标识级联属性 是 select 被关联JavaBean的持久层接口的方法,需要在方法前写上方法所在类的全类名 标识被关联JavaBean的方法,通过该方法获取的对象(部门对象)赋值给 property所标识的级联属性是 column 结果集中某一列的列名 标识查询出来的结果集中某一列的列名, select中标识的方法会根据该结果集列的值,来查询对应的被关联对象,最后再赋值给property的级联属性是 -
测试
-
创建被关联JavaBean的持久层接口及其映射文件,并添加相应方法
// 部门持久层接口 public interface DepartmentDao { /** * 根据id查询部门信息 */ Department getDeptById(Integer deptId); }<?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.key.mybatis.dao.DepartmentDao"> <!-- 根据id查询部门id --> <select id="getDeptById" resultType="com.key.mybatis.entity.Department"> select * from `department` where `dept_id` = #{deptId}; </select> </mapper> -
员工持久层接口中对应方法
Employee getEmpAndDeptByIdStep(Integer empId); -
员工映射文件中
<!-- 分步查询的映射规则 --> <resultMap id="stepQuery" type="com.key.mybatis.entity.Employee"> <!-- 主键 --> <id column="emp_id" property="empId"/> <!-- 其他属性 --> <result column="emp_name" property="empName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <!-- association实现分步查询 - property属性值(myDept):员工类中的部门属性 - select属性值:部门持久层接口全类名.对应方法 - column属性值(dept_id):查询出来的结果集中部门id的列名 --> <association property="myDept" select="com.key.mybatis.dao.DepartmentDao.getDeptById" column="dept_id"/> </resultMap> <!-- 根据id查询员工信息 --> <select id="getEmpAndDeptByIdStep" resultMap="stepQuery"> select * from `employee` where `emp_id` = #{empId}; </select> -
测试方法
@Test public void testStepQuery() { // 获取SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 获取mapper EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); // 调用分步查询的方法 Employee emp = mapper.getEmpAndDeptByIdStep(3); System.out.println("员工对象 --> " + emp); System.out.println("员工部门 --> " + emp.getMyDept()); // 关闭 sqlSession.close(); } -
打印结果

-
🔑使用3.0——延迟加载
-
❓什么是延迟加载:延迟加载也叫懒加载、按需加载,即在分步查询的基础上,按需要获取另一个结果集对象
💡 还是以上面为例,如果开启了延迟加载,则当我们只获取员工对象中的基本信息(姓名、性别等),不获取其部门信息时,MyBatis就只会加载第一条SQL语句(在控制台只有一条SQL语句被打印),只有当我们获取部门信息时,MyBatis才会加载第二条SQL语句,获取出对应的部门信息
-
开启延迟加载:在MyBatis全局配置文件中的
<setting>标签中设置下列两个参数参数 设置参数值 解释 lazyLoadingEnabled true 开启全局的延迟加载,默认是参数值是false aggressiveLazyLoading false 侵入延迟加载,默认参数值是true <!-- 设置MyBatis运行时的参数 --> <settings> <!-- 开启延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings> -
测试1.0——只获取员工的姓名
-
测试方法
// 调用分步查询的方法 Employee emp = mapper.getEmpAndDeptByIdStep(3); System.out.println("员工姓名 --> " + emp.getEmpName()); -
打印结果

-
-
测试2.0——直接打印员工对象emp,获取员工全部信息
-
测试方法
// 调用分步查询的方法 Employee emp = mapper.getEmpAndDeptByIdStep(3); System.out.println("员工全部信息 --> " + emp); -
打印结果

-
-
测试3.0——获取员工信息及其部门信息
-
测试方法
// 调用分步查询的方法 Employee emp = mapper.getEmpAndDeptByIdStep(3); System.out.println("员工全部信息 --> " + emp); System.out.println("员工部门 --> " + emp.getMyDept()); -
打印结果

-
4.3.4 collection标签
💬概述:<resultMap>另一个实现嵌套结果集的子标签——<collection>,用法与<association>和<resultMap>类似
🔑作用:设置结果集与集合类型属性之间的映射规则,集合中的元素类型(即泛型)是被关联的JavaBean类型
🔑主要属性
① property:属性值为<resultMap>中设置的JavaBean(部门类)中集合类型属性名,必须设置
② ofType:属性值为集合类型属性中,集合元素类型(员工类)的全类名,即被关联JavaBean全类名,必须设置
🔑<collection>的子标签:<collection>中的子标签中也有<id>和<result>,用于设置结果集与集合元素类型(员工类)的属性之间的映射规则,用法与<association>和<resultMap>一样
🔑使用1.0——简单的级联查询
-
在部门类
Department中添加一个集合类型属性,集合的泛型设置为员工类Employee,表示一个部门有多个员工 -
测试:根据部门id查询出部门的信息及其该部门下所有员工信息(左连接查询)
-
SQL语句
select * from `department` d left join `employee` e on d.`dept_id` = e.`dept_id` where d.`dept_id` = #{deptId}; -
部门持久层接口中对应方法
// 根据id查询部门信息及其部门下所有员工信息 Department getDeptAndEmpListById(Integer deptId); -
部门映射文件
<!-- 设置结果集映射规则 --> <resultMap id="collQuery" type="com.key.mybatis.entity.Department"> <!-- 设置主键 --> <id column="dept_id" property="deptId"/> <!-- 设置其他属性 --> <result column="dept_name" property="deptName"/> <!-- 设置集合类型属性(级联属性) --> <collection property="empList" ofType="com.key.mybatis.entity.Employee"> <!-- 主键 --> <id column="emp_id" property="empId"/> <!-- 其他属性 --> <result column="emp_name" property="empName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> </collection> </resultMap> -
测试方法
@Test public void testCollQuery() { // 获取SQlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 获取mapper DepartmentDao mapper = sqlSession.getMapper(DepartmentDao.class); // 调用部门持久层接口方法 Department dept = mapper.getDeptAndEmpListById(3); System.out.println("部门信息 --> " + dept); System.out.println("部门下的所有员工信息"); dept.getEmpList().forEach(System.out :: println); // 关闭sqlSession sqlSession.close(); } -
打印结果

-
🔑使用2.0——分步查询:<collection>实现分步查询与<association>类似
-
实现分步查询的重要属性
属性名 属性值 解释 是否必须添加 property <resultMap>中设置的JavaBean(部门类)中的集合类型的属性名(员工集合)与级联查询的使用方式一样,都是用来标识级联属性 是 select 被关联JavaBean的持久层接口的方法,需要在方法前写上方法所在类的全类名 标识被关联JavaBean的方法,通过该方法获取的集合(员工集合)赋值给 property所标识的级联属性(集合类型属性)是 column 结果集中某一列的列名 标识查询出来的结果集中某一列的列名, select中标识的方法会根据该结果集列的值,来查询对应的集合,最后再赋值给property的级联属性是 -
测试
-
员工持久层接口方法
// 根据部门id查询该部门下所有员工信息 List<Employee> getEesByDeptId(Integer deptId); -
员工映射文件
<!-- 根据部门id查询该部门下所有员工 --> <select id="getEesByDeptId" resultType="com.key.mybatis.entity.Employee"> select * from `employee` where `dept_id` = #{deptId}; </select> -
部门持久层接口方法
// 分步查询 Department getDeptAndEesByIdStep(Integer deptId); -
部门映射文件
<!-- 设置结果集映射规则 --> <resultMap id="collQueryStep" type="com.key.mybatis.entity.Department"> <!-- 主键 --> <id column="dept_id" property="deptId"/> <!-- 其他属性 --> <result column="dept_name" property="deptName"/> <!-- association实现分步查询 - property属性值(empList):部门类中的员工集合属性 - select属性值:员工持久层接口全类名.对应方法 - column属性值(dept_id):查询出来的结果集中部门id的列名 --> <collection property="empList" select="com.key.mybatis.dao.EmployeeDao.getEesByDeptId" column="dept_id"/> </resultMap> <!-- 分步查询 --> <select id="getDeptAndEesByIdStep" resultMap="collQueryStep"> select * from `department` where `dept_id` = #{deptId}; </select> -
测试方法
@Test public void testCollQueryStep() { // 获取SQlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 获取mapper DepartmentDao mapper = sqlSession.getMapper(DepartmentDao.class); // 调用部门持久层接口方法 Department dept = mapper.getDeptAndEesByIdStep(3); System.out.println("部门信息 --> " + dept); System.out.println("部门下的所有员工信息"); dept.getEmpList().forEach(System.out :: println); // 关闭sqlSession sqlSession.close(); } -
打印结果

-
🔑使用3.0——延迟加载:同上👆
4.3.5 association标签🆚collection标签
🔑共同点
- 都是
<resultMap>的子标签 - 都用于在嵌套结果集查询中设置级联属性的映射规则
- 都能实现分步查询
- 延迟加载对它们的分步查询影响一样
🔑区别
<association>标签用于设置对象类型的级联属性,而<collection>标签用于设置集合类型的级联属性<association>中使用javaType标识对象属性的类型,而<collection>中使用ofType标识集合元素的类型








![[ Linux ] 死锁以及如何避免死锁](https://img-blog.csdnimg.cn/img_convert/8733faf1ff66283a5c5db0ee6097fc95.png)










