事例:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUser(2);
代码分析
1、 sqlSession#getMapper
该方法返回的是一个代理类MapperProxy<T>。方法调用过程是这样的:
sqlSession#getMapper->configuration#getMapper->mapperRegistry#getMapper->mapperProxyFactory#newInstance
最后是由工厂类mapperProxyFactory创建的。
// sqlSession#getMapper
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 配置解析的时候,就已经把mapper接口存储map集合knownMappers中了
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);
}
}
// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
2、MapperProxy#invoke
接口方法的执行其实就是进入到MapperProxy#invoke方法中。我们直接来看此方法。
// MapperProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
/**
* 代理类中会继承Object类的方法,例如:toString、equals
* 这类方法不进行代理,直接执行了
*/
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
/**
* 创建MapperMethodInvoker
* 然后调用invoke
*/
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
1、创建MapperMethodInvoker
// MapperProxy#cachedInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
3、MapperMethodInvoker#invoke
此处我们分析PlainMethodInvoker这实现类的invoke方法,后面我们在讨论PlainMethodInvoker和DefaultMethodInvoker的区别。
mybatis中很多地方都是类包着类,一层裹一层。这里MapperMethodInvoker包了
MapperMethod,具体的操作都是MapperMethod来执行的。
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
4、MapperMethod#execute
4.1、MapperMethod
MapperMethod顾名思义就是Mapper接口要执行的方法。从构造函数来看:它持有mapper接口,执行方法,配置类的引用,然后,根据这三个参数构建了SqlCommand和MethodSignature。
public class MapperMethod {
/**
*sql命令相关
*/
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);
}
}
a) SqlCommand
该类中有两个属性:
name:可以定位mapper.xml中的CRUD方法。
SqlCommandType :要执行的是哪一中CRUD操作。
在MapperMethod#execute中发挥了作用!
public static class SqlCommand {
// MappedStatement的id,
// 例如com.gupaoedu.lsj.mybatis.mapper.UserMapper.selectUser
private final String name;
// enum类型:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
private final SqlCommandType type;
}
b) MethodSignature
它是对接口执行的方法作了分析,包括它的返回值类型,返回的是多条还是单条,是否有mapKey等。在MapperMethod#execute中发挥了作用!
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
}
4.2、execute方法执行
事例中,我执行的是单条查询方法,所以代码会进入到case SELECT这个分支。
最终会调用sqlSession#selectOne->sqlSession#selectList
接下来的分析就是这篇文章讲解的了。
selectList执行分析
// MapperMethod
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);
/**
* 最后这里进行了查询,接下来的分析就是这篇文章讲解的了。
* (https://www.jianshu.com/p/19b508e7b5c7)
*/
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;
}
总结:
1.mybatis最开始方法的执行是采用namespace+id的方式,即
sqlSession.selectOne("com.gupaoedu.lsj.mybatis.mapper.UserMapper.selectUser",1)
2.后来增加了mapper接口的调用,但是最终都会转化成第一种方式执行
sqlSession.getMapper(UserMapper.class);