MyBatis原理系列(二)-手把手帶你了解MyBatis的啟動(dòng)流程

MyBatis原理系列(一)-手把手帶你閱讀MyBatis源碼
MyBatis原理系列(二)-手把手帶你了解MyBatis的啟動(dòng)流程
MyBatis原理系列(三)-手把手帶你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系
MyBatis原理系列(四)-手把手帶你了解MyBatis的Executor執(zhí)行器
MyBatis原理系列(五)-手把手帶你了解Statement、StatementHandler、MappedStatement間的關(guān)系
MyBatis原理系列(六)-手把手帶你了解BoundSql的創(chuàng)建過程
MyBatis原理系列(七)-手把手帶你了解如何自定義插件
MyBatis原理系列(八)-手把手帶你了解一級(jí)緩存和二級(jí)緩存
MyBatis原理系列(九)-手把手帶你了解MyBatis事務(wù)管理機(jī)制

在上篇文章中寡喝,我們舉了一個(gè)例子如何使用MyBatis,但是對(duì)其中dao層历造,entity層,mapper層間的關(guān)系不得而知勿锅,從此篇文章開始帕膜,筆者將從MyBatis的啟動(dòng)流程著手,真正的開始研究MyBatis源碼了溢十。

1. MyBatis啟動(dòng)代碼示例

在上篇文章中垮刹,介紹了MyBatis的相關(guān)配置和各層代碼編寫,本文將以下代碼展開描述和介紹MyBatis的啟動(dòng)流程张弛,并簡(jiǎn)略的介紹各個(gè)模塊的作用荒典,各個(gè)模塊的細(xì)節(jié)部分將在其它文章中呈現(xiàn)。

回顧下上文中使用mybatis的部分代碼吞鸭,包括七步寺董。每步雖然都是一行代碼,但是隱藏了很多細(xì)節(jié)刻剥。接下來我們將圍繞這起步展開了解遮咖。

@Slf4j
public class MyBatisBootStrap {

    public static void main(String[] args) {
        try {
            // 1. 讀取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 創(chuàng)建SqlSessionFactory工廠
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 4. 獲取Mapper
            TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
            // 5. 執(zhí)行接口方法
            TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
            System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
            // 6. 提交事物
            sqlSession.commit();
            // 7. 關(guān)閉資源
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error(e.getMessage(), e);
        }
    }
}

2. 讀取配置

// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

在mybatis-config.xml中我們配置了屬性,環(huán)境造虏,映射文件路徑等御吞,其實(shí)不僅可以配置以上內(nèi)容,還可以配置插件漓藕,反射工廠陶珠,類型處理器等等其它內(nèi)容。在啟動(dòng)流程中的第一步我們就需要讀取這個(gè)配置文件享钞,并獲取一個(gè)輸入流為下一步解析配置文件作準(zhǔn)備揍诽。

mybatis-config.xml 內(nèi)容如下

<?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>

    <!--一些重要的全局配置-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <!--<setting name="lazyLoadingEnabled" value="true"/>-->
        <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
        <!--<setting name="useColumnLabel" value="true"/>-->
        <!--<setting name="useGeneratedKeys" value="false"/>-->
        <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
        <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
        <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
        <!--<setting name="defaultStatementTimeout" value="25"/>-->
        <!--<setting name="defaultFetchSize" value="100"/>-->
        <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
        <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
        <!--<setting name="localCacheScope" value="STATEMENT"/>-->
        <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
        <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
        <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://10.255.0.50:3306/volvo_bev?useUnicode=true"/>
                <property name="username" value="appdev"/>
                <property name="password" value="FEGwo3EzsdDYS9ooYKGCjRQepkwG"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--這邊可以使用package和resource兩種方式加載mapper-->
        <!--<package name="包名"/>-->
        <!--<mapper resource="./mappers/SysUserMapper.xml"/>
        <package name="com.example.demo.dao"/> -->
        <mapper resource="./mapper/TTestUserMapper.xml"/>
    </mappers>

</configuration>

3. 創(chuàng)建SqlSessionFactory工廠

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我們?cè)趯W(xué)習(xí)Java的設(shè)計(jì)模式時(shí),會(huì)學(xué)到工廠模式栗竖,工廠模式又分為簡(jiǎn)單工廠模式暑脆,工廠方法模式,抽象工廠模式等等狐肢。工廠模式就是為了創(chuàng)建對(duì)象提供接口添吗,并將創(chuàng)建對(duì)象的具體細(xì)節(jié)屏蔽起來,從而可以提高靈活性处坪。

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

由此可知SqlSessionFactory工廠是為了創(chuàng)建一個(gè)對(duì)象而生的根资,其產(chǎn)出的對(duì)象就是SqlSession對(duì)象。SqlSession是MyBatis面向數(shù)據(jù)庫的高級(jí)接口同窘,其提供了執(zhí)行查詢sql玄帕,更新sql,提交事物想邦,回滾事物裤纹,獲取映射代理類等等方法。

在此筆者列出了主要方法丧没,一些重載的方法就過濾掉了鹰椒。

public interface SqlSession extends Closeable {

  /**
  * 查詢一個(gè)結(jié)果對(duì)象
  **/ 
  <T> T selectOne(String statement, Object parameter);
  
   /**
  * 查詢一個(gè)結(jié)果集合
  **/ 
  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  
   /**
  * 查詢一個(gè)map
  **/ 
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

   /**
  * 查詢游標(biāo)
  **/ 
  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  
     /**
  * 插入
  **/ 
  int insert(String statement, Object parameter);

    /**
  * 修改
  **/ 
  int update(String statement, Object parameter);

  /**
  * 刪除
  **/
  int delete(String statement, Object parameter);

   /**
  * 提交事物
  **/
  void commit(boolean force);
  
   /**
  * 回滾事物
  **/
  void rollback(boolean force);

  List<BatchResult> flushStatements();

  void close();

  void clearCache();

  Configuration getConfiguration();

   /**
  * 獲取映射代理類
  **/
  <T> T getMapper(Class<T> type);

   /**
  * 獲取數(shù)據(jù)庫連接
  **/
  Connection getConnection();
}

回到開始,SqlSessionFactory工廠是怎么創(chuàng)建的出來的呢呕童?SqlSessionFactoryBuilder就是創(chuàng)建者漆际,以Builder結(jié)尾我們很容易想到了Java設(shè)計(jì)模式中的建造者模式,一個(gè)對(duì)象的創(chuàng)建是由眾多復(fù)雜對(duì)象組成的夺饲,建造者模式就是一個(gè)創(chuàng)建復(fù)雜對(duì)象的選擇奸汇,它與工廠模式相比,建造者模式更加關(guān)注零件裝配的順序往声。

public class SqlSessionFactoryBuilder {

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

}

其中XMLConfigBuilder就是解析mybatis-config.xml中每個(gè)標(biāo)簽的內(nèi)容擂找,parse()方法返回的就是一個(gè)Configuration對(duì)象.Configuration也是MyBatis中一個(gè)很重要的組件,包括插件浩销,對(duì)象工廠贯涎,反射工廠,映射文件慢洋,類型解析器等等都存儲(chǔ)在Configuration對(duì)象中塘雳。

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 {
      // issue #117 read properties first
      // 解析properties節(jié)點(diǎn)
      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);
    }
  }

在獲取到Configuration對(duì)象后,SqlSessionFactoryBuilder就會(huì)創(chuàng)建一個(gè)DefaultSqlSessionFactory對(duì)象且警,DefaultSqlSessionFactory是SqlSessionFactory的一個(gè)默認(rèn)實(shí)現(xiàn)粉捻,還有一個(gè)實(shí)現(xiàn)是SqlSessionManager。

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
創(chuàng)建SqlSessionFactory過程

4. 獲取sqlSession

  // 3. 獲取sqlSession
 SqlSession sqlSession = sqlSessionFactory.openSession();

在前面我們講到斑芜,sqlSession是操作數(shù)據(jù)庫的高級(jí)接口肩刃,我們操作數(shù)據(jù)庫都是通過這個(gè)接口操作的。獲取sqlSession有兩種方式杏头,一種是從數(shù)據(jù)源中獲取的盈包,還有一種是從連接中獲取。
獲取到的都是DefaultSqlSession對(duì)象醇王,也就是sqlSession的默認(rèn)實(shí)現(xiàn)呢燥。

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

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
獲取SqlSession步驟

5. 獲取Mapper代理類

在上一步獲取到sqlSession后,我們接下來就獲取到了mapper代理類寓娩。

 // 4. 獲取Mapper
 TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);

這個(gè)getMapper方法叛氨,我們看看DefaultSqlSession是怎么做的

DefaultSqlSession 的 getMapper 方法

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

Configuration 的 getMapper 方法

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

MapperRegistry 中有個(gè)getMapper方法呼渣,實(shí)際上是從成員變量knownMappers中獲取的,這個(gè)knownMappers是個(gè)key-value形式的緩存寞埠,key是mapper接口的class對(duì)象屁置,value是MapperProxyFactory代理工廠,這個(gè)工廠就是用來創(chuàng)建MapperProxy代理類的仁连。


public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

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

如果對(duì)java動(dòng)態(tài)代理了解的同學(xué)就知道蓝角,Proxy.newProxyInstance()方法可以創(chuàng)建出一個(gè)目標(biāo)對(duì)象一個(gè)代理對(duì)象。由此可知每次調(diào)用getMapper方法都會(huì)創(chuàng)建出一個(gè)代理類出來饭冬。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

回到上面使鹅,那這個(gè)MapperProxyFactory是怎么加載到MapperRegistry的knownMappers緩存中的呢?

在上面的Configuration類的parseConfiguration方法中昌抠,我們會(huì)解析 mappers標(biāo)簽患朱,mapperElement方法就會(huì)解析mapper接口。

private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      // 解析properties節(jié)點(diǎn)
      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);
    }
  }
  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.");
          }
        }
      }
    }
  }

解析完后炊苫,就講這個(gè)mapper接口加到 mapperRegistry中麦乞,

configuration.addMapper(mapperInterface);

Configuration的addMapper方法

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

最后還是加載到了MapperRegistry的knownMappers中去了

  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);
        }
      }
    }
  }
獲取mapper代理類過程

6. 執(zhí)行mapper接口方法

 // 5. 執(zhí)行接口方法
 TTestUser userInfo = userMapper.selectByPrimaryKey(16L);

selectByPrimaryKey是TTestUserMapper接口中定義的一個(gè)方法,但是我們沒有編寫TTestUserMapper接口的的實(shí)現(xiàn)類劝评,那么Mybatis是怎么幫我們執(zhí)行的呢姐直?前面講到,獲取mapper對(duì)象時(shí)蒋畜,是會(huì)獲取到一個(gè)MapperProxyFactory工廠類声畏,并創(chuàng)建一個(gè)MapperProxy代理類,在執(zhí)行Mapper接口的方法時(shí)姻成,會(huì)調(diào)用MapperProxy的invoke方法插龄。

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

如果是Object的方法就直接執(zhí)行,否則執(zhí)行cachedInvoker(method).invoke(proxy, method, args, sqlSession); 這行代碼科展,到這里均牢,想必有部分同學(xué)已經(jīng)頭暈了吧。怎么又來了個(gè)invoke方法才睹。
cachedInvoker 是返回緩存的MapperMethodInvoker對(duì)象徘跪,MapperMethodInvoker的invoke方法會(huì)執(zhí)行MapperMethod的execute方法。

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  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()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        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;
  }
}

然后根據(jù)執(zhí)行的接口找到mapper.xml中配置的sql琅攘,并處理參數(shù)垮庐,然后執(zhí)行返回結(jié)果處理結(jié)果等步驟。

7. 提交事物

// 6. 提交事物
sqlSession.commit();

事物就是將若干數(shù)據(jù)庫操作看成一個(gè)單元坞琴,要么全部成功哨查,要么全部失敗,如果失敗了剧辐,則會(huì)執(zhí)行執(zhí)行回滾操作寒亥,恢復(fù)到開始執(zhí)行的數(shù)據(jù)庫狀態(tài)邮府。

8. 關(guān)閉資源

 // 7. 關(guān)閉資源
sqlSession.close();
inputStream.close();

sqlSession是種共用資源,用完了要返回到池子中溉奕,以供其它地方使用挟纱。

9. 總結(jié)

至此我們已經(jīng)大致了解了Mybatis啟動(dòng)時(shí)的大致流程,很多細(xì)節(jié)都還沒有詳細(xì)介紹腐宋,這是因?yàn)樯婕暗降膶用嬗稚钣謴V,如果在一篇文章中介紹檀轨,反而會(huì)讓讀者如置云里霧里胸竞,不知所云。因此参萄,在接下來我將每個(gè)模塊的詳細(xì)介紹卫枝。如果文章有什么錯(cuò)誤或者需要改進(jìn)的,希望同學(xué)們指出來讹挎,希望對(duì)大家有幫助校赤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筒溃,隨后出現(xiàn)的幾起案子马篮,更是在濱河造成了極大的恐慌,老刑警劉巖怜奖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑测,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡歪玲,警方通過查閱死者的電腦和手機(jī)迁央,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滥崩,“玉大人岖圈,你說我怎么就攤上這事「破ぃ” “怎么了蜂科?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)短条。 經(jīng)常有香客問我崇摄,道長(zhǎng),這世上最難降的妖魔是什么慌烧? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任逐抑,我火速辦了婚禮,結(jié)果婚禮上屹蚊,老公的妹妹穿的比我還像新娘厕氨。我一直安慰自己进每,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布命斧。 她就那樣靜靜地躺著田晚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪国葬。 梳的紋絲不亂的頭發(fā)上贤徒,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音汇四,去河邊找鬼接奈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛通孽,可吹牛的內(nèi)容都是我干的序宦。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼背苦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼互捌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起行剂,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤秕噪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后厚宰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巢价,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年固阁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壤躲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡备燃,死狀恐怖碉克,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情并齐,我是刑警寧澤漏麦,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站况褪,受9級(jí)特大地震影響撕贞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜测垛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一捏膨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦号涯、人聲如沸目胡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽誉己。三九已至,卻和暖如春域蜗,著一層夾襖步出監(jiān)牢的瞬間巨双,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工霉祸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筑累,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓脉执,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親戒劫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子半夷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容