MyBatis XML 映射文件详解
适合对象:已经看懂
mybatis-config.xml,想继续理解 XML 中的 SQL 如何对应 Java 方法的同学。
官方参考:
1. XML 映射文件负责什么?
MyBatis 的 XML 映射文件,也常叫 Mapper XML,主要负责写 SQL,并把 SQL 和 Java 调用方式对应起来。
常见有两种调用思路:
| 调用思路 | 说明 |
|---|---|
| 字符串 SQL 标识调用 | 通过 session.selectOne("命名空间.SQL编号", 参数) 调用 |
| Mapper 接口代理调用 | 通过 session.getMapper(接口.class) 获取代理对象,再调用接口方法 |
入门阶段可以先看懂这句话:
XML 的
namespace加 SQL 标签的id,共同决定一条 SQL 的唯一标识。
2. XML 映射文件的基本结构
Mapper XML 通常这样开头:
1 |
|
其中:
| 内容 | 含义 |
|---|---|
DOCTYPE mapper |
使用 MyBatis Mapper XML 的 DTD 规则 |
<mapper> |
一个 SQL 映射文件的根标签 |
namespace |
命名空间,用来给这一组 SQL 做唯一定位 |
namespace 很重要。它既可以是一个普通名称,也可以是 Mapper 接口的全类名。使用接口代理方式时,通常写接口全类名。
3. 字符串 SQL 标识调用
先看一种最直观的写法:
1 | <mapper namespace="EmployeeMapper"> |
Java 中可以这样调用:
1 | Employee employee = session.selectOne("EmployeeMapper.selectEmployee", 1); |
匹配关系如下:
1 | EmployeeMapper.selectEmployee |
图示:
1 | flowchart LR |
这种方式容易理解,但字符串写错时,编译器不一定能发现。所以实际开发中更推荐 Mapper 接口代理方式。
4. Mapper 接口代理调用
先定义 Mapper 接口:
1 | public interface EmployeeMapper { |
XML 中让 namespace 对应接口全类名:
1 | <mapper namespace="com.bytepro.mapper.EmployeeMapper"> |
Java 中这样调用:
1 | EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); |
三者之间的对应关系是:
1 | flowchart TD |
记忆方式很简单:
- 接口全类名对应
namespace - 接口方法名对应 SQL 的
id - 方法参数对应 SQL 里的
#{} - 方法返回值对应 XML 里的
resultType或resultMap
5. select:查询数据
查询单个员工可以这样写:
1 | <select id="selectEmployee" resultType="emp"> |
对应接口方法:
1 | Employee selectEmployee(Integer id); |
几个关键点:
| XML 属性或语法 | 含义 |
|---|---|
select |
表示这是一条查询语句 |
id="selectEmployee" |
对应接口里的 selectEmployee 方法 |
resultType="emp" |
查询结果封装成 Employee 对象 |
#{id} |
接收 Java 方法传入的参数 |
调用示例:
1 | Employee employee = mapper.selectEmployee(1); |
最终执行时,可以理解为查询:
1 | select * from employee where id = 1 |
实际执行时 MyBatis 会使用预编译参数,不是简单字符串拼接。这也是 #{} 比直接拼接 SQL 更安全的原因。
6. insert:新增数据并回填自增主键
新增员工可以这样写:
1 | <insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id"> |
对应接口方法:
1 | Long insertEmployee(Employee employee); |
调用示例:
1 | Employee employee = new Employee(); |
这段 XML 中最重要的是:
1 | useGeneratedKeys="true" keyProperty="id" |
它们用于处理 MySQL 自增主键:
| 属性 | 含义 |
|---|---|
useGeneratedKeys="true" |
使用 JDBC 获取数据库生成的主键 |
keyProperty="id" |
把生成的主键值设置回 Employee 对象的 id 属性 |
也就是说,插入前 employee.getId() 可能是 null;插入成功后,MyBatis 可以把数据库生成的新 id 放回对象中。
#{name}、#{gender}、#{age}、#{homeAddress} 都来自传入的 Employee 对象属性。
7. selectKey:序列主键写法示例
有些数据库常用序列生成主键,可以使用 selectKey。例如:
1 | <insert id="insertEmployee" databaseId="oracle"> |
这段代码表示:
- 先执行
selectKey中的 SQL,拿到序列生成的主键。 - 把主键设置到
Employee对象的id属性。 - 再执行真正的
insert语句。
如果使用 MySQL 自增主键,通常重点看 useGeneratedKeys="true" 这种写法;如果使用序列主键,可以了解 selectKey。
8. update:修改数据
修改员工可以这样写:
1 | <update id="updateEmployee"> |
对应接口方法:
1 | Long updateEmployee(Employee employee); |
调用示例:
1 | Employee employee = new Employee(); |
传入的是一个完整对象,所以 XML 中可以直接通过属性名取值:
1 | #{id} |
修改操作会改变数据库数据,所以执行后需要提交事务。
9. delete:删除数据
删除员工可以这样写:
1 | <delete id="deleteEmployee"> |
对应接口方法:
1 | Long deleteEmployee(Integer id); |
调用示例:
1 | Long result = mapper.deleteEmployee(7); |
删除和查询单个员工都可以传入简单参数 id。区别是:
select返回查询结果delete返回影响行数,并且需要提交事务
10. Map 参数:用 key 对应 SQL 参数名
如果一个查询条件由多个值组成,可以传入 Map:
1 | Employee getEmployeeByMap(Map<String, Object> map); |
XML:
1 | <select id="getEmployeeByMap" resultType="com.bytepro.entity.Employee"> |
调用示例:
1 | Map<String, Object> map = new HashMap<>(); |
当参数是 Map 时:
| Map 的 key | XML 中的写法 |
|---|---|
id |
#{id} |
name |
#{name} |
也就是说,#{id} 会去 Map 里找 key 为 "id" 的值,#{name} 会去 Map 里找 key 为 "name" 的值。
11. 多参数:用 @Param 起名字
多个普通参数建议使用 @Param 明确命名:
1 | Employee getEmployeeByIdAndName( Integer id, String name); |
XML:
1 | <select id="getEmployeeByIdAndName" resultType="com.bytepro.entity.Employee"> |
@Param 和 XML 参数的关系如下:
| Java 参数 | 注解 | XML 参数 |
|---|---|---|
Integer id |
@Param("id") |
#{id} |
String name |
@Param("name") |
#{name} |
调用示例:
1 | Employee employee = mapper.getEmployeeByIdAndName(6, "李四new"); |
如果没有清晰的参数名,XML 里写 #{id}、#{name} 时就不够直观。@Param 的作用就是把 Java 参数名和 SQL 参数名明确绑定起来。
12. resultType:查询结果怎么变成对象
XML 中可以写完整类名:
1 | resultType="com.bytepro.entity.Employee" |
也可以写类型别名:
1 | resultType="emp" |
如果 Employee 上有注解:
1 |
|
那么 emp 就表示 Employee。
再配合下划线转驼峰配置:
1 | <setting name="mapUnderscoreToCamelCase" value="true"/> |
数据库列 home_address 就可以映射到 Java 属性 homeAddress。
简单查询用 resultType 就够了。复杂对象关系映射,比如一对多、多表关联结果封装,通常再学习 resultMap。
13. CRUD 映射总表
| Java 方法 | XML 标签 | SQL id | 参数来源 | 是否需要 commit |
|---|---|---|---|---|
selectEmployee(Integer id) |
select |
selectEmployee |
简单参数 id |
否 |
insertEmployee(Employee employee) |
insert |
insertEmployee |
Employee 对象属性 |
是 |
updateEmployee(Employee employee) |
update |
updateEmployee |
Employee 对象属性 |
是 |
deleteEmployee(Integer id) |
delete |
deleteEmployee |
简单参数 id |
是 |
getEmployeeByMap(Map<String, Object> map) |
select |
getEmployeeByMap |
Map 的 key |
否 |
getEmployeeByIdAndName(Integer id, String name) |
select |
getEmployeeByIdAndName |
@Param 指定参数名 |
否 |
14. 这一篇先记住什么?
- XML 的
namespace用来定位一组 SQL。 - Mapper 接口方式中,
namespace通常写接口全类名。 - XML 中 SQL 标签的
id要和接口方法名一致。 #{}是参数占位符,用来接收 Java 传进来的值。resultType决定查询结果封装成什么对象。- 对象参数可以直接通过属性名取值,例如
#{homeAddress}。 - 多个普通参数建议用
@Param命名。 - 新增、修改、删除后要记得
session.commit()。