MyBatis 的映射文件指导着 MyBatis 如何对数据库进行增删改查,即在 `mapper.xml` 文件中定义具体的 SQL 语句,以实现 DAO 层的接口。
`mapper.xml` 文件的约束头
```xml
```
在 ` ` 标签体中定义
## 增删改
### 编写 DAO 接口
```java
package cool.yzt.mapper;
import cool.yzt.entity.User;
import java.util.List;
public interface UserMapper {
// 添加一条记录
public void save(User user);
// 删除一条记录
public void deleteById(int id);
// 修改一条记录(根据ID 修改密码)
public void changePassword(User user);
}
```
### 编写 XML 映射文件
```xml
insert into User (username,password,birthday) value(#{username},#{password},#{birthday})
delete from user where id=#{id}
update user set password=#{password} where username=#{username}
```
### 使用
```java
// 通过流的方式加载全局配置文件
InputStream is = MyBatisTest.class.getClassLoader().getResourceAsStream("mybatis.xml");
// 获取 SqlSession 的工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 获取 SqlSession
SqlSession session = factory.openSession();
// 获取DAO接口的实现类对象,这个对象是 MyBatis 通过动态代理自动生成的
UserMapper mapper = session.getMapper(UserMapper.class);
// 通过实现类对象调用接口的方法
try {
mapper.deleteById(4);
// 增删改操作必须手动提交事务
session.commit();
} finally {
// 必须正确的关闭 SqlSession
session.close();
}
```
### 注意
1. 增删改操作的方法可以有 `Intger`、`Long`、`Boolean` 类型的返回值,即该操作的影响行数,若返回值大于 `0` 或为 `true`,则表示该操作成功
2. 增删改需要手动提交事务,获取 `SqlSession` 时可以设置为自动提交:`SqlSession session = factory.openSession(true)`
3. 无论操作成功与否都必须正确关闭 `SqlSession`
4. 对于 `insert` 操作,MyBatis 可以获取本次添加记录操作返回的自增主键值,对于 MySQL,支持自动增长主键,可以在映射文件的 标签中进行如下配置,表示获取添加的新记录的自增主键,并赋值给 `id` 属性
```xml
insert into User (username,password,birthday) value(#{username},#{password},#{birthday})
```
则在完成添加操作后,可以通过传入参数的对象,获取该主键的值
```java
mapper.save(user);
session.commit();
System.out.println(user.getId()); // 若不在映射文件中配置,则无法正确获取
```
## 参数处理
此处参数是指一个 `SQL` 语句执行所需要的参数,也是 `DAO` 接口中方法定义的参数
### 单个参数
1. 如果这个参数是基本类型(包括对应的包装类型),MyBatis 不会对其进行特殊处理,直接使用 `#{}` 取出使用即可,`{}` 中可以是任何值
2. 如果这个参数是自定义的对象类型,MyBatis 也不会特殊处理,使用 `#{}` 取出该对象的对应字段的值
### 多个参数
1. 对于多个参数,MyBatis 会将参数封装成一个 map,该 map 的 `key` 默认是 `param1`、`param2`...`paramN`,`value` 就是对应传入参数的值
2. 可以在 DAO 接口定义方法时,传入的参数前加上 `@Param` 注解,自定义该参数封装为 map 后的 `key`,
```java
// MyBatis 会将传入参数封装为一个map,key为 id 和 name
public User findByIdAndName(@Param("id")Integer id,@Param("name")String name);
```
在 SQL 语句中,使用 `#{key}` 取出 `key` 对应的值
### 封装 Map
既然对于多个参数 MyBatis 都会封装为 Map,而使用默认 `key`(`param1`、`param2`)或者使用 `@Param` 注解都非常麻烦,那么建议直接将要传入的参数封装为 Map 后传入,SQL 语句获取值时使用 `#{key}`,例如
DAO 接口中
```java
public User findByIdAndName(Map params);
```
映射文件中
```xml
```
使用
```java
Map params = new HashMap();
params.put("id","4");
params.put("name","李四");
User user = userMapper.findByIdAndName(params);
```
### POJO 和 TO
1. 如果要传入的参数恰好是业务逻辑中实体类的属性(的一部分),建议直接传入 `POJO`,使用 `#{}` 获取 POJO 中的属性值
2. 如果要传入的参数不是实体类中的属性,要么封装 Map,要么编写一个 TO 类(Transfer Object),专门用于参数的传递,例如分页查询中封装的 `pagebean`
### 集合类
对于集合类和数组类型的参数,MyBatis 也会封装成 Map,下表为类型和 `key` 的对应关系
|类型|key|
|---|---|
|Collection|collection|
|List|list|
|数组|array|
### #{} 与 ${} 的区别
1. `#{}`:以预编译的形式将参数设置到 SQL 语句中,使用 `?` 作为占位符,类似 JDBC 中的 `PreparedStatement`
2. `${}`:取出参数后直接以拼字符串的形式拼接在 SQL 语句中,虽然也可以完成与 `#{}` 一样的功能,但是无法防止 SQL 注入,应用场景:如果数据库进行了分表,需要根据参数查询不同的表,则表名处可以使用 `${}` 进行取值,因为表名处不支持使用 `?` 作为占位符,而必须使用拼接字符串的方式
## 查询
### 结果集自动封装
映射文件中使用 `` 标签定义查询语句,其中 `id` 属性值与 DAO 接口中定义的查询方法名相同,`parameterType` 属性定义参数类型(可以省略),`resultType` 定义返回值类型,返回值可以是基本类型、自定义类型(例如业务对象)、集合,如果是自定义类型,需要写该类的全类名,如果是集合类型,则写集合中存放的元素的类型(而不是集合类型),注意该属性不可以与 `resultMap` 同时使用
在数据库建一张 `emp` 表和 `dept` 表,并插入若干数据,如下,注意数据库中 `birthday` 字段的类型是 `bigint`,存储日期的时间戳,并在项目中定义了负责 Java 日期类型和 `Long` 类型的转换的 `TypeHandler`,MyBatis 会自动调用,完成转换


定义 `Employee` 的实体类,注意到数据库 `emp` 表中部门号的字段名为 `dept_id`,而实体类中的属性名为 `deptId`,其他字段名相同
```java
package cool.yzt.entity;
import java.util.Date;
public class Employee {
private int id;
private String name;
private String gender;
private Date birthday;
private int deptId;
/*
构造器、setter、getter、toString
*/
}
```
```java
package cool.yzt.entity;
public class Department {
private int id;
private String name;
/*
构造器、setter、getter、toString
*/
}
```
编写 DAO 层接口,定义查询方法
```java
package cool.yzt.mapper;
import cool.yzt.entity.Employee;
import java.util.List;
public interface EmployeeMapper {
// 根据传入的id查询
public Employee findById(Integer id);
// 查询所有记录,存储在List中
public List findAll();
}
```
编写 `EmployeeMapper.xml` 映射文件
```xml
```
测试
```java
public void test1() {
InputStream is = MultiTableTest.class.getClassLoader().getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
try {
Employee emp = mapper.findById(6);
System.out.println(emp);
} finally {
sqlSession.close();
}
}
```
输出结果,可以看到,除了 `deptId` 属性,其他字段的值都被正确查询并且封装到 `Employee` 对象中,这是因为 Java 实体类中的属性名和数据库表中的字段名相同,MyBatis 便可以完成自动映射封装
```
Employee{id=6, name='李四', gender='男', birthday=Sun Jul 19 14:11:17 CST 2020, deptId=0}
```
### 结果集封装为 Map
如果返回值并不是一个完整的业务对象,可以将结果(对应一条记录)封装为一个 Map,Map 的 `key` 是字段名,值时字段对应的数据库记录值
DAO 层接口方法编写
```java
public Map findNameAndBirthdayById(Integer id);
```
`Employee.xml` 映射文件配置
```xml
```
测试
```java
Map res = mapper.findNameAndBirthdayById(7);
System.out.println(res);
System.out.println(res.get("name"));
```
输出结果
```
{birthday=1595139077344, name=唐僧}
唐僧
```
### 自定义结果集的封装格式
除了 MyBatis 的结果集自动封装(使用 `resultType`),更常用的是自定义查询结果集的封装格式映射,使得结果集的封装更加灵活和方便,在映射文件中使用 ` ` 标签完成这个功能,`` 标签的 `id` 属性是此自定义映射的唯一表示,`type` 属性表示关联至一个 Java 类
```xml
```
`` 标签各个子标签的作用
|标签|作用|属性|
|---|---|---|
|id|表示某一个字段的映射,标记为id可以提高性能,一般用于主键|property:对应Java类中的属性,column:对应查询出的某一列的列名|
|result|表示某一个字段的映射,用于普通字段的映射|property和column属性作用与上同|
修改 `

MyBatis 映射文件详解