适合对象:已经看懂 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 通常这样开头:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.bytepro.mapper.EmployeeMapper">
    ...
</mapper>

其中:

内容含义
DOCTYPE mapper使用 MyBatis Mapper XML 的 DTD 规则
<mapper>一个 SQL 映射文件的根标签
namespace命名空间,用来给这一组 SQL 做唯一定位

namespace 很重要。它既可以是一个普通名称,也可以是 Mapper 接口的全类名。使用接口代理方式时,通常写接口全类名。

3. 字符串 SQL 标识调用

先看一种最直观的写法:

<mapper namespace="EmployeeMapper">
    <select id="selectEmployee" resultType="com.bytepro.entity.Employee">
        select * from employee where id = #{id}
    </select>

</mapper>

Java 中可以这样调用:

Employee employee = session.selectOne("EmployeeMapper.selectEmployee", 1);

匹配关系如下:

EmployeeMapper.selectEmployee
        │              │
        │              └── select 标签的 id
        └── mapper 标签的 namespace

图示:

session.selectOne
EmployeeMapper.selectEmployee
namespace=EmployeeMapper
id=selectEmployee
select * from employee where id = #{id}

这种方式容易理解,但字符串写错时,编译器不一定能发现。所以实际开发中更推荐 Mapper 接口代理方式。

4. Mapper 接口代理调用

先定义 Mapper 接口:

public interface EmployeeMapper {
    Employee selectEmployee(Integer id);

    Long insertEmployee(Employee employee);

    Long updateEmployee(Employee employee);

    Long deleteEmployee(Integer id);

    Employee getEmployeeByMap(Map<String, Object> map);

    Employee getEmployeeByIdAndName(@Param("id") Integer id, @Param("name") String name);
}

XML 中让 namespace 对应接口全类名:

<mapper namespace="com.bytepro.mapper.EmployeeMapper">
    <select id="selectEmployee" resultType="emp">
        select * from employee where id = #{id}
    </select>

</mapper>

Java 中这样调用:

EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectEmployee(1);

三者之间的对应关系是:

接口全类名
com.bytepro.mapper.EmployeeMapper
XML namespace
接口方法名
selectEmployee
XML SQL id
方法参数
Integer id
SQL 参数
#{id}
方法返回值
Employee
resultType=emp
MyBatis 代理对象

记忆方式很简单:

  • 接口全类名对应 namespace
  • 接口方法名对应 SQL 的 id
  • 方法参数对应 SQL 里的 #{}
  • 方法返回值对应 XML 里的 resultTyperesultMap

5. select:查询数据

查询单个员工可以这样写:

<select id="selectEmployee" resultType="emp">
    select * from employee where id = #{id}
</select>

对应接口方法:

Employee selectEmployee(Integer id);

几个关键点:

XML 属性或语法含义
select表示这是一条查询语句
id="selectEmployee"对应接口里的 selectEmployee 方法
resultType="emp"查询结果封装成 Employee 对象
#{id}接收 Java 方法传入的参数

调用示例:

Employee employee = mapper.selectEmployee(1);

最终执行时,可以理解为查询:

select * from employee where id = 1

实际执行时 MyBatis 会使用预编译参数,不是简单字符串拼接。这也是 #{} 比直接拼接 SQL 更安全的原因。

6. insert:新增数据并回填自增主键

新增员工可以这样写:

<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
    insert into employee(name,gender,age,home_address)
    values(#{name},#{gender},#{age},#{homeAddress})
</insert>

对应接口方法:

Long insertEmployee(Employee employee);

调用示例:

Employee employee = new Employee();
employee.setName("李四");
employee.setAge(20);
employee.setGender('1');
employee.setHomeAddress("安徽");

Long result = mapper.insertEmployee(employee);
session.commit();

这段 XML 中最重要的是:

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。例如:

<insert id="insertEmployee" databaseId="oracle">
    <selectKey keyProperty="id" order="BEFORE" resultType="Integer">
        select EMPLOYEE_SEQ.nextval from dual
    </selectKey>

    insert into employee(id,name,gender,age,home_address)
    values(#{id},#{name},#{gender},#{age},#{homeAddress})
</insert>

这段代码表示:

  1. 先执行 selectKey 中的 SQL,拿到序列生成的主键。
  2. 把主键设置到 Employee 对象的 id 属性。
  3. 再执行真正的 insert 语句。

如果使用 MySQL 自增主键,通常重点看 useGeneratedKeys="true" 这种写法;如果使用序列主键,可以了解 selectKey

8. update:修改数据

修改员工可以这样写:

<update id="updateEmployee">
    update employee set name=#{name},gender=#{gender},age=#{age},home_address=#{homeAddress}
    where id=#{id}
</update>

对应接口方法:

Long updateEmployee(Employee employee);

调用示例:

Employee employee = new Employee();
employee.setId(7);
employee.setName("李四new");
employee.setAge(29);
employee.setGender('1');
employee.setHomeAddress("安徽");

Long result = mapper.updateEmployee(employee);
session.commit();

传入的是一个完整对象,所以 XML 中可以直接通过属性名取值:

#{id}
#{name}
#{gender}
#{age}
#{homeAddress}

修改操作会改变数据库数据,所以执行后需要提交事务。

9. delete:删除数据

删除员工可以这样写:

<delete id="deleteEmployee">
    delete from employee where id = #{id}
</delete>

对应接口方法:

Long deleteEmployee(Integer id);

调用示例:

Long result = mapper.deleteEmployee(7);
session.commit();

删除和查询单个员工都可以传入简单参数 id。区别是:

  • select 返回查询结果
  • delete 返回影响行数,并且需要提交事务

10. Map 参数:用 key 对应 SQL 参数名

如果一个查询条件由多个值组成,可以传入 Map

Employee getEmployeeByMap(Map<String, Object> map);

XML:

<select id="getEmployeeByMap" resultType="com.bytepro.entity.Employee">
    select * from employee where id = #{id} and name = #{name}
</select>

调用示例:

Map<String, Object> map = new HashMap<>();
map.put("id", 6);
map.put("name", "李四new");

Employee employee = mapper.getEmployeeByMap(map);

当参数是 Map 时:

Map 的 keyXML 中的写法
id#{id}
name#{name}

也就是说,#{id} 会去 Map 里找 key 为 "id" 的值,#{name} 会去 Map 里找 key 为 "name" 的值。

11. 多参数:用 @Param 起名字

多个普通参数建议使用 @Param 明确命名:

Employee getEmployeeByIdAndName(@Param("id") Integer id, @Param("name") String name);

XML:

<select id="getEmployeeByIdAndName" resultType="com.bytepro.entity.Employee">
    select * from employee where id = #{id} and name = #{name}
</select>

@Param 和 XML 参数的关系如下:

Java 参数注解XML 参数
Integer id@Param("id")#{id}
String name@Param("name")#{name}

调用示例:

Employee employee = mapper.getEmployeeByIdAndName(6, "李四new");

如果没有清晰的参数名,XML 里写 #{id}#{name} 时就不够直观。@Param 的作用就是把 Java 参数名和 SQL 参数名明确绑定起来。

12. resultType:查询结果怎么变成对象

XML 中可以写完整类名:

resultType="com.bytepro.entity.Employee"

也可以写类型别名:

resultType="emp"

如果 Employee 上有注解:

@Alias("emp")
public class Employee {
    private Integer id;
    private String name;
    private Character gender;
    private Integer age;
    private String homeAddress;
}

那么 emp 就表示 Employee

再配合下划线转驼峰配置:

<setting name="mapUnderscoreToCamelCase" value="true"/>

数据库列 home_address 就可以映射到 Java 属性 homeAddress

简单查询用 resultType 就够了。复杂对象关系映射,比如一对多、多表关联结果封装,通常再学习 resultMap

13. CRUD 映射总表

Java 方法XML 标签SQL id参数来源是否需要 commit
selectEmployee(Integer id)selectselectEmployee简单参数 id
insertEmployee(Employee employee)insertinsertEmployeeEmployee 对象属性
updateEmployee(Employee employee)updateupdateEmployeeEmployee 对象属性
deleteEmployee(Integer id)deletedeleteEmployee简单参数 id
getEmployeeByMap(Map<String, Object> map)selectgetEmployeeByMapMap 的 key
getEmployeeByIdAndName(Integer id, String name)selectgetEmployeeByIdAndName@Param 指定参数名

14. 这一篇先记住什么?

  • XML 的 namespace 用来定位一组 SQL。
  • Mapper 接口方式中,namespace 通常写接口全类名。
  • XML 中 SQL 标签的 id 要和接口方法名一致。
  • #{} 是参数占位符,用来接收 Java 传进来的值。
  • resultType 决定查询结果封装成什么对象。
  • 对象参数可以直接通过属性名取值,例如 #{homeAddress}
  • 多个普通参数建议用 @Param 命名。
  • 新增、修改、删除后要记得 session.commit()