学习地址🔗
一、简介
💬概述: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 {
void addEmp(Employee newEmployee);
void updateEmpInfo(Employee updatedEmp);
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>
<delete id="deleteEmpById" parameterType="int">
delete
from `employee`
where emp_id = #{empId};
</delete>
</mapper>
-
测试类 public class EmployeeTest {
@Test
public void testInsert() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee newEmp = new Employee(null, "周星驰",
"男", "zhouxingchi@qq.com");
mapper.addEmp(newEmp);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testUpdate() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee updatedEmp = new Employee(2, "周润发",
"男", "zhourunfa@gmail.com");
mapper.updateEmpInfo(updatedEmp);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDelete() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
mapper.deleteEmpById(4);
sqlSession.commit();
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);
<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个形参) -
测试
<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("自定义参数名")注解,属性值就是为对应参数自定义的名称(一般命名为与形参名一样) -
测试
Employee getEmpByIdAndName(@Param("empId") Integer empId, String empName);
<select id="getEmpByIdAndName" resultType="com.key.mybatis.entity.Employee">
select *
from `employee`
where `emp_id` = #{empId} and
`emp_name` = #{param2};
</select>
🔑传入单个特殊类型形参
🔑传入形参中既有普通类型又有对象类型:在持久层接口方法中可以添加不同类型的形参,当添加一个普通类型形参和一个对象类型形参,在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);
<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();
<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);
<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("empId")
Map<Integer, Employee> getAllEesReturnMap();
<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 = '部门表';
alter table `employee`
add column `dept_id` int unsigned not null comment '员工所在部门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;
}
-
在Employee类中添加对象类型属性(级联属性)——Department myDept(注意添加的不是部门的id,是部门对象) public class Employee {
private Integer empId;
private String empName;
private String gender;
private String email;
private Department myDept;
}
🔑设置级联查询的结果集映射规则:在级联查询中,需要使用<resultMap>标签设置级联查询结果集的映射规则(封装规则),保证级联查询的结果,在子标签<result>中通过【对象.属性】的形式可以对级联属性进行赋值,此时【对象.属性】中的【对象】为JavaBean(员工类)中的对象类型属性名(部门属性)
<resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee">
<id column="emp_id" property="empId"/>
<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` =
-
员工持久层接口中的查询方法 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"/>
<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 = MyBatisUtil.getSqlSession();
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"/>
<association property="myDept" javaType="com.key.mybatis.entity.Department">
<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——分步查询
🔑使用3.0——延迟加载
-
?什么是延迟加载:延迟加载也叫懒加载、按需加载,即在分步查询的基础上,按需要获取另一个结果集对象
💡 还是以上面为例,如果开启了延迟加载,则当我们只获取员工对象中的基本信息(姓名、性别等),不获取其部门信息时,MyBatis就只会加载第一条SQL语句(在控制台只有一条SQL语句被打印),只有当我们获取部门信息时,MyBatis才会加载第二条SQL语句,获取出对应的部门信息
-
开启延迟加载:在MyBatis全局配置文件中的<setting>标签中设置下列两个参数
| 参数 | 设置参数值 | 解释 |
|---|
| lazyLoadingEnabled | true | 开启全局的延迟加载,默认是参数值是false | | aggressiveLazyLoading | false | 侵入延迟加载,默认参数值是true |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
-
测试1.0——只获取员工的姓名
-
测试2.0——直接打印员工对象emp,获取员工全部信息
-
测试3.0——获取员工信息及其部门信息
4.3.4 collection标签
💬概述:<resultMap>另一个实现嵌套结果集的子标签——<collection>,用法与<association>和<resultMap>类似
🔑作用:设置结果集与集合类型属性之间的映射规则,集合中的元素类型(即泛型)是被关联的JavaBean类型
🔑主要属性
① property:属性值为<resultMap>中设置的JavaBean(部门类)中集合类型属性名,必须设置 ② ofType:属性值为集合类型属性中,集合元素类型(员工类)的全类名,即被关联JavaBean全类名,必须设置
🔑<collection>的子标签:<collection>中的子标签中也有<id>和<result>,用于设置结果集与集合元素类型(员工类)的属性之间的映射规则,用法与<association>和<resultMap>一样
🔑使用1.0——简单的级联查询
🔑使用2.0——分步查询:<collection>实现分步查询与<association>类似
-
实现分步查询的重要属性
| 属性名 | 属性值 | 解释 | 是否必须添加 |
|---|
| property | <resultMap>中设置的JavaBean(部门类)中的集合类型的属性名(员工集合) | 与级联查询的使用方式一样,都是用来标识级联属性 | 是 | | select | 被关联JavaBean的持久层接口的方法,需要在方法前写上方法所在类的全类名 | 标识被关联JavaBean的方法,通过该方法获取的集合(员工集合)赋值给property所标识的级联属性(集合类型属性) | 是 | | column | 结果集中某一列的列名 | 标识查询出来的结果集中某一列的列名,select中标识的方法会根据该结果集列的值,来查询对应的集合,最后再赋值给property的级联属性 | 是 |
-
测试
-
员工持久层接口方法
List<Employee> getEesByDeptId(Integer deptId);
-
员工映射文件
<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"/>
<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 = MyBatisUtil.getSqlSession();
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.close();
}
-
打印结果
🔑使用3.0——延迟加载:同上👆
4.3.5 association标签🆚collection标签
🔑共同点
- 都是
<resultMap>的子标签 - 都用于在嵌套结果集查询中设置级联属性的映射规则
- 都能实现分步查询
- 延迟加载对它们的分步查询影响一样
🔑区别
<association>标签用于设置对象类型的级联属性,而<collection>标签用于设置集合类型的级联属性<association>中使用javaType标识对象属性的类型,而<collection>中使用ofType标识集合元素的类型
|