首先,我们的起点是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怼,暂且当一个模式匹配,共计四个模式:
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;
}
}
让我们先花一点时间看看这个构造器,该构造器分别初始化了两个参数
public static class SqlCommand {
// Dao方法名
private final String name;
// 操作的类型:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// Dao中方法的的名字
final String methodName = method.getName();
// 解析出该Dao方法是那种类型的数据库操作
// 获取方法声明的类的Class实例
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 static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
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;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
}
MethodSignature更多的是对方法签名的解析,下面是各个属性的的含义:
我们看一下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;
}
两步走:
本质上,该方法其实将参数转换委托给了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;
}
}
三步走:
这里需要注意的一点是,就是返回值类型,当参数的数量 > 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中的实现。
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
insert方法只是一个壳,最终还是委托给update方法来实现
马上我们将看到Mybatis对参数的第二次包装
public class DefaultSqlSession implements SqlSession {
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
public static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -5741767162221585340L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
}
return super.get(key);
}
}
}
这里的 代码,需要结合前面首次参数操作:
在convertArgsToSqlCommandParam方法中,对参数数量等于0,以及参数数量等于1且没有@Param包装的入参,直接放行,没有进行任何的包装处理,其余的所有情况,都会被包装成ParamMap,而ParamMap试试不满足上面代码中if判断的任何情况的,所以,这段代码其实只适配于参数数量等于1且没有@Param包装的情况。
三步走:
happy~