zoukankan      html  css  js  c++  java
  • Mybatis原理初探

    开篇略谈

      谈到Mybatis,对于我们猿们来说是熟悉不过了。但是有没有兴趣去探一下其实现原理呢?是的,请往下看 ↓  come on...

    Mybatis综述

      Mybatis一个数据持久层轻量级框架,回顾我们原始的开发即没有持久层框架的年代。话不多说上代码 ↓

    Connection con = DriverManager.getConnection(url, "...", "..."); // 首先我们得获得一个数据库连接
    Statement stmt = con.createStatement(); // 不管你是获得statement还是preparedStatement,总之在项目越来越大得时候这些代码会有点累赘
    // PreparedStatement prestmt = con.prepareStatement(...);

       而我们得Mybatis则将其封装了起来,构成持久层框架,我们只要按照它的规定去配置就可以了,而无需在关注上面得代码,只需关注sql就行了,接下来入正题

    Mybatis源码解析

      提到Mybatis我们总会想起那个耳熟能详的东西sqlSession,是的,这是Mybatis的核心所在,sqlSession是由单例sqlSessionFactory创建而来的,而sqlSessionFactory又是由sqlSessionFactoryBuilder创建而来的,因此sqlSession得老祖宗就是它。在这里不管它的父辈,我们就来研究一下sqlSession的默认defaultSqlSession,多说无益看代码

    // 可以看到 DefaultSqlSession 实现了SqlSession,这很容易让我们想起模板方法模式,请往下看
    public class DefaultSqlSession implements SqlSession {
       // 下面是一些变量
      private Configuration configuration; // 这个东西是核心关注点也是此次重点讲解点,它包含了Mybatis的所有配置信息,我们此次所要研究的就是configuration是如何将sql获取到的
      private Executor executor;
    
      private boolean autoCommit;
      private boolean dirty;
      private List<Cursor<?>> cursorList;
    
      // 以下是对SqlSession方法的实现    
      @Override
      public <T> T selectOne(String statement) {
        return this.<T>selectOne(statement, null);
      }
    
      @Override
      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.<T>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;
        }
      }
      // 这个是获取configuration的mapper的,也就是获取sql语句的,这是重点讲解的
      @Override
      public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
      }

    public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
    }
    // 获取数据库连接  @Override public Connection getConnection() { try { return executor.getTransaction().getConnection(); } catch (SQLException e) { throw ExceptionFactory.wrapException("Error getting a new connection. Cause: " + e, e); } } } // 而SqlSession又继承了Closeable,很明显Closeable就是关闭connection的,这里就不详细说明 public interface SqlSession extends Closeable { <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); .......此处省略,详情可自己看源码 } 

    以上是DefaultSqlSession的方法介绍,此次要从addMapper方法入手弄明白Mybatis它是如何获取我们写的SQL语句的,come on

    // 以下是mapperRegistry
    public class MapperRegistry {
      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<T>(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(); // 这是使用了MapperAnnotationBuilder的parse方法进行了解析,那么它是如何解析的呢,解析的又是什么?
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    
       ..... 其他代码就省略了
    }

    接下来我们看它是如何解析的

    public class MapperAnnotationBuilder {
      public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) { // 首先会判断这个接口即Mapper是否加载过了
          loadXmlResource(); // 这是对mapper的xml文件进行解析
          configuration.addLoadedResource(resource); // 加载进去
          assistant.setCurrentNamespace(type.getName());
          parseCache();
          parseCacheRef();
          Method[] methods = type.getMethods(); // 获得当前Mapper接口的所有方法
          for (Method method : methods) {
            try {
              // issue #237
              if (!method.isBridge()) {
                parseStatement(method); // 对方法进行解析
              }
            } catch (IncompleteElementException e) {
              configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
          }
        }
        parsePendingMethods();
      }
    }
      // 这个方法也是MapperAnotationBuilder的
    void parseStatement(Method method) {
        Class<?> parameterTypeClass = getParameterType(method); // 获得参数类型
        LanguageDriver languageDriver = getLanguageDriver(method); 
        SqlSource sqlSource = getSqlSourceFromAnnotations(method,  // 这里是获取sqlSource parameterTypeClass, languageDriver);
        if (sqlSource != null) {
          Options options = method.getAnnotation(Options.class);
          final String mappedStatementId = type.getName() + "." + method.getName();
          Integer fetchSize = null;
          Integer timeout = null;
          StatementType statementType = StatementType.PREPARED;
          ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
          SqlCommandType sqlCommandType = getSqlCommandType(method);
          boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
          boolean flushCache = !isSelect;
          boolean useCache = isSelect;
    
          KeyGenerator keyGenerator;
          String keyProperty = "id";
          String keyColumn = null;
          if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // first check for SelectKey annotation - that overrides everything else
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
              keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
              keyProperty = selectKey.keyProperty();
            } else if (options == null) {
              keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            } else {
              keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
              keyProperty = options.keyProperty();
              keyColumn = options.keyColumn();
            }
          } else {
            keyGenerator = NoKeyGenerator.INSTANCE;
          }
    
          if (options != null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
              flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
              flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            resultSetType = options.resultSetType();
          }
    
          String resultMapId = null;
          ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
          if (resultMapAnnotation != null) {
            String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            for (String resultMap : resultMaps) {
              if (sb.length() > 0) {
                sb.append(",");
              }
              sb.append(resultMap);
            }
            resultMapId = sb.toString();
          } else if (isSelect) {
            resultMapId = parseResultMap(method);
          }
    
          assistant.addMappedStatement(
              mappedStatementId,
              sqlSource,
              statementType,
              sqlCommandType,
              fetchSize,
              timeout,
              // ParameterMapID
              null,
              parameterTypeClass,
              resultMapId,
              getReturnType(method),
              resultSetType,
              flushCache,
              useCache,
              // TODO gcode issue #577
              false,
              keyGenerator,
              keyProperty,
              keyColumn,
              // DatabaseID
              null,
              languageDriver,
              // ResultSets
              options != null ? nullOrEmpty(options.resultSets()) : null);
        }
      }
    
      private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
        try {
          Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); // 这里会判断方法是否使用了select等注解
          Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); // 是否使用了provider注解
          if (sqlAnnotationType != null) {
            if (sqlProviderAnnotationType != null) {
              throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
            }
            Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
            final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
            return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
          } else if (sqlProviderAnnotationType != null) {
            Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
            return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
          }
          return null;
        } catch (Exception e) {
          throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
        }
      }  private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
        try {
          Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
          Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
          if (sqlAnnotationType != null) {
            if (sqlProviderAnnotationType != null) {
              throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
            }
            Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
            final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
            return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
          } else if (sqlProviderAnnotationType != null) {
            Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
            return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
          }
          return null;
        } catch (Exception e) {
          throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
        }
      }

    从上面两个方法我们可以解释为什么我们使用provider和直接在mapper接口方法上加select语句会有效

  • 相关阅读:
    如何向尝试登录Windows 10的用户展示提示信息
    如何在Windows 10上创建和设置虚拟硬盘
    处理器虚拟化——VMX
    处理器虚拟化——基本数据结构
    DP
    Heap与Stack的区别
    获得内核模块 通过DriverSection
    软件管理器
    获取电脑软件信息 和 浏览器信息
    字符串之间的转换
  • 原文地址:https://www.cnblogs.com/lzj123/p/9404845.html
Copyright © 2011-2022 走看看