Mybatis原理初探

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 开篇略谈  谈到Mybatis,对于我们猿们来说是熟悉不过了。但是有没有兴趣去探一下其实现原理呢?是的,请往下看 ↓  come on...Mybatis综述  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语句会有效

 

目录
相关文章
|
7月前
|
SQL XML Java
|
7月前
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
171 1
|
7月前
|
SQL Java 数据库连接
mybatis常见分页技术和自定义分页原理实战
mybatis常见分页技术和自定义分页原理实战
246 0
|
23天前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
2月前
|
SQL XML Java
Mybatis的原理和MybaitsPlus
这篇文章对比分析了Mybatis和Mybatis Plus的特点与底层实现机制,探讨了两者之间的差异及各自的优势。
73 0
|
5月前
|
SQL Java 数据库连接
springboot~mybatis-pagehelper原理与使用
【7月更文挑战第15天】MyBatis-PageHelper是用于MyBatis的分页插件,基于MyBatis的拦截器机制实现。它通过在SQL执行前动态修改SQL语句添加LIMIT子句以支持分页。使用时需在`pom.xml`添加依赖并配置方言等参数。示例代码: PageHelper.startPage(2, 10); List&lt;User&gt; users = userMapper.getAllUsers(); PageInfo&lt;User&gt; pageInfo = new PageInfo&lt;&gt;(users); 这使得分页查询变得简单且能获取总记录数等信息。
119 2
|
6月前
|
SQL Java 数据库连接
深入探索MyBatis Dynamic SQL:发展、原理与应用
深入探索MyBatis Dynamic SQL:发展、原理与应用
|
5月前
|
SQL Java 数据库连接
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
87 0
|
6月前
|
Java 数据库连接 数据库
MyBatis TypeHandler详解:原理与自定义实践
MyBatis TypeHandler详解:原理与自定义实践
|
6月前
|
SQL Java 数据库连接
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践