今天这是学习Mybatis
的第五篇文章,在今天的文章中我们学习下binding
模块。这个模块是用来将Mapper
与其映射文件进行绑定。
这个模块位于org.apache.ibatis.binding
包下,核心的类有如下四个
MapperRegistry
映射器注册器MapperProxyFactory
映射器代理类工厂MapperProxy
映射器代理类MapperMethod
映射器方法类
在今天的文章中,我们就针对上述的四个类进行介绍。
1 MapperRegistry
MapperRegistry
是映射器注册器,这个类用来记录Mapper
类与MapperProxyFactory
的映射关系。该类的属性字段如下:
// Configuration对象 mybatis的配置信息都会解析后存储到该对象里
private final Configuration config;
// 记录Mapper接口与MapperProxyFactory的对应关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
复制代码
这里的Configuration
对象是Mybatis
中的一个核心类,Mybatis
中相关的配置解析后都会记录到这个对象中,在后面的文章我们详细的介绍这个类。这里就不展开说明了。
在这个类中有addMapper
和getMapper
两个核心的方法,分别用于注册和获取映射器,接下来我们分别看下这两个方法的逻辑。
1.1 addMapper()
方法
addMapper
方法用来注册映射器,将其添加到knownMappers
中,如果大家对Mybatis(二): 执行流程
的内容还有印象的话,我们在那篇文章的Mapper
映射文件的解析时提到过这个方法,忘了也没关系,我们今天再详细的学习下,这个方法的源码如下:
public <T> void addMapper(Class<T> type) {
// type为接口
if (type.isInterface()) {
// 判断是否已经加载过
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
// 成功加载标记
boolean loadCompleted = false;
try {
// 添加到knowMappers中
knownMappers.put(type, new MapperProxyFactory<>(type));
// 留到之后的builder模块中进行介绍
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
// 如果发生异常,需要移除该mapper
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
复制代码
在该类中除了单个Mapper
的注册方法,还提供了根据包来注册的方法,逻辑如下:
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 通过反射找到父类为superType的类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 遍历进行注册
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
复制代码
1.2 getMapper()
方法
在我们前面的示例代码中,当进行数据操作时我们会先调用SqlSession.getMapper
方法获取到Mapper
对象,这个方法的逻辑就是调用到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("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 这里是用了JDK的动态代理 生成了Mapper的代理对象 MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
复制代码
2 MapperProxyFactory
MapperProxyFactory
是映射器代理类的对象工厂,看到这个名称应该很容易想到工厂模式,这个类的主要功能就是用来创建映射器代理对象。该类的属性如下:
// 该代理对象所属的Mapper接口Class对象
private final Class<T> mapperInterface;
// 存储了Mapper中方法和MapperMethodInvoker的关系
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
复制代码
这个类中创建映射器代理对象的方法如下:
public T newInstance(SqlSession sqlSession) {
// 创建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 创建实现了mapperInterface接口的代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
复制代码
通过上面的代码我们发现getMapper
获取的是一个通过JDK
动态代理生成的Mapper
接口的代理对象。这也是为什么我们在使用Mybatis
时只需要定义Mapper
接口而不需要定义他的实现的原因。
3 MapperProxy
MapperProxy
是映射器代理类,这个类实现了InvocationHandler
接口,在JDK动态代理
中代理类需要实现这个接口并实现其中的invoke
方法。
这个类的属性字段如下:
// SqlSession对象
private final SqlSession sqlSession;
// Mapper接口的Class对象
private final Class<T> mapperInterface;
// Mapper接口中的方法和MapperMethodInvoker的对应关系
private final Map<Method, MapperMethodInvoker> methodCache;
复制代码
MapperMethodInvoker
是MapperProxy
中的一个内部接口,其有两个实现类,源码如下:
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 执行MapperMethod中的execute方法
return mapperMethod.execute(sqlSession, args);
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
复制代码
当我们调用Mapper
中的方法时,会调用到MapperProxy.invoke
方法,这个方法的逻辑如下:
@Override
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 {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
// 判断是否为接口的默认方法
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 返回PlainMethodInvoker对象 这里维护了MapperMethod的对象
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
复制代码
通过这段代码分析,我们自己定义到Mapper
中方法,最终是调用的PlainMethodInvoker.invoke
方法,而这个方法会调用MapperMethod.excute
方法。接下来我们看看MapperMethod
这个类。
4 MapperMethod
MapperMethod
中记录了Mapper
中的方法信息,以及对应的映射文件中的SQL
语句信息,这个类的属性如下:
// SqlCommand对象 记录SQL语句的名称和类型
private final SqlCommand command;
// Mapper接口中的方法信息
private final MethodSignature method;
复制代码
构造方法如下:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
复制代码
在构造方法中就是为两个属性进行赋值,接下来我们先看看SqlCommand
和MethodSignature
两个类。
4.1 SqlCommand
SqlCommand
中记录了Mapper
中方法的名称和类型,其属性如下:
// 名称 组成方式 Mapper接口的Class名称.方法名
private final String name;
// SQL类型 取值有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
private final SqlCommandType type;
复制代码
这个类的构造方法如下:
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取方法名称
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 获取MappedStatement对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
// 处理@Flush注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 设置name和type
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) {
// statementId是由接口名称 + "." + 接口中的方法名称组成
String statementId = mapperInterface.getName() + "." + methodName;
// 判断configuration中是否记录了该statement
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
// 如果这个接口是在该Mapper中定义 返回null
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;
}
复制代码
4.2 MethodSignature
MethodSignature
方法记录了映射器中方法的信息,其属性如下:
// 返回类型是否为列表
private final boolean returnsMany;
// 返回值类型是否为map
private final boolean returnsMap;
// 返回值是否为空
private final boolean returnsVoid;
// 返回值是否为cursor类型
private final boolean returnsCursor;
// 返回值类型是否为Optional
private final boolean returnsOptional;
// 返回值类型
private final Class<?> returnType;
//
private final String mapKey;
// 参数列表中ResultHandler类型参数所处位置
private final Integer resultHandlerIndex;
// 参数列表中RowBounds类型参数所处位置
private final Integer rowBoundsIndex;
// paramNameResolver对象
private final ParamNameResolver paramNameResolver;
复制代码
MethodSignature
的构造方法如下:
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.returnsOptional = Optional.class.equals(this.returnType);
// 获取mapKey
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);
}
private String getMapKey(Method method) {
String mapKey = null;
// 返回值类型是否为Map
if (Map.class.isAssignableFrom(method.getReturnType())) {
// @MapKey注解
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
// 使用@MapKey注解指定的value值
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
复制代码
我们先看看里面的paramNameResolver
属性对应的类。
4.2.1 ParamNameResolver
ParamNameResolver
是用来处理Mapper
中方法的参数列表,这个类的属性如下:
// 记录方法的参数位置和值的关系
private final SortedMap<Integer, String> names;
// 记录是否使用了@Param注解
private boolean hasParamAnnotation;
复制代码
SortedMap<Integer, String> names
用来存储参数位置boolean hasParamAnnotation
标记参数中是否使用了@Param
注解
该类的构造方法如下:
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
// 获取参数
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取参数的注解列表
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
// 参数数量
int paramCount = paramAnnotations.length;
// get names from @Param annotations
// 遍历参数
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 如果是特殊类型则跳过
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// 遍历参数上的注解
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 如果注解是@Param
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 名称取注解指定的值
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (useActualParamName) {
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);
}
复制代码
在其构造方法中的处理逻辑如下:
- 获取到方法的参数列表
- 判断参数类型是否为特殊类型(
RowBounds
或ResultHandler
),特殊类型直接跳过 - 或者
@Param
注解的value
属性,作为参数名称,如果没有指定则使用方法中定义的参数名 - 将参数索引和名称放入map中
这个类中比较重要的一个方法是getNamedParams
,这个方法是用来将方法接收到的参数同参数名称进行关联,其源码如下:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
// 参数列表是否为空 为空返回null
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 参数长度为1
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 遍历参数列表
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 将参数名作为key 接收的值作为value 放入map中
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
// 如果names不包含参数名 则使用param(i+1)作为参数名
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
复制代码
4.3 excute()
方法
excute()
方法是MapperMethod
中的核心方法,这个方法中会根据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(command.getName(), param) 调用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:
if (method.returnsVoid() && method.hasResultHandler()) {
// 返回值为空 且 ResultSet通过 ResultHandler处理的方法
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);
// 普通 select 语句的执行入口 >>
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("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
复制代码
通过上面得源码我们看到INSERT
、UPDATE
和DELETE
都会调用rowCountResult
方法,这个方法得逻辑如下:
// 这个方法中知识根据返回类型进行下转换 没什么逻辑
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("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
复制代码
查询方法也没有太多得逻辑,SqlSession
得逻辑我们在后面得文章中详细得进行介绍,这里就不进行说明了。
3 总结
我们今天的文章到此就结束了,接下来我们简单的回顾下今天的内容。
-
binding
模块是用来将Mapper
和其映射文件进行关联 -
这个模块有四个核心类,分别如下
MapperRegistry
Mapper
注册器,在Mybatis
启动时会将Mapper
的Class
类与MapperProxyFactory
进行关联MapperProxyFactory
MapperProxy
的创建工厂类,会通过JDK
动态代理创建Mapper
的代理类MapperProxy
对象MapperProxy
Mapper
接口的代理类 我们执行Mapper
中的方法会调用到其中的invoke()
方法MapperMethod
用来描述Mapper
接口中的方法,在其中会负责参数的处理并真正调用到SqlSession
相关的方法
-
Mybatis
会通过动态代理为我们创建的Mapper
接口创建一个代理类对象,我们无需定义其实现
感谢您的阅读,欢迎关注我的公众号:Bug搬运小能手
会定期的更新Java相关的知识