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 创建过程解析
-
用户调用
SqlSession.getMapper()用户通过已创建的
SqlSession对象,传入目标 Mapper 接口(如UserMapper.class),请求获取接口实例。 -
SqlSession 委托给 MapperRegistry
SqlSession内部并不直接创建 Mapper,而是将请求转发给Configuration中的MapperRegistry(Mapper注册中心),同时传递自身实例作为参数。 -
MapperRegistry 查找代理工厂
MapperRegistry从内部缓存knownMappers中,根据接口类型查找对应的MapperProxyFactory(每个Mapper接口对应一个专属工厂)。这个映射关系,是在配置解析过程中,通过
MapperRegistry的addMapper方法注册的:
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();
}
}
}
- 代理工厂创建代理实例
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实现了InvocationHandler的invoke方法,这是动态代理的核心拦截方法。当调用 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);
}
}
逻辑解析
- 对于
Object类的方法(如toString()、hashCode()),直接通过反射调用原生实现,不涉及数据库操作。 - 对于 Java8+ 的接口默认方法(带
default关键字),通过invokeDefaultMethod()处理,也就是直接调Mapper中的default方法。 cachedMapperMethod(method):从缓存中获取当前方法对应的MapperMethod,缓存中没有则创建并缓存。
private MapperMethod cachedMapperMethod(Method method) {
// 从缓存获取,若不存在则创建并缓存
return methodCache.computeIfAbsent(method, k ->
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
);
}
Q:还记得methodCache是从哪里来的么?
A: MapperProxyFactory在创建的时候提供的,methodCache是MapperProxyFactory的一个属性,因此一个MapperProxyFactory无论创建多少Mapper,methodCache都是共享的。
- 通过
MapperMethod.execute(sqlSession, args);执行SQL
3.3 MapperMethod
核心属性 MapperMethod有两个核心属性,在构造是初始化:
SqlCommand:封装 SQL 命令的元信息(如SQL类型、对应的MappedStatementID)。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做了缓存,以避免重复的解析。