使用MyBatis的Mapper时,只声明了Mapper接口,而没有具体的实现。而使用时却可以通过VersionMapper mapper = session.getMapper(VersionMapper.class);返回具体的对象。准确的说,这个过程中MyBatis通过JDK的动态代理,创建了实现Mapper接口的代理对象,然后通过改变代理对象的执行行为来达到实现Mapper的具体功能。
回顾一下MyBatis启动时,先解析配置的Mapper信息,然后通过MapperRegistry注册。容器是Map<Class<?>, MapperProxyFactory<?>>,注册时,为每个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<T>(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时,取得注册时的Mapper工厂,然后创建一下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 {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
然后通过动态代理实例化出代理对象。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
newProxyInstance方法有三个参数,第一个是类加载器,代理对象的class信息将装载到该加载器中。第二个参数是要实现的接口,就是开发时写的Mapper接口。第三个是代理行为类,决定了代理对象执行方法时,要怎么执行。MyBatis就是通过定义mapperProxy来控制mapper的执行行为的。
MapperProxy有三个属性SqlSession sqlSession,提供数据源;Class<T> mapperInterface,用类型来标识Mapper唯一的性;Map<Method, MapperMethod> methodCache方法的缓存,使得调用过的方法,不需要重复去包装。此外,MapperProxy实现了InvocationHandler接口,使得它具有改变代理方法行为的能力。接下来看一下它是如何控制代理对象的执行行为的。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
invoke方法就是调用代理对象方法时,具体执行到的方法。通过判断声明这个方法的类是否是Object。是的话,直接调用Object的方法。如果不是,将从缓存中取,如果没有取到,就新建一个包装方法,然后放到方法缓存中。最后调用包装方法的execute真正执行。
接下来看调用包装方法的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;
}
在新建包装方法对象的时候,根据Mapper类名作为命名空间,加上方法名称,在全局配置中取到对应的MapperStatement对象。然后判断SQL的类型,转换入参,调用sqlSession对应的API来执行。