1. Mybatis Mapper动态代理创建&实现

Mybatis Mapper动态代理的实现

一、Mapper创建示例

大家通常在Spring的环境下使用Mybatis,可能已经忘记了原生的Mybatis是如何使用了,这里举个简单的例子,帮助大家回忆一下:

java 复制代码
// 1. 加载MyBatis核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 构建SqlSessionFactory(会话工厂)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 开启数据库会话(try-with-resources自动关闭资源)
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    // 4. 获取Mapper接口的代理实例
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 5. 执行数据库操作
    User user = userMapper.selectById(1L);
    System.out.println("查询结果:" + user);
}

二、Mapper创建过程解析

从例子中可以看到,原生的Mybatis是通过SQLSession的getMapper方法创建实例的,接下来我们通过时序图来解析下这个Mapper是如何创建的。

2.1 Mapper创建时序图

sequenceDiagram participant User participant SqlSession participant Configuration participant MapperRegistry participant MapperProxyFactory participant MapperProxy User->>SqlSession: getMapper(UserMapper.class) SqlSession->>Configuration: getMapper(UserMapper.class, this) Configuration->>MapperRegistry: getMapper(UserMapper.class, sqlSession) MapperRegistry->>MapperRegistry: getMapperProxyFactory(UserMapper.class) MapperRegistry->>MapperProxyFactory: newInstance(sqlSession) MapperProxyFactory->>MapperProxy: 构造MapperProxy实例 MapperProxyFactory->>MapperProxy: 绑定SqlSession与接口 MapperProxyFactory-->>MapperRegistry: 返回代理对象 MapperRegistry-->>SqlSession: 返回代理对象 SqlSession-->>User: 返回UserMapper代理实例

2.2 创建过程解析

  1. 用户调用 SqlSession.getMapper()

    用户通过已创建的SqlSession对象,传入目标 Mapper 接口(如UserMapper.class),请求获取接口实例。

  2. SqlSession 委托给 MapperRegistry

    SqlSession内部并不直接创建 Mapper,而是将请求转发给Configuration中的MapperRegistry(Mapper注册中心),同时传递自身实例作为参数。

  3. MapperRegistry 查找代理工厂

    MapperRegistry从内部缓存knownMappers中,根据接口类型查找对应的MapperProxyFactory(每个Mapper接口对应一个专属工厂)。

    这个映射关系,是在配置解析过程中,通过MapperRegistryaddMapper方法注册的:

java 复制代码
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {  // 只处理接口
        if (!hasMapper(type)) {
            // 创建该接口对应的MapperProxyFactory
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // 解析接口中的注解或XML映射(如方法上的@Select等)
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
        }
    }
}
  1. 代理工厂创建代理实例 MapperProxyFactory接收SqlSession参数,创建MapperProxy(实现InvocationHandler的代理处理器),并通过JDK动态代理生成UserMapper接口的代理对象。
java 复制代码
// Mapper中每个方法代理实现的缓存
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();  

public T newInstance(SqlSession sqlSession) {  
    // 创建MapperProxy
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);  
    return newInstance(mapperProxy);  
}

@SuppressWarnings("unchecked")  
protected T newInstance(MapperProxy<T> mapperProxy) {  
    // 通过JDK的动态代理创建Mapper的代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  
}  

2.3 类之间关系

classDiagram direction TB class UserMapper { <> // 用户自定义的数据库操作接口 +User selectById(Long id) // 根据ID查询用户 +int insert(User user) // 插入新用户 } class MapperRegistry { - Map, MapperProxyFactory> knownMappers // 存储Mapper接口与代理工厂的映射关系 + addMapper(Class type) void // 注册Mapper接口到容器 + getMapper(Class type, SqlSession sqlSession) T // 获取Mapper接口的代理实例 } class MapperProxyFactory { - Class mapperInterface // 关联的目标Mapper接口(如UserMapper) + MapperProxyFactory(Class mapperInterface) // 初始化时绑定Mapper接口 + newInstance(SqlSession sqlSession) T // 生成MapperProxy代理对象 } class MapperProxy { - SqlSession sqlSession // 用于执行数据库操作的会话对象 - Class mapperInterface // 被代理的Mapper接口 - Map methodCache // 缓存方法对应的执行逻辑 + invoke(Object proxy, Method method, Object[] args) Object // 拦截接口方法调用并处理 } MapperRegistry "1" *-- "*" MapperProxyFactory : 管理多个代理工厂(聚合关系) MapperProxyFactory "1" -- "1" UserMapper : 对应一个用户自定义接口 MapperProxyFactory "1" -- "1" MapperProxy : 创建代理实例(工厂模式)

三、动态代理的实现:MapperProxy

MapperProxy实现了InvocationHandler接口,通过JDK实现了对Mapper的动态代理,下面我们解析下MapperProxy。

3.1 核心属性

java 复制代码
public class MapperProxy<T> implements InvocationHandler, Serializable {
    // 数据库会话,用于执行SQL(如selectOne、insert等)
    private final SqlSession sqlSession;
    // 被代理的Mapper接口类型(如UserMapper.class)
    private final Class<T> mapperInterface;
    // 缓存Mapper接口方法与MapperMethod的映射,避免重复解析
    private final Map<Method, MapperMethod> methodCache;
    // ... 构造方法等
}

3.2 核心方法invoke

MapperProxy实现了InvocationHandlerinvoke方法,这是动态代理的核心拦截方法。当调用 Mapper 接口的任何方法时,都会被该方法拦截并处理:

kotlin 复制代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 1. 处理Object类的方法(如toString、hashCode等),直接调用原生实现
    if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
    }
    // 2. 处理接口默认方法(Java 8+)
    else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
    }
    // 3. 处理Mapper接口的业务方法(核心逻辑)
    else {
        // 从缓存获取或创建MapperMethod,执行SQL操作
        return cachedMapperMethod(method).execute(sqlSession, args);
    }
}

逻辑解析

  1. 对于Object类的方法(如toString()hashCode()),直接通过反射调用原生实现,不涉及数据库操作。
  2. 对于 Java8+ 的接口默认方法(带default关键字),通过invokeDefaultMethod()处理,也就是直接调Mapper中的default方法。
  3. cachedMapperMethod(method):从缓存中获取当前方法对应的MapperMethod,缓存中没有则创建并缓存。
java 复制代码
    private MapperMethod cachedMapperMethod(Method method) {
        // 从缓存获取,若不存在则创建并缓存
        return methodCache.computeIfAbsent(method, k -> 
            new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
        );
    }

Q:还记得methodCache是从哪里来的么?

A: MapperProxyFactory在创建的时候提供的,methodCacheMapperProxyFactory的一个属性,因此一个MapperProxyFactory无论创建多少Mapper,methodCache都是共享的。

  1. 通过MapperMethod.execute(sqlSession, args);执行SQL

3.3 MapperMethod

核心属性 MapperMethod有两个核心属性,在构造是初始化:

  • SqlCommand :封装 SQL 命令的元信息(如SQL类型、对应的MappedStatement ID)。
  • MethodSignature:封装方法的签名信息(如参数解析、返回值类型、是否返回集合等)。
java 复制代码
public class MapperMethod {
  private final SqlCommand command;
  private final MethodSignature method;

  // 构造函数:初始化SqlCommand和MethodSignature
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
  // ...
}

MapperMethod通过方法类型(增删改查)以及返回参数,执行不同的分支:

java 复制代码
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据SQL命令类型(如SELECT/INSERT/UPDATE/DELETE)执行对应操作
    switch (command.getType()) {
        case INSERT:
            Object param = methodSignature.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        case UPDATE:
            Object param = method.convertArgsToSqlCommandParam(args);  
            result = rowCountResult(sqlSession.update(command.getName(), param));  
            break;
        case DELETE:
            Object param = method.convertArgsToSqlCommandParam(args);  
            result = rowCountResult(sqlSession.delete(command.getName(), param));  
            break;
        case SELECT:
            if (methodSignature.returnsVoid() && methodSignature.hasResultHandler()) {
                // 处理带ResultHandler的查询
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (methodSignature.returnsMany()) {
                // 处理返回集合的查询(selectList)
                result = executeForMany(sqlSession, args);
            } else if (methodSignature.returnsMap()) {
                // 处理返回Map的查询
                result = executeForMap(sqlSession, args);
            } else {
                // 处理返回单个对象的查询(selectOne)
                Object param = methodSignature.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        case FLUSH:  
            result = sqlSession.flushStatements();  
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {  
        throw new BindingException("Mapper method '" + command.getName()  
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");  
        }  
        return result;
}

3.4 关系图

classDiagram class MapperProxy { - SqlSession sqlSession // 数据库会话,用于执行SQL操作 - Class mapperInterface // 被代理的Mapper接口类型 - Map methodCache // 缓存方法与MapperMethod的映射 + MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) // 构造函数 + invoke(Object proxy, Method method, Object[] args) Object // 拦截方法调用的核心方法 - cachedMapperMethod(Method method) MapperMethod // 获取或创建缓存的MapperMethod } class MapperMethod { - SqlCommand sqlCommand // 封装SQL命令信息(ID和类型) - MethodSignature methodSignature // 封装方法签名(参数、返回值等) + MapperMethod(Class mapperInterface, Method method, Configuration config) // 构造函数 + execute(SqlSession sqlSession, Object[] args) Object // 执行SQL操作的核心方法 } class SqlSession { + T selectOne(String statement, Object parameter) // 执行单条查询 + List selectList(String statement, Object parameter) // 执行多条查询 + int insert(String statement, Object parameter) // 执行插入操作 + int update(String statement, Object parameter) // 执行更新操作 + int delete(String statement, Object parameter) // 执行删除操作 + void commit() // 提交事务 + void rollback() // 回滚事务 + T getMapper(Class type) // 获取Mapper代理对象 + void close() // 关闭会话 } class SqlCommand { - String name // MappedStatement的ID(namespace+methodName) - SqlCommandType type // SQL命令类型(SELECT/INSERT/UPDATE/DELETE等) + getName() String // 获取MappedStatement的ID + getType() SqlCommandType // 获取SQL命令类型 } class MethodSignature { - ParamNameResolver paramNameResolver // 参数解析器 - Class returnType // 方法返回值类型 - boolean returnsMany // 是否返回集合类型 + convertArgsToSqlCommandParam(Object[] args) Object // 转换参数为SQL可识别的格式 + getReturnType() Class // 获取返回值类型 + returnsMany() boolean // 判断是否返回集合 } MapperProxy "1" --> "1" SqlSession : 持有(依赖执行SQL) MapperProxy "1" --> "*" MapperMethod : 缓存(通过methodCache关联) MapperMethod "1" --> "1" SqlCommand : 包含(组合关系) MapperMethod "1" --> "1" MethodSignature : 包含(组合关系) MapperMethod "1" --> "1" SqlSession : 依赖(调用其方法执行SQL)

小结

MapperProxy作为 MyBatis 动态代理的核心处理器,通过JDK动态代理,拦截Mapper接口方法调用,把每个方法的执行,委托给对应MapperMethod。而MapperMethod通过SqlSession,执行真正的SQL。为了避免方法的重复解析,MapperProxy为每个MapperMethod做了缓存,以避免重复的解析。

相关推荐
两码事28 分钟前
告别繁琐的飞书表格API调用,让飞书表格操作像操作Java对象一样简单!
java·后端
shark_chili1 小时前
面试官再问synchronized底层原理,这样回答让他眼前一亮!
后端
灵魂猎手1 小时前
2. MyBatis 参数处理机制:从 execute 方法到参数流转全解析
java·后端·源码
易元1 小时前
模式组合应用-桥接模式(一)
后端·设计模式
柑木1 小时前
隐私计算-SecretFlow/SCQL-SCQL的两种部署模式
后端·安全·数据分析
泉城老铁2 小时前
在秒杀场景中,如何通过动态调整线程池参数来应对流量突增
后端·架构
小悲伤2 小时前
金蝶eas-dep反写上游单据
后端
用户9194287745952 小时前
FastAPI (Python 3.11) Linux 实战搭建与云部署完全指南(经验)
后端
白露与泡影2 小时前
Spring容器初始化源码解析
java·python·spring