在使用Mybatis时一般都会写个Dao接口,然后调用方法时,总结通过dao.方法完成sql查询,使用时代码如下:
public interface UserMapper {
//根据id查找
User selectById(int id);
//一对多根据id查找
UserAndStudent selectInfo(int id);
//一对多查找
List<StudentAndCourse> selectCourse(int id);
}
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user2 = userMapper.selectById(1);
System.out.println("通过约定查找"+user2);
复制代码
其输出结果,就是我们在xml配置文件中,写的sql语句的执行结果 现在问题来了:mybatis是怎么通过一个没有实现的接口完成方法调用的,并且sql语句是怎么调用出来的
根据之前的学习,想到一个能通过接口完成方法调用的模式---代理模式中的动态代理博主之前写了一篇文章 软件设计模式---代理模式 动态代理可以将接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理,这就是mybatis通过接口完成sql查询的原理
首先观察接口创建过程
UserMapper mapper = sqlSession.getMapper(UserMapper.class); 复制代码
通过sqlSession的getMapper方法,传入的参数是一个被调用的接口类,我们已经大概知道了动态代理的模式,那这个接口还是没有具体的实现类啊,难道这个方法是在代码中被new的么?? 继续进入getMapper方法
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
复制代码
return了之前我们的configuration对象中的Mapper 继续进入
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
复制代码
原来Mapper是存储在MapperRegistry中,这个正是我们之前加载过的配置文件 继续进入
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);
}
}
复制代码
观察代码final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); 复制代码
其knownMappers被定义了一个HashMap,这个map是以class作为键,代理工厂作为值的一个map 接着判断mapperProxyFactory是否为空 不为空,进入下一个方法
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
复制代码
欧吼,return了mapperProxy代理对象 没错,这个newInstance就是我们的反射里面的那个,观察点入newInstance方法,最后调用了一个Proxy.newProxyInstance(),动态代理来了,第一个参数传入类加载器,第二个参数实现接口数组,第三个参数代理实例的调用处理程序
由动态代理的知识我们可以知道,传入的代理类代理了这个接口。 也就是说,当这个接口的方法被调用的时候,都会先调用代理类中的invoke方法。
我们现在已经有了动态代理,它调用方法时会先调用代理类中的invoke方法 那么,现在我们进入动态代理类---MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable 复制代码
他来了,InvocationHandler接口 查看重写的invoke方法
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 (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
复制代码
第一个参数:代理实例(不需要管,没用到) 第二个参数:当我们调用接口方法时,因为有代理类所以会调用invoke方法同时将调用的是什么方法传入进来。 第三个参数:是方法调用时的参数 上面的判断先不管,查看代码
final MapperMethod mapperMethod = cachedMapperMethod(method); 复制代码
根据invoke参数,method是我们的接口方法
所以mapperMethod就是我们的UserMapper的selectById方法 下一步调用mapperMethod.execute(sqlSession, args); 进入方法
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;
}
复制代码
在之前解析xml是,解析了select标签,所以现在的switch语句进入的是case select通过判断语句,我们最后调用的方法如下
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); 复制代码
之前的动态代理,args是我们的传入参数,我们的传入参数的1 接着,有意思的又来了,sqlSession.selectOne(),这是不是...sqlSession自己调用的seleceOne方法...并且第一个参数...好像是namespace+id的组合的一个string把...赶紧打个参数瞅瞅
欧吼,namespace+id组合来了,又回到了sqlSession.selectOne()执行过程了 现在,不妨看看咱们的MapperMethod对象中,name属性怎么来的把
现在再回到我们的invoke方法中的这一段代码
final MapperMethod mapperMethod = cachedMapperMethod(method); 复制代码
MapperMethod对象是在这里创建的,进入
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;
}
复制代码
发现一段代码
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); 复制代码
在这里new的,继续进入
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
复制代码
咱们的command是在这里来的,那继续进入把
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
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);
}
}
}
复制代码
来了来了
methodName是方法名,declaringClass是接口名 再进入
resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration); 复制代码
查看源码
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
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;
}
}
复制代码
好家伙,一上来就是在拼字符串
String statementId = mapperInterface.getName() + "." + methodName; 复制代码
然后在判断hasStatement(statementId),看名字可以明白,是判断configuration中有没有statementId这个namespace+id组合最后返回configuration.getMappedStatement(statementId); 回到SqlCommand的构造方法 咱们的ms不为空,总结是最后的
name = ms.getId(); type = ms.getSqlCommandType(); 复制代码
打个断点瞅瞅
现在水到渠成了 调用sqlSession.selectOne的需要对象全有了,接着就是走sqlSession.selectOne的过程了
很遗憾的说,推酷将在这个月底关闭。人生海海,几度秋凉,感谢那些有你的时光。
原文 https://juejin.im/post/5f194071e51d4534af68945d