zoukankan      html  css  js  c++  java
  • mybatis(五)mybatis工作流程

    转载:https://www.cnblogs.com/wuzhenzhao/p/11103017.html

    先来看一下MyBatis 的编程式使用的方法:

    复制代码
    public void testMapper() throws IOException {
      String resource = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      SqlSession session = sqlSessionFactory.openSession();
      try {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        Blog blog = mapper.selectBlogById(1);
        System.out.println(blog);
      } finally {
        session.close();
      }
    }
    复制代码

      我们再来捋MyBatis 的主要工作流程:首先在MyBatis 启动的时候我们要去解析配置文件,包括全局配置文件和映射器配置文件,这里面包含了我们怎么控制MyBatis 的行为,和我们要对数据库下达的指令,也就是我们的SQL 信息。我们会把它们解析成一个Configuration 对象。接下来就是我们操作数据库的接口,它在应用程序和数据库中间,代表我们跟数据库之间的一次连接:这个就是SqlSession 对象。我们要获得一个会话, 必须有一个会话工厂SqlSessionFactory 。SqlSessionFactory 里面又必须包含我们的所有的配置信息,所以我们会通过一个Builder 来创建工厂类。

      我们知道,MyBatis 是对JDBC 的封装,也就是意味着底层一定会出现JDBC 的一些核心对象,比如执行SQL 的Statement,结果集ResultSet。在Mybatis 里面,SqlSession 只是提供给应用的一个接口,还不是SQL 的真正的执行对象。我们通过查看SqlSession源码发现,SqlSession 持有了一个Executor 对象,用来封装对数据库的操作。在执行器Executor 执行query 或者update 操作的时候我们创建一系列的对象,来处理参数、执行SQL、处理结果集,这里我们把它简化成一个对象:StatementHandler,这个就是MyBatis 主要的工作流程,如图:

    MyBatis 架构分层与模块划分:

      在MyBatis 的主要工作流程里面,不同的功能是由很多不同的类协作完成的,它们分布在MyBatis jar 包的不同的package 里面。我们来看一下MyBatis 的jar 包(基于3.5.1)

    跟Spring 一样,MyBatis 按照功能职责的不同,所有的package 可以分成不同的工作层次。我们可以把MyBatis 的工作流程类比成餐厅的服务流程。

      第一个是跟客户打交道的服务员,它是用来接收程序的工作指令的,我们把它叫做接口层。

      第二个是后台的厨师,他们根据客户的点菜单,把原材料加工成成品,然后传到窗口。这一层是真正去操作数据的,我们把它叫做核心层。

      最后就是餐厅也需要有人做后勤(比如清洁、采购、财务),来支持厨师的工作和整个餐厅的运营。我们把它叫做基础层。

    来看一下这张图,我们根据刚才的分层,和大体的执行流程,做了这么一个总结。当然,从不同的角度来描述,架构图的划分有所区别,这张图画起来也有很多形式。我们先从总体上建立一个印象。

    接口层:

      首先接口层是我们打交道最多的。核心对象是SqlSession,它是上层应用和MyBatis打交道的桥梁,SqlSession 上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。

    核心处理层:

      接下来是核心处理层。既然叫核心处理层,也就是跟数据库操作相关的动作都是在这一层完成的。核心处理层主要做了这几件事:

    1. 把接口中传入的参数解析并且映射成JDBC 类型;
    2. 解析xml 文件中的SQL 语句,包括插入参数,和动态SQL 的生成;
    3. 执行SQL 语句;
    4. 处理结果集,并映射成Java 对象。

      插件也属于核心层,这是由它的工作方式和拦截的对象决定的。

    基础支持层:

      最后一个就是基础支持层。基础支持层主要是一些抽取出来的通用的功能(实现复用),用来支持核心处理层的功能。比如数据源、缓存(请点击跳转至缓存详解)、日志、xml 解析、反射、IO、事务等等这些功能。

      这个就是MyBatis 的主要工作流程和架构分层。

    从以上的编程式的例子来看,mybatis的工作流程大致可以分为一下四步:

    1. 通过建造者模式创建一个工厂类,定位,加载,解析配置文件的就是在这一步完成的,包括mybatis-config.xml 和Mapper 适配器文件。
    2. 通过SqlSessionFactory 创建一个SqlSession。
    3. 获得Mapper 对象。
    4. 调用接口方法(insert,delete,update,select)。

    第一步配置解析过程:

      定位资源位置,加载资源,将xml加载成流这里就不分析了。

    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);

      首先我们要清楚的是配置解析的过程全部只解析了两种文件。一个是mybatis-config.xml 全局配置文件。另外就是可能有很多个的Mapper.xml 文件,也包括在Mapper 接口类上面定义的注解。我们从mybatis-config.xml 开始。在之前已经分析了核心配置了,大概明白了MyBatis 有哪些配置项,和这些配置项的大致含义。这里我们再具体看一下这里面的标签都是怎么解析的,解析的时候做了什么。

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

      首先我们new 了一个SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个build 方法的重载,最终返回的是一个SqlSessionFactory 对象(单例模式)。我们点进去build 方法。这里面创建了一个XMLConfigBuilder 对象(Configuration 对象也是这个时候创建的)。

    复制代码
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            SqlSessionFactory var5;
            try {
    // 创建全局文件解析器 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // 调用解析,解析完构建默认的SessionFactory var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException var13) { } } return var5; }
    复制代码

      XMLConfigBuilder 是抽象类BaseBuilder 的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:XMLMapperBuilder:解析Mapper 映射器,XMLStatementBuilder:解析增删改查标签。

      根据我们解析的文件流,这里后面两个参数都是空的,创建了一个parser。这里有两步,第一步是调用parser 的parse()方法,它会返回一个Configuration类。也就是配置文件里面所有的信息都会放在Configuration 里面。Configuration 类里面有很多的属性,有很多是跟config 里面的标签直接对应的。

    复制代码
    public Configuration parse() {
       // 判断是否已经解析过 if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true;
          // 解析的开始 this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
    复制代码

      首先会检查是不是已经解析过,也就是说在应用的生命周期里面,config 配置文件只需要解析一次,生成的Configuration 对象也会存在应用的整个生命周期中。接下来就是parseConfiguration 方法:

      这下面有十几个方法,对应着config 文件里面的所有一级标签。MyBatis 全局配置文件的顺序不可以颠倒。

    复制代码
    private void parseConfiguration(XNode root) {
            try {
                this.propertiesElement(root.evalNode("properties"));
                Properties settings = this.settingsAsProperties(root.evalNode("settings"));
                this.loadCustomVfs(settings);
                this.loadCustomLogImpl(settings);
                this.typeAliasesElement(root.evalNode("typeAliases"));
                this.pluginElement(root.evalNode("plugins"));
                this.objectFactoryElement(root.evalNode("objectFactory"));
                this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
                this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
    //解析settings标签 this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
    复制代码

      在settings标签的解析中,我们就可以看到对应官网上介绍的各种配置,及其默认值,所有的值,都会赋值到Configuration 的属性里面去。简单的来看一下:

    复制代码
    private void settingsElement(Properties props) {
            this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
            this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
            this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));//二级缓存
            this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
            this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
            this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
            this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
            this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
            this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
            this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));//默认执行器类型
            this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));
            this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));
            this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
            this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
            this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));//本地缓存级别
            this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
            this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
            this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
            this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
            this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
            this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
            this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
            this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
            this.configuration.setLogPrefix(props.getProperty("logPrefix"));
            this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
    }
    复制代码

      最后就是<mappers>标签的解析,对应匹配如下四种方式:

    复制代码
    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      <mapper url="file:///var/mappers/BlogMapper.xml"/>
      <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    复制代码

      首先会判断是不是接口,只有接口才解析;然后判断是不是已经注册了,单个Mapper重复注册会抛出异常。如果是以资源位置及URL的配置方式,还需要创建Mapper解析器解析。

    复制代码
    private void mapperElement(XNode parent) throws Exception {
            if (parent != null) {
                Iterator var2 = parent.getChildren().iterator();
                while(true) {
                    while(var2.hasNext()) {
                        XNode child = (XNode)var2.next();
                        String resource;
    //配置包的方式 if ("package".equals(child.getName())) { resource = child.getStringAttribute("name");
                   //解析完加入全局配置类,填充属性 this.configuration.addMappers(resource); } else { resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream inputStream;
    //配置以资源的方式 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); inputStream = Resources.getResourceAsStream(resource); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse();
    //配置以URL的方式 } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); inputStream = Resources.getUrlAsStream(url); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments()); mapperParser.parse();
    // 配置以class单个接口 方式 } else {//都没配置报异常 if (resource != null || url != null || mapperClass == null) { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } Class<?> mapperInterface = Resources.classForName(mapperClass); this.configuration.addMapper(mapperInterface); } } } return; } } }
    复制代码

      XMLMapperBuilder.parse()方法,是对Mapper 映射器的解析。里面有两个方法:configurationElement()—— 解析所有的子标签, 其中 buildStatementFromContext()最终获得MappedStatement 对象。bindMapperForNamespace()——把namespace(接口类型)和工厂类绑定起来。

      无论是按package 扫描,还是按接口扫描,最后都会调用到MapperRegistry 的addMapper()方法。最后,MapperRegistry 也会放到Configuration 里面去。MapperRegistry 里面维护的其实是一个Map 容器,存储接口和代理工厂的映射关系。

    复制代码
    public <T> void addMapper(Class<T> type) {
            if (type.isInterface()) {//判断是否接口
                if (this.hasMapper(type)) { //是否已经注册
                    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
                }
                boolean loadCompleted = false;
                try {//利用Map来维护mapper
                    this.knownMappers.put(type, new MapperProxyFactory(type));
                    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                    parser.parse();//解析
                    loadCompleted = true;
                } finally {
                    if (!loadCompleted) {//注册过程出现异常,移除
                        this.knownMappers.remove(type);
                    }
                }
            }
    }
    复制代码

      除了映射器文件,在这里也会去解析Mapper 接口方法上的注解。在addMapper()方法里面创建了一个MapperAnnotationBuilder,我们点进去看一下parse()方法。parseCache() 和parseCacheRef() 方法其实是对@CacheNamespace 和@CacheNamespaceRef 这两个注解的处理。parseStatement()方法里面的各种getAnnotation(),都是对注解的解析,比如@Options,@SelectKey,@ResultMap 等等。最后同样会解析成MappedStatement 对象,也就是说在XML 中配置,和使用注解配置,最后起到一样的效果。

    复制代码
    public void parse() {
            String resource = this.type.toString();
            if (!this.configuration.isResourceLoaded(resource)) {
                this.loadXmlResource();//加载XML资源
                this.configuration.addLoadedResource(resource);
                this.assistant.setCurrentNamespace(this.type.getName());
                this.parseCache();//解析@CacheNamespace注解
                this.parseCacheRef();//解析@CacheNamespaceRef注解
                Method[] methods = this.type.getMethods();
                Method[] var3 = methods;
                int var4 = methods.length;
    
                for(int var5 = 0; var5 < var4; ++var5) {
                    Method method = var3[var5];
                    try {
                        if (!method.isBridge()) {
                            this.parseStatement(method);//处理各种注解
                        }
                    } catch (IncompleteElementException var8) {
                        this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                    }
                }
            }
            this.parsePendingMethods();
    }
    复制代码

      最后会将这些解析的结果填充倒Configuration中。在这一步,我们主要完成了config 配置文件、Mapper 文件、Mapper 接口上的注解的解析。我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration 的实例。如下是第一阶段的时序图:

     第二步会话创建过程:

      这是第二步, 我们跟数据库的每一次连接, 都需要创建一个会话, 我们用openSession()方法来创建。

    复制代码
    SqlSession session = sqlSessionFactory.openSession();
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
    //从先前所解析的配置中获取环境
    Environment environment = this.configuration.getEnvironment();
    //获取事务工厂类
    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
    //创建事务
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //创建执行器类 默认SIMPLE
    Executor executor = this.configuration.newExecutor(tx, execType);
    //创建默认的SqlSession
    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
    this.closeTransaction(tx);
    throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
    } finally {
    ErrorContext.instance().reset();
    }
    return var8;
    }
    复制代码

      这个会话里面,需要包含一个Executor 用来执行SQL。Executor 又要指定事务类型和执行器的类型。所以我们会先从Configuration 里面拿到Enviroment,Enviroment 里面就有事务工厂。创建事务会有以下两种选择:

      如果配置的是JDBC,则会使用Connection 对象的commit()、rollback()、close()管理事务。如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS,Weblogic。如果是Spring + MyBatis , 则没有必要配置, 因为我们会直接在applicationContext.xml 里面配置数据源和事务管理器,覆盖MyBatis 的配置。

      我们知道,Executor 的基本类型有三种:SIMPLE、BATCH、REUSE,默认是SIMPLE(settingsElement()读取默认值),他们都继承了抽象类BaseExecutor。

    • SimpleExecutor:每执行一次update 或select,就开启一个Statement 对象,用完立刻关闭Statement 对象。
    • ReuseExecutor:执行update 或select,以sql 作为key 查找Statement 对象,存在就使用,不存在就创建,用完后,不关闭Statement 对象,而是放置于Map 内,供下一次使用。简言之,就是重复使用Statement 对象。
    • BatchExecutor:执行update(没有select,JDBC 批处理不支持select),将所有sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement 对象,每个Statement 对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC 批处理相同。
    复制代码
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //判断使用的执行器类型,之所以判断两次是防止手贱的人把执行器配置为空 executorType = executorType == null ? this.defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Object executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (this.cacheEnabled) {//是否启用二级缓存,启用了进行包装 executor = new CachingExecutor((Executor)executor); }
    //插件包装,转换成拦截器链interceptorChain,对于插件我下一篇博客会详细介绍 Executor executor = (Executor)this.interceptorChain.pluginAll(executor); return executor; }
    复制代码

      如果配置了cacheEnabled=ture,会用装饰器模式对executor 进行包装:newCachingExecutor(executor)。最终返回DefaultSqlSession,属性包括Configuration、Executor 对象。

      总结:创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,它是SQL 的执行者。

     

    第三步获得Mapper 对象:

      现在我们已经有一个DefaultSqlSession 了,必须找到Mapper.xml 里面定义的Statement ID,才能执行对应的SQL 语句。找到Statement ID 有两种方式:一种是直接调用session 的方法,在参数里面传入Statement ID,这种方式属于硬编码,我们没办法知道有多少处调用,修改起来也很麻烦。另一个问题是如果参数传入错误,在编译阶段也是不会报错的,不利于预先发现问题。

    Blog blog = (Blog) session.selectOne("com.wuzz.mapper.BlogMapper.selectBlogById", 1);

      所以在MyBatis 后期的版本提供了第二种方式,就是定义一个接口,然后再调用Mapper 接口的方法。由于我们的接口名称跟Mapper.xml 的namespace 是对应的,接口的方法跟statement ID 也都是对应的,所以根据方法就能找到对应的要执行的SQL。

    BlogMapper mapper = session.getMapper(BlogMapper.class);

      在这里我们主要研究一下Mapper 对象是怎么获得的,它的本质是什么。DefaultSqlSession 的getMapper()方法,调用了Configuration 的getMapper()方法。继而调到 MapperRegistry.getMapper:

    复制代码
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
    复制代码

      我们知道,在解析mapper 标签和Mapper.xml 的时候已经把接口类型和类型对应的MapperProxyFactory 放到了一个Map 中。获取Mapper 代理对象,实际上是从Map 中获取对应的工厂类后,最终通过代理模式返回代理对象。

      那么JDK 动态代理和MyBatis 用到的JDK 动态代理有什么区别呢?JDK 动态代理代理,在实现了InvocationHandler 的代理类里面,需要传入一个被代理对象的实现类。而在mybatis里面,我们并没有实现了InvocationHandler 的代理类。不需要实现类的原因:我们只需要根据接口类型+方法的名称,就可以找到Statement ID 了,而唯一要做的一件事情也是这件,所以不需要实现类。在MapperProxy里面直接执行逻辑(也就是执行SQL)就可以。

      获得Mapper 对象的过程,实质上是获取了一个MapperProxy 的代理对象。MapperProxy 中有sqlSession、mapperInterface、methodCache。

    第四步执行sql:

      由于所有的Mapper 都是MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的invoke()方法。

    复制代码
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {//首先判断是否需要去执行SQL,还是直接执行方法。
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                }
                //Object 本身的方法和Java 8 中接口的默认方法不需要去执行SQL。
                if (this.isDefaultMethod(method)) {
                    return this.invokeDefaultMethod(proxy, method, args);
                }
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
          //获取缓存  这里加入缓存是为了提升MapperMethod 的获取速度
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    复制代码

       接下来又调用了mapperMethod 的execute 方法,我们debug来看一下:

      MapperMethod 里面主要有两个属性, 一个是SqlCommand (SQL命令), 一个是MethodSignature(方法签名),这两个都是MapperMethod 的内部类。另外定义了多个execute()方法。

    复制代码
    public Object execute(SqlSession sqlSession, Object[] args) {
            Object result;
            Object param;//根据不同的type 和返回类型 调用sqlSession 的insert()、update()、delete()、selectOne ()方法
            switch(this.command.getType()) {
            case INSERT:
           // 调用convertArgsToSqlCommandParam()将参数转换为SQL 的参数。 param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT://查询的方法又要根据返回类型进行区分执行 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 if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH://刷新statement result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } 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; } }
    复制代码

      我们以查询  (mapper1.selectBlogById(1002))  为例 会走如下代码,会走到selectOne()方法。

    复制代码
    public <T> T selectOne(String statement, Object parameter) {
            List<T> list = this.selectList(statement, parameter);
            if (list.size() == 1) {
                return list.get(0);
            } else if (list.size() > 1) {
                throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
            } else {
                return null;
            }
        }
    复制代码

      这里抛得异常是不是很熟悉,经常有小伙伴在开发过程中会发现sql执行报这个异常,是因为定义的sql是查询的一个结果,可是返回了多个,这里会执行selectList(statement, parameter):

    复制代码
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
            List var5;
            try {//获取初始化阶段所封装的对象先根据command name(Statement ID)从Configuration中拿到MappedStatement
           MappedStatement ms = this.configuration.getMappedStatement(statement);
                var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
            } catch (Exception var9) {
                throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
            } finally {
                ErrorContext.instance().reset();
            }
            return var5;
    }
    复制代码

      这个ms 上面有我们在xml 中配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等。

      然后执行了Executor 的query()方法。前面我们说到了Executor 有三种基本类型,SIMPLE/REUSE/BATCH,还有一种包装类型,CachingExecutor。那么在这里到底会选择哪一种执行器呢?我们要回过头去看看DefaultSqlSession 在初始化的时候是怎么赋值的,这个就是我们的会话创建过程。如果启用了二级缓存,就会先调用CachingExecutor 的query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的SimpleExecutor。在没有开启二级缓存的情况下,先会走到BaseExecutor 的query()方法(否则会先走到CachingExecutor)。

      BaseExecutor:

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
            BoundSql boundSql = ms.getBoundSql(parameter);
            CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
            return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }

      从Configuration 中获取MappedStatement, 然后从BoundSql 中获取SQL 信息,创建CacheKey。这个CacheKey 就是缓存的Key。然后再调用另一个query()方法。

    复制代码
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
            ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
            if (this.closed) {
                throw new ExecutorException("Executor was closed.");
            } else {//queryStack 用于记录查询栈,防止递归查询重复处理缓存。flushCache=true 的时候,会先清理本地缓存(一级缓存)
                if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
    //清除缓存 this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null ? (List)this.localCache.getObject(key) : null; if (list != null) {//从缓存中获取数据 this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else {//如果没有缓存,会从数据库查询:queryFromDatabase() list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { Iterator var8 = this.deferredLoads.iterator(); while(var8.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next(); deferredLoad.load(); } this.deferredLoads.clear();
    //如果LocalCacheScope == STATEMENT,会清理本地缓存。 if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }
    复制代码

      从数据库查询(queryFromDatabase):

    复制代码
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
         // 先在缓存用占位符占位。执行查询后,移除占位符,放入数据。 this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); List list; try {//执行Executor 的doQuery();默认是SimpleExecutor。 list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { this.localCache.removeObject(key); } this.localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list; }
    复制代码

      SimpleExecutor.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

    复制代码
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
            Statement stmt = null;
    
            List var9;
            try {
                Configuration configuration = ms.getConfiguration();
                StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                stmt = this.prepareStatement(handler, ms.getStatementLog());
                var9 = handler.query(stmt, resultHandler);
            } finally {
                this.closeStatement(stmt);
            }
    
            return var9;
        }
    复制代码

      在configuration.newStatementHandler()中,new 一个StatementHandler,先得到RoutingStatementHandler。RoutingStatementHandler 里面没有任何的实现, 是用来创建基本的StatementHandler 的。这里会根据MappedStatement 里面的statementType 决定StatementHandler 的类型。默认是PREPARED ( STATEMENT 、PREPARED 、CALLABLE)。

    复制代码
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
            switch(ms.getStatementType()) {
            case STATEMENT:
                this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
            }
    }
    复制代码

      StatementHandler 里面包含了处理参数的ParameterHandler 和处理结果集的ResultSetHandler。这两个对象都是在上面new 的时候创建的。

    this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);

      这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。(对于详细的插件机制将在下篇博客中详细介绍)

    StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
    parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
    ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);

      四大对象还有一个是谁?在什么时候创建的?(Executor)在第二步创建会话的时候创建的。用new 出来的StatementHandler 创建Statement 对象——prepareStatement()方法对语句进行预编译,处理参数。执行的StatementHandler 的query()方法,RoutingStatementHandler 的query()方法。

    delegate 委派,最终执行PreparedStatementHandler 的query()方法。

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
            PreparedStatement ps = (PreparedStatement)statement;
            ps.execute();
            return this.resultSetHandler.handleResultSets(ps);
    }

      执行PreparedStatement 的execute()方法,后面就是JDBC 包中的PreparedStatement 的执行了。

      最后通过 resultSetHandler.handleResultSets(ps) 处理结果集。

     

       最后总结一下整个源码的过程所涉及的核心类:

  • 相关阅读:
    go基础系列:结构struct
    梯度下降法解决线性回归
    梯度下降法解决线性回归
    梯度下降法解决线性回归
    【 Linux 】单台服务器上并发TCP连接数(转)
    【 Linux 】单台服务器上并发TCP连接数(转)
    【 Linux 】单台服务器上并发TCP连接数(转)
    axios在vue项目中的一种封装方法
    快速排序算法(C#实现)
    你必须知道的261个Java语言问题
  • 原文地址:https://www.cnblogs.com/flgb/p/12688346.html
Copyright © 2011-2022 走看看