欢迎光临
我们一直在努力

深入理解 MyBatis 映射文件与注解:从原理到 Spring 集成

深入理解 MyBatis 映射文件与注解:从原理到 Spring 集成

一、MyBatis 核心概念:映射文件与接口的关系

在 MyBatis 配置中,除了数据源、事务等基础配置,映射文件(Mapper XML) 和 Mapper 接口 是实现 SQL 映射的核心。它们的协作关系可以拆解为以下几个关键步骤:

1.1 Mapper 接口的必要性

  • Mapper 接口是 MyBatis 动态代理的“契约”。加载映射文件时,MyBatis 会解析文件的 namespace 属性,并根据该值加载对应的接口类。
  • 如果接口不存在,MyBatis 会直接抛出 ClassNotFoundException,因此必须确保接口与映射文件的 namespace 完全一致。

1.2 动态代理对象的构建

当映射文件加载完成后,MyBatis 会完成以下操作:

  • 将接口的 Class 对象作为 Key,对应的 MapperProxyFactory 作为 Value,存入 MapperRegistry 的 knownMappers 集合中。
  • 解析映射文件中的 SQL 语句,以方法的权限定名(如 com.example.mapper.UserMapper.selectById)为 Key,将方法属性封装为代理对象并存入 MappedStatement 集合。
  • 最终,所有代理对象会被封装到以 namespace 为标识的动态代理对象中,保存在 Configuration 里。
  • // MapperRegistry 核心代码
    public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
    return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
    }
    }


    二、将 MyBatis 代理对象注册到 Spring 容器

    MyBatis 初始化时,动态代理对象仅存在于 Configuration 中,无法被 Spring 直接依赖注入。为了解耦并实现 Spring 容器的管理,需要将这些代理对象注册到 Spring 容器中,主要有两种方式:

    2.1 单个 Mapper 注册:@Mapper 与 MapperFactoryBean

    这种方式针对单个 Mapper 接口进行注册,适合少量 Mapper 的场景。

    XML 配置方式

    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.mapper.UserMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

    注解方式

    在 Mapper 接口上添加 @Mapper 注解,Spring 会自动扫描并注册该接口的代理对象:

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

    2.2 批量 Mapper 注册:@MapperScan 与 MapperScannerConfigurer

    这种方式针对整个包下的所有 Mapper 接口进行批量注册,适合大规模 Mapper 的场景。

    XML 配置方式

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.mapper" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    注解方式

    在 Spring 启动类上添加 @MapperScan 注解,指定扫描的包路径:

    @SpringBootApplication
    @MapperScan("com.example.mapper")
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

    2.3 两种方式的核心原理

    无论是单个注册还是批量注册,底层逻辑都是:

  • MyBatis 初始化时,动态代理对象已构建并存入 Configuration。
  • MapperFactoryBean 或 MapperScannerConfigurer 会解析指定的 Mapper 接口,以 Class 为 Key 从 Configuration 中获取代理对象。
  • 将代理对象注册到 Spring 容器中,实现依赖注入。

  • 三、映射文件(Mapper XML)的加载方式

    3.1 Spring XML 配置方式

    通过 SqlSessionFactoryBean 的 mapperLocations 属性指定 Mapper XML 文件的路径:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations" value="classpath:mapper/*.xml" />
    <property name="typeAliasesPackage" value="com.example.entity" />
    </bean>

    3.2 代码配置方式

    通过 SqlSessionFactoryBean 的 setMapperLocations 方法加载 Mapper XML 文件:

    @Primary
    @Bean(name = "test1dbSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("test1db") DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setTypeAliasesPackage("com.maxplus1.demo.entity");
    factoryBean.setMapperLocations(
    new PathMatchingResourcePatternResolver().getResources("classpath:mapper/test1db/*.xml")
    );
    return factoryBean.getObject();
    }


    四、注解式开发:脱离 XML 的 SQL 映射

    除了传统的 XML 映射方式,MyBatis 还支持通过注解直接在 Mapper 接口中定义 SQL,无需编写 Mapper XML 文件。

    4.1 常用注解示例

    @Mapper
    public interface DqlInstanceMapper {

    @Select({
    "select",
    " id,",
    " gmt_create as gmtCreate,",
    " gmt_modified as gmtModified,",
    " dql,",
    " dql_instance_id as dqlInstanceId",
    "from",
    " tg_rpc2_dql_dql",
    "where",
    " deleted = 'N' and",
    " dql_instance_id = #{dqlInstanceId}",
    "order by id desc"
    })
    List<DqlInstanceDO> getByDqlInstanceId(@Param("dqlInstanceId") String dqlInstanceId);
    }

    4.2 注解式开发的加载方式

    注解式 Mapper 的加载依赖于 MyBatis 的核心配置文件 mybatis-config.xml,通过 <mappers> 标签指定扫描的包:

    <configuration>
    <properties resource="jdbc.properties" />
    <typeAliases>
    <package name="com.example.entity" />
    </typeAliases>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC" />
    <dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <package name="com.example.mapper" />
    </mappers>
    </configuration>

    然后在 Spring 配置中通过 configLocation 属性加载该配置文件:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    </bean>


    五、完全脱离 XML 的 MyBatis 配置

    在 Spring Boot 等现代化项目中,我们可以完全脱离 XML 配置,通过纯代码方式构建 MyBatis 环境:

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setTypeAliasesPackage("com.example.entity");

    // 直接添加 Mapper 接口(替代 XML 配置)
    Configuration configuration = new Configuration();
    configuration.addMappers("com.example.mapper"); // 批量添加
    // 或单个添加:configuration.addMapper(UserMapper.class);
    factoryBean.setConfiguration(configuration);

    return factoryBean.getObject();
    }


    补充:MyBatis 与 Spring 集成常见问题

    一、Mapper 接口无法注入(NoSuchBeanDefinitionException)

    现象

    启动 Spring 项目时抛出 NoSuchBeanDefinitionException: No qualifying bean of type 'xxx.Mapper' available。

    常见原因

  • @MapperScan 注解扫描的包路径错误(如拼写错误、层级不对);
  • Mapper 接口未添加 @Mapper 注解(未使用 @MapperScan 时);
  • MapperScannerConfigurer 配置的 basePackage 错误;
  • 多数据源场景下,Mapper 未绑定到指定的 SqlSessionFactory。
  • 解决方案

  • 校验包路径:确保 @MapperScan("com.example.mapper") 与 Mapper 接口的实际包一致;
  • 兜底方案:在 Mapper 接口上添加 @Mapper 注解(优先级高于 @MapperScan);
  • 多数据源配置示例:@MapperScan(basePackages = "com.example.mapper.test1", sqlSessionFactoryRef = "test1dbSqlSessionFactory")
    @MapperScan(basePackages = "com.example.mapper.test2", sqlSessionFactoryRef = "test2dbSqlSessionFactory")
  • 二、映射文件加载失败(BindingException: Invalid bound statement)

    现象

    调用 Mapper 方法时抛出 BindingException: Invalid bound statement (not found): xxx.Mapper.xxxMethod。

    常见原因

  • Mapper XML 文件的 namespace 与接口全类名不一致;
  • XML 中 id 与接口方法名不一致;
  • Mapper XML 文件未被加载(如路径配置错误、文件不在 resources 目录);
  • Maven 打包时过滤了 XML 文件(仅编译 Java 文件,忽略 XML)。
  • 解决方案

  • 严格校验:namespace="com.example.mapper.UserMapper" 必须与接口全类名完全一致;
  • 检查文件加载配置:// 确保路径匹配 XML 实际位置(如 resources/mapper/test1db/*.xml)
    factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
    .getResources("classpath:mapper/test1db/*.xml"));
  • 修复 Maven 打包配置(pom.xml):<build>
    <resources>
    <resource>
    <directory>src/main/java</directory>
    <includes>
    <include>**/*.xml</include>
    </includes>
    </resource>
    <resource>
    <directory>src/main/resources</directory>
    </resource>
    </resources>
    </build>
  • 三、动态代理生成失败(ClassNotFoundException)

    现象

    初始化时抛出 ClassNotFoundException: xxx.Mapper。

    常见原因

  • Mapper 接口的类路径拼写错误(如 XML 中 namespace 少写包名);
  • Mapper 接口未被编译(如不在 src/main/java 目录);
  • 依赖冲突:MyBatis 与 Spring 版本不兼容。
  • 解决方案

  • 核对全类名:确保 namespace 是「包名+接口名」(如 com.example.mapper.UserMapper);
  • 版本兼容:Spring Boot 2.x 建议使用 MyBatis 3.5+,Spring 5.x 建议搭配 MyBatis-Spring 2.0+;<!– 推荐版本组合 –>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
    </dependency>
  • 四、注解式 SQL 执行报错(SQLSyntaxErrorException)

    现象

    使用 @Select/@Insert 等注解时抛出 SQL 语法错误。

    常见原因

  • 注解内 SQL 拼接错误(如缺少空格、关键字拼写错误);
  • 参数绑定错误(如 @Param 注解缺失、参数名不匹配);
  • 字段别名与实体类属性不匹配(如数据库 gmt_create 未映射到 gmtCreate)。
  • 解决方案

  • SQL 格式化:注解内 SQL 分行编写,确保关键字、空格正确:@Select({
    "select id, gmt_create as gmtCreate, name",
    "from t_user",
    "where id = #{id}" // 避免行尾缺少空格导致拼接错误
    })
  • 必加 @Param:多参数时必须通过 @Param 指定参数名:List<User> selectByCondition(@Param("name") String name, @Param("age") Integer age);
  • 五、多数据源切换失效

    现象

    配置多数据源后,所有操作都走默认数据源。

    常见原因

  • 未指定 sqlSessionFactoryRef,导致所有 Mapper 绑定到默认数据源;
  • 事务管理器未绑定对应的数据源;
  • 动态数据源切换逻辑未生效(如 AOP 切面未拦截)。
  • 解决方案

  • 明确绑定 Mapper 到指定数据源:@MapperScan(basePackages = "com.example.mapper.test2",
    sqlSessionFactoryRef = "test2dbSqlSessionFactory",
    sqlSessionTemplateRef = "test2dbSqlSessionTemplate")
  • 配置专属事务管理器:@Bean(name = "test2dbTransactionManager")
    public DataSourceTransactionManager test2dbTransactionManager(@Qualifier("test2db") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
    }
  • 六、TypeAlias 失效(ClassNotFoundException: xxx.Entity)

    现象

    XML 中使用实体类简名(如 <select resultType="User">)时抛出类找不到。

    常见原因

  • 未配置 typeAliasesPackage,或包路径错误;
  • 实体类未添加 @Alias 注解(自定义别名时)。
  • 解决方案

  • 配置类型别名扫描:factoryBean.setTypeAliasesPackage("com.example.entity"); // 扫描整个包
  • 自定义别名(可选):@Alias("User")
    public class UserDO { ... }
  • 七、XML 中动态 SQL 解析失败(InvalidMappedStatementException)

    现象

    使用 <if>/<foreach> 等动态标签时抛出解析异常。

    常见原因

  • 动态标签语法错误(如 <if test="name != null"> 缺少闭合标签);
  • OGNL 表达式错误(如 test 属性中使用 == 比较字符串);
  • 参数类型不匹配(如传入 List 但 <foreach> 中指定 array)。
  • 解决方案

  • 校验标签语法:确保所有标签成对闭合,表达式正确:<if test="name != null and name != ''">
    and name like concat('%', #{name}, '%')
    </if>
  • <foreach> 正确配置:<foreach collection="ids" item="id" open="(" separator="," close=")">
    #{id}
    </foreach>
  • 八、Spring Boot 中 MyBatis 配置不生效

    现象

    application.yml 中配置的 MyBatis 属性(如 mybatis.mapper-locations)无效。

    常见原因

  • 配置前缀错误(如混用 mybatis 和 mybatis-plus 前缀);
  • 自定义 SqlSessionFactory 覆盖了自动配置;
  • 配置文件编码问题(如中文注释导致解析失败)。
  • 解决方案

  • 正确的配置前缀(纯 MyBatis):mybatis:
    mapper-locations: classpath:mapper/*.xml
    type-aliases-package: com.example.entity
    configuration:
    map-underscore-to-camel-case: true # 开启下划线转驼峰
  • 自定义 SqlSessionFactory 时手动加载配置:factoryBean.getConfiguration().setMapUnderscoreToCamelCase(true);

  • 总结

  • 核心原则:MyBatis 与 Spring 集成的核心是「Mapper 接口代理对象的注册+SQL 映射文件的加载」,所有问题都围绕这两点展开;
  • 排查顺序:先校验配置(包路径、namespace、文件名)→ 再检查代码(注解、方法名、参数)→ 最后核对环境(版本、打包、数据源);
  • 避坑技巧:
    • 统一命名规范:Mapper 接口、XML 文件、namespace 保持一致;
    • 优先使用注解+XML 混合模式:简单 SQL 用注解,复杂 SQL 用 XML;
    • 多数据源场景明确绑定关系,避免默认数据源覆盖。
  • 赞(0)
    未经允许不得转载:171主机测评 » 深入理解 MyBatis 映射文件与注解:从原理到 Spring 集成
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址