带你彻底搞懂MyBatis的底层实现之binding模块
Posted 波波烤鸭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你彻底搞懂MyBatis的底层实现之binding模块相关的知识,希望对你有一定的参考价值。
基础支持层位于MyBatis整体架构的最底层,支撑着MyBatis的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块。不仅仅为MyBatis提供基础支撑,也可以在合适的场景中直接复用。
上篇文章我们给大家聊了下日志模块,本篇文章我们重点来聊下binding模块。
binding模块
接下来我们看看在org.apache.ibatis.binding包下给我们提供的Binding模块,这个模块在我们前面使用的
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
这种方式中发挥了很重要的作用,我们先来看这个包中给我们提供的工具类
大家会发现这个包里面提供的工具比较少,就几个,我们先来分别了解下他们的作用,然后在串联起来。
1 MapperRegistry
通过名称我们可以看出这显然是一个注册中心,这个注册中是用来保存MapperProxyFactory对象的,所以这个注册器中提供的功能肯定是围绕MapperProxyFactory的添加和获取操作,我们来看看具体的代码逻辑
成员变量
private final Configuration config;
// 记录 Mapper 接口和 MapperProxyFactory 之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
addMapper方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 检测 type 是否为接口
if (hasMapper(type)) { // 检测是否已经加装过该接口
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
knownMappers.put(type, new MapperProxyFactory<>(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.
// 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
getMapper方法
/**
* 获取Mapper接口对应的代理对象
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取Mapper接口对应的 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 {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
通过这个方法本质上获取的就是Mapper接口的代理对象。
2 MapperProxyFactory
MapperProxyFactory是一个工厂对象,专门负责创建MapperProxy对象。其中核心字段的含义和功能如下:
/**
* MapperProxyFactory 可以创建 mapperInterface 接口的代理对象
* 创建的代理对象要实现的接口
*/
private final Class<T> mapperInterface;
// 缓存
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
// ....
/**
* 创建实现了 mapperInterface 接口的代理对象
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
3 MapperProxy
通过MapperProxyFactory创建的MapperProxy是Mapper接口的代理对象,实现了InvocationHandler接口,通过前面讲解的动态代理模式,那么这部分的内容就很简单了。
private final SqlSession sqlSession; // 记录关联的 SqlSession对象
private final Class<T> mapperInterface; // Mapper接口对应的Class对象
// 用于缓存MapperMethod对象,key是Mapper接口方法对应的Method对象,value是对应的MapperMethod对象。‘
// MapperMethod对象会完成参数转换以及SQL语句的执行
// 注意:MapperMethod中并不会记录任何状态信息,可以在多线程间共享
private final Map<Method, MapperMethodInvoker> methodCache;
MapperProxy.invoke() 方法是代理对象执行的主要逻辑,实现如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// toString hashCode equals getClass等方法,无需走到执行SQL的流程
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
// 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
然后在cachedInvoker中主要负责维护methodCache这个缓存集合
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// Java8 中 Map 的方法,根据 key 获取值,如果值是 null,则把后面Object 的值赋给 key
// 如果获取不到,就创建
// 获取的是 MapperMethodInvoker(接口) 对象,只有一个invoke方法
// 根据method 去methodCache中获取 如果返回空 则用第二个参数填充
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// 接口的默认方法(Java8),只要实现接口都会继承接口的默认方法,例如 List.sort()
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 {
// 创建了一个 MapperMethod
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
4 MapperMethod
MapperMethod中封装了Mapper接口中对应方法的信息,以及SQL语句的信息,我们可以把MapperMethod看成是配置文件中定义的SQL语句和Mapper接口的桥梁。
属性和构造方法
// statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 SQL 类型
private final SqlCommand command;
// 方法签名,主要是返回值的类型
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);
}
4.1 SqlCommand
SqlCommand是MapperMethod中定义的内部类,记录了SQL语句名称以及对应的类型(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)
public static class SqlCommand {
private final String name; // SQL语句的的名称
private final SqlCommandType type; // SQL 语句的类型
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("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// statementId = Mapper接口全路径 + 方法名称 比如:com.gupaoedu.mapper.UserMapper
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {// 检查是否有该名称的SQL语句
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 如果Mapper接口还有父类 就递归处理
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也是MapperMethod的内部类,在其中封装了Mapper接口中定义的方法相关信息。
private final boolean returnsMany; // 判断返回是否为 Collection类型或者数组类型
private final boolean returnsMap; // 返回值是否为 Map类型
private final boolean returnsVoid; // 返回值类型是否为 void
private final boolean returnsCursor; // 返回值类型是否为 Cursor 类型
private final boolean returnsOptional; // 返回值类型是否为 Optional 类型
private final Class<?> returnType; // 返回值类型
private final String mapKey; // 如果返回值类型为 Map 则 mapKey 记录了作为 key的 列名
private final Integer resultHandlerIndex; // 用来标记该方法参数列表中 ResultHandler 类型参数的位置
private final Integer rowBoundsIndex; // 用来标记该方法参数列表中 rowBounds 类型参数的位置
private final ParamNameResolver paramNameResolver; // 该方法对应的 ParamNameResolver 对象
构造方法中完成了相关信息分初始化操作
/**
* 方法签名
* @param configuration
* @param mapperInterface
* @param 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.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// getUniqueParamIndex 查找指定类型的参数在 参数列表中的位置
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
getUniqueParamIndex的主要作用是 查找指定类型的参数在参数列表中的位置
/**
* 查找指定类型的参数在参数列表中的位置
* @param method
* @param paramType
* @return
*/
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 {
// RowBounds 和 ResultHandler 类型的参数只能有一个,不能重复出现
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
4.3 execute方法
最后我们需要来看下再MapperMethod中最核心的方法execute方法,这个方法完成了数据库操作
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);
以上是关于带你彻底搞懂MyBatis的底层实现之binding模块的主要内容,如果未能解决你的问题,请参考以下文章