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 { 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中字段及含义
private final Configuration config;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()) { if (hasMapper(type)) { throw new BindingException ("..." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory <T>(type)); 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) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null ) { throw new BindingException ("..." ); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException ("Error getting mapper instance. Cause: " + e, e); } }
MapperProxyFactory MapperProxyFactory
主要负 责 创 建代理对 象,其中核心字段的含义 和功能如下
private final Class<T> mapperInterface;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) { final MapperProxy<T> mapperProxy = new MapperProxy <T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
MapperProxy MapperProxy
实现了InvocationHandler
接口,InvocationHandler
是实现JDK
代理对象的核心逻辑。
MappProxy
中核心字段含义和功能:
private final SqlSession sqlSession;private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache;
MapperProxy.invoke()
方法是代理对象执行的主要逻辑,实现如下:
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
MapperProxy.cachedMapperMethod()
方法主要负责维护methodCache
这个缓存集合:
private MapperMethod cachedMapperMethod (Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null ) { mapperMethod = new MapperMethod (mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
MapperMethod MapperMethod
中封装了 Mapper
接口中对应方法的信息,以及对应 SQL
语句的信息。
可以将 MapperMethod
看作连接 Mapper
接口以及映射配置文件中定义的SQL
语句的桥梁。
MapperMethod
中各个字段的信息如下:
private final SqlCommand command;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 ) { 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) { String statementId = mapperInterface.getName() + "." + methodName; if (configuration.hasStatement(statementId)) { 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; for (int paramIndex = 0 ; paramIndex < paramCount; paramIndex++) { if (isSpecialParameter(paramTypes[paramIndex])) { continue ; } String name = null ; for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { hasParamAnnotation = true ; name = ((Param) annotation).value(); break ; } } if (name == null ) { if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null ) { 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()]); final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1 ); if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
MethodSignature MethodSignature
也 是 MapperMethod
中定义 的内 部类 ,其中封装 了 Mapper
接口中定义 的方法的相关 信息, MethodSignature
中核心字段的含义 如下:
private final boolean returnsMany;private final boolean returnsMap;private final boolean returnsVoid;private final boolean returnsCursor;private final Class<?> returnType;private final String mapKey;private final Integer resultHandlerIndex;private final Integer rowBoundsIndex;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); 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(); for (int i = 0 ; i < argTypes.length; i++) { if (paramType.isAssignableFrom(argTypes[i])) { if (index == null ) { index = i; } else { throw new BindingException ("..." ); } } } return index; }
convertArgsToSqlCommandParam()
辅助方法:
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()) { case INSERT: { Object param = method.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 (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null ; } else if (method.returnsMany()) { 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); } 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) { 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(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); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } 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 = 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技术内幕》