MyBatis基础支持层位于 Mybatis 整体架构的最底层,支撑着 Mybatis 的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为 Mybatis 提供基础支撑,也可以在合适的场景中直接复用。

整体架构

这篇文章介绍MyBatis的binding模块

iBatis (MyBatis 的前身)中,查 询 一个 Blog 对 象时 需要调 用 SqlSession.queryForObject ("selectBlog", blogld)方法。

其中,SqlSession.queryForObject()方法会 执 行指定的 SQL 语 句进 行 查 询 并 返回一个 结 果对 象,第一个 参 数 selectBlog指明了具体 执 行的SQL语 句的id,该 SQL 语 句定义 在相应 的映射配置文件中。

如果我们 错 将 selectBlog写 成了 selectBlogl,在初始 化过 程中,MyBatis是无法提示该 错 误 的,而在实 际 调 用queryForObject(selectBlog1,blogld) 方法时才会抛出异常,开发人员才能知道该错误。

MyBatis提供了 binding模块 用于解决 上述问 题 ,我们 可以定义 一个 Mapper接口,该 示例中为 BlogMapper接口,具体 代码 如下所示。

这 里的 BlogMapper接口并 不需要继 承任何其他接口,而且开 发 人员 不需要提供该 接口的实 现 。

public interface BlogMapper {
// 在映射文件中存在一个<select>接口,id为 selectById
public Blog selectById(int id);
}

Mapper接口中定义 了 SQL语 句对 应 的方法,这 些方法在MyBatis初始化过 程中会 与 映 射配置文件中定义 的SQL语 句相关 联 。如果存在无法关 联 的SQL语 句,在 MyBatis的初始化 节 点就会 抛出异 常。

我们可以调 用Mapper接口中的方法执 行相应 的SQL语 句,这样编译器就可以帮助我们提早发现上述问题 。

查询blog:

blogMapper mapp = session.getMapper(BlogMapper.class);
Blog blog = mapp.selectById(1);

binding模块核心组件

image-20200610142911476

MapperRegistry&MapperProxyFactory

MapperRegistry

MapperRegistryMapper接口及其对 应 的代理对 象工厂 的注册 中心。ConfigurationMyBatis全局性的配置对 象,在 MyBatis初始化的过 程中,所有配置信息会 被解析成相应 的对 象并 记 录 到Configuration对 象中。这 里 关 注 Configuration.mapperRegistry字 段 , 它 记 录 当 前 使 用 的 MapperRegistry对 象 。

MapperRegistry中字段及含义

// Configuration对象,MyBatis中全局唯一的配置对象,其中包含了所有配置信息
private final Configuration config;

// 记录Mapper接口与对应MapperRegistry之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers
= new HashMap<Class<?>, MapperProxyFactory<?>>();

MyBatis初始化过程中读取映射配置文件以及Mapper接口中的注解信息,并 调 用 MapperRegistry.addMapper()方 法 填 充 MapperRegistry.knownMappers 集 合 , 该 集 合 的 key 是 Mapper接口对 应 的Class对 象,valueMapperProxyFactory工厂 对 象,可以为 Mapper接口创 建代理对 象。MapperRegistry.addMapper()方法的 部分实 现 如下:

public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 检测type是否为接口
if (hasMapper(type)) {
// 如果已经添加果该接口,则直接抛出异常
throw new BindingException("...");
}
boolean loadCompleted = false;
try {
// Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
knownMappers.put(type, new MapperProxyFactory<T>(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.
// XML解析和注解处理
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

在需要执 行某SQL语 句时 ,会 先调 用MapperRegistry.getMapper()方法获 取实 现 了 Mapper 接口的代理对 象,例如本节 开 始的示例中,session.getMapper(BlogMapper.class)方法得到的实 际 上 是 MyBatis通 过 JDK动 态 代 理BlogMapper接 口 生 成 的 代 理 对 象 。MapperRegistry.getMapper() 方法的代码 如下。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 查找指定type的MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory
= (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("...");
}
try {
// 创建实现了type接口的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

MapperProxyFactory

MapperProxyFactory主要负 责 创 建代理对 象,其中核心字段的含义 和功能如下

// 当前MapperProxyFactory对象可以创建实现了mapperlnterface接口的代理对象
private final Class<T> mapperInterface;

// 缓存,key是mapperlnterface接口中某方法对应的Method对象,value是对应的MapperMethod对象
private final Map<Method, MapperMethod> methodCache
= new ConcurrentHashMap<Method, MapperMethod>();

MapperProxyFactory.newInstance()方法实现了创建实现了mapperlnterface 接口的代理对象的功能,具体代码如下:

protected T newInstance(MapperProxy<T> mapperProxy) {
// 创建代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
// 创建MapperProxy对象,每次调用都会创建新的MapperProxy对象
final MapperProxy<T> mapperProxy
= new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

MapperProxy

MapperProxy实现了InvocationHandler接口,InvocationHandler是实现JDK代理对象的核心逻辑。

MappProxy中核心字段含义和功能:

// 记录了关联的SqlSession对象
private final SqlSession sqlSession;

// mapper接口对应的class对象
private final Class<T> mapperInterface;

// 用于缓存MapperMethod对象,其中key是Mapper接口中方法对应的Method对象,value是对应的MapperMethod对象
// MapperMethod对象会完成参数转换以及SQL语句的执行功能
// MapperMethod中并不记录任何状态相关的信息,所以可以在多个代理对象之间共享
private final Map<Method, MapperMethod> methodCache;

MapperProxy.invoke()方法是代理对象执行的主要逻辑,实现如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果目标方法继承自Object,则直接调用目标方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
// 这里是针对java7以上版本动态类型语言的支持
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取MapperMethod对象,如果缓存中没有,则创建新的MapperMethod对象并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行SQL语句
return mapperMethod.execute(sqlSession, args);
}

MapperProxy.cachedMapperMethod()方法主要负责维护methodCache这个缓存集合:

private MapperMethod cachedMapperMethod(Method method) {
// 先从缓存中获取MapperMethod对象
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
// 缓存就爱你mapperMethod对象并放到缓存中
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

MapperMethod

MapperMethod中封装了 Mapper接口中对应方法的信息,以及对应 SQL语句的信息。

可以将 MapperMethod看作连接 Mapper接口以及映射配置文件中定义的SQL语句的桥梁。

MapperMethod中各个字段的信息如下:

// 记录SQL语句和类型
private final SqlCommand command;

// Mapper接口对应方法的信息
private final MethodSignature method;

SqlCommand

SqlCommandMapperMethod中定义 的内 部类 ,它 使用name字段记 录 了 SQL语 句的名称 , 使用type字 段 (SqlCommandType类 型)记 录 了 SQL语 句的类 型。

SqlCommandType是枚举类型 ,有效取值为UNKNOWNINSERTUPDATEDELETESELECTFLUSH

SqlCommand的构造方法会初始化name字段和type字段,代码如下:

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
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("...");
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}

private MappedStatement resolveMappedStatement(
Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// SQL语句的名称是由Mapper接口的名称与对应的方法名称组成的
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) { // 检测是否有该名称的SQL语句
// 从Configuration.mappedStatements集合中查找对应的MappedStatement对象,
// MappedStatement对象中封装了SQL语句相关的信息,在MyBatis初始化时创建,后面详细描述
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;
}
}

ParamNameResolver

MethodSignature中 ,会 使 用 ParamNameResolver处 理 Mapper接 口 中 定 义 的 方 法 的 参 数 列 表 。

ParamNameResolver 使用 name 字 段 (SortedMap<Integer, String>类 型 )记 录 了 参 数 在 参
数 列表中的位置索引与 参 数 名称 之间 的对 应 关 系,其中key表示参 数 在参 数 列表中的索引位置, value表示参 数 名称 ,参 数 名称 可以通过 @Param注解指定,如果没 有指定@Param注解,则 使 用参 数 索引作为 其名称 。

如果参 数 列表中包含RowBounds类 型 或 ResultHandler类 型的参 数 , 则 这 两 种 类 型的参 数 并 不会 被记 录 到name集合中,这 就会 导 致参 数 的索引与 名称 不一致。

image-20200610153922096

ParamNameResolverhasParamAnnotation字 段 (boolean类 型 )记 录 对 应 方 法 的 参 数 列 表 中是否使用了 @Param注 解 。

ParamNameResolver的构 造方法中,会 通过 反射的方式读 取Mapper接口中对 应 方法的信息,并初始化以上字段:

public ParamNameResolver(Configuration config, Method method) {
// 获取参数列表中每个参数的类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取参数列表上的注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 记录参数索引与参数名称的对应关系
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
// 如果参数是RowBounds类型或ResultHandler类型,则跳过对该参数的分析
continue;
}
String name = null;
// 遍历参数对应的注解集合
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
// 如果出现过@Param就把hasParamAnnotation初始化为true
hasParamAnnotation = true;
// 获取@Param注解指定的参数名称
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}

names集合主要在ParamNameResolver.getNamedParams()方法中使用,该 方法接收的参 数 是 用户传入的实参列表,并将实参与其对应名称进行关联,具体代码如下:

public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}

MethodSignature

MethodSignature 也 是 MapperMethod中定义 的内 部类 ,其中封装 了 Mapper接口中定义 的方法的相关 信息, MethodSignature中核心字段的含义 如下:

// 返回值类型是否为 Collection类型或是数组类型
private final boolean returnsMany;
// 返回值类型是否为Map类型
private final boolean returnsMap;
// 返回值类型是否为Void类型
private final boolean returnsVoid;
// 返回类型是否为Cursor类型
private final boolean returnsCursor;
// 返回值类型
private final Class<?> returnType;
// 如果返回值类型是Map,则该字段记录了作为key的列名
private final String mapKey;
// 用来标记该方法参数列表中ResultHandler类型参数的位置
private final Integer resultHandlerIndex;
// 用来标记该方法参数列表中RowBounds类型参数的位置
private final Integer rowBoundsIndex;
// 方法对应的ParamNameResolver对象
private final ParamNameResolver paramNameResolver;

MethodSignature的构 造函数 中会 解析相应 的Method对 象,并 初始化上述字段,具体 代 码 如下:

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);
// 若MethodSignature对 应 方法的返回值 是Map且指定了@MapKey注解,则 使用getMapKey()方法处 理
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}

getUniqueParamIndex()方法的主要功能是查找指定类型的参数在参数列表中的位置。

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
// 遍历MethodSignature对应方法的参数列表
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) { // 记录paramType类型参数在参数列表中的位置索引
index = i;
} else {
// RowBounds和ResultHandler类型的参数只能有一个,不能重复出现
throw new BindingException("...");
}
}
}
return index;
}

convertArgsToSqlCommandParam()辅助方法:

// 负责将args[]数组(用户传入的实参列表)转换成SQL语句对应的参数列表,它是通过上面介绍的
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}

MapperMethod.execute()

MapperMethod中 最核心的方法是execute()方法,它 会 根据SQL语 句的类 型调 用SqlSession对 应 的方法完成数 据 库 操作:

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {// 根据SQL语句的类型调用SqlSession对应的方法
case INSERT: {
// 使用ParamNameResolver处理args[]数组(用户传入的实参列表),将用户传入的实参与
// 指定参数名称关联起来
Object param = method.convertArgsToSqlCommandParam(args);
// 用SqlSession.insert()方法,rowCountResult()方法会根据method字段中记录的方法的
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:
// 处理返回值为Void且ResultSet通过ResultHandler处理的方法
if (method.returnsVoid() && method.hasResultHandler()) {
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);
}
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;
}

执 行 INSERTUPDATEDELETE类 型 的 SQL语 句时 ,其执 行结 果都需要经 过MapperMethod.rowCountResult()方 法 处 理 。

SqlSession 中 的 insert()等 方 法 返 回 的 是 int 值 , rowCountResult()方法会 将 该 int值 转 换 成Mapper接口中对 应 方法的返回值 ,具体 实 现 如下:

private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType())
|| Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType())
|| Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType())
|| Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("...");
}
return result;
}

如 果 Mapper接口中定义 的方法准备 使用ResultHandler处 理查 询 结 果集,则 通过 MapperMethod.executeWithResultHandler()方法处 理,具体 实 现 如下:

private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
// 获 取SQL语 句对 应 的MappedStatement对象
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("...");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
// 调用SqlSession.select()方法,执行查询 ,并由指定的ResultHandler处理结果对象
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}

如 果 Mapper接口中对 应 方法的返回值 为 数 组 或是Collection接口实 现 类 ,则 通过 MapperMethod.executeForMany()方 法 处 理 ,具 体 实 现 如 下 :

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 调用SqlSession.selectList()方法完成查询
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}

convertToDeclaredCollection()方 法 和 convertToArray()方 法 的 功 能 类 似 ,主 要 负 责 将 结 果 对 象转 换 成Collection集合对 象和数 组 对 象,具体 实 现 如下:

private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {

// 通过反射创建集合对象
Object collection = config.getObjectFactory().create(method.getReturnType());
// 创建MetaObject对象
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}

@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
Class<?> arrayComponentType = method.getReturnType().getComponentType();
Object array = Array.newInstance(arrayComponentType, list.size());
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
return array;
} else {
return list.toArray((E[])array);
}
}

如果Mapper接口中对 应 方法的返回值 为 Map类 型,则 通过 MapperMethod.executeForMap() 方法处理,具体实现如下:

private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(
command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}

executeForCursor()方法与 executeForMap()方法类似,唯一区别就是调 selectCursor()方法。

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》