Fork me on GitHub

mybatis中Mapper接口的代理逻辑

特点

(1)Mapper文件中有Mapper接口映射关系的唯一标识,比如findById在接口中定义此方法,那么在mapper.xml肯定也有findById标签对应的sql模板,如果写错会在Mybatis启动的时候报错,达到提前校验的目的。

(2)Mapper接口在使用时不用为其实现接口,就可以自动绑定映射其对应的sql模板执行方法。在spring环境中也可以接口注入直接使用。这里注入的是Mapper接口的代理类。

这些功能就在mybatis框架的binding包下。

binging的核心组件及关系如下:

image-20220718170805045

MapperRegistry

MapperRegistry是Mybatis初始化过程中构造的一个对象,主要作用是维护Mapper接口和其对应的MapperProxyFactory。

核心字段:

1
2
3
4
5
6
7
8
public class MapperRegistry {
// 全局唯一的configuration对象,存放解析之后的全部Mybatis配置信息
private final Configuration config;
// 已知的所有映射
// key:mapperInterface,即dao的数据库接口
// value:MapperProxyFactory,即映射器代理工厂
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
}

addMapper和getMapper方法

addMapper是为Mapper接口添加对应的代理工厂到kownsMapper中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public <T> void addMapper(Class<T> type) {
// 要加入的肯定是接口,否则不添加
if (type.isInterface()) {
// 加入的是接口
if (hasMapper(type)) {
// 如果添加重复
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

getMapper是获取Mapper接口的一个代理对象,也是通过获取到knownMappers map中的MapperFactoryProxy,然后通过newInstance方法来获取新的代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 找出指定映射接口的代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通过mapperProxyFactory给出对应代理器的实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

MapperProxyFactory

MapperProxyFactory逻辑很简单,就是生成代理类的工厂。

其中核心字段为:

  • mapperInterface 即要代理的 Mapper接口
  • methodCache 用来存放Method和MapperMethod对象的键值对。 这里MapperMethod就是最终在MethodProxy中用于执行sql的地方。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MapperProxyFactory<T> {

// 对应SQL的java接口类
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

/**
* MapperProxyFactory构造方法
* @param mapperInterface 映射接口
*/
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

public Class<T> getMapperInterface() {
return mapperInterface;
}

public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 三个参数分别是:
// 创建代理对象的类加载器、要代理的接口、代理类的处理器(即具体的实现)。
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

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

}

MapperProxy

MapperProxy实现了InvocationHandler接口,用于拦截生成代理类。

代理逻辑是利用Method对应的MapperMethod去执行对应execut方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
// 与当前mapperProxy关联的sqlSession对象 用于访问数据库
private final SqlSession sqlSession;
// 被代理的mapper接口
private final Class<T> mapperInterface;
// 该Map的键为方法,值为MapperMethod对象。通过该属性,完成了MapperProxy内(即映射接口内)方法和MapperMethod的绑定
private final Map<Method, MapperMethod> methodCache;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) { // 继承自Object的方法
// 直接执行原有方法
return method.invoke(this, args);
} else if (method.isDefault()) { // 默认方法
// 执行默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 找对对应的MapperMethod对象 去执行
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用MapperMethod中的execute方法
return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}

MapperMethod

MapperMethod是最终执行sql的地方,也是存储了当前执行Mapper接口方法的Method对象。其中包含两个核心字段 sqlCommond、methodSignature。这两个都是其中的静态内部类。

SqlCommand

sqlCommand变量维护了关联sql语句的相关信息。

  • name 即唯一标识
  • type 标识是哪种类型的sql语句

其在构造函数中根据传入的Mapper接口和method方法来初始化SqlCommond。逻辑其实就是从传入接口或其父类中解析出MapperStatement对象,其能标识mapper.xml中的完整的一个sql模板。再从中解析出name和commandType。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public static class SqlCommand {

// SQL语句的名称 即唯一标识
private final String name;
// SQL语句的种类,一共分为以下六种:增、删、改、查、清缓存、未知
private final SqlCommandType type;

/**
* 根据传入接口和方法封装sql信息
* @param configuration
* @param mapperInterface
* @param method
*/
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 方法名称
final String methodName = method.getName();
// 方法所在的类。可能是mapperInterface,也可能是mapperInterface的子类
final Class<?> declaringClass = method.getDeclaringClass();
// mapper接口名称、方法名称拼起来做唯一标识
// 到configuration全局配置对象中查找sql语句
// mappedStatement就是Mapper.xml配置文件中一条SQL解析之后得到的对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
// 标记flush注解
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}

public String getName() {
return name;
}

public SqlCommandType getType() {
return type;
}

/**
* 找出指定接口指定方法对应的MappedStatement对象
* @param mapperInterface 映射接口
* @param methodName 映射接口中具体操作方法名
* @param declaringClass 操作方法所在的类。一般是映射接口本身,也可能是映射接口的子类
* @param configuration 配置信息
* @return MappedStatement对象
*/
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 数据库操作语句的编号是:接口名.方法名
String statementId = mapperInterface.getName() + "." + methodName;
// configuration保存了解析后的所有操作语句,去查找该语句
if (configuration.hasStatement(statementId)) {
// 从configuration中找到了对应的语句,返回
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
// 说明递归调用已经到终点,但是仍然没有找到匹配的结果
return null;
}
// 从方法的定义类开始,沿着父类向上寻找。找到接口类时停止
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
// 递归查找父类
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}

MethodSignature

MethodSignature主要维护了当前接口方法的信息,如返回值类型、参数和实际入参的绑定关系(运用了ParamNameResolver工具类)等。

在methodSignature.convertArgsToSqlCommandParam方法中,也是处理了@Param注解与sql模板中的参数绑定关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public static class MethodSignature {

// 返回类型是否为集合类型
private final boolean returnsMany;
// 返回类型是否是map
private final boolean returnsMap;
// 返回类型是否是空
private final boolean returnsVoid;
// 返回类型是否是cursor类型
private final boolean returnsCursor;
// 返回类型是否是optional类型
private final boolean returnsOptional;
// 返回类型
private final Class<?> returnType;
// 如果返回为map,这里记录所有的map的key @MapKey注解
private final String mapKey;
// resultHandler参数的位置
private final Integer resultHandlerIndex;
// rowBounds参数的位置
private final Integer rowBoundsIndex;
// 引用参数名称解析器
private final ParamNameResolver paramNameResolver;

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// rowBoundsIndex和resultHandlerIndex
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 解析参数名称的工具类
this.paramNameResolver = new ParamNameResolver(configuration, method);
}

public Object convertArgsToSqlCommandParam(Object[] args) {
// 通过传入实参获取 实参和参数名称的映射表
return paramNameResolver.getNamedParams(args);
}

public boolean hasRowBounds() {
return rowBoundsIndex != null;
}

public RowBounds extractRowBounds(Object[] args) {
return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
}

public boolean hasResultHandler() {
return resultHandlerIndex != null;
}

public ResultHandler extractResultHandler(Object[] args) {
return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
}

public String getMapKey() {
return mapKey;
}

public Class<?> getReturnType() {
return returnType;
}

public boolean returnsMany() {
return returnsMany;
}

public boolean returnsMap() {
return returnsMap;
}

public boolean returnsVoid() {
return returnsVoid;
}

public boolean returnsCursor() {
return returnsCursor;
}

/**
* return whether return type is {@code java.util.Optional}.
* @return return {@code true}, if return type is {@code java.util.Optional}
* @since 3.5.0
*/
public boolean returnsOptional() {
return returnsOptional;
}

// 返回指定参数的index
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}

private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}

execute方法

最终sql的执行都是通过MapperMethod的execute方法执行,这里依赖了其中的sqlCommond和methodSignature两个变量。

execute核心逻辑就是根据具体的sqlCommondType来选择执行具体的方法。其中也处理了不同的返回值

  • 对于Insert、Update、delete类型的sql执行,返回值回采用rowCountResult方法来处理,内部做了对影响行数的处理(可以直接返回boolean类型)
  • 对应Select类型,

    • 如果有executeWithResultHandler类型的参数,会按照resultHandler的回调来处理返回值。
    • 如果方法返回值为集合类型或是数组类型,则会调用 executeForMany() 方法,底层依赖 SqlSession.selectList() 方法进行查询,并将得到的 List 转换成目标集合类型。
    • 如果方法返回值为 Map 类型,则会调用 executeForMap() 方法,底层依赖 SqlSession.selectMap() 方法完成查询,并将结果集映射成 Map 集合。
    • 针对 Cursor 以及 Optional返回值的处理,也是依赖的 SqlSession 的相关方法完成查询的,这里不再展开。
    • 针对单条数据,也会依赖sqlSession.selectOne方法完成查询。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    /**
    * 执行映射接口中的方法 MapperMethod的核心方法
    * execute方法会根据要执行的sql语句的具体类型执行sqlsession的具体方法完成数据库操作
    * @param sqlSession sqlSession接口的实例,通过它可以进行数据库的操作
    * @param args 执行接口方法时传入的参数
    * @return 数据库操作结果
    */
    public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根据SQL语句类型,执行不同操作
    case INSERT: { // 如果是插入语句
    // 将参数顺序与实参对应好
    Object param = method.convertArgsToSqlCommandParam(args);
    // 执行操作并返回结果
    // rowCounntResult方法会根据方法的返回值类型对结果进行转换
    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: { // 如果是删除语句MappedStatement
    // 将参数顺序与实参对应好
    Object param = method.convertArgsToSqlCommandParam(args);
    // 执行操作并返回结果
    result = rowCountResult(sqlSession.delete(command.getName(), param));
    break;
    }
    case SELECT: // 如果是查询语句
    /**
    * 如果在方法参数列表中有 ResultHandler 类型的参数存在,则会使用 executeWithResultHandler() 方法完成查询,底层依赖的是 SqlSession.select() 方法,结果集将会交由传入的 ResultHandler 对象进行处理。
    * 如果方法返回值为集合类型或是数组类型,则会调用 executeForMany() 方法,底层依赖 SqlSession.selectList() 方法进行查询,并将得到的 List 转换成目标集合类型。
    * 如果方法返回值为 Map 类型,则会调用 executeForMap() 方法,底层依赖 SqlSession.selectMap() 方法完成查询,并将结果集映射成 Map 集合。
    * 针对 Cursor 以及 Optional返回值的处理,也是依赖的 SqlSession 的相关方法完成查询的,这里不再展开。
    */
    if (method.returnsVoid() && method.hasResultHandler()) { // 方法返回值为void,且有结果处理器
    // 使用结果处理器执行查询
    executeWithResultHandler(sqlSession, args);
    result = null;
    } else if (method.returnsMany()) { // 多条结果查询
    result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) { // Map结果查询
    result = executeForMap(sqlSession, args);
    } else if (method.returnsCursor()) { // 游标类型结果查询
    result = executeForCursor(sqlSession, args);
    } else { // 单条结果查询
    Object param = method.convertArgsToSqlCommandParam(args);
    result = sqlSession.selectOne(command.getName(), param);
    if (method.returnsOptional()
    && (result == null || !method.getReturnType().equals(result.getClass()))) {
    result = Optional.ofNullable(result);
    }
    }
    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()) {
    // 查询结果为null,但返回类型为基本类型。因此返回变量无法接收查询结果,抛出异常。
    throw new BindingException("Mapper method '" + command.getName()
    + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
    }

总结

重点介绍了 MyBatis 中的 binding 模块,正是该模块实现了 Mapper 接口与 Mapper.xml 配置文件的映射功能。

首先,介绍了 MapperRegistry 这个注册中心,其中维护了 Mapper 接口与代理工厂对象之间的映射关系。

然后,分析了 MapperProxy 和 MapperProxyFactory,其中 MapperProxyFactory 使用 JDK 动态代理方式为相应的 Mapper 接口创建了代理对象,MapperProxy 则封装了核心的代理逻辑,将拦截到的目标方法委托给对应的 MapperMethod 处理。

最后,详细讲解了 MapperMethod,分析了它是如何根据方法签名执行相应的 SQL 语句。

-------------本文结束感谢您的阅读-------------

本文标题:mybatis中Mapper接口的代理逻辑

文章作者:夸克

发布时间:2019年04月08日 - 17:04

最后更新:2022年07月22日 - 16:07

原始链接:https://zhanglijun1217.github.io/2019/04/08/mybatis中Mapper接口的代理逻辑/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。