上一篇,我们讲过DAO层接口,是怎么被mybatis自动实现的。DAO层接口,为什么能操作数据库
这篇,我们继续下去,讲一讲DAO层接口中selectById的流程。
和上一篇文章中,用到的代码几乎没有区别,只是Main类有所调整
public class Main {
public static SqlSession getSqlSession() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory.openSession();
}
public static void main(String[] args) throws IOException {
TestDAO testDAO = getSqlSession().getMapper(TestDAO.class);
Test test = testDAO.selectById(1);
testDAO.testDefaultMethod();
//类文件是缓存在java虚拟机中,我们将类文件打印到文件中,便于查看
// generateProxyFile("F:/TestDAOProxy.class");
}
private static void generateProxyFile(String path){
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class>[]{TestDAO.class});
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类class文件写入成功");
} catch (Exception e) {
System.out.println("写文件错误");
}
}
}
1、起始位置
从main方法中的Test test = testDAO.selectById(1);这一行开始debug。探究selectById的流程
2、UML图
这里先给出一个UML图,照着这个UML进行讲解。当然这里UML图太繁琐了,根本记不住,我们会在最后给出一个简化的UML图,方便理解记忆。
3、TestDAO的selectById方法,实际上是调用了它的代理类MapperProxy的invoke方法
那么我们就来理一理这个invoke方法:
1)Object.class.equals(method.getDeclaringClass()) 这句话的意思是:判断method这个方法,是不是Object类中的方法,比如没有在子类中被重写的toString()、hashCode()、equals()方法。
2)isDefaultMethod(method) 的代码如下:
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
我们可以看到这个判断语句,前半段的意思是:这个方法被public修饰,但是不被abstract、static修饰。后半段的意思是:这个方法是接口中的方法。
又要是接口中的方法,又不能被abstract、static修饰,接口中真的有这种方法吗?
确实是有的,JDK8以后,接口可以有方法体。
4、很明显selectById不是接口默认方法,那么我们只能走final MapperMethod mapperMethod = cachedMapperMethod(method);这一步了。
从methodCache中get出一个MapperMethod。这个methodCache又是哪来的?
我们回溯一下,发现methodCache,是从MapperProxy中的构造方法来的。那么看看谁调用了这个构造方法。
好了,我们知道methodCache是自己new出来的。
5、很明显这个时候methodCache是空的,那么就要自己new 一个MapperMethod
其中,SqlCommand如下:
6、MapperMethod已经被创建了,我们回到MapperProxy的invoke方法,发现下一步是调用MapperMethod的execute方法
其中Object param = method.convertArgsToSqlCommandParam(args)是获取参数的值,也就是我们selectById(1)传进来的1。
7、继续下一步:result = sqlSession.selectOne(command.getName(), param);
defaultSqlSession的selectOne,实际上是调用了selectList。那么我们继续跟踪selectList
8、接下来调用了CachingExecutor类的query方法。那么这个CachingExecutor是什么时候被创建的?回溯一下,发现是在DefaultSqlSessionFactory的openSession方法中,被创建的。也就是我们代码中的这一句。
我们已经知道了CachingExecutor是什么时候创建的,那么继续探索它的query方法
获取BoundSql和CacheKey之后,继续query
又调用了SimpleExecutor的query方法,这里用到了装饰器模式(同宗同源)。
9、来到BaseExecutor的queryFromDatabase方法,终于调用了doQuery方法
10、在doQuery方法中,我们来看一下StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
这句话,生成了3个handler,分别是StatementHandler、ParameterHandler、ResultSetHandler。后两个是作为StatementHandler的实例变量存在的。
这三个类的用法,可以参考官网,关于 插件 (plugins)的描述。
顺道说一下,Executor、StatementHandler、ParameterHandler、ResultSetHandler这四个插件的生成顺序是
Executor --> ParameterHandler --> ResultSetHandler --> StatementHandler。
11、生成3个handler之后,就来到了stmt = prepareStatement(handler, ms.getStatementLog());
这个方法的作用类似于我们使用JDBC时,获取连接,创建PreparedStatement,做一些前期准备。sql语句中的占位符也被传进来的参数替代了。
12、执行handler.
前面两句,是执行sql语句。
resultSetHandler.
这个步骤也太冗长了,我么简化一下,方便记忆