国庆节10月1日
--


把 SQL 从接口上的注解搬进 XML 映射文件——XML 的三条默认规则(同包同名、namespace 是接口全限定名、语句 id 是方法名且返回类型一致)、注解与 XML 各自适合什么场景、XML 换个位置该怎么配 mybatis.mapper-locations,以及 MybatisX 插件与一份本机实测的 XML 查询日志

54 篇把增删改查四个操作都写在注解里了。这一篇(PPT 第 38-42 页)说的是另一种写法:把 SQL 搬进 XML 映射文件。两种方式在 MyBatis 里长期并存,选哪个不是风格问题,而是”这条 SQL 复杂到什么程度”的问题——54 篇那种简单语句用注解就够,复杂 SQL 才是 XML 的主场。
本机的课程工程里两种写法都跑过:springboot-mybatis-quickstart 是 yml + XML(findAll 的 SQL 放在 XML 里),aliyun-mybatis-quickstart 是 properties + 注解(findAll 的 SQL 写在 @Select 上),两套都跑通了 5 个测试——这个差异在下面”辅助配置”一节会讲清楚。
PPT 第 38 页是本节目录页,五格里这一篇进的是最后一格「XML映射配置」(前四格分别见 51 篇/52 篇/53 篇/54 篇)。
PPT 第 39 页开门见山:
在 Mybatis 中,既可以通过注解配置 SQL 语句,也可以通过 XML 配置文件配置 SQL 语句。
也就是说,54 篇里 @Select / @Delete 那些注解并不是唯一的做法;同样一条 SQL,写进 XML 文件里照样生效——但 XML 文件必须放对位置、写对名字,这就是下一页那三条”默认规则”。
PPT 第 39 页(第 41 页的问答又重复了一遍)给了三条规则,一条都不能少:
- XML 映射文件的名称与 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口放置在相同包下(同包同名)。
- XML 映射文件的
namespace属性为 Mapper 接口全限定名一致。- XML 映射文件中 SQL 语句的
id与 Mapper 接口中的方法名一致,并保持返回类型一致。

src/main/java/com/itheima/mapper/UserMapper.java,XML 在 src/main/resources/com/itheima/mapper/UserMapper.xml(在 resources 下把包路径一级一级建出来),两者”包名 + 文件名”完全对应,这就是”同包同名”这里有个初学时容易卡住的细节:接口在 java 目录、XML 在 resources 目录,为什么算”同包”? 因为 Java 代码编译后 class 文件在 classpath:/com/itheima/mapper/ 下,而 resources 里的东西是原样复制到 classpath 根的——所以在 resources 下建出 com/itheima/mapper/ 这串目录、把 UserMapper.xml 放进去,编译之后它在 classpath 里的位置正好和 com.itheima.mapper.UserMapper 这个接口”同包同名”。这样 MyBatis 拿着接口名就能默认找到同名 XML,不用写任何配置。
接口这边长这样(去掉 SQL,只剩方法签名):

@Mapper,方法只剩一句 public List<User> findAll();,SQL 不在接口上了课程工程里 findAll 的注解就是被注释掉、SQL 交给 XML 的(源码原样):
1 /**2 * 查询所有用户3 */4 //@Select("select id, username, password, name, age from user")5 public List<User> findAll();XML 那份文件的内容(课程 UserMapper.xml 全文):
1<?xml version="1.0" encoding="UTF-8" ?>2<!DOCTYPE mapper3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"4 "https://mybatis.org/dtd/mybatis-3-mapper.dtd">5<mapper namespace="com.itheima.mapper.UserMapper">6
7 <!--resultType: 查询返回的单条记录所封装的类型-->8 <select id="findAll" resultType="com.itheima.pojo.User">9 select id, username, password, name, age from user10 </select>11
12</mapper>逐行对照三条规则:
| XML 里的东西 | 规则 | 这份文件里的值 |
|---|---|---|
| 文件名与位置 | 同包同名 | resources/com/itheima/mapper/UserMapper.xml ⇄ 接口 com.itheima.mapper.UserMapper |
<mapper namespace="…"> | 接口全限定名 | com.itheima.mapper.UserMapper |
<select id="…"> | 接口里的方法名 | findAll(接口里正是 List<User> findAll()) |
<select resultType="…"> | 返回类型一致(单条记录封装成什么类型) | com.itheima.pojo.User——接口返回 List<User>,所以单条的类型就是 User |
前两行 <?xml …?> 和 <!DOCTYPE mapper …> 是 XML 的声明与约束(DTD),MyBatis 官方文档里给了现成的,照着复制过来就行,别手打。
resultType 写的是”单条记录”封装的类型:接口返回 List<User>,XML 里写的还是 com.itheima.pojo.User(课程源码的注释原话是「查询返回的单条记录所封装的类型」)。这是最容易写错的一处——不要因为接口返回 List 就把 resultType 写成 java.util.List。
PPT 第 39 页还给了一份错误的对照,用来把三条规则钉死:

namespace 是接口全限定名 com.itheima.mapper.UserMapper,语句 id 是接口方法名 findAll
namespace="itheima"(不是接口全限定名)、id="abc"(不是方法名),这样 MyBatis 根本找不到”这个方法该执行哪条 SQL”,项目启动时就会报错接口上没有 SQL 了,那条 SQL 在 XML 里,跑起来是什么样?本机把工程里的查询测试跑了一遍:
本机实测(springboot-mybatis-quickstart,findAll 的 SQL 在 XML 里)——日志与结果:
1==> Preparing: select id, username, password, name, age from user2==> Parameters:3<== Total: 54User(id=1, username=daqiao, password=123456, name=大乔, age=22)5User(id=2, username=xiaoqiao, password=123456, name=小乔, age=18)6User(id=3, username=diaochan, password=123456, name=貂蝉, age=24)7User(id=4, username=lvbu, password=123456, name=吕布, age=28)8User(id=5, username=zhaoyun, password=12345678, name=赵云, age=27)三行日志说明三件事:Preparing 里就是 XML 里那条 SQL(从 XML 读出来的);这条语句没有参数,所以 Parameters: 后面是空的;Total: 5 表示查回 5 条,forEach 逐条打印后就是那 5 个 User 对象——说明”接口声明方法 + XML 写 SQL + namespace/id 对上”这一套确实接通了。
PPT 第 40 页专门回答这个问题:
那在 Mybatis 的开发中,到底使用注解开发还是使用 XML 开发呢? 使用 Mybatis 的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的 SQL 功能,建议使用 XML 来配置映射语句。
PPT 还给了官方说明的地址(MyBatis 中文文档「入门」页):https://mybatis.net.cn/getting-started.html。
把这句话背后的道理展开:
| 注解 | XML | |
|---|---|---|
| 写在哪 | 接口方法上方(@Select("…")) | 独立的 .xml 文件 |
| 适合什么 | 简单的增删改查——一行 SQL 能写完的 | 复杂 SQL——多表连接、动态条件(<if>/<where>/<foreach> 这类标签)、需要复用/拼装的语句 |
| 代码观感 | 接口和方法紧挨着,一眼看到”这个方法干什么” | 接口保持干净,SQL 集中在一处维护 |
| 配置成本 | 零 | 要遵守三条默认规则(或配 mapper-locations) |
所以选择标准很直接:语句短、条件固定 → 注解;语句长、条件/表名要动态拼 → XML。这两种方式在同一个工程里可以混用(有的方法走注解、有的走 XML),课程工程就是这么做的。
默认规则要求”同包同名”,但项目里也常见把 XML 统一放在 resources/mapper/ 目录下的做法(本机的课程工程就是):

springboot-mybatis-quickstart 的目录——XML 放在 src/main/resources/mapper/UserMapper.xml,并没有按”同包同名”放在 resources/com/itheima/mapper/ 下这样一来 MyBatis 默认就找不到了,得在配置文件里告诉它去哪儿找:
1#指定XML映射配置文件的位置2mybatis.mapper-locations=classpath:mapper/*.xml这行配置的含义:
mybatis.mapper-locations —— 告诉 MyBatis “XML 映射文件在哪些路径下”;classpath:mapper/*.xml —— classpath 指的是编译后的类路径根(也就是 resources 里的内容),这串通配符的意思是”根目录下 mapper 文件夹里的所有 .xml 文件”;两种放法对照着记:
| 方式一:同包同名(默认) | 方式二:统一目录 + 配置 | |
|---|---|---|
| XML 位置 | resources/com/itheima/mapper/UserMapper.xml(把接口的包路径在 resources 下建出来) | resources/mapper/UserMapper.xml(或任何目录) |
要不要配 mapper-locations | 不用(MyBatis 按接口名默认去找) | 必须配(本课程用的是 classpath:mapper/*.xml) |
| 对应 PPT | 第 39 页的三条默认规则 | 第 42 页的辅助配置 |
本机实测跑的是方式二:课程 springboot-mybatis-quickstart 把 XML 放在 resources/mapper/ 下,并在 yml 里配了 mybatis.mapper-locations: classpath:mapper/*.xml;PPT 第 39 页演示的是方式一(同包同名、零配置)。选哪种都行,但一个工程里最好统一,别混着放——否则”为什么这条 XML 生效、那条不生效”会很难查。
课程工程完整的那份配置(application.yml,这里顺带把上一节的日志配置也放一起看):
1#Mybatis的相关配置2mybatis:3 configuration:4 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl5 mapper-locations: classpath:mapper/*.xml而 aliyun-mybatis-quickstart 那套工程没有任何 XML——findAll 的 SQL 就写在 @Select 注解上:
1 /**2 * 查询所有用户3 */4 @Select("select id, username, password, name, age from user")5 public List<User> findAll();两个工程,两条路,结果一样(都跑通 5 个测试、都查出那 5 条用户)——这就是”注解与 XML 并存”最直观的证明。
XML 和接口分在两个文件里,来回跳转很烦,PPT 第 42 页推荐一个 IDEA 插件:
MybatisX 是一款基于 IDEA 的快速开发 Mybatis 的插件,为效率而生。

mybatisx,点击 Install 安装(装完重启 IDEA 生效)装上它以后最大的好处是:Mapper 接口和 XML 之间可以互相跳转——点接口方法能跳到 XML 里对应的语句,点 XML 里的语句也能跳回接口方法(插件功能列表里的 “mapper and xml can jump back and forth”)。配合下面这些提示功能,写 XML 时不容易把方法名/参数写错:
mybatis.xml/mapper.xml 相关标签有代码提示;| PPT 的问题 | 答案 |
|---|---|
| XML 映射文件的定义规则? | ① 文件名与 Mapper 接口名称一致,并且放置在相同包下(同包同名);② XML 文件的 namespace 属性为 Mapper 接口全限定名一致;③ XML 文件中 SQL 语句的 id 与 Mapper 接口中的方法名一致 |
(“到底用注解还是 XML 开发”这个问题的答案在第 40 页:简单的增删改查用注解,复杂 SQL 用 XML。)
| 问题 | 答案 |
|---|---|
| SQL 有哪两种配置方式? | 注解(@Select / @Delete …写在接口方法上)和 XML 映射文件;两者可以混用 |
| XML 的三条默认规则? | 同包同名(名称与接口一致、放相同包下);namespace = 接口全限定名;语句 id = 方法名且返回类型一致 |
为什么 XML 放 resources/com/itheima/mapper/ 就算”同包”? | resources 下的内容会被原样复制到 classpath 根,所以在那里把接口的包路径建出来,编译后两者在 classpath 中就是同包同名——不用任何配置 |
resultType 写什么? | 单条记录封装的类型(接口返回 List<User> 时也写 com.itheima.pojo.User) |
XML 换到 resources/mapper/ 下要配什么? | mybatis.mapper-locations=classpath:mapper/*.xml(指定 XML 的位置);namespace 和 id 两条规则照旧要守 |
| 本机实测怎么证明 XML 生效? | findAll 的日志:Preparing: select id, username, password, name, age from user + 空 Parameters + Total: 5,接着打印 5 个 User 对象 |
| 课程两个工程的差别? | springboot-mybatis-quickstart = yml + XML(resources/mapper/ + mapper-locations),aliyun-mybatis-quickstart = properties + 注解(无 XML);两套都跑通 5 个测试 |
| MybatisX 干什么? | IDEA 插件:Mapper 接口与 XML 互相跳转、XML 标签提示、自动生成语句骨架——为效率而生 |
@Select("…")),也可以写进 XML 映射文件;同一个工程里可以混用resources/com/itheima/mapper/ 下建出接口的包路径(resources 会原样复制到 classpath 根,所以编译后算同包)<mapper namespace="…"> 的属性值必须是 Mapper 接口的全限定名,课程里是 com.itheima.mapper.UserMapperid 与接口方法名一致;resultType 写单条记录封装的类型(接口返回 List<User> 时也写 com.itheima.pojo.User)<?xml version="1.0" encoding="UTF-8" ?> + <!DOCTYPE mapper …> 声明与约束,从官方文档复制,不要手打mybatis.mapper-locations=classpath:mapper/*.xml——告诉 MyBatis 去哪里找 XML 映射文件;配了它位置自由,但 namespace、id 两条规则仍要遵守findAll 走 XML 时的日志是 Preparing: select id, username, password, name, age from user、Parameters:(空)、Total: 5,然后打印出 5 个 User 对象springboot-mybatis-quickstart = yml + XML(XML 在 resources/mapper/,用 mapper-locations 指定);aliyun-mybatis-quickstart = properties + 注解(没有 XML),两套都跑通了 5 个测试 2-1 把”查询所有用户”的 SQL 从接口搬进 XML
需求:UserMapper 接口里有一个查全部用户的方法;现在把 SQL 挪出接口,写进一个独立的 XML 映射文件里,位置让 MyBatis 默认就能找到(不用改任何配置),保证项目启动后调用这个方法仍能查出 5 条数据。
(练习文件 test_55_XML映射文件.xml 的题目2-1 里给了写作区;接口方法签名与实体类位置作为素材已写在素材区。)
一级 · 思路:先决定 XML 放哪、叫什么名字(默认规则);文件里从官方文档抄声明与约束头,再写一个 mapper 根标签,把”接口全限定名”和”这条 SQL 的 id/返回类型”分别填对
二级 · 方法:文件放 resources/com/itheima/mapper/UserMapper.xml;根标签用 <mapper namespace="…">;查询语句用 <select id="…" resultType="…">
三级 · 骨架:<?xml version="1.0" encoding="UTF-8" ?> + <!DOCTYPE mapper …> + <mapper namespace="____"> <select id="____" resultType="____"> select … from user </select> </mapper>
文件:src/main/resources/com/itheima/mapper/UserMapper.xml
1<?xml version="1.0" encoding="UTF-8" ?>2<!DOCTYPE mapper3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"4 "https://mybatis.org/dtd/mybatis-3-mapper.dtd">5<mapper namespace="com.itheima.mapper.UserMapper">6
7 <!--resultType: 查询返回的单条记录所封装的类型-->8 <select id="findAll" resultType="com.itheima.pojo.User">9 select id, username, password, name, age from user10 </select>11
12</mapper>接口那边只留方法签名(原来方法上的 @Select 要注释/删掉,否则会冲突或让人看混):
1@Mapper2public interface UserMapper {3 public List<User> findAll();4}检查三处是否对上:文件名与位置(resources/com/itheima/mapper/UserMapper.xml)、namespace(com.itheima.mapper.UserMapper)、id 与 resultType(findAll / com.itheima.pojo.User)。本机工程(放的是 resources/mapper/)里 findAll 走 XML 的实测日志是 Preparing: select id, username, password, name, age from user、Parameters:(空)、Total: 5——XML 放在上面哪种位置,跑起来这条日志都一样。
2-2 在同一个 XML 里再加一条”按 id 查询用户”
需求:接口里新增一个方法——按用户 id 查一条用户记录并返回;SQL 也写在同一个 XML 文件里(不要用注解)。写完后调用它查 id 为 1 的用户,确认能拿到”大乔”那条数据。
(练习文件 test_55_XML映射文件.xml 的题目2-2 里给了写作区。)
一级 · 思路:接口加方法签名,XML 加一条语句;语句的 id 必须等于新方法名,返回类型还是单条记录的类型;参数照样用 #{}
二级 · 方法:新建的语句用 <select id="findById" resultType="…">;SQL 里用 where id = #{id};接口方法叫 User findById(Integer id);
三级 · 骨架:接口 public ____ findById(____ id); / XML <select id="____" resultType="____"> select id, username, password, name, age from user where id = ____ </select>
接口新增:
1public User findById(Integer id);同一个 XML 文件里新增一条语句:
1 <select id="findById" resultType="com.itheima.pojo.User">2 select id, username, password, name, age from user where id = #{id}3 </select>要点:一条方法对应一条语句,id 和方法名一一对应;XML 里 #{id} 取的就是接口方法的形参(单个参数,名字随意但建议一致)。查 id=1 得到的就是 User(id=1, username=daqiao, password=123456, name=大乔, age=22) 这一条(该行数据见54 篇实测里的 findAll 输出)。
2-3 找错:这份 XML 为什么会让项目起不来 背景:有同学把 XML 写成了下面这样,启动项目时报错说找不到方法对应的 SQL 语句:
1<mapper namespace="itheima">2 <select id="abc" resultType="com.itheima.pojo.User">3 select id, username, password, name, age from user4 </select>5</mapper>完成:① 指出这份 XML 违反了三条默认规则里的哪两条;② 把它们改成正确的值;③ 再回答:如果 XML 不放在”同包同名”的位置,还需要在哪份配置里加一行什么,才能让 MyBatis 找到它?
(练习文件 test_55_XML映射文件.xml 的题目2-3 里给了写作区。)
一级 · 思路:对照三条规则一行行看——根标签的属性对不对?语句的 id 对不对?再想想”换目录”时那条配置
二级 · 方法:根标签的 namespace 要写接口全限定名;语句 id 要写接口里的方法名;位置自由靠 mybatis.mapper-locations
三级 · 骨架:<mapper namespace="____.____.____"> / <select id="____" …> / mybatis.mapper-locations=classpath:____/*.xml
① 违反了两条:namespace 不是接口全限定名(写成 itheima)、语句 id 不是接口方法名(写成 abc)——这两处对不上,MyBatis 就建立不起”接口方法 → SQL 语句”的映射关系,启动即报错。第三条规则(同包同名)这份代码没体现,它决定”能不能被默认找到”。
② 改成:
1<mapper namespace="com.itheima.mapper.UserMapper">2 <select id="findAll" resultType="com.itheima.pojo.User">3 select id, username, password, name, age from user4 </select>5</mapper>③ 如果 XML 不放在”同包同名”的位置(比如统一放到 resources/mapper/ 下),要在配置文件(application.properties 或 application.yml)里加:
1mybatis.mapper-locations=classpath:mapper/*.xml注意:这行只解决”去哪儿找文件”;namespace 和 id 那两条规则任何方式下都要遵守。这也是课程 springboot-mybatis-quickstart 的实际做法(XML 在 resources/mapper/,配置里有这一行)。
3-1 把注解版 CRUD 改造成 XML 版,并对照两边的日志 把 54 篇写好的四个注解方法整体迁到 XML 里,走一遍”两条路做同一件事”的完整流程。
UserMapper 里保留 findAll、deleteById、insert、update、findByUsernameAndPassword 五个方法,把方法上的 @Select/@Delete/@Insert/@Update 全部注释掉;resources/com/itheima/mapper/UserMapper.xml(同包同名、零配置),要么放 resources/mapper/UserMapper.xml 并在配置里加 mybatis.mapper-locations=classpath:mapper/*.xml;id 与方法名一致、resultType 与返回类型一致,参数用 #{} 接);Preparing/Parameters 抄到练习文件末尾;Preparing 有什么不同?为什么?(提示:看参数是 ? 还是具体值)test_55_XML映射文件.xml 的”综合题”一段里按这 6 步给了写作区。)涉及知识点
| 知识点 | 在这里的应用 |
|---|---|
| 三条默认规则 | 第 2、3 步——位置、namespace、id 与返回类型 |
mapper-locations | 第 2 步——XML 换目录时必须配 |
| XML 语句标签 | 第 3 步——<select> / <insert> / <update> / <delete> 的 id 与参数 |
#{} 预编译 | 第 4、5 步——XML 里同样生成 ? + Parameters |
| 注解与 XML 的选择 | 第 6 步——简单增删改查 vs 复杂 SQL |
一级 · 思路:把54 篇四条注解里的 SQL 原样抄进 XML 的四个标签里,接口只留方法签名;测完拿日志对照
二级 · 方法:XML 里查询用 <select>、新增 <insert>、修改 <update>、删除 <delete>,每个都带 id;参数照旧 #{};换个目录就在配置里写 mybatis.mapper-locations
三级 · 骨架:<select id="findAll" resultType="____">…</select> / <delete id="deleteById">delete from user where id = ____</delete> / <insert id="insert">insert into user(____) values(____)</insert> / <update id="update">…</update>
3-1
1~3. 接口(只留签名)与 XML(放在 resources/mapper/UserMapper.xml,并在配置里加 mybatis.mapper-locations=classpath:mapper/*.xml):
1@Mapper2public interface UserMapper {3 public List<User> findAll();4 public Integer deleteById(Integer id);5 public void insert(User user);6 public void update(User user);7 public User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);8}1<?xml version="1.0" encoding="UTF-8" ?>2<!DOCTYPE mapper3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"4 "https://mybatis.org/dtd/mybatis-3-mapper.dtd">5<mapper namespace="com.itheima.mapper.UserMapper">6
7 <select id="findAll" resultType="com.itheima.pojo.User">8 select id, username, password, name, age from user9 </select>10
11 <delete id="deleteById">12 delete from user where id = #{id}13 </delete>14
15 <insert id="insert">16 insert into user(username, password, name, age) values (#{username}, #{password}, #{name}, #{age})17 </insert>18
19 <update id="update">20 update user set username = #{username}, password = #{password}, name = #{name}, age = #{age} where id = #{id}21 </update>22
23 <select id="findByUsernameAndPassword" resultType="com.itheima.pojo.User">24 select * from user where username = #{username} and password = #{password}25 </select>26
27</mapper>4~5. 本机实测里 findAll 走 XML 的日志是:
1==> Preparing: select id, username, password, name, age from user2==> Parameters:3<== Total: 5而 54 篇里那几条走注解的实测日志形如 Preparing: delete from user where id = ? + Parameters: 4(Integer)。两边都生成了预编译 SQL——Preparing 行是最终发给数据库的语句模板,注解或 XML 只是”SQL 写在哪儿”的区别,#{} 的机制完全一样(这就是54 篇那条”#{…} 会变成 ?”的结论在两处都成立的原因)。
6. 按 PPT 第 40 页的标准:这五个方法都是简单的增删改查(每条一句话、条件固定),用注解更合适;只有出现多表连接、条件个数不定(需要 <if>/<where>)、SQL 很长需要单独维护时,才把它们搬到 XML 里。所以真实项目里常见”注解为主、XML 兜底”的混用方式——本机课程的两个工程正好各占一边。
如果你喜欢,那么欢迎来到我的世界!
了解更多暂未播放



"所有的相遇都是久别重逢。"—— 未知


