In this paper, we share MyBatis of Binding module, the corresponding binding
package. As shown below: binding
Package
In the "exhausted MyBatis source code parsing - List Project structure" , a brief introduction to this module are as follows:
When calling the appropriate method SqlSession perform database operations, you need to specify the SQL node mapping defined in the file, if spelling errors, we can only find the appropriate exception at run time. In order to find such an error, by MyBatis Binding module, associate the user-defined interface Mapper mapping configuration file, the system can execute a method Mapper interface corresponding SQL statement by invoking self-defined database operation, thereby avoiding the above problems early .
Readers worth noting that developers need to write custom interfaces Mapper implementation, MyBatis will automatically create a dynamic proxy object. In some scenarios, custom Mapper interface can be completely replaced mapping configuration file, but some mapping rules and definitions SQL statements or written in the mapping configuration file is more convenient, such as the definition of dynamic SQL statements.
As shown in FIG referred to herein as Class: class diagram
2. Folders Registry
org.apache.ibatis.binding.MapperRegistry
, Mapper registry.
2.1 Constructor
// MapperRegistry.java |
2.2 addMappers
#addMappers(String packageName, ...)
The method, designated scanning package, and matching class, added to the knownMappers
medium. code show as below:
// MapperRegistry.java |
<1>
, The class specified in the packet specified ResolverUtil scan. Detailed analysis, in - "fine analysis do MyBatis source IO Module" There are in.
2.3 hasMapper
#hasMapper(Class<T> type)
The method determines whether there Mapper. code show as below:
// MapperRegistry.java |
2.4 addMapper
#addMapper(Class<T> type)
Method to add knownMappers
in. code show as below:
// MapperRegistry.java |
<1>
, The judgetype
must be an interface, that is to say Mapper interface.<2>
Department, has been added, an exception is thrown BindingException.<3>
Department, added to theknownMappers
medium.<4>
At parsing comment Mapper configuration.<5>
At MapperAnnotationBuilder create objects of analytical notes Mapper configuration.<6>
At If loading is not completed, theknownMappers
removal of.
2.5 getMapper
#getMapper(Class<T> type, SqlSession sqlSession)
Method to obtain Mapper Proxy objects. code show as below:
// MapperRegistry.java |
<1>
处,从knownMappers
中,获得 MapperProxyFactory 对象。<2>
处,调用MapperProxyFactory#newInstance(SqlSession sqlSession)
方法,创建 Mapper Proxy 对象。详细解析,见 「3. MapperProxyFactory」 。
3. MapperProxyFactory
org.apache.ibatis.binding.MapperProxyFactory
,Mapper Proxy 工厂类。
3.1 构造方法
// MapperProxyFactory.java |
3.2 newInstance
#newInstance(...)
方法,创建 Mapper Proxy 对象。代码如下:
// MapperProxyFactory.java |
- 依然是稳稳的基于 JDK Proxy 实现,而 InvocationHandler 参数是 MapperProxy 对象。关于 MapperProxy ,详细解析,见 「4. MapperProxy」 。
4. MapperProxy
org.apache.ibatis.binding.MapperProxy
,实现 InvocationHandler、Serializable 接口,Mapper Proxy 。关键是 java.lang.reflect.InvocationHandler
接口,你懂的。
4.1 构造方法
// MapperProxy.java |
4.2 invoke
#invoke(Object proxy, Method method, Object[] args)
方法,调用方法。代码如下:
// MapperProxy.java |
<1>
处,如果是 Object 定义的方法,直接调用。-
<2>
处,调用#isDefaultMethod((Method method)
方法,判断是否为default
修饰的方法,若是,则调用#invokeDefaultMethod(Object proxy, Method method, Object[] args)
方法,进行反射调用。代码如下:// MapperProxy.java
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}- JDK8 在接口上,新增了
default
修饰符。怎么进行反射调用,见 《java8 通过反射执行接口的default方法》 一文。 - 关于这段代码,在 https://github.com/mybatis/mybatis-3/issues/709 上有讨论。
- JDK8 在接口上,新增了
-
<3.1>
处,调用#cachedMapperMethod(Method method)
方法,获得 MapperMethod 对象。代码如下:// MapperProxy.java
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}- 默认从
methodCache
缓存中获取。如果不存在,则进行创建,并进行缓存。
- 默认从
<3.2>
处,调用MapperMethod#execute(SqlSession sqlSession, Object[] args)
方法,执行 MapperMethod 方法。关于 MapperMethod ,在 「5. MapperMethod」 中,详细解析。
5. MapperMethod
org.apache.ibatis.binding.MapperMethod
,Mapper 方法。在 Mapper 接口中,每个定义的方法,对应一个 MapperMethod 对象。
5.1 构造方法
// MapperMethod.java |
command
属性,SqlCommand 对象。关于它的详细解析,见 「6. SqlCommand」 。method
属性,MethodSignature 对象。关于它的详细解析,见 「7. MethodSignature」 。
5.2 execute
因为涉及比较多的后面的内容,所以放在 详细解析。
心急的胖友,可以先看看 《Mybatis3.3.x技术内幕(十一):执行一个Sql命令的完整流程》 。
6. SqlCommand
SqlCommand ,是 MapperMethod 的内部静态类,SQL 命令。
6.1 构造方法
// SqlCommand.java |
name
属性,对应MappedStatement#getId()
方法获得的标识。实际上,就是${NAMESPACE_NAME}.${语句_ID}
, 例如:"org.apache.ibatis.autoconstructor.AutoConstructorMapper.getSubject2"
。-
type
属性,SQL 命令类型。org.apache.ibatis.mapping.SqlCommandType
类,代码如下:// SqlCommandType.java
public enum SqlCommandType {
/**
* 未知
*/
UNKNOWN,
/**
* 插入
*/
INSERT,
/**
* 更新
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 查询
*/
SELECT,
/**
* FLUSH
*/
FLUSH
}
<1>
处,调用#resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration)
方法,获得 MappedStatement 对象。详细解析,胖友先跳到 「6.2 resolveMappedStatement」 。<2>
处,如果找不到 MappedStatement 对象,说明该方法上,没有对应的 SQL 声明。那么在判断是否有@Flush
注解,如果有,说明该方法是用于执行 flush 操作,否则,抛出 BindingException 异常。<3>
处,如果找到 MappedStatement 对象,则初始化name
和type
属性。
6.2 resolveMappedStatement
#resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration)
方法,获得 MappedStatement 对象。代码如下:
// SqlCommand.java |
<1>
处,获得编号。这个编号,和我们上文提到的${NAMESPACE_NAME}.${语句_ID}
。<2>
处,通过Configuration#hasStatement(String statementId)
方法,判断是否有 MappedStatement 。如果有,则调用Configuration#getMappedStatement(String statementId)
方法,获得 MappedStatement 对象。关于 Configuration ,我们在后续的文章中,详细解析。在这里,胖友只需要知道,Configuration 里缓存了所有的 MappedStatement ,并且每一个 XML 里声明的例如<select />
或者<update />
等等,都对应一个 MappedStatement 对象。<3>
处,如果没有,并且当前方法就是declaringClass
声明的,则说明真的找不到。<4>
处,遍历父接口,递归继续获得 MappedStatement 对象。因为,该该方法定义在父接口中。
7. MethodSignature
MethodSignature ,是 MapperMethod 的内部静态类,方法签名。
7.1 构造方法
// MethodSignature.java |
-
<1>
处,调用#getMapKey(Method method)
方法,获得注解的{@link MapKey#value()}
。代码如下:// MethodSignature.java
private String getMapKey(Method method) {
String mapKey = null;
// 返回类型为 Map
if (Map.class.isAssignableFrom(method.getReturnType())) {
// 使用 @MapKey 注解
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
// 获得 @MapKey 注解的键
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
-
<2>
处,调用#getUniqueParamIndex(Method method, Class<?> paramType)
方法,获得指定参数类型在方法参数中的位置。代码如下:// MethodSignature.java
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;
// 如果重复类型了,则抛出 BindingException 异常
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
7.2 convertArgsToSqlCommandParam
#convertArgsToSqlCommandParam(Object[] args)
方法,获得 SQL 通用参数。代码如下:
// MethodSignature.java |
- 在内部,会调用
ParamNameResolver#getNamedParams(Object[] args)
方法。在 《精尽 MyBatis 源码分析 —— 反射模块》 中,有详细解析。 - 如下图是一个
args
和转换后的结果的示例:示例