MyBatis 中 XML 与 DAO 接口的位置关系及扫描机制详解

MyBatis中XML与DAO接口的位置关系及扫描机制详解

在使用MyBatis进行开发时,很多开发者都会有这样的疑问:为什么XML映射文件和DAO接口(Mapper接口)通常要放在同一个包下?为什么需要进行包扫描?扫描的是DAO包还是XML文件的包?@Mapper和@MapperScan又有什么区别?本文将详细解答这些问题,帮助你理解MyBatis的底层工作逻辑。

一、为什么XML文件和DAO接口要放在同一个包下?

MyBatis中XML映射文件与DAO接口放在同一个包下,主要是基于"约定优于配置"的设计理念,这一约定可以大大简化配置并提高开发效率。

1. 核心匹配规则

MyBatis通过以下两个规则来关联DAO接口和XML映射文件:

  • DAO接口的全限定名(包名+接口名)必须与XML文件的namespace属性值完全一致
  • DAO接口中的方法名必须与XML文件中SQL标签的id属性完全一致

例如,有如下DAO接口:

java 复制代码
package com.example.mapper;

public interface UserMapper {
    User selectById(Long id);
}

对应的XML文件应该这样配置:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <select id="selectById" resultType="com.example.entity.User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

2. 路径约定

当XML文件与DAO接口位于同一个包下时,MyBatis会自动根据DAO接口的全限定名去查找对应的XML文件。在Maven项目中,通常的目录结构是:

  • DAO接口:src/main/java/com/example/mapper/UserMapper.java
  • XML文件:src/main/resources/com/example/mapper/UserMapper.xml

这种结构让MyBatis能够通过类路径快速定位到对应的映射文件,而无需额外的配置。

二、为什么需要扫包?扫包的作用是什么?

MyBatis中的"扫包"是连接接口与框架的关键步骤,其核心作用是让框架识别并管理Mapper接口,具体体现在以下三个方面:

  1. 识别Mapper接口

    扫包告诉MyBatis"哪些接口是需要处理的Mapper接口",避免框架误将普通接口当作Mapper处理。

  2. 生成代理对象

    MyBatis不会直接实例化接口(接口无法实例化),而是为扫包范围内的Mapper接口生成代理对象(MapperProxy)。当调用接口方法时,实际是代理对象在执行具体逻辑(解析XML、执行SQL、处理结果集)。

  3. 集成Spring容器

    在Spring环境中,扫包后生成的代理对象会被注册到Spring容器中,使得Service层可以通过@Autowired直接注入并使用Mapper接口,无需手动创建实例。

三、扫描的是DAO包还是XML包?

扫包操作仅扫描DAO接口所在的包,而非XML文件的包。XML文件的加载是通过"接口全限定名+路径约定"间接关联的,具体流程如下:

  1. 框架扫描指定包下的所有DAO接口(如com.example.mapper),识别出哪些是Mapper接口。
  2. 针对每个DAO接口,根据其全限定名(如com.example.mapper.UserMapper),到类路径中查找对应的XML文件。
  3. 查找规则是:在与接口相同的包路径下,寻找与接口同名的XML文件(如UserMapper.xml),并通过XML的namespace属性确认匹配关系。

如果XML文件与DAO接口不在同一包下,需要在MyBatis配置文件中手动指定XML路径(如<mapper resource="xmls/UserMapper.xml"/>),但这种方式会增加配置成本,违背"约定优于配置"的原则。

四、@MapperScan和@Mapper的区别及使用方法

@Mapper@MapperScan都是MyBatis与Spring整合时用于注册Mapper接口的注解,但它们的作用范围和使用场景有所不同。

1. @Mapper注解

  • 作用范围:作用于单个Mapper接口(类级别注解)
  • 功能:标记该接口是MyBatis的Mapper接口,让框架为其生成代理对象
  • 使用场景:接口数量较少时(如1-5个),无需配置包路径

使用示例:

java 复制代码
import org.apache.ibatis.annotations.Mapper;

@Mapper // 标记当前接口为MyBatis Mapper
public interface UserMapper {
    User selectById(Long id);
}

启动类无需额外配置:

java 复制代码
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. @MapperScan注解

  • 作用范围:作用于包路径,批量扫描多个Mapper接口(通常加在启动类上)
  • 功能:指定需要扫描的包路径,自动注册该路径下所有接口为Mapper接口
  • 使用场景:接口数量较多时(如10个以上),避免重复添加@Mapper注解

使用示例:

java 复制代码
// 启动类(通过@MapperScan批量扫描)
@SpringBootApplication
@MapperScan("com.example.mapper") // 扫描com.example.mapper包下的所有接口
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

此时,Mapper接口上无需再加@Mapper注解:

java 复制代码
// 无需@Mapper注解,会被@MapperScan扫描到
public interface UserMapper {
    User selectById(Long id);
}

3. @MapperScan的高级用法

  • 扫描多个包:

    java 复制代码
    @MapperScan({"com.example.mapper", "com.example.dao"})
  • 排除特定接口:

    java 复制代码
    @MapperScan(
        basePackages = "com.example.mapper",
        excludeFilters = @ComponentScan.Filter(
            type = FilterType.ASSIGNABLE_TYPE,
            classes = {TestMapper.class} // 排除TestMapper接口
        )
    )

五、如何选择合适的注解?

  • 小型项目 (Mapper接口数量少):使用@Mapper更直观,无需关心包路径管理。
  • 中大型项目 (Mapper接口多):使用@MapperScan更高效,统一管理扫描路径,减少重复代码。

注意 :两者无需同时使用。如果用了@MapperScan,接口上无需再加@Mapper;反之亦然(同时使用不会报错,但属于冗余配置)。

六、总结

  1. XML与DAO接口同包放置,是为了让MyBatis通过"全限定名+路径约定"自动关联两者,减少手动配置。
  2. 扫包的核心作用是让框架识别Mapper接口、生成代理对象并集成到Spring容器,是MyBatis实现接口与SQL绑定的关键步骤。
  3. 扫包扫描的是DAO接口包,XML文件通过接口全限定名间接关联,无需单独扫描。
  4. @Mapper@MapperScan都是为了注册Mapper接口,前者适合少量接口,后者适合批量管理,根据项目规模选择即可。

理解这些机制,能帮助我们规避因路径或配置错误导致的常见问题,更高效地使用MyBatis进行开发。