Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解

文章目录

  • 一、什么是MyBatis?
  • 二、Mybatis框架执行流程概括
  • 三、MyBatis框架详解
      • (1)搭建MyBatis源码阅读环境
      • (2)MyBatis是如何从 XML 中构建 SqlSessionFactory实例对象?
      • (3)MyBatis是如何从 SqlSessionFactory 中获取 SqlSession对象?
      • (4)MyBatis是如何执行SQL语句?

一、什么是MyBatis?

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO为数据库中的记录。

二、Mybatis框架执行流程概括

  • Mybatis框架执行流程,首先MyBatis将SQL语句和数据库配置信息保存在配置文件,然后再Mybatis运行时,利用SqlSessionFactoryBuilder().build()方法,将配置信息存储在Configuration对象中,返回一个SqlSessionFactory 的实例,然后利用SqlSessionFactory实例在创建SqlSession对象时需要提供Configuration、dirty以及Executor执行器对象来进行SqlSession对象的创建,利用SqlSession进行事物的提交和关闭。
  • 其中,根据dirty的属性值决定提交或回滚,当dirty为true时,SQL执行完毕后,可以进行事务提交,当dirty为false时,SQL语句执行错误,事务进行回滚。
  • Executor执行器对象是创建Statement对象,创建过程中依靠MapperStatement对象将赋值内容与SQL占位符进行绑定。

三、MyBatis框架详解

(1)搭建MyBatis源码阅读环境

Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第1张图片
1.利用maven项目管理工具添加mybatis(版本3.5.3)依赖和MySQL驱动包(版本8.0.17)

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.17</version>
    </dependency>
</dependencies>

2.在resources下添加MyBatis的核心配置文件mybatis.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>
	<!-- default引用environment的id,当前所使用的环境 -->
	<environments default="default">
		<!-- 声明可以使用的环境 -->
		<environment id="default">
			<!-- 使用原生JDBC事务 -->
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
				<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
				<property name="username" value="root"/>
				<property name="password" value="root"/>
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper  resource="mapper/UserMapper.xml"/>
	</mappers>
</configuration>

3.添加实体类model和mapper接口以及mapper.xml

3.1 User.java

/**
 * @author yly
 * @ClassName User
 * @Date 2020/2/28 15:51
 * @Version 1.0
 **/
public class User {
    private Integer id;
    private String name;
    private String age;
    //忽略getter/setter方法
 }

3.2 UserMapper接口

/**
 * @author yly
 * @ClassName UserMapper
 * @Date 2020/2/28 17:59
 * @Version 1.0
 **/
public interface UserMapper {
    int SaveUser(User user);

    List<User> selectUser();
}

3.3 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yly.mysql.mapper.UserMapper">
    <select id="selectUser" resultType="com.yly.mysql.model.User">
        select * from user
    </select>
    <insert id="SaveUser" parameterType="com.yly.mysql.model.User">
        insert into  user VALUES (#{name},#{age},#{id})
    </insert>
</mapper>

3.4 创建MyBatisSoundCodeDemo执行mybatis。

/**
 * @author yly
 * @ClassName SqlDemo
 * @Date 2020/2/28 15:58
 * @Version 1.0
 **/
public class MyBatisSoundCodeDemo {
    public static void main(String[] args) throws IOException {
        //获取mybatis的核心配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        //获取SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //从 SqlSessionFactory 中获取 SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> users = mapper.selectUser();

        users.forEach(i -> System.out.println(i.getName()));

        sqlSession.commit();

        sqlSession.close();
    }
}

(2)MyBatis是如何从 XML 中构建 SqlSessionFactory实例对象?

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

 		//获取mybatis的核心配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        //获取SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //从 SqlSessionFactory 中获取 SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

  1. 首先通过Resources.getResourceAsStream(“mybatis.xml”);来获取核心配置文件
		//获取mybatis的核心配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
  1. 利用SqlSessionFactoryBuilder().build()方法,将配置信息存储在Configuration对象中,返回一个SqlSessionFactory 的实例。
		//获取SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

通过build(InputStream inputStream)方法构建SqlSessionFactory实例

  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.
      }
    }
  }

追踪源码,首先我们看到build()方法new一个XMLConfigBuilder(XML配置构建器),通过XMLConfigBuilder类中的parse()方法返回一个Configuration对象。

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

parse()方法首先对XMLConfigBuilder 进行判断是否解析过,如果已经解析则抛出异常,如果没有则设置parsed 为true。然后通过XPathParser对象解析mybatis的核心配置文件中的configuration根节点,通过parseConfiguration(XNode root)方法解析configuration根节点中的子节点。

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      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);
    }
  }

找到 mapperElement(root.evalNode(“mappers”));着重看解析mappers节点。

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

在mapperElement(XNode parent)方法中,通过解析mybatis.xml文件中的mappers节点,首先判断父节点是否为空,若不为空,遍历mappers父节点的子节点

  • 如果配置mappers的子节点为package,则执行下面代码
	<mappers>
		<package  name=""/>
	</mappers>
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }
  通过child.getStringAttribute("name");获取到package的属性值,调用configuration对象的addMappers()方法,将包配置到configuration对象中去。
  • 如果配置mappers的子节点为mapper,则执行下面代码
          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中的属性值resource、url、mapperClass只配置一个即可。由于我们配置的是resource,所以跟踪配置resource的源码,

ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

通过resource配置的mapper/UserMapper.xml路径获取到UserMapper.xml资源,然后通过XMLMapperBuilder的parse()方法进行xml解析

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

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

XMLMapperBuilder的parse()首先判断resource是否被装载过,如果没有被装载过,则进行mapper节点解析(详解在下一部分MyBatis是如何执行SQL语句中进行讲解),将resource装载进configuration对象中,通过bindMapperForNamespace();方法获取到namespace属性值,然后通过namespace获取interface com.yly.mysql.mapper.UserMapper接口对象,并赋值给configuration对象。

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

在 configuration.addMapper(boundType);中将UserMapper接口对象加入到一个人HashMap中。

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

将所有方法执行完之后返回一个configuration对象继续调用SqlSessionFactory的build(Configuration config)方法,将Configuration 对象传入进去,new一个DefaultSqlSessionFactory(config)对象并返回。

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

至此SqlSessionFactory的实例对象获取完成。

其中在获取inputStream资源之后不需要关闭资源,是因为在mybatis在构建SqlSessionFactory 之后替我们关闭了资源。
Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第2张图片

(3)MyBatis是如何从 SqlSessionFactory 中获取 SqlSession对象?

1.1SqlSessionFactory 通过openSession();方法获取SqlSession 对象。

  //从 SqlSessionFactory 中获取 SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

因为之前构建的SqlSessionFactory 为DefaultSqlSessionFactory对象,所以构建SqlSession对象时使用的是DefaultSqlSessionFactory的openSession()方法。

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

返回一个SqlSession 接口对象,通过调用openSessionFromDataSource()方法,默认的Executor(执行器)类型为SIMPLE(低级的)执行器。

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

进入openSessionFromDataSource()方法中,首先获取一个默认的环境配置对象Environment,然后通过Environment对象获取一个默认的TransactionFactory对象,调用newTransaction();方法来获取一个事务对象,值得注意的是,在mybatis框架中,设置自动提交autoCommit默认为false。最后通过new DefaultSqlSession(configuration, executor, autoCommit);返回SqlSession对象

(4)MyBatis是如何执行SQL语句?

MyBatis在执行SQL语句是需要一个非常重要的对象MapperMethod对象。
在上一部分中,我们通过查看XMLMapperBuilder中的parse()方法,代码如下

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

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

其中configurationElement(parser.evalNode("/mapper"));则用于解析mapper节点,进行执行SQL语句配置。进入configurationElement()方法

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

首先获取到mapper的namespace属性值进行判断,然后解析mapper中的各个子节点,我们着重看buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));方法,从buildStatementFromContext(List list)方法中传入解析过后的SQL集合。如果从configuration对象中获取到DatabaseId的值,则将SQL集合传入buildStatementFromContext()方法中去进行解析
Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第3张图片

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

其中真正处理解析SQL语句的是 statementParser.parseStatementNode();方法,在此方法中有SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 获取SQL资源,进入到LanguageDriver接口的XMLLanguageDriver实现类中的createSqlSource()方法中,new了一个XMLScriptBuilder对象,调用parseScriptNode()方法进行SQL解析。

  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

首先查看是不是动态SQL,如果不是则new一个RawSqlSource对象。

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

其中getSql(configuration, rootSqlNode)是将xml文件中的SQL通过递归的方式转换成字符串类型。
通过this调用其构造方法为

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }

利用SqlSourceBuilder的parse()方法解析并替换SQL语句中的#{}替换成成?占位符并返回成SqlSource对象

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第4张图片
mybatis默认通过jdk动态代理创建MapperMethod对象。

    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);

将mapperInterface,Method,config存入SqlCommand和MethodSignature对象中。调用MapperMethod对象的execute()方法,获取SQL执行的类型,通过switch语句进行类型调用处理。我们着重看INSERT语句。

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

进入sqlSession.insert(command.getName(), param)方法中,再次调用了update()方法,即源代码如下。

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

在update中,将dirty设置为true并获取MappedStatement对象,通过执行器Executor调用update()方法,其中MappedStatement对象中存在着我们所需要的所有数据。MappedStatement数据是之前构建SqlSessionFactory实例的时候就已经将UserMapper接口对象存入到HashMap中。所以MappedStatement ms = configuration.getMappedStatement(statement);也只是从一个HashMap中取值。
Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第5张图片
跟随源码进入到update()方法中,一直到SimpleExecutor,默认处理的执行器,简单执行器。调用doUpdate()方法。

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

知道这里。我们已经看到了非常熟悉的对象Statement 对象,这已经离我们原生的JDBC执行仅有一步之遥。我们继续跟进stmt = prepareStatement(handler, ms.getStatementLog());,进入到prepareStatement()方法中。

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

我们看到Connection 对象,连接数据库对象,你可能会问getConnection()方法是如何获取到数据库连接的,因为在返回SqlSession的时候调用newTransaction()方法时初始化了DataSource,所以可以通过DataSource对象获取数据库连接。
Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第6张图片
获取数据库连接之后,继续跟踪源码到BaseStatementHandler对象的prepare()方法。

 @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

其中ErrorContext.instance().sql(boundSql.getSql());获取到SQL执行语句,statement = instantiateStatement(connection);进行Statement对象的创建
Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第7张图片
其中对于Statement对象的赋值在handler.parameterize(stmt);方法中;跟踪源码到DefaultParameterHandler对象的setParameters()方法中,即源码如下

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

进入typeHandler.setParameter(ps, i + 1, value, jdbcType);之后,通过调用setNonNullParameter(ps, i, parameter, jdbcType);方法完成对PreparedStatement赋值。

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }

返回一个Statement对象即一个完整的SQL语句
Mybatis源码学习(二)Mybatis框架执行流程(24000字)详解_第8张图片
继续跟踪handler.update(stmt)得到PreparedStatementHandler对象的update()方法

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

我们看到非常熟悉的JDBC执行语句 ps.execute();至此SQL语句执行完毕。
之前我们知道JDBC中设置的AutoCommit(自动提交为)true,而mybatis中设置的自动提交为false,需要我们手动commit(),我们开始在MyBatis执行流程中提到过,mybatis根据dirty的属性值决定提交或回滚,当dirty为true时,SQL执行完毕后,可以进行事务提交,当dirty为false时,SQL语句执行错误,事务进行回滚。

跟进sqlSession.commit();代码进入DefaultSqlSession类中的commit()方法中。

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

其中isCommitOrRollbackRequired(force),是否进行提交或者回滚,传入的值force=false,由于autoCommit 为false,dirty为true(具体赋值在执行DefaultSqlSession的update()方法时对dirty进行赋值),所以(!autoCommit && dirty)为true即执行executor.commit(true);

private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }
  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
  }

跟进 delegate.commit(required);中进入BaseExecutor的commit()方法中

  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

在跟进transaction.commit();方法中,我们豁然发现 connection.commit();提交事务。

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

当执行器将commit()方法执行完毕之后将dirty设置成false,然后执行sqlSession.close();至此MyBatis执行完毕,将值也写入了数据库。

你可能感兴趣的:(MyBatis,java,mybatis,jdbc,数据库,mysql)