首先,我们的起点是MapperProxy类,该类是Mybatis对Dao的代理,见名析意
FROM:MapperPorxy.class @Override // proxy:即MapperProxy对象 // method:Dao中方法的Method对象 // args,其实是一个object[]数组,即我们传给Dao方法的参数,因为mybatis还未对其做操作,数据类型是object[] 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 (method.isDefault()) { if (privateLookupInMethod == null) { return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // Mybatis为了提高效率,加了一层Memory缓存 final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } // 通俗而言,将Dao方法的Method对象,封装成一个MapperMethod,新的MapperMethod包含的东西更多 // ① 其中除了包含Method的原始接口信息 // ② method // ③ Mybatis的Configuration,该对象是不可变的,项目启动后,Configuration就是不变的了 private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }MapperMethod类是对Dao中方法的丰富包装,并且提供了一个execute方法,该方法是所有数据库操作的入口。
execute总体比较长,也没写什么设计模式,就是switch怼,暂且当一个模式匹配,共计四个模式:
INSERTUPDATEDELETESELECT public class MapperMethod { private final SqlCommand command; private final MethodSignature method; // 唯一构造器 // 将传入的参数,转换为两个内部参数 // SqlCommand // MethodSignature public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } 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); 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; } }让我们先花一点时间看看这个构造器,该构造器分别初始化了两个参数
MethodSignature更多的是对方法签名的解析,下面是各个属性的的含义:
returnsMany:返回值类型是否是Collection或者Array类型returnsMap:返回值类型是否是Map类型returnsVoid:返回值是VoidreturnsCursor:返回值是Cursor类型returnsOptional:返回值是Optional类型returnType:返回值类型的Class实例mapKey:rowBoundsIndex:参数中RowBounds.class类型值的索引,有唯一校验resultHandlerIndex:参数中ResultHandler.class类型值的索引,有唯一校验paramNameResolver:ParamNameResolver类实例,该类会负责对Dao的入参进行第一手的转换,在new该对象的构造器中,进行了一些复杂的计算,为后面参数转换座好了铺垫我们看一下ParamNameResolver的创建过程
public class ParamNameResolver { private static final String GENERIC_NAME_PREFIX = "param"; private final SortedMap<Integer, String> names; private boolean hasParamAnnotation; public ParamNameResolver(Configuration config, Method method) { // 获取参数类型的Class实例列表 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++) { // isSpecialParameter方法判断是否是特殊的入参类型 // 其实就是排除RowBounds和ResultHandler这俩特殊入参类型 if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; // 查找@Param注解,并提取@Param的value属性值,设置给name for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } if (name == null) { // @Param was not specified. // 如果name = null,说明@Param没有设置value,或者压根没有@Param注解 // 那么就看看Configuration是有有配置使用形参名 if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 // 如果没有配置使用形参名,那么久用索引来当name // insert(String username,String age) // 最终会用"0"标识username,用"1"标识age name = String.valueOf(map.size()); } } // 将计算得到的name,存起来 map.put(paramIndex, name); } // 排序 + 不可变 names = Collections.unmodifiableSortedMap(map); } }ParamNameResolver在构造器中对参数名进行了解析,当然
顾名思义:数据库插入操作
case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; }两步走:
参数转换SQL执行本质上,该方法其实将参数转换委托给了paramNameResolver的getNamedParams方法,如我们在2.0.3中提到那样,下面我们着重看一下这个方法
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<>(); 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; } }三步走:
无参数无@Param注解,且参数数量是1,直接返回 【返回类型:参数值的原始类型】有@Param注解,或者参数数量 > 1,那么首先将构造器中解析出的name绑定到args中对应的参数值上,可以理解为将形参与实参绑定起来,但是mybatis多做了一点糖,即[param0、param1、param2…],代码很简单 【返回类型:ParamMap】这里需要注意的一点是,就是返回值类型,当参数的数量 > 1 或者参数存在@Param注解,Mybatis都会用一个ParamMap封装,这也是Mybatis做的第一手参数转换!!
Spring中默认的SqlSession实现是SqlSessionTemplate,该实现不仅线程安全,而且与Spring的TransactionManager互联 但是,在底层,SqlSessionTemplate只不过使用代理模式,用一个自定义的SqlSessionInterceptor(它是InvocationHandler)来创建了一个DefaultSqlSession的代理实现,并将该代理对象作为SqlSessionTemplate#sqlSessionProxy属性中。
/** * SqlSessionTemplate的构造器,我们主要看如何实例化sqlSessionProxy的 * 代理模式,通过SqlSessionInterceptor,在DefaultSqlSession的功能之外,增加了事务控制能力 */ public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } @Override /** * statement = Dao方法的全限定签名 * parameter = 前面convertArgsToSqlCommandParam方法转换出来的参数列表 */ public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); }我们可以看出来,SqlSessionTemplate使用委托机制,完全将核心功能交给了代理对象DefaultSqlSession,而该代理对象在原生DefaultSqlSession所支持的功能之外,还增加了对Spring事务的支持。
因此,sqlSessionProxy的insert方法调用,会首选执行代理拦截器的invoke方法,该方法中完成了SqlSession的创建,事务的创建等等,然后最终将执行权交给呗代理的DefaultSqlSession对象,从上面代码中,我们也不难看出来,从一开始,我们也是想调用SqlSession的insert方法,因此我们只看DefaultSqlSession中的实现。
insert方法只是一个壳,最终还是委托给update方法来实现 马上我们将看到Mybatis对参数的第二次包装
这里的 代码,需要结合前面首次参数操作: 在convertArgsToSqlCommandParam方法中,对参数数量等于0,以及参数数量等于1且没有@Param包装的入参,直接放行,没有进行任何的包装处理,其余的所有情况,都会被包装成ParamMap,而ParamMap试试不满足上面代码中if判断的任何情况的,所以,这段代码其实只适配于参数数量等于1且没有@Param包装的情况。 三步走:
如果参数是Collection类型,那么map.put(“collection”, object),如果参数还是List,再map.put(“list”, object);如果参数是数组,那么map.put(“array”, object);放行happy~