1、目标
这篇文章的主要目标是完成SqlSession对象的创建和Mapper方法的调用过程的源码阅读和分析。
2、源码分析
2.1 SqlSession session = sqlSessionFactory.openSession()
执行sqlSessionFactory对象的openSession方法
该方法会创建SqlSession对象,该对象中包含了配置类configuration对象、Executor执行器对象
进入newExecutor方法
该方法会创建一个SimpleExecutor对象,它包含了JdbcTransaction对象,默认cacheEnabled是true,因此会创建CachingExecutor并将SimpleExecutor对象封装到其中
同时,创建Executor的同时会执行InterceptorChain拦截器链
2.2 BlogMapper mapper = session.getMapper(BlogMapper.class);
执行SqlSession对象的getMapper方法
该方法会创建一个JDK动态代理对象,之前生成SqlSessionFactory的源码中解析mapper.xml文件中的namespace得到接口的Class类对象type,并存放到knownMappers容器中,作用是执行getMapper方法的时候可以从knownMappers容器中根据key是type找到工厂类对象从而创建JDK代理对象
进入newInstance方法
该方法会利用Proxy的newProxyInstance方法得到JDK动态代理对象,其中方法的参数是MapperProxy对象,MapperProxy类实现了InvocationHandler接口,重写了invoke方法
2.3 Blog blog = mapper.selectBlog(“lisi”);
执行mapper动态代理对象的selectBlog方法,参数是lisi
该方法会调用MapperProxy对象的invoke方法,它会判断方法如果是Object类的方法就利用反射调用方法,如果不是Object类的方法会创建一个MapperProxy类的PlainMethodInvoker静态内部类对象然后执行invoke方法用来真正执行查询数据库的selectOne方法
cachedInvoker(method)方法得到PlainMethodInvoker对象,它包含了MapperMethod对象,其中包含了SqlCommand对象和MethodSignature对象,SqlCommand对象封装了接口方法全类名name和接口方法类型type,MethodSignature对象封装了paramNameResolver和returnType,paramNameResolver封装了hasParamAnnotation如果是true表示接口方法的参数上有@Param注解,还封装了names,它是一个集合包含了下标0和对应的接口方法参数名字author
args是接口方法的参数值是下标为0的参数值是lisi
2.3.1 进入cachedInvoker(method)方法
该方法会判断如果方法不是默认方法的话会先创建MapperMethod对象然后创建PlainMethodInvoker方法,默认方法是JDK8中接口的新特性,即允许有default默认方法,默认方法是public、不是abstract(有方法体body)、实例方法(非静态方法)
2.3.1.1 创建MapperMethod对象
进入MapperMethod的构造器,会先创建SqlCommand对象,然后创建MethodSignature对象
先创建SqlCommand对象
该方法会得到MappedStatement对象,然后封装name和type到SqlCommand对象中,其中name是接口方法的全类名,type是接口方法的类型select
该方法会得到MappedStatement对象,根据mappedStatements这个map和key=id得到MappedStatement对象
然后创建MethodSignature对象,重点是接口方法的参数名字解析
MethodSignature对象封装了ParamNameResolver对象,这个构造器会用TreeMap存放@Param注解的参数名字,TreeMap有两种构造器,无参构造器会按照key进行自然排序,有参构造器需要传入Comparator接口实现类,最后将map放到names属性中
因此,names这个TreeMap保存了接口方法的参数名字
2.3.1.2 创建PlainMethodInvoker对象
创建PlainMethodInvoker对象
PlainMethodInvoker对象只是对MappedMethod进行包装
2.3.2 执行PlainMethodInvoker对象的invoke方法
进入execute方法
该方法会先调用convertArgsToSqlCommandParam方法得到一个map,存放参数名字和参数值,然后根据SqlSession对象的selectOne方法查询数据库
2.3.2.1 进入convertArgsToSqlCommandParam方法
该方法会根据之前解析的names这个TreeMap,它保存了解析的接口方法的参数名字,还根据args保存的参数值,可以映射到paramMap,这个map的key是接口方法的参数名字,value是接口方法的参数值
2.3.2.2 进入sqlSession.selectOne方法
进入selectOne方法
selectOne方法会调用selectList方法,参数statement是接口方法的全类名,参数parameter是一个map,它保存了接口方法的参数名字和参数值
进入selectList方法
该方法会先查询mappedStatements得到MappedStatement对象,然后调用Executor执行器的query方法查询数据库得到查询结果
2.4 执行query方法
调用Executor执行器的query方法查询数据库可以得到查询结果
2.4.1 执行CachingExecutor.query方法
进入CachingExecutor的query方法
该方法会查询二级缓存CachingExecutor,发现没有二级缓存就查询一级缓存BaseExecutor
2.4.2 执行BaseExecutor.query方法
进入BaseExecutor的query方法
该方法会查询一级缓存发现没有就查询数据库
2.4.3 执行SimpleExecutor.doQuery方法
进入SimpleExecutor的doQuery方法
该方法先创建StatementHandler对象,然后创建Statement对象,最后根据StatementHandler对象调用query方法
2.4.3.1 创建StatementHandler对象
创建RoutingStatementHandler对象,StatementHandler对象可以设置InterceptorChain拦截器链
创建PreparedStatementHandler对象
2.4.3.2 创建Statement对象
创建Statement对象会执行prepareStatement方法,先执行getConnection方法获取连接,然后执行prepare方法创建Statement对象,最后执行parameterize方法将sql中的?替换成字符串,并且这个字符串必须加上单引号防止sql注入
执行getConnection方法会执行popConnection方法来获取连接,判断如果数据库连接池有空闲连接会直接返回这个连接,如果数据库连接池没有空闲连接会创建一个新的连接
执行prepare方法会利用反射创建Statement对象,还会输出sql,当然logImpl需要配置成STDOUT_LOGGING,如下图所示
执行parameterize方法会执行setString方法,它会将sql中的?替换成字符串,并且这个字符串必须加上单引号防止sql注入,因为字符串包含or 1 = 1也没关系,因为字符串加上了单引号,这个字符串不会作为where查询条件的拼接,它只是一个参数值
2.4.3.3 根据StatementHandler对象调用query方法
执行ps的execute方法会输出Parameters,如下图所示
执行handleResultSets方法会输出查询结果,如下图所示
2.4.4 执行BaseExecutor的queryFromDatabase方法
该方法查询数据库后会将查询结果放到一级缓存中
一级缓存是一个map,key是CacheKey,value是查询结果Blog对象
输出结果是