<?xml version="1.0" encoding="UTF-8"?>
<!--

    Copyright ${license.git.copyrightYears} the original author or authors.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<document xmlns="http://maven.apache.org/XDOC/2.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">

  <properties>
    <title>MyBatis-Spring | 事务</title>
    <author email="hpresnall@gmail.com">Hunter Presnall</author>
    <author email="eduardo.macarron@gmail.com">Eduardo Macarron</author>
    <author email="nanlei1987@gmail.com">Nan Lei</author>
  </properties>
                             
  <body>
    <section name="事务">
      <p>
        一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器，MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。
      </p>
      <p>
        一旦配置好了 Spring 的事务管理器，你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解和 AOP 风格的配置。在事务处理期间，一个单独的 <code>SqlSession</code> 对象将会被创建和使用。当事务完成时，这个 session 会以合适的方式提交或回滚。
      </p>
      <p>
        事务配置好了以后，MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。
      </p>
  
    <subsection name="标准配置" id="configuration">
      <p>
        要开启 Spring 的事务处理功能，在 Spring 的配置文件中创建一个 <code>DataSourceTransactionManager</code> 对象：
      </p>
      <source><![CDATA[
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>]]></source>

      <source><![CDATA[
@Bean
public DataSourceTransactionManager transactionManager() {
  return new DataSourceTransactionManager(dataSource());
}]]></source>

      <p>
        传入的 <code>DataSource</code> 可以是任何能够与 Spring 兼容的 JDBC <code>DataSource</code>。包括连接池和通过 JNDI 查找获得的 <code>DataSource</code>。
      </p>
      <p>
        注意：为事务管理器指定的 <code>DataSource</code> <b>必须</b>和用来创建 <code>SqlSessionFactoryBean</code> 的是同一个数据源，否则事务管理器就无法工作了。
      </p>
    </subsection>

    <subsection name="交由容器管理事务" id="container">
      <p>
        如果你正使用一个 JEE 容器而且想让 Spring 参与到容器管理事务（Container managed transactions，CMT）的过程中，那么 Spring 应该被设置为使用 JtaTransactionManager 或由容器指定的一个子类作为事务管理器。最简单的方式是使用 Spring 的事务命名空间或使用 <code>JtaTransactionManagerFactoryBean</code>：
      </p>
      <source><![CDATA[<tx:jta-transaction-manager />]]></source>

      <source><![CDATA[
@Bean
public JtaTransactionManager transactionManager() {
  return new JtaTransactionManagerFactoryBean().getObject();
}]]></source>

      <p>
        在这个配置中，MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器，并注入一个 <code>SqlSession</code>。如果没有正在进行的事务，而基于事务配置需要一个新的事务的时候，Spring 会开启一个新的由容器管理的事务。
      </p>
      <p>
        注意，如果你想使用由容器管理的事务，而<b>不想</b>使用 Spring 的事务管理，你就<b>不能</b>配置任何的 Spring 事务管理器。并<b>必须配置</b> <code>SqlSessionFactoryBean</code> 以使用基本的 MyBatis 的 <code>ManagedTransactionFactory</code>：
      </p>
      <source><![CDATA[<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="transactionFactory">
    <bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
  </property>  
</bean>]]></source>

      <source><![CDATA[
@Bean
public SqlSessionFactory sqlSessionFactory() {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(dataSource());
  factoryBean.setTransactionFactory(new ManagedTransactionFactory());
  return factoryBean.getObject();
}]]></source>

    </subsection>
  
    <subsection name="编程式事务管理" id="programmatic">
      <p>
        MyBatis 的 <code>SqlSession</code> 提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时，你的 bean 将会注入由 Spring 管理的 <code>SqlSession</code> 或映射器。也就是说，Spring 总是为你处理了事务。
      </p>
      <p>
        你不能在 Spring 管理的 <code>SqlSession</code> 上调用 <code>SqlSession.commit()</code>，<code>SqlSession.rollback()</code> 或 <code>SqlSession.close()</code> 方法。如果这样做了，就会抛出 <code>UnsupportedOperationException</code> 异常。在使用注入的映射器时，这些方法也不会暴露出来。
      </p>
      <p>
        无论 JDBC 连接是否设置为自动提交，调用 <code>SqlSession</code> 数据方法或在 Spring 事务之外调用任何在映射器中方法，事务都将会自动被提交。
      </p>
      <p>
        如果你想编程式地控制事务，请参考 <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-programmatic">the Spring reference document(Data Access -Programmatic transaction management-)</a>。下面的代码展示了如何使用 <code>PlatformTransactionManager</code> 手工管理事务。
      </p>
      <source><![CDATA[
TransactionStatus txStatus =
    transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
  userMapper.insertUser(user);
} catch (Exception e) {
  transactionManager.rollback(txStatus);
  throw e;
}
transactionManager.commit(txStatus);]]></source>

      <p>
        在使用 <code>TransactionTemplate</code> 的时候，可以省略对 <code>commit</code> 和 <code>rollback</code> 方法的调用。
      </p>

      <source><![CDATA[
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
  userMapper.insertUser(user);
  return null;
});]]></source>

      <p>
        注意：虽然这段代码使用的是一个映射器，但换成 SqlSession 也是可以工作的。
      </p>
      </subsection>
    </section>
  </body>
</document>
