Mybatis 源码解析

在一般的 MyBatis-Spring 用法中, 你不需要直接使用 SqlSessionFactoryBean 或和其对 应的 SqlSessionFactory。相反,session 工厂将会被注入到 MapperFactoryBean 或其它扩 展了 SqlSessionDaoSupport 的 DAO(Data Access Object,数据访问对象,译者注)中。

测试代码:

String resource = "mybatis.cfg.xml"; 
Reader reader = Resources.getResourceAsReader(resource);  
 //读取xml 文件,生成 SqlSessionFactory
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);  
//通过SqlSessionFactory来获得 sqlSession
SqlSession session = ssf.openSession();  
 try {  
           StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            return studentMapper.findStudentById(studId);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            session.close();  
        }

mybatis通过JDK的动态代理方式,在启动加载配置文件时,根据配置mapper的xml去生成代理实现类,然后通过 JDBC的 API 进行 sql 的执行操作。

  1. mybatis 通过SqlSessionFactoryBuilder从 xml 配置文件中构建出 SqlSessionFactory

  2. Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂。

    mapperRegistry.addMapper(type);  
    knownMappers.put(type, new MapperProxyFactory<T>(type));
    
  3. Mapper 注册之后,可以从SqlSession中get

  4. SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。

  5. 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。

  6. MapperProxy最终调用Executor 来执行sql,通过 JDBC 的 Statement 来实现


来依次看一下源码的实现:

1、SqlSessionFactoryBuilder读取xml 配置文件并生成 SqlSessionFactory。

 public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

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

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

可以看到返回的是一个 SqlSessionFactory 的实例对象。

在调用 build 方法之前,先调用了 XMLConfigBuilder.parse()方法,这个方法是将 xml 文件解析并生成 Configuration 对象。

public class XMLConfigBuilder extends BaseBuilder {

  //.........

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 {
       //解析子节点properties
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      //解析子节点typeAliases 别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析子节点plugins 插件
      pluginElement(root.evalNode("plugins"));
      //解析子节点objectFactory mybatis为结果创建对象时都会用到objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析子节点objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析settings定义一些全局性的配置
      settingsElement(root.evalNode("settings"));
      //解析environments 可以配置多个运行环境,但是每个SqlSessionFactory 实例只能选择一个运行环境
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      //解析databaseIdProvider MyBatis能够执行不同的语句取决于你提供的数据库供应商。许多数据库供应商的支持是基于databaseId映射
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析typeHandlers 当MyBatis设置参数到PreparedStatement 或者从ResultSet 结果集中取得值时,就会使用TypeHandler  来处理数据库类型与java 类型之间转换
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers 主要的crud操作都是在mappers中定义的
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
    //...........
}

这个方法中,可以看到 mapper 是在这一步被加载的:

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

这个方法的关键在于mapperParser.parse();

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

这个方法又调用了bindMapperForNamespace()

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

这里先去判断该namespace能不能找到对应的class,若可以则调用

configuration.addMapper(boundType);

configuration委托给MapperRegistry:

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

MapperRegistry 通过 addMapper 生成 mapper 的代理工厂类,然后放到 knownMappers 中。

key = Mapper class, value = 创建当前Mapper的工厂。

 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();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

2、创建 SqlSession

SqlSessionFactory 创建完成以后,就可以通过 SqlSessionFactory.openSession()来获取 SqlSession了,而openSession调用的是openSessionFromDataSource

  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, autoCommit);
        //返回一个DefaultSqlSession实例
      return new DefaultSqlSession(configuration, executor);
    } 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();
    }
  }

传入参数说明:

(1)ExecutorType:执行类型,ExecutorType主要有三种类型:SIMPLE, REUSE, BATCH,默认是SIMPLE,都在枚举类ExecutorType里面。

(2)TransactionIsolationLevel:事务隔离级别,都在枚举类TransactionIsolationLevel中定义。

(3)autoCommit:是否自动提交,主要是事务提交的设置。

DefaultSqlSession是SqlSession的实现类,该类主要提供操作数据库的方法给开发人员使用。

到这一步,SqlSession也创建完成了,接下来就可以通过 SqlSession获取 Mapper 来执行 SQL 了。

3、SqlSession.getMapper

DefaultSqlSession 是通过 configuration.getMapper来获取 mapper 的

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

configuration:

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

mapperRegistry:

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

这样一来就涉及到了上文我们提到的,在mapperRegistry.addMapper中,生成了mapperProxyFactory 对象,存放在knownMappers中,现在根据 Class 来获取该 mapper 的mapperProxyFactory,然后通过mapperProxyFactory.newInstance(sqlSession)来生成实例:

 @SuppressWarnings("unchecked")
  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);
  }

到这里就很明显了,通过newInstance动态代理生成代理类,然后 mapperProxy实现InvocationHandler接口进行拦截代理

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    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;
  }

}

这里的代理拦截,主要是寻找到MapperMethod,通过它去执行SQL。

那么到底最后是怎么执行 SQL 的呢?

4、Sql 执行

上面看到MapperProxy.invoke中调用了mapperMethod.execute(sqlSession, args);

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }

从这个方法中可以看到MapperMethod委托给SqlSession去执行sql。

比如拿 sqlSession.selectOne来举例,最终调用的方法是:

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

(1)根据SQL的ID到配置信息中找对应的MappedStatement,在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析并放到一个MappedStatement里面,并将MappedStatement对象放到一个Map里面进行存放,Map的key值是该SQL块的ID。

(2)调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象(此处为空)和结果处理方式。

那么执行器是怎么执行 Sql 的呢?

默认使用SimpleExecutor的话,可以看到executor 最终调用的是 doQuery方法:

  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 handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

doQuery方法的内部执行步骤:

(1) 获取配置信息对象。

(2)通过配置对象获取一个新的StatementHandler,该类主要用来处理一次SQL操作。

(3)预处理StatementHandler对象,得到Statement对象。

(4)传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理。

来仔细一步步看一下:

configuration.newStatementHandler做了什么呢?

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

上面代码的执行步骤:

(1)根据相关的参数获取对应的StatementHandler对象。

(2)为StatementHandler对象绑定拦截器插件。

 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

根据 MappedStatement对象的StatementType来创建不同的StatementHandler,这个跟前面执行器的方式类似。StatementType有STATEMENT、PREPARED和CALLABLE三种类型,跟JDBC里面的Statement类型一一对应。

我们再返回 simpleExecutor 看下prepareStatement做了什么:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection);
  handler.parameterize(stmt);
  return stmt;
}

其实就是对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数

最后就是调用handler.<E>query(stmt, resultHandler);来执行 sql。

  public <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }

可以看到:

(1) 通过 statement.execute(sql);来执行 sql

(2)将结果集交给resultSetHandler来处理,返回 list

spring-mybatis:

1、通过@Autowired注入动态代理创建的 mapper 接口的实现类

2、spring 在扫描 DAO 的时候,为每一个接口分别创建一个MapperFactoryBean

然后通过 getObject 方法来获取 Mapper

spring 通过 MapperScannerConfigurer来将 mapper适配成spring bean,同时注入SqlSessionFactory

 public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }