深入理解 MyBatis 映射文件与注解:从原理到 Spring 集成
一、MyBatis 核心概念:映射文件与接口的关系
在 MyBatis 配置中,除了数据源、事务等基础配置,映射文件(Mapper XML) 和 Mapper 接口 是实现 SQL 映射的核心。它们的协作关系可以拆解为以下几个关键步骤:
1.1 Mapper 接口的必要性
- Mapper 接口是 MyBatis 动态代理的“契约”。加载映射文件时,MyBatis 会解析文件的 namespace 属性,并根据该值加载对应的接口类。
- 如果接口不存在,MyBatis 会直接抛出 ClassNotFoundException,因此必须确保接口与映射文件的 namespace 完全一致。
1.2 动态代理对象的构建
当映射文件加载完成后,MyBatis 会完成以下操作:
// 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 两种方式的核心原理
无论是单个注册还是批量注册,底层逻辑都是:
三、映射文件(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(basePackages = "com.example.mapper.test2", sqlSessionFactoryRef = "test2dbSqlSessionFactory")
二、映射文件加载失败(BindingException: Invalid bound statement)
现象
调用 Mapper 方法时抛出 BindingException: Invalid bound statement (not found): xxx.Mapper.xxxMethod。
常见原因
解决方案
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/test1db/*.xml"));
<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。
常见原因
解决方案
<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 语法错误。
常见原因
解决方案
"select id, gmt_create as gmtCreate, name",
"from t_user",
"where id = #{id}" // 避免行尾缺少空格导致拼接错误
})
五、多数据源切换失效
现象
配置多数据源后,所有操作都走默认数据源。
常见原因
解决方案
sqlSessionFactoryRef = "test2dbSqlSessionFactory",
sqlSessionTemplateRef = "test2dbSqlSessionTemplate")
public DataSourceTransactionManager test2dbTransactionManager(@Qualifier("test2db") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
六、TypeAlias 失效(ClassNotFoundException: xxx.Entity)
现象
XML 中使用实体类简名(如 <select resultType="User">)时抛出类找不到。
常见原因
解决方案
public class UserDO { ... }
七、XML 中动态 SQL 解析失败(InvalidMappedStatementException)
现象
使用 <if>/<foreach> 等动态标签时抛出解析异常。
常见原因
解决方案
and name like concat('%', #{name}, '%')
</if>
#{id}
</foreach>
八、Spring Boot 中 MyBatis 配置不生效
现象
application.yml 中配置的 MyBatis 属性(如 mybatis.mapper-locations)无效。
常见原因
解决方案
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰
总结
- 统一命名规范:Mapper 接口、XML 文件、namespace 保持一致;
- 优先使用注解+XML 混合模式:简单 SQL 用注解,复杂 SQL 用 XML;
- 多数据源场景明确绑定关系,避免默认数据源覆盖。



