当我们通过DefaultSqlSession的Mapper方式操作数据库时使用如下api:
T getMapper(Class type);
此方法返回一个实现了type接口的实现类的实力,我们分析一下此实力的创建过程。
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
}
}
可以看到getMapper方法实际是调用MapperRegistry的getMapper方法,MapperRegistry我们在前面已经分析过,它保存了Mapper接口与MapperProxyFactory的映射。通过Mapper接口取得对应的MapperProxyFactory,此类的目的是为了创建 Mapper接口的代理对象MapperProxy,对代理对象的调用委托给了MapperMethod对象的execute方法,具体过程在我前面分析过的一篇文章《Mapper映射的解析过程》里,这里不再叙述。 我们详细分析一下MapperMethod。
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);
}
通过MapperMethod的构造方法传入了Mapper的接口类,所有执行的Mapper方法和Mybatis配置中心。构造方法中会新创建两个对象SqlCommand和MethodSignature。下面分析一下这两个对象具体是什么东西。
SqlCommand:这个类会保存两个成员变量,一个是MappedStatement的id属性,一个是所代表执行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);
}
}
}
内部有一个resolveMappedStatement方法,此方法返回在解析xml映射文件保存在Configuration中的MappedStatement,将其id属性保存赋值给name成员变量。将其sqlCommandType赋值给type成员变量。
MethodSignature:包含了关于执行的Mapper方法的参数类型和返回类型。
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.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);
}
其中包括方法的返回类型,返回类型是否为void,返回类型是否为Collection或数组或游标,如果参数中含有RowBounds类型保存其在参数列表的索引值,ResultHandler同理,最后还new了一个ParamNameResolver对象,MethodSignature通过此对象可以对外提供获取参数索引值或参数名的方法,因为此对象内部缓存了方法参数列表中除了RowBounds和ResultHandler类型参数的索引值与参数名的键值对。举个列子:
aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
其中参数名由@Param注解指定若没有此注解会通过java8的Parameter的获取真实参数名,前提Mapper类是使用java8编译的并且开启了--parameters编译选项。否则参数名为arg0,arg1...的形式了。
接下来着重讲一下execute方法。
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);
}
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;
}
可以看到execute的执行分为增删改查和刷新,除了FLUSH类型操作,都会调用MethodSignature的convertArgsToSqlCommandParam方法将Object[] args的参数类型转换为null,或者单个参数对象或ParamMap三种类型中的一种。
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
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 param = new ParamMap
如果参数数组中只有一个参数且没有标记@Param则将参数转换为args[0],否则将参数数组转换为一个map其中,key为参数名(也可能是arg0,arg1) value为原始参数对象,并且还为多提供一套k如param1,param2的键值对组合。下面我们以SELECT类型详细分析一下execute方法。
SELECT类型一个有5个分支,我们分析最常用的三种:
1.返回类型为Collection或数组
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
可以看到这个方法最终调用了sqlSession的selectList方法。然后在将结果转换为数组或相应的Collection。
2.返回类型是Map
private Map executeForMap(SqlSession sqlSession, Object[] args) {
Map result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
可以看到是调用了sqlSession的selectMap,而selectMap内部其实也是调用了它的selectList方法。
3.返回类型为一个普通对象
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
所以这三种返回类型的查询操作核心就是SqlSession的selectList方法,MapperMethod将SqlCommand的那么属性,通过MethodSignature转换后的参数对象或ParamMap和RowBounds对象(可为空)传入SqlSession的selectList方法执行后再将结果转换为MethodSignature的returnType指定的类型。
下一篇分析DefaultSqlSession的selectList方法。