MyBatis学习 | SQL映射文件
文章目录
- 一、简介
- 二、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——
Department
public 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
标识集合元素的类型