Binding 模块
MyBatis 代理的 Binding 映射模块实现原理。包括 MapperRegistry、MapperProxyFactory、MapperProxy、MethodHandle 动态代理、MapperMethod、SqlCommand、SqlCommand
在 XML 代理开发模式中,我们会为每个 mapper.xml 配置文件创建一个对应的 Mapper 接口,无须提供 Mapper 接口实现,就可以直接调用 Mapper 接口对象的方法执行 mapper.xml 配置文件中的 SQL 语句。对此,我们可以引出几个疑问:
- 为什么需要 Mapper 接口来执行对应的 SQL 语句?
- 为什么无须提供 Mapper 接口的实现类?
- 实际使用的 Mapper 接口对象是什么?
- Mapper 对象是如何创建的?
接下来让我们通过分析源码来解决这些疑问。
在 MyBatis 中,实现 Mapper 接口与 Mapper.xml 配置文件映射功能的是 Binding 模块 (org.apache.ibatis.binding
包下),其中涉及的核心类如下图所示:
binding.MapperRegistry
MapperRegistry
是 MyBatis 初始化过程中构造的一个对象,主要作用就是统一维护 Mapper 接口以及这些 Mapper 的代理对象工厂。我们先来看 MapperRegistry
中的核心字段:
Configuration config;
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
config
:指向 MyBatis 全局唯一的Configuration
对象,其中维护了解析之后的全部 MyBatis 配置信息。knownMappers
:维护了所有解析到的 Mapper 接口以及MapperProxyFactory
工厂对象之间的映射关系。
在 MyBatis 初始化时,会读取全部 Mapper.xml 配置文件,还会扫描全部 Mapper 接口中的注解信息,之后会调用 MapperRegistry.addMapper()
方法填充 knownMappers
集合。在 addMapper()
方法填充 knownMappers
集合之前,MapperRegistry
会先保证传入的 type
参数是一个接口且 knownMappers
集合没有加载过 type
类型,然后才会创建相应的 MapperProxyFactory
工厂并记录到 knownMappers
集合中:
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
knownMappers.put(type, new MapperProxyFactory<>(type));
// 接口方法、注解解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
在我们使用 XxxMapper.find()
方法执行数据库查询的时候,MyBatis 会先从 MapperRegistry
中获取 Mapper 接口的代理对象,这里就使用到 MapperRegistry.getMapper()
方法,它会拿到前面创建的 MapperProxyFactory
工厂对象,并调用其 newInstance()
方法创建 Mapper 接口的代理对象:
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 {
// 创建 Mapper 接口的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
binding.MapperProxyFactory
MapperProxyFactory
的核心功能就是创建 Mapper 接口的代理对象,其核心原理就是 JDK 动态代理。
在 MapperRegistry
中会依赖 MapperProxyFactory
的 newInstance()
方法创建代理对象,如下代码所示,其内部使用的 InvocationHandler
实现是 MapperProxy
:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用下方重载方法
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 创建实现了 mapperInterface 接口的动态代理对象,这里使用的 InvocationHandler 实现是 MapperProxy
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] { mapperInterface }, mapperProxy);
}
binding.MapperProxy
通过分析 MapperProxyFactory
这个工厂类,我们可以清晰地看到 MapperProxy
是生成 Mapper 接口代理对象的关键,它实现了 InvocationHandler
接口。下面我们先来介绍一下 MapperProxy
中的核心字段:
SqlSession sqlSession;
Map<Method, MapperMethodInvoker> methodCache;
Class<T> mapperInterface;
Constructor<Lookup> lookupConstructor;
Method privateLookupInMethod;
sqlSession
:记录了当前MapperProxy
关联的SqlSession
对象。在与当前MapperProxy
关联的代理对象中,会用该SqlSession
访问数据库。mapperInterface
:Mapper 接口类型,也是当前MapperProxy
关联的代理对象实现的接口类型。methodCache
:用于缓存MapperMethodInvoker
对象的集合。methodCache
中的key
是 Mapper 接口中的方法,value
是该方法对应的MapperMethodInvoker
对象。lookupConstructor
:针对 JDK 8 中的特殊处理,该字段指向了MethodHandles.Lookup
的构造方法。privateLookupInMethod
:除了 JDK 8 之外的其他 JDK 版本会使用该字段,该字段指向MethodHandles.privateLookupIn()
方法。
这里涉及 MethodHandle
的内容,所以下面我们就来简单介绍一下 MethodHandle
的基础知识点。
MethodHandle 反射机制
从 Java 7 开始,除了反射之外,在 java.lang.invoke
包中新增了 MethodHandle
这个类,它的基本功能与反射中的 Method 类似,但它比反射更加灵活。反射是 Java API 层面支持的一种机制,而 MethodHandle
则是由 JVM 实现的机制。相比之下,MethodHandle
更轻量级,性能也比反射更好。
使用 MethodHandle
进行方法调用的时候,往往会涉及下面几个核心步骤:
- 创建
MethodType
对象,确定方法的签名,这个签名会涉及方法参数及返回值的类型; - 在
MethodHandles.Lookup
这个工厂对象中,根据方法名称以及上面创建的MethodType
查找对应MethodHandle
对象; - 将
MethodHandle
绑定到一个具体的实例对象; - 调用
MethodHandle.invoke()
、invokeWithArguments()
或invokeExact()
等方法,完成方法调用。
下面是 MethodHandle
的一个简单示例:
public class MethodHandleDemo {
// 定义一个 sayHello() 方法
public String sayHello(String s) {
return "Hello, " + s;
}
public static void main(String[] args) throws Throwable {
// 初始化 MethodHandleDemo 实例
MethodHandleDemo subMethodHandleDemo = new SubMethodHandleDemo();
// 定义 sayHello() 方法的签名,第一个参数是方法的返回值类型,第二个参数是方法的参数列表
MethodType methodType = MethodType.methodType(String.class, String.class);
// 根据方法名和 MethodType 在 MethodHandleDemo 中查找对应的 MethodHandle
MethodHandle methodHandle = MethodHandles.lookup()
.findVirtual(MethodHandleDemo.class, "sayHello", methodType);
// 将 MethodHandle 绑定到一个对象上,然后通过 invokeWithArguments() 方法传入实参并执行
System.out.println(methodHandle.bindTo(subMethodHandleDemo)
.invokeWithArguments("MethodHandleDemo"));
// 下面是调用 MethodHandleDemo 对象(即父类)的方法
MethodHandleDemo methodHandleDemo = new MethodHandleDemo();
System.out.println(methodHandle.bindTo(methodHandleDemo)
.invokeWithArguments("MethodHandleDemo"));
}
public static class SubMethodHandleDemo extends MethodHandleDemo{
// 定义一个 sayHello() 方法
public String sayHello(String s) {
return "Sub Hello, " + s;
}
}
}
在 MethodHandle
调用方法的时候,也是支持多态的,在通过 MethodHandle.bindTo()
方法绑定到某个实例对象的时候,在 bind 过程中会进行类型检查等一系列检查操作。
通过上面这个示例我们可以看出,使用 MethodHandle
实现反射的效果,更像我们平时通过 Java 代码生成的字节码,例如,在字节码中可以看到创建的方法签名(MethodType
)、方法的具体调用方式(findStatic()
、findSpecial()
、findVirtual()
等方法)以及类型的隐式转换。
binding.MethodProxy 中的代理逻辑
介绍完 MethodHandle
的基础之后,我们回到 MethodProxy
继续分析。
MapperProxy.invoke()
方法是代理对象执行的入口,其中会拦截所有非 Object
方法,针对每个被拦截的方法,都会调用 cachedInvoker()
方法获取对应的 MapperMethod
对象,并调用其 invoke()
方法执行代理逻辑以及目标方法。
在 cachedInvoker()
方法中,首先会查询 methodCache
缓存,如果查询的方法为 default
方法,则会根据当前使用的 JDK 版本,获取对应的 MethodHandle
并封装成 DefaultMethodInvoker
对象写入缓存;如果查询的方法是非 default
方法,则创建 PlainMethodInvoker
对象写入缓存。
cachedInvoker()
方法的具体实现如下:
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) {
// 针对 default 方法的处理
try {
// 这里根据 JDK 版本的不同,获取方法对应的 MethodHandle 的方式也有所不同
// 在 JDK 8 中使用的是 lookupConstructor 字段,而在 JDK 9 中使用的是
// privateLookupInMethod 字段。获取到 MethodHandle 之后,会使用
// DefaultMethodInvoker 进行封装
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} // catch...
} else {
// 对于其他方法,会创建 MapperMethod 并使用 PlainMethodInvoker 封装
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
);
} // catch...
}
其中使用到的 DefaultMethodInvoker
和 PlainMethodInvoker
都是 MapperProxy
的内部接口 MapperMethodInvoker
的实现,如下图所示:
在 DefaultMethodInvoker.invoke()
方法中,会通过底层维护的 MethodHandle
完成方法调用,核心实现如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 首先将 MethodHandle 绑定到一个实例对象上,然后调用 invokeWithArguments() 方法执行目标方法
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
在 PlainMethodInvoker.invoke()
方法中,会通过底层维护的 MapperMethod
完成方法调用,其核心实现如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
binding.MapperMethod
通过对 MapperProxy
的分析我们知道,MapperMethod
是最终执行 SQL 语句的地方,同时也记录了 Mapper 接口中的对应方法,其核心字段也围绕这两方面的内容展开。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
}
MapperMethod.SqlCommand
MapperMethod
的第一个核心字段是 command
(MapperMethod$SqlCommand
类型),其中维护了关联 SQL 语句的相关信息。在 MapperMethod$SqlCommand
这个内部类中,通过 name
字段记录了关联 SQL 语句的唯一标识,通过 type
字段(SqlCommandType
类型)维护了 SQL 语句的操作类型,这里 SQL 语句的操作类型分为 INSERT
、UPDATE
、DELETE
、SELECT
和 FLUSH
五种:
public class MapperMethod {
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
}
}
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
}
下面我们就来看看 SqlCommand
如何查找 Mapper 接口中一个方法对应的 SQL 语句的信息,该逻辑在 SqlCommand
的构造方法中实现,如下:
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取 Mapper 接口中对应的方法名称
final String methodName = method.getName();
// 获取 Mapper 接口的类型
final Class<?> declaringClass = method.getDeclaringClass();
// 将 Mapper 接口名称和方法名称拼接起来作为 SQL 语句唯一标识,
// 到 Configuration 这个全局配置对象中查找 SQL 语句
// MappedStatement 对象就是 Mapper.xml 配置文件中一条 SQL 语句解析之后得到的对象
MappedStatement ms = resolveMappedStatement(mapperInterface,
methodName, declaringClass, configuration);
if (ms == null) {
// 针对 @Flush 注解的处理
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else { // 没有 @Flush 注解,会抛出异常
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 记录 SQL 语句唯一标识
name = ms.getId();
// 记录 SQL 语句的操作类型,UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
这里调用的 SqlCommand.resolveMappedStatement()
方法不仅会尝试根据 SQL 语句的唯一标识从 Configuration
全局配置对象中查找关联的 MappedStatement
对象,还会尝试顺着 Mapper 接口的继承树进行查找,直至查找成功为止。具体实现如下:
private MappedStatement resolveMappedStatement(
Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 将 Mapper 接口名称和方法名称拼接起来作为 SQL 语句唯一标识
String statementId = mapperInterface.getName() + "." + methodName;
// 检测 Configuration 中是否包含相应的 MappedStatement 对象
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
// 如果方法就定义在当前接口中,则证明没有对应的 SQL 语句,返回 null
return null;
}
// 如果当前检查的 Mapper 接口 (mapperInterface) 中不是定义该方法的接口 (declaringClass),
// 则会从 mapperInterface 开始,沿着继承关系向上查找递归每个接口,
// 查找该方法对应的 MappedStatement 对象
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(
superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
MapperMethod.MethodSignature
MapperMethod
的第二个核心字段是 method
字段(MapperMethod$MethodSignature
类型),其中维护了 Mapper 接口中方法的相关信息。
Boolean returnsMany;
Boolean returnsMap;
Boolean returnsVoid;
Boolean returnsCursor;
Boolean returnsOptional;
Class<?> returnType;
String mapKey;
Integer resultHandlerIndex;
Integer rowBoundsIndex;
ParamNameResolver paramNameResolver;
- 首先是 Mapper 接口方法返回值的相关信息,涉及下面七个字段:
returnsMany
、returnsMap
、returnsVoid
、returnsCursor
、returnsOptional
(boolean
类型):用于表示方法返回值是否为Collection
集合或数组、Map
集合、void
、Cursor
、Optional
类型。returnType
(Class<?>
类型):方法返回值的具体类型。mapKey
(String
类型):如果方法的返回值为Map
集合,则通过mapKey
字段记录了作为key
的列名。mapKey
字段的值是通过解析方法上的@MapKey
注解得到的。
- 接下来是与 Mapper 接口方法的参数列表相关的三个字段:
resultHandlerIndex
(Integer
类型):记录了 Mapper 接口方法的参数列表中ResultHandler
类型参数的位置。rowBoundsIndex
(Integer
类型):记录了 Mapper 接口方法的参数列表中RowBounds
类型参数的位置。paramNameResolver
(ParamNameResolver
类型):用来解析方法参数列表的工具类。
在上述字段中,需要着重讲解的是 ParamNameResolver
这个解析方法参数列表的工具类。
public class ParamNameResolver {
private final boolean useActualParamName;
/**
* <p>
* 键是索引,值是参数的名称。<br> 如果指定,名称从{@link Param}获取。
* 当未指定 {@link Param} 时,使用参数索引。
* 请注意,当该方法具有特殊参数(即 {@link RowBounds} 或 {@link ResultHandler})时,此索引可能与实际索引不同。
* </p>
* <ul>
* <li>aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}</li>
* <li>aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}</li>
* <li>aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}</li>
* </ul>
*/
private final SortedMap<Integer, String> names;
}
在 ParamNameResolver
中有一个 names
字段(SortedMap<Integer, String>
类型)记录了各个参数在参数列表中的位置以及参数名称,其中:
key
是参数在参数列表中的位置索引;value
为参数的名称。
完成 names
集合的初始化之后,我们再来看如何从 names
集合中查询参数名称,该部分逻辑在 ParamNameResolver.getNamedParams()
方法中,它会将 Mapper 接口方法的实参与 names
集合中记录的参数名称相关联,其核心逻辑如下:
public Object getNamedParams(Object[] args) {
// 获取方法中非特殊类型 (RowBounds 类型和 ResultHandler 类型)的参数个数
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null; // 方法没有非特殊类型参数,返回 null 即可
} else if (!hasParamAnnotation && paramCount == 1) {
// 方法参数列表中没有使用 @Param 注解,且只有一个非特殊类型参数
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
} else {
// 处理存在 @Param 注解或是存在多个非特殊类型参数的场景
// param 集合用于记录了参数名称与实参之间的映射关系
// 这里的 ParamMap 继承了 HashMap,与 HashMap 的唯一不同是:
// 向 ParamMap 中添加已经存在的 key 时,会直接抛出异常,而不是覆盖原有的 Key
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 将参数名称与实参的映射保存到 param 集合中
param.put(entry.getValue(), args[entry.getKey()]);
// 同时,为参数创建 "param + 索引" 格式的默认参数名称,具体格式为:param1, param2 等,
// 将 "param + 索引" 的默认参数名称与实参的映射关系也保存到 param 集合中
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
了解了 ParamNameResolver
的核心功能之后,我们回到 MethodSignature
继续分析,在其构造函数中会解析方法中的返回值、参数列表等信息,并初始化前面介绍的核心字段,这里也会使用到前面介绍的 ParamNameResolver
工具类。下面是 MethodSignature
构造方法的核心实现:
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 通过 TypeParameterResolver 工具类解析方法的返回值类型,初始化 returnType 字段值
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();
}
// 根据返回值类型,初始化 returnsVoid、returnsMany、returnsCursor、
// returnsMap、returnsOptional 这五个与方法返回值类型相关的字段
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);
// 如果返回值为 Map 类型,则从方法的 @MapKey 注解中获取 Map 中为 key 的字段名称
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// 解析方法中 RowBounds 类型参数以及 ResultHandler 类型参数的下标索引位置,
// 初始化 rowBoundsIndex 和 resultHandlerIndex 字段
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 创建 ParamNameResolver 工具对象,在创建 ParamNameResolver 对象的时候,
// 会解析方法的参数列表信息
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
在初始化过程中,我们看到会调用 getUniqueParamIndex()
方法查找目标类型参数的下标索引位置,其核心原理就是遍历方法的参数列表,逐个匹配参数的类型是否为目标类型,如果匹配成功,则会返回当前参数的下标索引。getUniqueParamIndex()
方法的具体实现比较简单,这里就不再展示了。
MapperMethod#execute 方法
分析完 MapperMethod
中的几个核心内部类,我们回到 MapperMethod
继续介绍。
execute()
方法是 MapperMethod
中最核心的方法之一。execute()
方法会根据要执行的 SQL 语句的具体类型执行 SqlSession
的相应方法完成数据库操作,其核心实现如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { // 判断 SQL 语句的类型
case INSERT: {
// 通过 ParamNameResolver.getNamedParams() 方法将方法的实参与
// 参数的名称关联起来
Object param = method.convertArgsToSqlCommandParam(args);
// 通过 SqlSession.insert() 方法执行 INSERT 语句,
// 在 rowCountResult() 方法中,会根据方法的返回值类型对结果进行转换
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 通过 SqlSession.update() 方法执行 UPDATE 语句
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 (method.returnsVoid() && method.hasResultHandler()) {
// 如果方法返回值为 void,且参数中包含了 ResultHandler 类型的实参,
// 则查询的结果集将会由 ResultHandler 对象进行处理
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// executeForMany() 方法处理返回值为集合或数组的场景
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
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()) {
throw new BindingException("...");
}
return result;
}
在 execute()
方法中,对于 INSERT
、UPDATE
、DELETE
三类 SQL 语句的返回结果,都会通过 rowCountResult()
方法处理。我们知道,上述三种类型的 SQL 语句的执行结果是一个数字,多数场景中代表了 SQL 语句影响的数据行数(注意,这个返回值的具体含义根据 MySQL 的配置有所变化),rowCountResult()
方法会将这个 int
值转换成 Mapper 接口方法的返回值,具体规则如下:
- Mapper 方法返回值为
void
,则忽略 SQL 语句的int
返回值,直接返回 null; - Mapper 方法返回值为
int
或Integer
类型,则将 SQL 语句返回的int
值直接返回; - Mapper 方法返回值为
long
或Long
类型,则将 SQL 语句返回的int
值转换成long
类型之后返回; - Mapper 方法返回值为
boolean
或Boolean
类型,则将 SQL 语句返回的int
值与 0 比较大小,并将比较结果返回。
接下来看 execute()
方法针对 SELECT
语句查询到的结果集的处理。
- 如果在方法参数列表中有
ResultHandler
类型的参数存在,则会使用executeWithResultHandler()
方法完成查询,底层依赖的是SqlSession.select()
方法,结果集将会交由传入的ResultHandler
对象进行处理。 - 如果方法返回值为集合类型或是数组类型,则会调用
executeForMany()
方法,底层依赖SqlSession.selectList()
方法进行查询,并将得到的List
转换成目标集合类型。 - 如果方法返回值为
Map
类型,则会调用executeForMap()
方法,底层依赖SqlSession.selectMap()
方法完成查询,并将结果集映射成Map
集合。 - 针对
Cursor
以及Optional
返回值的处理,也是依赖的SqlSession
的相关方法完成查询的,这里不再展开。