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

290 阅读4分钟

Mybatis Mapper动态代理的实现

一、Mapper创建示例

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

// 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方法注册的:

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接口的代理对象。
// 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 {
        <<interface>>  // 用户自定义的数据库操作接口
        +User selectById(Long id)  // 根据ID查询用户
        +int insert(User user)  // 插入新用户
    }

    class MapperRegistry {
        - Map<Class<?>, MapperProxyFactory<?>> knownMappers  // 存储Mapper接口与代理工厂的映射关系
        + addMapper(Class<T> type) void  // 注册Mapper接口到容器
        + getMapper(Class<T> type, SqlSession sqlSession) T  // 获取Mapper接口的代理实例
    }

    class MapperProxyFactory {
        - Class<T> mapperInterface  // 关联的目标Mapper接口(如UserMapper)
        + MapperProxyFactory(Class<T> mapperInterface)  // 初始化时绑定Mapper接口
        + newInstance(SqlSession sqlSession) T  // 生成MapperProxy代理对象
    }

    class MapperProxy {
        - SqlSession sqlSession  // 用于执行数据库操作的会话对象
        - Class<T> mapperInterface  // 被代理的Mapper接口
        - Map<Method, MapperMethod> 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 核心属性

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 接口的任何方法时,都会被该方法拦截并处理:

@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,缓存中没有则创建并缓存。
    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:封装方法的签名信息(如参数解析、返回值类型、是否返回集合等)。
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通过方法类型(增删改查)以及返回参数,执行不同的分支:

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<T> mapperInterface  // 被代理的Mapper接口类型
        - Map<Method, MapperMethod> methodCache  // 缓存方法与MapperMethod的映射
        + MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache)  // 构造函数
        + invoke(Object proxy, Method method, Object[] args) Object  // 拦截方法调用的核心方法
        - cachedMapperMethod(Method method) MapperMethod  // 获取或创建缓存的MapperMethod
    }

    class MapperMethod {
        - SqlCommand sqlCommand  // 封装SQL命令信息(ID和类型)
        - MethodSignature methodSignature  // 封装方法签名(参数、返回值等)
        + MapperMethod(Class<T> mapperInterface, Method method, Configuration config)  // 构造函数
        + execute(SqlSession sqlSession, Object[] args) Object  // 执行SQL操作的核心方法
    }

    class SqlSession {
        + <T> T selectOne(String statement, Object parameter)  // 执行单条查询
        + <E> List<E> 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> T getMapper(Class<T> 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做了缓存,以避免重复的解析。