MyBatis 源码解析:Mapper 接口动态代理原理

摘要

MyBatis 通过动态代理为 Mapper 接口生成实现类,开发者无需手动编写实现类,SQL 语句会在运行时自动执行。本文深入解析 MyBatis 中的动态代理机制,讲解 MapperProxy 如何拦截接口方法调用并执行 SQL。通过自定义实现,我们将模拟 MyBatis 的代理机制,帮助开发者更好地理解其工作原理。


前言

MyBatis 动态代理机制的核心是通过 MapperProxy 生成 Mapper 接口的实现类。开发者定义的接口无需手动实现,而是 MyBatis 在运行时为接口动态创建代理类。当调用接口中的方法时,代理类会根据方法名与 SQL 映射,执行对应的数据库操作。

在本文中,我们将通过源码解析和自定义实现,详细讲解 MyBatis 中的 Mapper 动态代理原理,并帮助开发者理解该技术的实际应用。


自定义实现:模拟 MyBatis 的动态代理机制

目标与功能

我们将通过自定义实现一个简化版的动态代理机制,模拟 MyBatis 中的 Mapper 动态代理机制,主要实现以下功能:

  1. 生成 Mapper 接口的代理类:利用 Java 动态代理机制生成 Mapper 接口的实现。
  2. 拦截方法调用:拦截接口方法的调用,模拟 SQL 查询的生成与执行。
  3. 返回查询结果:返回与 SQL 查询对应的结果。

实现步骤

  1. 定义 Mapper 接口:创建一个简单的 Mapper 接口,用于模拟数据库操作。
  2. 创建动态代理类 :实现 InvocationHandler 接口,拦截接口方法并生成 SQL。
  3. 测试代理类:通过代理类调用方法,验证 SQL 生成及结果返回。

1. 定义 Mapper 接口

首先,定义一个简单的 UserMapper 接口,用于模拟数据库中的用户查询操作。

java 复制代码
public interface UserMapper {
    String getUserById(int id);
}
  • 功能 :getUserById(int id) 方法用于根据用户 ID 查询用户信息。
  • 说明:接口本身没有实现类,动态代理将在运行时为该接口生成实现类。

2. 创建动态代理类:MapperProxy

接下来,我们实现一个 MapperProxy 动态代理类,它通过实现 InvocationHandler 接口来拦截对接口方法的调用,并生成相应的 SQL 语句。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MapperProxy implements InvocationHandler {

    // 用于创建代理实例
    public static <T> T newInstance(Class<T> mapperInterface) {
        return (T) Proxy.newProxyInstance(
                mapperInterface.getClassLoader(),
                new Class[]{mapperInterface},
                new MapperProxy()
        );
    }

    // 拦截接口方法调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("getUserById")) {
            // 模拟 SQL 生成和执行
            System.out.println("执行SQL:SELECT * FROM users WHERE id = " + args[0]);
            // 返回模拟查询结果
            return "User" + args[0];
        }
        return null;
    }
}
实现细节
  • newInstance 方法 :通过 Proxy.newProxyInstance 方法创建 Mapper 接口的代理实例。
  • invoke 方法:拦截接口方法调用,根据方法名和参数生成 SQL 语句。在这里,我们模拟了 SQL 查询语句,并返回查询结果。

3. 测试动态代理类

通过调用动态代理生成的 UserMapper 代理类来验证我们的实现。

java 复制代码
public class TestMapperProxy {
    public static void main(String[] args) {
        // 通过代理生成 UserMapper 实例
        UserMapper userMapper = MapperProxy.newInstance(UserMapper.class);
        
        // 调用 getUserById 方法
        String result = userMapper.getUserById(1);
        
        // 打印查询结果
        System.out.println("查询结果:" + result);
    }
}

输出结果:

复制代码
执行SQL:SELECT * FROM users WHERE id = 1
查询结果:User1
关键点说明
  • SQL 生成模拟 :代理类拦截了对 getUserById 方法的调用,生成了对应的 SQL 语句,这模拟了 MyBatis 中 SQL 执行的逻辑。
  • 返回结果:代理类返回了模拟的查询结果,展示了根据传入参数生成相应的 SQL 语句及返回值。

4. 扩展功能:支持多个方法

我们可以通过扩展 MapperProxy 来支持多个接口方法。以下是支持多个方法的版本:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class EnhancedMapperProxy implements InvocationHandler {

    // 拦截接口方法调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("getUserById")) {
            System.out.println("执行SQL:SELECT * FROM users WHERE id = " + args[0]);
            return "User" + args[0];
        } else if (methodName.equals("getAllUsers")) {
            System.out.println("执行SQL:SELECT * FROM users");
            return "All users data";
        }
        return null;
    }

    // 创建代理实例
    public static <T> T newInstance(Class<T> mapperInterface) {
        return (T) Proxy.newProxyInstance(
                mapperInterface.getClassLoader(),
                new Class[]{mapperInterface},
                new EnhancedMapperProxy()
        );
    }
}
扩展支持的功能:
  • 在 EnhancedMapperProxy 中,除了 getUserById,我们还支持了 getAllUsers 方法,用于返回所有用户信息。

自定义实现类图

UserMapper +getUserById(int id) +getAllUsers() EnhancedMapperProxy +invoke(Object proxy, Method method, Object\[\] args) +newInstance(Class mapperInterface)


动态代理执行流程图

创建接口代理 调用接口方法 进入MapperProxy的invoke方法 判断方法名称 生成SQL 返回结果 结束


源码解析:MyBatis 中的动态代理机制

MyBatis 的 Mapper 动态代理主要由 MapperProxyFactory 和 MapperProxy 共同完成。MapperProxyFactory 用于创建代理类,MapperProxy 则负责拦截方法调用并执行 SQL。

1. MapperProxyFactory 类

MapperProxyFactory 负责为每个 Mapper 接口生成代理类的实例:

java 复制代码
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                                          new Class[] { mapperInterface },
                                          mapperProxy);
    }
}
  • 功能 :通过 newInstance 方法创建 MapperProxy 的代理实例。

2. MapperProxy 类

MapperProxy 是 MyBatis 中负责拦截接口方法调用并执行 SQL 的核心类。它实现了 InvocationHandler 接口,通过 SqlSession 执行数据库操作。

java 复制代码
public class MapperProxy<T> implements InvocationHandler {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        return sqlSession.selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
    }
}

总结与互动

本文详细解析了 MyBatis 的 Mapper 接口动态代理机制,通过自定义实现模拟了 MapperProxy 的工作原理。MyBatis 利用 Java 动态代理,简化了数据库操作逻辑,开发者只需定义接口,框架便会自动执行相应的 SQL 操作。希望这篇文章能帮助你更好地理解 MyBatis 的动态代理机制。

如果你觉得这篇文章对你有帮助,请点赞、收藏,并在评论区留言分享你的想法!

相关推荐
CoderYanger1 天前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)
java·数据库·spring·面试·职场和发展·java-ee·mybatis
真上帝的左手3 天前
30. 技术专题-‌应用框架组件-关系型数据库-MySQL-Mybatis
数据库·分布式·mysql·中间件·mybatis
雪芽蓝域zzs3 天前
第 2 章:整合 MySQL + MyBatis,搭建数据库基础、实体类、Mapper、测试查询
数据库·mysql·mybatis
wljt4 天前
公司二-面试题
java·开发语言·mybatis
VimorphTech4 天前
文本已经改了,旧的音频生成结果还能回写吗?
java·数据库·mybatis
麻瓜code4 天前
双写一致性:为什么是“删缓存“,而不是“改缓存“
java·缓存·mybatis
郭小李5 天前
mybatis 动态 sql
java·sql·mybatis
知守观5 天前
技术考古:iBATIS 2.3.4 时代的持久层,是如何用 Map 玩转 307 个存储过程的?
java·后端·mybatis
captain3765 天前
MyBatis Plus(1)
java·服务器·mybatis
代码不停5 天前
MyBatis进阶
spring boot·mybatis