将Mapper接口中的方法和配置文件中SQL语句进行绑定。binding模块中涉及到的核心组件之间的关系如下图,其中MapperRegistry和MapperProxyFactory之间的关系是一对多。mybatis初始化时,MapperRegistry会将MapperProxyFactory注册进来,并通过MapperProxyFactory创建Mapper的代理对象MapperProxy,在真正执行mapper中方法时会创建MapperMethod对象并放入缓存,MapperMethod是执行sql语句的入口。
一、MapperRegistry
MapperRegistry是mapper接口及其对应的代理工厂的注册中心。
//将mapper信息和MapperProxyFactory注册进来
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
//获取Mapper的代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//创建代理对象MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
二、MapperProxyFactory
该工厂生成mapper的代理类。
//使用JDK的动态代理创建
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
三、MapperProxy
mapper的代理类。
//MapperProxy是代理类,核心方法是invoke
@Override
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);
}
//先查缓存,没有再创建新的MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
四、MapperMethod
SQL语句执行的起点。封装了Mapper接口对应的方法信息,以及对应的Sql语句信息。可看做是mapper接口和sql语句的桥梁。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
//根据sql类型选择分支
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()) {
//如果方法的返回值是void,且参数列表中含有ResultHandler则返回值的处理在自定义
//ResultHandler中执行。
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//返回多个值,区分集合和数组
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回Map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//返回单一对象:包括基本类型或者普通bean
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;
}
SqlCommand: 记录了sql语句的名称和类型;MethodSignature: mapper接口中对应的方法信息;
ParamNameResolver: 处理Mapper接口中方法的参数列表。
public ParamNameResolver(Configuration config, Method method) {
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++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
//参数列表中包含RowBounds(类似分页),ResultHandler(自定义结果处理器)跳过。
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
//默认为true,走此分支
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
//无参数
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//未使用@Param且参数只有一个
return args[names.firstKey()];
} else {
//使用了@Param注解 或者 含有多个参数
//param 中记录了参数名和实参之间的对应关系
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, ...)
//添加通过的参数名称如 param1;即如果第一个参数名为userId,sql语句中
//可以使用#{userId}也可以使用#{param1}
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;
}
}