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模块核心组件
MapperRegistry&MapperProxyFactory
MapperRegistry
MapperRegistry
是 Mapper
接口及其对 应 的代理对 象工厂 的注册 中心。Configuration
是 MyBatis
全局性的配置对 象,在 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
对 象,value
为 MapperProxyFactory
工厂 对 象,可以为 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
SqlCommand
是 MapperMethod
中定义 的内 部类 ,它 使用name
字段记 录 了 SQL
语 句的名称 , 使用type
字 段 (SqlCommandType
类 型)记 录 了 SQL
语 句的类 型。
SqlCommandType
是枚举类型 ,有效取值为UNKNOWN
、INSERT
、UPDATE
、DELETE
、SELECT
、FLUSH
。
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
集合中,这 就会 导 致参 数 的索引与 名称 不一致。
ParamNameResolver
的 hasParamAnnotation
字 段 (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;
}
执 行 INSERT
、UPDATE
、DELETE
类 型 的 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技术内幕》

相关推荐
版权声明
- 本文链接: https://www.cayzlh.com/2019/10/30/e275ec7f.html
- 版权声明: 文章内容仅用作学习和分享,如有雷同,纯属拷贝。
留言区