MyBatis-Spring 讓 Mapper 接口無需寫實現類,直接注入 Service 即可使用。本文深度解析 MapperFactoryBean 代理機制、MapperScannerConfigurer 掃描配置、多數據源場景下的精準注入策略,助你構建乾淨、可維護的持久層架構。
Mapper 接口 (UserMapper)
↓
MapperFactoryBean (FactoryBean)
↓ 生成 JDK 動態代理
SqlSessionTemplate (線程安全 SqlSession 包裝)
↓ 委派
SqlSessionFactory (會話工廠)
↓
DataSource / TransactionManager
public interface UserMapper {
User getUser(String userId);
void insertUser(User user);
List selectUsers(@Param("dto") UserQueryDTO dto);
}
INSERT INTO users(...) VALUES(...)
| 屬性 | 類型 | 說明 | 默認值 |
|---|---|---|---|
| basePackage | String | 掃描基礎包路徑,遞歸掃描子包 | 無(必填) |
| sqlSessionFactory | String/Object | 指定 SqlSessionFactory Bean 名稱/引用 | 自動檢測單個 |
| sqlSessionTemplate | String/Object | 指定 SqlSessionTemplate Bean 名稱/引用 | 自動檢測單個 |
| annotationClass | Class | 標記註解:僅掃描帶此註解的接口 | null(掃描所有) |
| markerInterface | Class | 標記接口:僅掃描繼承此接口的接口 | null(掃描所有) |
| nameGenerator | BeanNameGenerator | Bean 命名策略 | 首字母小寫類名 |
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBatisMapper {}
// Mapper 接口加註解
@MyBatisMapper
public interface UserMapper { ... }
// 掃描器配置:僅掃描帶 @MyBatisMapper 的接口
原則:每個數據源對應一組 Mapper 包、一個 SqlSessionFactory、一個 MapperScannerConfigurer。
@SpringBootApplication
@MapperScan("com.example.mapper") // 或多包 @MapperScan({"pkg1", "pkg2"})
public class Application { ... }
@Mapper // 單個接口標記,無需 @MapperScan
public interface UserMapper { ... }
mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.model
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
# 多數據源需手動配置 SqlSessionFactory Bean
public class MapperFactoryBean implements FactoryBean, InitializingBean {
private Class mapperInterface;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
@Override
public T getObject() throws Exception {
return getSqlSessionTemplate().getMapper(mapperInterface);
}
// SqlSessionTemplate.getMapper()
public T getMapper(Class type) {
return getSqlSessionFactory().getConfiguration().getMapper(type, this);
}
// Configuration.getMapper() → MapperRegistry.getMapper()
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory mapperProxyFactory =
(MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
return mapperProxyFactory.newInstance(sqlSession); // JDK 動態代理
}
}
public class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
private Class mapperInterface;
private Map methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object 方法直接調用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// default 方法(JDK 8+ 接口默認方法)
if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
// 核心:委派給 MapperMethod 執行
return cachedMapperMethod(method).execute(sqlSession, args);
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| BindingException: Type not known to MapperRegistry | Mapper 接口未被掃描/註冊、包路徑錯誤 | 檢查 @MapperScan basePackage、XML namespace、接口是否 public |
| 多數據源 Mapper 注入錯誤 | MapperScannerConfigurer 自動綁定到錯誤的 SqlSessionFactory | 顯式指定 sqlSessionFactory 屬性、分包掃描 |
| 事務不生效 | SqlSessionTemplate 未使用 Spring 管理的事務管理器 | 確保 SqlSessionFactoryBean 注入 dataSource、配置 DataSourceTransactionManager |
| Mapper 方法重複定義 | 同名方法在不同 Mapper 接口、XML id 衝突 | namespace 隔離、方法名唯一化 |
| 泛型參數類型擦除導致映射錯誤 | List | 使用 @Param 或 ResultType 指定、或自定義 TypeHandler |
mapper 包,便於多數據源分離掃描UserMapper.java ↔ UserMapper.xml,Maven 資源複製配置userMapper),Service 注入字段同名cache-enabled: false,分佈式環境用 Redismap-underscore-to-camel-case: true,減少 resultMap 手寫MyBatis-Spring 的設計精髓在於:接口即契約,代理隱藏實現,Spring 管理生命週期。掌握 MapperFactoryBean 代理鏈、MapperScannerConfigurer 掃描策略、多數據源精準綁定,能讓持久層代碼保持極致簡潔,業務邏輯聚焦於 Service 層,實現真正的關注點分離。