MethodSignature
hasNamedParams
private boolean hasNamedParams(Method method) {
boolean hasNamedParams = false;
//方法上的每一个参数都可以有多个注解,所以是2维数组
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
//检查这些参数中的注解是否包含有Param注解
for (Object aParamAnno : paramAnno) {
if (aParamAnno instanceof Param) {
hasNamedParams = true;
break;
}
}
}
return hasNamedParams;
}
hasNamedParams是判断方法上是否有Param注解的方法。如果你使用过Param注解就知道这个注解是处理参数别名的。
上面这个方法主要是为了判断需不需要处理参数的别名,只在getParam方法是需要知道是否需要处理别名。
下面看一下getParam方法:
getParam
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++) {
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;
}
getParam方法主要是把方法的参数处理为对应位置和名称的集合。如果是RowBounds和ResultHandler就不处理。如果有Param别名就处理为Param别名的值,如果没有Param别名就处理为对应位置的索引值(参数位置-1)。
convertArgsToSqlCommandParam
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()];
} 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()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
convertArgsToSqlCommandParam方法主要是把方法参数转换为SQL命令参数。前面我们知道params存放的是方法参数位置与参数位置或参数位置与别名的SortedMap,convertArgsToSqlCommandParam把处理成了参数位置与参数位置或者别名与参数位置的ParamMap(HashMap),并且把参数都加上了一个paramX的别名,其中X是参数的位置索引。
现在知道经常出现的错误param1,param2,param3......出现在什么地方了吧。
getMapKey
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
这个方法是处理方法上的MapKey注解的,这个是在select查询返回值是Map类型使用的。 这个还简单就是检查方法的返回值是不是Map类型,如果是就检查方法上是不是有MapKey注解,有就获取,没有就返回null。
getUniqueParamIndex
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;
}
getUniqueParamIndex方法就是检查方法是否有唯一参数类型,并且返回指定类型的参数的位置索引。就是方法指定类型paramType的参数最多只能有一个。其实只是为了处理2种类型的方法参数一种是RowBounds,另一种是ResultHandler。
SqlCommand
对于SqlCommand,我们看一下构造方法就可以了:
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
SqlCommand其实只想知道2件事情,1是MappedStatement的id,而是MappedStatement的SQL类型是insert,update,delete,select的哪一种。
mapperInterface其实就是mapper接口,所以我们还清楚的知道mapper接口和xml的映射关系。就是mapper接口的权限定名称+方法的名称作为一个语句(MappedStatement)的key。
看到上面有一个issue35,这个是处理接口继承的问题,就是为了解决继承的方法也可以映射的到mapperInterface对应的xml中。可以参考gitHub的#issue35
MapperMethod
execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
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()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} 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;
}
我们可以看到execute调用的是SqlSession执行了SQL语句,execute方法当然非常重要,但是这里我们先不关心SqlSession对于语句的执行逻辑。我们关心的是执行之前对Mapper接口的处理。
rowCountResult方法就是对insert,update,delete语句影响行数的转换没有什么说的,感兴趣可以自己看一下代码。注意这3个的支持int,Integer,long,Long,boolean,Boolean这3中类型。所以不要说我知道只会删除一个我的返回值就使用short类型。
接下来我们看一下具体查询之前的处理的3个方法:
executeWithResultHandler
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
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);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
convertArgsToSqlCommandParam前面已经说了把接口方法的参数转换为SQL命令使用的参数。
先判断方法的参数中是否包含RowBounds类型,这个是在MethodSignature#getUniqueParamIndex检查唯一类型参数就处理了。如果有就把类型参数提取出来,提取方法很简单就不说了。最后把RowBounds参数注入到查询语句中。
提取ResultHandler和RowBounds一样,逻辑和代码都是一样的,只是类型参数不同而已。
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);
}
// 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;
}
executeForMany主要是处理有多条结果的情况。方法的逻辑非常清晰,如果Mapper接口的方法的返回值类型不是结果的List类型,那么就特殊处理一下。如果是方法的返回类型是数组就将List转换为数组。否则就转换为返回值类型的集合类型。
我们来看一下将List转换为数组的方法,和指定类型的集合的方法:
List转换为数组:
convertToArray
@SuppressWarnings("unchecked")
private <E> E[] convertToArray(List<E> list) {
E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
array = list.toArray(array);
return array;
}
这是一个非常典型的集合转数组的方法。因为不知道具体的类型,所以使用Array类来动态生成数组。因为已经检查了方法的返回值是数组类型所以可以直接使用getComponentType方法。是什么让他有自信可以使用"unchecked",是因为Mapper接口的返回值类型是和MappedStatement的ResultType(ResultMap)是对应的,就是E的实际类型,如果不匹配就出错。
convertToDeclaredCollection
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
convertToDeclaredCollection就是把结果转换为Mapper接口返回值对应的类型的集合类型。如果感兴趣可以看一下DefaultObjectFactory的代码,代码还是比较简单的,就是把指定的类型转化为特定的类型,例如返回是Map,那么就构造一个HashMap。为什么不用new,那是因为没有办法处理泛型,另外最重要的没有办法处理所有的类型。
executeForMap
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
executeForMap方法就是把结果处理成Map。这个可以看一下SqlSession的实现。
我们看到3个查询都处理了RowBounds参数,所以现在知道在Mapper接口中传入pageNo,pageSize,然后还用动态SQL来处理limit语句有多傻了吧。
总结
- 处理了RowBounds参数
- 处理了ResultHandler参数
- 处理了@Param注解
- 处理了@MapKey注解
- 处理了返回结果