# Mybatis事务

## 概述

### **事务管理器（transactionManager）**

在 MyBatis 中有两种类型的事务管理器（也就是 type=”\[JDBC|MANAGED]”）：

* JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置，它依赖于从数据源得到的连接来管理事务作用域。
* MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接，而是让容器来管理事务的整个生命周期（比如 JEE 应用服务器的上下文）。 默认情况下它会关闭连接，然而一些容器并不希望这样，因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。例如:

  ```
  <transactionManager type="MANAGED">
    <property name="closeConnection" value="false"/>
  </transactionManager>
  ```

> 提示如果你正在使用 Spring + MyBatis，则没有必要配置事务管理器， 因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

这两种事务管理器类型都不需要任何属性。它们不过是类型别名，换句话说，你可以使用 TransactionFactory 接口的实现类的完全限定名或类型别名代替它们。

```
public interface TransactionFactory {
  void setProperties(Properties props);  
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);  
}
```

任何在 XML 中配置的属性在实例化之后将会被传递给 setProperties() 方法。你也需要创建一个 Transaction 接口的实现类，这个接口也很简单：

```
public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}
```

使用这两个接口，你可以完全自定义 MyBatis 对事务的处理。

## MyBatis事务遇到的问题 <a href="#id-0mybatis-shi-wu-yu-dao-de-wen-ti" id="id-0mybatis-shi-wu-yu-dao-de-wen-ti"></a>

如果开启MyBatis事务管理，则需要手动进行事务提交，否则事务会回滚到原状态;

```
String resource = "mybatis/config.xml";
InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sessionFactory.openSession();

User user = new User();
user.setName("liuliu");
user.setPassword("123123");
user.setScore("88");
String statement = "mybatis.mapping.UserMapper.insertUser";
session.insert(statement,user);
session.close();
```

> 如果在具体操作执行完后不通过sqlSession.commit()方法提交事务，事务在sqlSession关闭时会自动回滚到原状态；只有执行了commit()事务提交方法才会真正完成操作；
>
> 如果不执行sqlSession.commit()操作，直接执行sqlSession.close()，则会在close()中进行事务回滚；
>
> 如果不执行sqlSession.commit()操作也不手动关闭sqlSession，在程序结束时关闭数据库连接时会进行事务回滚；

```
String resource = "mybatis/config.xml";
InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sessionFactory.openSession();

User user = new User();
user.setName("liuliu");
user.setPassword("123123");
user.setScore("88");
String statement = "mybatis.mapping.UserMapper.insertUser";
session.insert(statement,user);
session.commit();
session.close();
```

## 事务管理入口 <a href="#id-1-shi-wu-guan-li-ru-kou" id="id-1-shi-wu-guan-li-ru-kou"></a>

在XML配置文件中定义事务工厂类型，JDBC或者MANAGED分别对应JdbcTransactionFactory.class和ManagedTransactionFactory.class;

> 如果type=”JDBC”则使用JdbcTransactionFactory事务工厂则MyBatis独立管理事务，直接使用JDK提供的JDBC来管理事务的各个环节：提交、回滚、关闭等操作；
>
> 如果type=”MANAGED”则使用ManagedTransactionFactory事务工厂则MyBatis不在ORM层管理事务而是将事务管理托付给其他框架，如Spring

## 事务对UPDATE操作的影响 <a href="#id-2-shi-wu-dui-update-cao-zuo-de-ying-xiang" id="id-2-shi-wu-dui-update-cao-zuo-de-ying-xiang"></a>

### 事务的提交 <a href="#shi-wu-de-ti-jiao" id="shi-wu-de-ti-jiao"></a>

在sqlSession中执行了UPDATE操作，需要执行sqlSession.commit()方法提交事务，不然在连接关闭时候会自动回滚；

```
@Override
public void commit() {
  commit(false);
}

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

exector.commit()事务提交方法归根到底是调用了transaction.commit()事务的提交方法；这里的transaction就是根据配置对应的JdbcTransaction或者ManagedTransaction；

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

如果是JdbcTransaction的commit()方法，通过调用connection.commit()方法通过数据库连接实现事务提交；

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

如果是ManagedTransaction的commit()方法，则为空方法不进行任何操作；

```
@Override
public void commit() throws SQLException {
  // Does nothing
}

@Override
public void rollback() throws SQLException {
  // Does nothing
}
```

### 事务的回滚 <a href="#shi-wu-de-hui-gun" id="shi-wu-de-hui-gun"></a>

sqlSession执行close()关闭操作时，如果close()操作之前进行了UPDATE操作未进行commit()事务提交则会进行事务回滚然后再关闭会话；如果update后执行了commit则直接关闭会话；

在DefaultSqlSession类中如果执行了UPDATE操作则会将标志位dirty赋值为true

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

在事务提交时会将dirty赋值为false;

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

在关闭会话时会判断dirty是否为true，如果为true则需要进行事务回滚操作，否则直接关闭会话

```
@Override
public void close() {
  try {
    executor.close(isCommitOrRollbackRequired(false));
    closeCursors();
    dirty = false;
  } finally {
    ErrorContext.instance().reset();
  }
}
```

判断dirty标志是否为true

```
private boolean isCommitOrRollbackRequired(boolean force) {
  return (!autoCommit && dirty) || force;
}
```

如果dirty为true则判定forceRollback为true，执行回滚操作；

```
@Override
public void close(boolean forceRollback) {
  try {
    try {
      rollback(forceRollback);
    } finally {
      if (transaction != null) {
        transaction.close();
      }
    }
  } catch (SQLException e) {
    // Ignore.  There's nothing that can be done at this point.
    log.warn("Unexpected exception on closing transaction.  Cause: " + e);
  } finally {
    transaction = null;
    deferredLoads = null;
    localCache = null;
    localOutputParameterCache = null;
    closed = true;
  }
}
```

## 事务对SELECT的影响 <a href="#id-3-shi-wu-dui-select-de-ying-xiang" id="id-3-shi-wu-dui-select-de-ying-xiang"></a>

事务对select操作的影响主要体现在对缓存的影响上，主要包括一级缓存和二级缓存

### 一级缓存 <a href="#yi-ji-huan-cun" id="yi-ji-huan-cun"></a>

因为一级缓存是Session级别的，事务的提交回滚对MyBatis的一级缓存没有影响；一级缓存放在BaseExector中的PerpectualCache类型的localCache中；

```
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}
```

### 二级缓存 <a href="#er-ji-huan-cun" id="er-ji-huan-cun"></a>

在Mapper.xml文件解析时会根据文件中的标签或者创建Cache实例，并将该实例放入每一个MappedStatement中，在MappedStatement执行select操作时候会获取该cache;

```
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  Cache cache = ms.getCache();
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
```

#### 查询二级缓存 <a href="#cha-xun-er-ji-huan-cun" id="cha-xun-er-ji-huan-cun"></a>

根据Statement所在的Mapper的cache缓存对象和根据statement生成的cacheKey，从正式缓存中取缓存数据

```
List<E> list = (List<E>) tcm.getObject(cache, key);
```

根据Statement所在的Mapper的cache缓存对象在TransactionManager中定位对应的TransactionCache，TransactionCache中保存这正式缓存delegate和临时未提交缓存entiryToAddOnCommit；

```
public Object getObject(Cache cache, CacheKey key) {
  return getTransactionalCache(cache).getObject(key);
}
```

获取缓存时直接从正式缓存delegate中查询

```
@Override
public Object getObject(Object key) {
  // issue #116
  Object object = delegate.getObject(key);
  if (object == null) {
    entriesMissedInCache.add(key);
  }
  // issue #146
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}
```

#### 存储二级缓存 <a href="#cun-chu-er-ji-huan-cun" id="cun-chu-er-ji-huan-cun"></a>

如果从二级缓存中未命中缓存，则需要从数据库中查取，再将查询结果放入二级缓存中；查询结果首先放入二级缓存临时缓存中，只有执行了commit()事务提交才正式转移到正式缓存中；也就是说只有执行了commit()方法的缓存才被下次查询使用，不然仍会执行数据库查询任务并覆盖上次的临时缓存；

根据Statement所在的Mapper的cache缓存对象在TransactionManager中定位对应的TransactionCache

```
public void putObject(Cache cache, CacheKey key, Object value) {
  getTransactionalCache(cache).putObject(key, value);
}
```

将数据放入TransactionCache中的临时缓存entiryToAddOnCommit中

```
public void putObject(Object key, Object object) {
  entriesToAddOnCommit.put(key, object);
}
```

#### 提交二级缓存 <a href="#ti-jiao-er-ji-huan-cun" id="ti-jiao-er-ji-huan-cun"></a>

执行事务提交时候会调用TransactionCacheManager内的commit()方法提交缓存，每一个cachingExecutor对应一个TransactionCacheManager，也就是一个SqlSeesion对应一个TransactionCacheManager，因此sqlSession.commit()是提交了当前会话的所以二级缓存的临时缓存；每个sqlSession对每一个Mapper的cache都有一个临时缓存，多个sqlSession共享一个Mapper的cache的正式缓存；

```
public void commit() {
  for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
  }
}
```

```
public void commit() {
  if (clearOnCommit) {
    delegate.clear();
  }
  flushPendingEntries();
  reset();
}
```

将临时缓存entiryToAddOnCommit中的数据转移到正式缓存delegate中

```
private void flushPendingEntries() {
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
    delegate.putObject(entry.getKey(), entry.getValue());
  }
  for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
      delegate.putObject(entry, null);
    }
  }
}
```

#### 回滚二级缓存 <a href="#hui-gun-er-ji-huan-cun" id="hui-gun-er-ji-huan-cun"></a>

* 如果之前未执行UPDATE操作，dirty标志为false，在关闭会话之前没有进行事务提交则进行数据库回滚和缓存提交；
* 如果执行了UPDATE操作，dirty标志为true，在关闭会话之前没有进行事务提交则进行数据库回滚和缓存回滚；
* 可以调用session.close(true)关闭会话时强行回滚缓存；

```
@Override
public void close(boolean forceRollback) {
  try {
    if (forceRollback) { 
      tcm.rollback();
    } else {
      tcm.commit();
    }
  } finally {
    delegate.close(forceRollback);
  }
}
```

## 总结 <a href="#zong-jie" id="zong-jie"></a>

* MyBatis可以通过XML配置是否独立处理事务，可以选择不单独处理事务，将事务托管给其他上层框架如spring等；
* 如果MyBatis选择处理事务则事务会对数据库操作产生影响
* 对UPDATE操作的影响主要表现在UPDATE操作后如果没有进行事务提交则会子啊会话关闭时进行数据库回滚；
* 对SELECT操作的影响主要表现在二级缓存上，执行SELECT操作后如果未进行事务提交则缓存会被放在临时缓存中，后续的SELECT操作无法使用该缓存，直到进行commit()事务提交，临时缓存中的数据才会被转移到正式缓存中；


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tuonioooo-notebook.gitbook.io/application-framework/mybatispian/mybatisshi-wu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
