美文网首页springMybatis
MyBatis原理系列(二)-手把手带你了解MyBatis的启动

MyBatis原理系列(二)-手把手带你了解MyBatis的启动

作者: Renaissance_ | 来源:发表于2020-12-21 16:10 被阅读0次

    MyBatis原理系列(一)-手把手带你阅读MyBatis源码
    MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
    MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
    MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
    MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
    MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
    MyBatis原理系列(七)-手把手带你了解如何自定义插件
    MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
    MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制

    在上篇文章中,我们举了一个例子如何使用MyBatis,但是对其中dao层,entity层,mapper层间的关系不得而知,从此篇文章开始,笔者将从MyBatis的启动流程着手,真正的开始研究MyBatis源码了。

    1. MyBatis启动代码示例

    在上篇文章中,介绍了MyBatis的相关配置和各层代码编写,本文将以下代码展开描述和介绍MyBatis的启动流程,并简略的介绍各个模块的作用,各个模块的细节部分将在其它文章中呈现。

    回顾下上文中使用mybatis的部分代码,包括七步。每步虽然都是一行代码,但是隐藏了很多细节。接下来我们将围绕这起步展开了解。

    @Slf4j
    public class MyBatisBootStrap {
    
        public static void main(String[] args) {
            try {
                // 1. 读取配置
                InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
                // 2. 创建SqlSessionFactory工厂
                SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
                // 3. 获取sqlSession
                SqlSession sqlSession = sqlSessionFactory.openSession();
                // 4. 获取Mapper
                TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
                // 5. 执行接口方法
                TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
                System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
                // 6. 提交事物
                sqlSession.commit();
                // 7. 关闭资源
                sqlSession.close();
                inputStream.close();
            } catch (Exception e){
                log.error(e.getMessage(), e);
            }
        }
    }
    

    2. 读取配置

    // 1. 读取配置
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    

    在mybatis-config.xml中我们配置了属性,环境,映射文件路径等,其实不仅可以配置以上内容,还可以配置插件,反射工厂,类型处理器等等其它内容。在启动流程中的第一步我们就需要读取这个配置文件,并获取一个输入流为下一步解析配置文件作准备。

    mybatis-config.xml 内容如下

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <!--一些重要的全局配置-->
        <settings>
            <setting name="cacheEnabled" value="true"/>
            <!--<setting name="lazyLoadingEnabled" value="true"/>-->
            <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
            <!--<setting name="useColumnLabel" value="true"/>-->
            <!--<setting name="useGeneratedKeys" value="false"/>-->
            <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
            <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
            <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
            <!--<setting name="defaultStatementTimeout" value="25"/>-->
            <!--<setting name="defaultFetchSize" value="100"/>-->
            <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
            <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
            <!--<setting name="localCacheScope" value="STATEMENT"/>-->
            <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
            <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
            <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
        </settings>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://10.255.0.50:3306/volvo_bev?useUnicode=true"/>
                    <property name="username" value="appdev"/>
                    <property name="password" value="FEGwo3EzsdDYS9ooYKGCjRQepkwG"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <!--这边可以使用package和resource两种方式加载mapper-->
            <!--<package name="包名"/>-->
            <!--<mapper resource="./mappers/SysUserMapper.xml"/>
            <package name="com.example.demo.dao"/> -->
            <mapper resource="./mapper/TTestUserMapper.xml"/>
        </mappers>
    
    </configuration>
    

    3. 创建SqlSessionFactory工厂

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

    我们在学习Java的设计模式时,会学到工厂模式,工厂模式又分为简单工厂模式,工厂方法模式,抽象工厂模式等等。工厂模式就是为了创建对象提供接口,并将创建对象的具体细节屏蔽起来,从而可以提高灵活性。

    public interface SqlSessionFactory {
    
      SqlSession openSession();
    
      SqlSession openSession(boolean autoCommit);
    
      SqlSession openSession(Connection connection);
    
      SqlSession openSession(TransactionIsolationLevel level);
    
      SqlSession openSession(ExecutorType execType);
    
      SqlSession openSession(ExecutorType execType, boolean autoCommit);
    
      SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
    
      SqlSession openSession(ExecutorType execType, Connection connection);
    
      Configuration getConfiguration();
    
    }
    

    由此可知SqlSessionFactory工厂是为了创建一个对象而生的,其产出的对象就是SqlSession对象。SqlSession是MyBatis面向数据库的高级接口,其提供了执行查询sql,更新sql,提交事物,回滚事物,获取映射代理类等等方法。

    在此笔者列出了主要方法,一些重载的方法就过滤掉了。

    public interface SqlSession extends Closeable {
    
      /**
      * 查询一个结果对象
      **/ 
      <T> T selectOne(String statement, Object parameter);
      
       /**
      * 查询一个结果集合
      **/ 
      <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
      
       /**
      * 查询一个map
      **/ 
      <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
    
       /**
      * 查询游标
      **/ 
      <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
    
      void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
      
         /**
      * 插入
      **/ 
      int insert(String statement, Object parameter);
    
        /**
      * 修改
      **/ 
      int update(String statement, Object parameter);
    
      /**
      * 删除
      **/
      int delete(String statement, Object parameter);
    
       /**
      * 提交事物
      **/
      void commit(boolean force);
      
       /**
      * 回滚事物
      **/
      void rollback(boolean force);
    
      List<BatchResult> flushStatements();
    
      void close();
    
      void clearCache();
    
      Configuration getConfiguration();
    
       /**
      * 获取映射代理类
      **/
      <T> T getMapper(Class<T> type);
    
       /**
      * 获取数据库连接
      **/
      Connection getConnection();
    }
    
    

    回到开始,SqlSessionFactory工厂是怎么创建的出来的呢?SqlSessionFactoryBuilder就是创建者,以Builder结尾我们很容易想到了Java设计模式中的建造者模式,一个对象的创建是由众多复杂对象组成的,建造者模式就是一个创建复杂对象的选择,它与工厂模式相比,建造者模式更加关注零件装配的顺序。

    public class SqlSessionFactoryBuilder {
    
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    
    }
    

    其中XMLConfigBuilder就是解析mybatis-config.xml中每个标签的内容,parse()方法返回的就是一个Configuration对象.Configuration也是MyBatis中一个很重要的组件,包括插件,对象工厂,反射工厂,映射文件,类型解析器等等都存储在Configuration对象中。

    public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
      private void parseConfiguration(XNode root) {
        try {
          // issue #117 read properties first
          // 解析properties节点
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    在获取到Configuration对象后,SqlSessionFactoryBuilder就会创建一个DefaultSqlSessionFactory对象,DefaultSqlSessionFactory是SqlSessionFactory的一个默认实现,还有一个实现是SqlSessionManager。

      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    
    创建SqlSessionFactory过程

    4. 获取sqlSession

      // 3. 获取sqlSession
     SqlSession sqlSession = sqlSessionFactory.openSession();
    

    在前面我们讲到,sqlSession是操作数据库的高级接口,我们操作数据库都是通过这个接口操作的。获取sqlSession有两种方式,一种是从数据源中获取的,还有一种是从连接中获取。
    获取到的都是DefaultSqlSession对象,也就是sqlSession的默认实现。

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType);
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    
    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        try {
          boolean autoCommit;
          try {
            autoCommit = connection.getAutoCommit();
          } catch (SQLException e) {
            // Failover to true, as most poor drivers
            // or databases won't support transactions
            autoCommit = true;
          }
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          final Transaction tx = transactionFactory.newTransaction(connection);
          final Executor executor = configuration.newExecutor(tx, execType);
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    
    获取SqlSession步骤

    5. 获取Mapper代理类

    在上一步获取到sqlSession后,我们接下来就获取到了mapper代理类。

     // 4. 获取Mapper
     TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
    

    这个getMapper方法,我们看看DefaultSqlSession是怎么做的

    DefaultSqlSession 的 getMapper 方法

      public <T> T getMapper(Class<T> type) {
           return this.configuration.getMapper(type, this);
       }
    

    Configuration 的 getMapper 方法

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            return this.mapperRegistry.getMapper(type, sqlSession);
        }
    

    MapperRegistry 中有个getMapper方法,实际上是从成员变量knownMappers中获取的,这个knownMappers是个key-value形式的缓存,key是mapper接口的class对象,value是MapperProxyFactory代理工厂,这个工厂就是用来创建MapperProxy代理类的。

    
    public class MapperRegistry {
        private final Configuration config;
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    
        public MapperRegistry(Configuration config) {
            this.config = config;
        }
    
        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);
                }
            }
        }
    }
    

    如果对java动态代理了解的同学就知道,Proxy.newProxyInstance()方法可以创建出一个目标对象一个代理对象。由此可知每次调用getMapper方法都会创建出一个代理类出来。

    public class MapperProxyFactory<T> {
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
    
        public MapperProxyFactory(Class<T> mapperInterface) {
            this.mapperInterface = mapperInterface;
        }
    
        public Class<T> getMapperInterface() {
            return this.mapperInterface;
        }
    
        public Map<Method, MapperMethod> getMethodCache() {
            return this.methodCache;
        }
    
        protected T newInstance(MapperProxy<T> mapperProxy) {
            return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
        }
    
        public T newInstance(SqlSession sqlSession) {
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    }
    

    回到上面,那这个MapperProxyFactory是怎么加载到MapperRegistry的knownMappers缓存中的呢?

    在上面的Configuration类的parseConfiguration方法中,我们会解析 mappers标签,mapperElement方法就会解析mapper接口。

    private void parseConfiguration(XNode root) {
        try {
          // issue #117 read properties first
          // 解析properties节点
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    
      private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }
    

    解析完后,就讲这个mapper接口加到 mapperRegistry中,

    configuration.addMapper(mapperInterface);
    

    Configuration的addMapper方法

      public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
      }
    

    最后还是加载到了MapperRegistry的knownMappers中去了

      public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    
    获取mapper代理类过程

    6. 执行mapper接口方法

     // 5. 执行接口方法
     TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
    

    selectByPrimaryKey是TTestUserMapper接口中定义的一个方法,但是我们没有编写TTestUserMapper接口的的实现类,那么Mybatis是怎么帮我们执行的呢?前面讲到,获取mapper对象时,是会获取到一个MapperProxyFactory工厂类,并创建一个MapperProxy代理类,在执行Mapper接口的方法时,会调用MapperProxy的invoke方法。

     @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
    

    如果是Object的方法就直接执行,否则执行cachedInvoker(method).invoke(proxy, method, args, sqlSession); 这行代码,到这里,想必有部分同学已经头晕了吧。怎么又来了个invoke方法。
    cachedInvoker 是返回缓存的MapperMethodInvoker对象,MapperMethodInvoker的invoke方法会执行MapperMethod的execute方法。

    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);
              if (method.returnsOptional()
                  && (result == null || !method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
              }
            }
            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;
      }
    }
    

    然后根据执行的接口找到mapper.xml中配置的sql,并处理参数,然后执行返回结果处理结果等步骤。

    7. 提交事物

    // 6. 提交事物
    sqlSession.commit();
    

    事物就是将若干数据库操作看成一个单元,要么全部成功,要么全部失败,如果失败了,则会执行执行回滚操作,恢复到开始执行的数据库状态。

    8. 关闭资源

     // 7. 关闭资源
    sqlSession.close();
    inputStream.close();
    

    sqlSession是种共用资源,用完了要返回到池子中,以供其它地方使用。

    9. 总结

    至此我们已经大致了解了Mybatis启动时的大致流程,很多细节都还没有详细介绍,这是因为涉及到的层面又深又广,如果在一篇文章中介绍,反而会让读者如置云里雾里,不知所云。因此,在接下来我将每个模块的详细介绍。如果文章有什么错误或者需要改进的,希望同学们指出来,希望对大家有帮助。

    相关文章

      网友评论

        本文标题:MyBatis原理系列(二)-手把手带你了解MyBatis的启动

        本文链接:https://www.haomeiwen.com/subject/dvhkgktx.html