MyBatis Mapper 接口动态代理与 Spring 注入原理

1. Mapper 接口的本质

public interface UserMapper {
    User findById(Long id);
}
  • 接口没有实现类,只有方法定义。
  • MyBatis 通过 XML 或注解映射 SQL。
  • Spring 容器要注入 Mapper,必须生成对象实例。

2. Spring + MyBatis 如何生成 Mapper Bean

2.1 扫描 Mapper 接口

@MapperScan("com.elias.pay.mapper")
  • Spring + MyBatis 的 MapperScannerConfigurer 会扫描指定包下的接口。
  • 找到所有 Mapper 接口,例如 UserMapper, OrderMapper

2.2 为接口生成代理对象

  • JDK 动态代理(默认方式):proxy 就是一个 接口的实现对象
UserMapper proxy = (UserMapper) Proxy.newProxyInstance(
    interfaceClass.getClassLoader(),
    new Class[]{UserMapper.class},
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 调用 SqlSession 执行 SQL
            return sqlSession.selectOne(method.getName(), args);
        }
    }
);
  • 说明:

    • Proxy.newProxyInstance 生成接口代理对象
    • invoke() 拦截方法调用,执行对应 SQL
    • 返回的 proxy 是 Spring 容器中注入的 Mapper Bean
  • CGLIB 代理

    • 通过继承类生成代理(可选)
    • 原理类似:拦截方法调用 → 执行 SQL

3. Spring 注入流程

  1. 扫描 Mapper 接口 → 找到所有接口
  2. 生成 MapperFactoryBean → 注册到 Spring 容器
  3. 创建动态代理对象 → 实现接口、拦截方法调用
  4. 注入到容器 → 使用 @Autowired 注入 Bean
  5. 调用 Mapper 方法 → 代理拦截 → 执行 SQL → 返回结果
@Autowired
private UserMapper userMapper;

userMapper.findById(1L); // 实际执行代理对象的 invoke() → 执行 SQL

4. 内部调用与事务代理说明

  • 代理对象的方法调用 → 触发切面逻辑(事务、日志等)
  • 同类内部方法调用 this.method() → 绕过代理对象 → 不触发事务
  • 外部调用 Bean 方法 → 通过代理对象 → 事务生效
调用方式 事务是否生效 原因
内部调用 this.method() 不生效 绕过代理对象,事务拦截器没触发
外部调用 Bean 方法 生效 调用代理对象 → 事务拦截器触发

5. 核心总结

  1. Mapper 接口本身没有实现类,Spring + MyBatis 通过动态代理生成对象
  2. 代理对象负责拦截方法调用,并执行对应 SQL。
  3. 注入容器的是 代理对象,调用方法时会执行 SQL。
  4. 内部调用不会触发事务 / 切面逻辑,外部调用代理对象方法才生效。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐