zoukankan      html  css  js  c++  java
  • mybatis源码阅读

    Mybatis源码解析

    mybatis的使用主要有一下步骤:

    1. 创建SqlSessionFactory工厂对象

      // 加载mybatis配置文件
      InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
      // 创建SqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
      

      整个流程的关键在于build()方法,build方法中对配置文件进行解析XMLConfigBuilder,并创建对象DefaultSqlSessionFactory,mybatis默认的SqlSessionFactory为DefaultSqlSessionFactory,有两个重要的方法:

      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      var5 = this.build(parser.parse());
      

      通过XMLConfigBuilder来解析mybatis配置文件:

      		public Configuration parse() {
              if (this.parsed) {
                  throw new BuilderException("Each XMLConfigBuilder can only be used once.");
              } else {
                  this.parsed = true;
                // 获取configuration标签下的内容
                  this.parseConfiguration(this.parser.evalNode("/configuration"));
                  return this.configuration;
              }
          }
      

      在parseConfiguration方法中:

      private void parseConfiguration(XNode root) {
          try {
            // 首先读取外部配置文件,解析properties标签
            propertiesElement(root.evalNode("properties"));
            // 解析settings标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            // 解析typeAliases标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析plugins标签
            pluginElement(root.evalNode("plugins"));
            // 解析objectFactory标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析objectWrapperFactory标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析feflectorFactory
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);
            // 解析environments标签
            environmentsElement(root.evalNode("environments"));
            // 解析datasourceIdProvider标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析typeHandlers标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析mappers标签
            mapperElement(root.evalNode("mappers"));
          } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
          }
        }
      
      • 解析properties标签

        private void propertiesElement(XNode context) throws Exception {
            if (context != null) {
              // 首先获取<properties>标签下的子标签,取出<property>标签配置的变量信息
              Properties defaults = context.getChildrenAsProperties();
              String resource = context.getStringAttribute("resource");
              String url = context.getStringAttribute("url");
              if (resource != null && url != null) {
                throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
              }
              if (resource != null) {
                defaults.putAll(Resources.getResourceAsProperties(resource));
              } else if (url != null) {
                defaults.putAll(Resources.getUrlAsProperties(url));
              }
              Properties vars = configuration.getVariables();
              if (vars != null) {
                defaults.putAll(vars);
              }
              parser.setVariables(defaults);
              configuration.setVariables(defaults);
            }
          }
        

        在解析properties标签时,优先加载标签中的内容,然后加载resource属性配置的路径或url配置的路径,两个路径只能存在一个,如果两个都存在会抛出异常。而且加载外部文件中的属性会覆盖标签中配置的相同名变量。加载完成后将变量信息保存如全局配置。

      变量的替换

      在mybatis的配置文件中,使用${}引用properties导入的环境变量,环境变量的替换发生在root.evalNode("")期间。进入此方法:

      public XNode evalNode(Object root, String expression) {
          Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
          if (node == null) {
            return null;
          }
          return new XNode(this, node, variables);
        }
      

      可以看到调用了new XNode(this,node,variables),而在此构造器中:

      public XNode(XPathParser xpathParser, Node node, Properties variables) {
          this.xpathParser = xpathParser;
          this.node = node;
          this.name = node.getNodeName();
          this.variables = variables;
          this.attributes = parseAttributes(node);
          this.body = parseBody(node);
        }
      

      调用了parseAttributes(node),进入此方法:

      private Properties parseAttributes(Node n) {
          Properties attributes = new Properties();
          NamedNodeMap attributeNodes = n.getAttributes();
          if (attributeNodes != null) {
            for (int i = 0; i < attributeNodes.getLength(); i++) {
              Node attribute = attributeNodes.item(i);
              String value = PropertyParser.parse(attribute.getNodeValue(), variables);
              attributes.put(attribute.getNodeName(), value);
            }
          }
          return attributes;
        }
      

      调用了PropertyParser.parse(attribute.getNodeValue(), variables);方法,进入此方法:

        public static String parse(String string, Properties variables) {
          VariableTokenHandler handler = new VariableTokenHandler(variables);
          GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
          return parser.parse(string);
        }
      

      可以看到就是在这里发生了变量的替换。

      其他的步骤都是解析标签然后替换变量信息并解析标签的内容,如果没有值则设置为默认值。在最后mapperElement()方法中加载了mapper.xml文件并解析放入configuration中。

      至此第一步方法分析完毕,主要工作就是:

      1. 加载mybatis-config.xml配置文件
      2. 替换环境变量并解析配置文件及mapper.xml文件。
      3. 创建DefaultSqlSessionFactory对象并返回。
    2. 开启一个会话。

      通过SqlSession sqlSession = sqlSessionFactory.openSession();可以开启一个session会话。进入openSession方法中:

      // 从方法名可以看出此方法为:从数据源中打开一个session会话
      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
          Transaction tx = null;
          try {
            // 首先获取环境信息
            final Environment environment = configuration.getEnvironment();
            // 通过环境信息获取事物工厂:
            //<environments default="mysql">
            //<environment id="mysql">
            //      <transactionManager type="JDBC"/>
            //      <dataSource type="POOLED">
             //         ....
             //     </dataSource>
            //  </environment>
          //</environments>
            // 在配置文件中配置的是JDBC事物,然后就会返回相应的事物工厂。
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            // 通过事物工厂创建一个事物
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 通过配置信息获取执行器,执行器是mybatis的核心
            final Executor executor = configuration.newExecutor(tx, execType);
            // 通过执行器创建一个DefaultSqlSession
            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();
          }
        }
      

      进入getTransactionFactoryFromEnvironment()方法:

        private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
          if (environment == null || environment.getTransactionFactory() == null) {
            return new ManagedTransactionFactory();
          }
          return environment.getTransactionFactory();
        }
      

      可以看到只是通过Environment获取到了事物工厂。

      进入transactionFactory.newTransaction()

      @Override
      public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
        return new JdbcTransaction(ds, level, autoCommit);
      }
      

      查看newExecutor方法

      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        // 默认的ExecutorType为 SIMPLE
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor 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);
        }
        // 默认cacheEnabled属性为true,使用cachingExecutor装饰器装饰Executor
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        // 使用自定义插件中的Executor插件装饰执行器
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
      

      通过上面的分析,我们可以得出结论:

      • 在openSession阶段,并没有跟数据库建立连接。
      • 创建了事物,执行器,并根据执行器创建了DefaultSqlSession。
    3. 获取Mapper接口:

      当获取到sqlSession后,就会通过ActorDao mapper = sqlSession.getMapper(ActorDao.class);来获取一个Mapper接口。进入getMapper方法:

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

      继续进入方法:

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

      通过mapperRegistry获取Mapper接口。在前面构建sqlSessionFactory阶段解析mapper.xml时,mapperElement方法中向MapperRegistry中注册了此mapper信息以及关联的mapper接口。那么就可以通过这个接口查到对应的Mapper信息。

      继续进入getMapper方法:

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 通过knowMappers获取接口对应的mapper代理工厂
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
          // 通过代理工厂创建一个mapper接口的代理类。
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
      

      进入newInstance方法

        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);
      }
      

      可以看到通过Proxy代理也就是jdk动态代理创建了Mapper接口的代理类。代理类的类型为MapperProxy。

      至此我们通过ActorDao mapper = sqlSession.getMapper(ActorDao.class);获取到的接口mapper就是一个代理类。

      总结这一阶段的事情:

      • 通过制定的mapper接口类型,到mapperregistry中查找到对应的MapperProxyFactory,然后在通过MapperProxyFactory创建一个代理类。
      • 目前为止仍未与数据库建立连接
    4. 调用接口的方法:

      Actor actor = mapper.selectByPrimaryKey(1L);
      

      通过断点进入此方法:

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 直接进入了MapperProxy的invoke方法
        try {
          // 判断调用的方法是否是Object累的方法:equals,hashcode等,如果是,则直接调用
          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);
        }
        // 缓存次方法并返回MapperMethod
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 执行方法
        return mapperMethod.execute(sqlSession, args);
      }
      
      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;
      }
      

    查看execute方法:

    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()) {
              // 执行结果处理器并返回null
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              // 方法返回为数据集
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              // 方法返回为Map
              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:
            // 刷新statement
            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;
      }
    

    主要查看的方法是:

    Object param = method.convertArgsToSqlCommandParam(args);
    result = sqlSession.selectOne(command.getName(), param);
    

    进入method.convertArgsToSqlCommandParam(args);方法,最终调用方法为ParamNameResolver

    public Object getNamedParams(Object[] args) {
      // 获取参数的数组大小
      final int paramCount = names.size();
      if (args == null || paramCount == 0) {
        // 没有参数
        return null;
      } else if (!hasParamAnnotation && paramCount == 1) {
        // 参数只有一个直接取出第一个
        return args[names.firstKey()];
      } else {
        // 参数有多个,默认为param1, param2
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
          // ensure not to overwrite parameter named with @Param
          // 如果参数有@param注解,那么注解的值会被缓存到names中,通过names替换param1,param2
          if (!names.containsValue(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }
    

    然后 就是执行方法sqlSession.selectOne()方法,最终调用的是selectList:

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
      try {
        // 通过配置获取MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 通过执行器执行
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
    

    最终执行query方法为:

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
      // 从缓存中获取-- 二级缓存
      Cache cache = ms.getCache();
      if (cache != null) {
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
          ensureNoOutParams(ms, boundSql);
          @SuppressWarnings("unchecked")
          List<E> list = (List<E>) tcm.getObject(cache, key);
          if (list == null) {
            // 真正执行查询数据库的方法, delegate为真正工作的Executor
            list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578 and #116
          }
          return list;
        }
      }
      return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    

    查看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 (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          //一级缓存
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 从数据库获取数据
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
    

    继续进入queryFromDatabase方法:

      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          // doQuery方法: 真正执行查询的方法
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    

    进入doQuery方法,本次查询使用的是SimpleExecutor执行器,进入方法:

      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          // 获取配置信息
          Configuration configuration = ms.getConfiguration();
          // 获取一个statementHandler
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          // 预处理StatementHandler
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
    

    上面这个方法比较重要,主要做了以下事情:

    • 获取StatementHandler,在newStatementHandler方法中,创建了一个RoutingStatementHandler方法,查看父类的构造方法可以看到包装了ParameterHandler,ResultSetHandler等处理器,然后又包装了StatementHandler

    • prepareStatement方法,真正与数据库建立连接的方法

      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
          Statement stmt;
        // 创建数据库连接
          Connection connection = getConnection(statementLog);
        // 通过数据库连接创建一个Statement
          stmt = handler.prepare(connection, transaction.getTimeout());
          handler.parameterize(stmt);
          return stmt;
        }
      
    • 使用Statement执行sql,查看handler.query()方法

        @Override
        public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
          String sql = boundSql.getSql();
          // 执行sql语句
          statement.execute(sql);
          // 处理结果集
          return resultSetHandler.<E>handleResultSets(statement);
        }
      

      至此,mybatis源码的整体流程梳理完毕。

  • 相关阅读:
    Uva11584 Partitioning by Palindromes
    GYM100741 A Queries
    Uva11400 Lighting System Design
    UVA12563 Jin Ge Jin Qu hao
    Uva116 Unidirectional TSP
    HDU2089 不要62
    BZOJ3670: [Noi2014]动物园
    Uva11384 Help is needed for Dexter
    Uva1347 Tour
    BZOJ1924: [Sdoi2010]所驼门王的宝藏
  • 原文地址:https://www.cnblogs.com/Zs-book1/p/14568180.html
Copyright © 2011-2022 走看看