在MyBatis中,Mapper是一个接口,不是实体类。而在Java中,接口是无法运行的,所以MyBatis运用了动态代理模式来运行Mapper接口。
关于动态代理,可以看这篇 深度解析JDK动态代理。
MyBatis首先创建了一个MapperProxy类,实现了InvocationHandler接口。就相当于上篇文章的MyHandler类。
由于动态代理只能代理有接口实现的类。需要传入要代理的接口,所以传入sqlSession.就相当于上篇文章的People接口。
既然是代理,就有invoke方法。入参method是真实对象要实现的业务方法。
1.先判断是否是class类,
method.getDeclaringClass() //Java解释 返回由此method对象表示的方法的类的Class对象,其实就是判断method所在对象的类是哪种类型,是interface还是class.
2.判断是否是默认方法
isDefaultMethod(method) //是Java8 新引入的语言特性 默认方法
默认方法,主要为了解决接口跟实现类耦合太紧密的问题。 如果要在接口中新增一个方法,需要改动所有的实现类,改动太大。所以Java8新增了默认方法。只需要在接口方法中用default。
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}
继承方式有3种,可以选择不重写、重写继续声明为default、重写去掉default,声明为抽象方法。
3.生成MapperMethod对象
维护了一个map类型的缓存。缓存中没有则new一个MapperMethod出来。
4.调用mapperMethod.execute。
接下来分析下MapperMethod类。
该类有两个类变量。
public class MapperMethod {
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);
}
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);
result = sqlSession.selectOne(command.getName(), param);
}
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表示数据库执行的一个 Transact-SQL 语句或存储过程
怎么去拿,需要进入SqlCommand。而SqlCommand是一个内部类。
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {
final String methodName = method.getName();
final Class> declaringClass = method.getDeclaringClass();
//这里是去获取MappedStatment。主要逻辑所在
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
private MappedStatement resolveMappedStatement(Class> mapperInterface, String methodName,
Class> declaringClass, Configuration configuration) {
//statementId 就是唯一标识,接口名+方法名,这里就是需要xml的命名空间需要跟接口匹配的原因。
String statementId = mapperInterface.getName() + "." + methodName;
//能匹配到statementId,就可以拿到MappedStatement。这里为什么要用configuration去匹配,
// 是因为configuration里配置了扫描xml文件的路径,能够拿到所有的xml文件,进而拿到所有的statement.
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
//用了递归,循环获取。
for (Class> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}