继续上一篇,了解到,MyBatis中利用的JDK的动态代理,实现了接口管联xml中的sql,这里呢,只是知道了用什么方式,但是具体的怎么关联不清楚,需要再次学习其中的内容,go。
知道了动态代理后,先看MapperProxy类,他实现了InvocationHandler,那么也重写了invoke方法,找个这个方法
MapperProxy.class 中
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
都知道执行ActivityCzMapper 接口的方法,会调用这里MapperProxy的invoke方法,看到第一行代码if判断
Object.class.equals(method.getDeclaringClass())
这里的method肯定是我们调用ActivityCzMapper 接口的方法,getDeclaringClass()肯定返回null, 因为我们的Mapper接口就没有实现类,不可能走下去,所以执行else里的代码,else里面执行了cachedMapperMethod()方法,我们看一下
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if(mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
首先在methodCache的map中get传来的method(就是访问Mapper接口的方法)key,结果肯定是null,执行下面代码初始化MapperMethod三个参数的构造方法,第一个参数Mapper接口,第二个接口的方法,第三个是一个sqlSession的getConfiguration方法代表sqlsession的所有信息,在上一篇中传递的那个sqlsession,初始化MapperProxy类的构造方法时。
现在跳到这个MapperMethod的构造方法查看
MapperMethod.class中
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, method);
}
看到初始化两个成员变量,command,method,在继续看SqlComman()方法是干什么的,跳到MapperProxyFactory类中的静态内部类SqlCommand,看到代码
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
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())) {
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);
} else {
this.name = ms.getId();
this.type = ms.getSqlCommandType();
if(this.type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + this.name);
}
}
}
public String getName() {
return this.name;
}
public SqlCommandType getType() {
return this.type;
}
}
哇,看这一条代码
String statementName = mapperInterface.getName() + "." + method.getName();
这个 mapperInterface.getName()就是Mapper接口的类名字,他必然和xml的
<mapper namespace="com.car.service.dao.ActivityCzMapper" > 一致,method.getName()得到方法的名字,
那就是mapper里的id,然后判断
MappedStatement ms = null;
if(configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
}
成立后,看下MappedStatement的意思,MappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条SQL语句。
这样把他包装起来,在执行下面的代码,ms != null 后赋值,name 就是mapper xml 中sql 的id值,type就是sql的类型(sel,up,de)如果不知道,那就异常。
OK现在我们返回到MapperMethod中,说白了,传来的sqlSession到SqlCommand,在到MapperMethod。
现在在回到MapperProxy的invoke方法中走最后一步, return mapperMethod.execute(this.sqlSession, args);
查看execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if(SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if(SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if(SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else {
if(SqlCommandType.SELECT != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if(this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if(this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if(this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
有点多,但是很明确,清楚看出来,首先判断sql的类型,up,sel,de的,在select中还有一层判断Mapper接口方法的返回值,我们查的是selectByPrimaryKey单个的,那就走selectOne()方法,例如我们的返回值List,那需要走executeForMany,可以看下这个源代码:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if(this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;
}
恩,很稳,贴上一句关键点
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
这就不多说了,将结果返回。这样通过MapperProxy的 public Object invoke(Object proxy, Method method, Object[] args)方法
返回结果。这样就是一个执行Mapper接口方法执行sql的过程。在整个过程中还有很多地方未探索的,就比如在mybatis配置文件加载的时候干了什么,例如那个addMapper()方法到底在MyBatis哪个加载过程中add的,需要再次探索。以上是我的整个学习过程。