1 概述
为了便于对Configuration对象的初始化过程的分析,我们这里首先来看看MapperMethod的源码。MapperMethod的作用就是处理Mapper接口函数的注解、参数和返回值。
2 内部类
2.1 MethodSignature
这个内部类主要用于处理函数的参数、注解和返回值。
(1) hasNamedParams函数
该函数用于判断方法上是否有Param注解。
private boolean hasNamedParams(Method method) {
//获取参数对应的注解
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
//判断参数注解是否为Param
if (aParamAnno instanceof Param) {
return true;
}
}
}
return false;
}
(2) getParams函数
该函数的作用是把函数的参数处理成对应位置和名称的集合。
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
//获取方法对应的参数类型
final Class<?>[] argTypes = method.getParameterTypes();
//遍历参数类型
for (int i = 0; i < argTypes.length; i++) {
//如果参数类型是RowBounds或者ResultHandler就不处理
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && ! ResultHandler.class.isAssignableFrom(argTypes[i])) {
String paramName = String.valueOf(params.size());
//参数有别名,转换成别名,如果没有别名直接用索引
if (hasNamedParameters) {
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
return params;
}
(3) convertArgsToSqlCommandParam函数
该函数的作用是将函数参数转换成sql命令参数。
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//遍历参数名称集合,并将参数名称和参数值转换成集合
for (Map.Entry<Integer, String> entry : params.entrySet()) {
//将参数名称和参数值存入集合
param.put(entry.getValue(), args[entry.getKey().intValue()]);
//将参数加上param的前缀别名
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
(4) getMapKey函数
该函数是处理函数上的mapKey注解的。
private String getMapKey(Method method) {
String mapKey = null;
//检查函数的返回类型是不是Map
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
//检测函数是否有注解mapKey
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
该函数其实就是检测函数是否是返回map并且是否有mapKey注解,如果有就返回。
(5) getUniqueParamIndex函数
该函数用于检测函数是否有唯一参数类型,当然仅仅用于检测参数类型RowBound和ResultHandler。
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 {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
2.2 SqlCommand
这个内部类主要用于处理SQL命令的名称和类型。
针对SqlCommand我们仅仅来看一下它的构造函数。
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//获取节点key(这里的接口全名+函数名 = 命名空间+id)
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
//拥有节点key
if (configuration.hasStatement(statementName)) {
//获取节点key对应的MappedStatement对象
ms = configuration.getMappedStatement(statementName);
//函数不属于接口,这里主要是为了解决继承问题。
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
//设置sql名称和类型
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
我们可以看出上面的构造函数的作用就是处理<select|update|delete|insert>节点与接口函数的映射关系,并且确定sql命令的名称和类型。
由此我们知道了要获取到接口函数对应的sql,仅仅需要将接口的名称和函数名称组合起来作为key到Configuration中的mappedStatements属性去获取就行。
3 主要函数
(1) execute函数
该函数的主要作用就是调用SqlSession执行sql语句。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根据命令类型执行不同的sql语句
if (SqlCommandType.INSERT == command.getType()) {
//将函数参数转换成sql命令参数
Object param = method.convertArgsToSqlCommandParam(args);
//获得函数影响的结果
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//返回为void并且有返回结果处理类
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//返回结果是集合或者数组
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//返回结果是Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
//返回结果为一般对象
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
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;
}
针对SqlSession的具体逻辑我们这里先不分析。
在此我们首先来看一下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 = Integer.valueOf(rowCount);
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = Long.valueOf(rowCount);
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = Boolean.valueOf(rowCount > 0);
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
可以看出该函数的作用就是将执行结果转换成Integer、Long或者Boolean。
接下来我们来看一下针对查询处理的几个函数。
(2) executeWithResultHandler函数
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
//获取到查询节点对应的MappedStatement
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
//节点如果没有返回类型则直接抛出异常
if (void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
//参数中含有RowBounds类型
if (method.hasRowBounds()) {
//获取RouBounds参数,把参数注入到查询语句中
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
(3) executeForMany函数
执行有多个返回结果的查询情况。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
//如果返回类型不是List,则进行处理
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
//如果返回类型是数组则将结果转换成数组
if (method.getReturnType().isArray()) {
return convertToArray(result);
//将结果转换成要求的集合类型
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
从上面我们可以看出,executeForMany函数其实也是依赖于SqlSession,这里主要做了就是针对函数的返回结果不是List的情况进行了相应的处理。
(4) executeForMap函数
该函数的作用是针对返回结果为Map的查询。函数比较简单,这里就不做进一步分析了。
最后我们补充对一个类的说明,MappedStatement维护了一条<select|update|delete|insert>节点的封装。里面封装了参数类型、返回类型、查询超时时间、是否使用缓存、主键生成等信息。
至此完成了对MapperMethod核心内容的分析,接下来将分析MapperProxyFactory和MapperProxy类的源码,欢迎交流。