Spring之事务管理


    事务管理对于企业应用至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性。
    就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作过程中机器突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保证用户和银行的利益都不受损失。
 
一、事务的介绍
    事务是若干个操作组合在一起,形成一个原子性的工作单元。这一组操作要么全部成功,要么全部失败。
 
    事务的特性:
    事务有四个特性,分别是ACID,也就是原子性、一致性、隔离性和持久性。
    原子性(Atomicity):事务是一个原子操作,由一系列的动作组成,要么全部成功,要么全部失败。
    一致性(Consistency):事务一旦完成,系统必须确保数据的完整性,而不会部分成功,部分失败。
    隔离性(Isolation):可能存在多个事务处理相同的数据,这时,事务之间应该隔离开来,防止数据被损坏。
    持久性(Durability):事务一旦完成,数据就被持久化到硬盘了。
 
    隔离存在的问题:
    脏读(dirty read):A事务读到了B事务未提交了数据,而B事务又回滚了,所以A事务读到的是无效数据。
    不可重复读(norepeatable read):A事务两次(或者多次)查询到的数据不相同,因为B事务在A的两次查询期间对数据进行了更新。
    幻读(phantom read):A事务两次(或者多次)查询到的数据不相同,因为B事务在A的两次查询期间插入了新的数据
 
    因为存在隔离问题,所以有隔离级别来解决这些问题。
    隔离级别:
    read uncommitted:读未提交。
    read committed:读已提交。该级别解决了脏读。
    repeatable read:可重复读。该级别解决了脏读和不可重复读。
    serializable:串行化。解决全部问题,但是效率最低。因为它在操作的时候,完全锁定了相关的表,其他事务只能处于等待状态。
 
    Java模拟事务操作:
Connection conn = null;
try{
    conn = getConnection();
    conn.setAutoCommit(false);    //关闭事务的自动提交
    A
    B
    C
    D
    conn.commit();
}catch(){
    conn.rollback();
}
    ABCD是同一个事务内的动作。ABCD有任何一个动作抛了异常,都将进入catch块中进行事务回滚。
    
     Java模拟带保存点的事务操作:   
Connection conn = null;
Savepoint savepoint = null;
try{
    conn = getConnection();
    conn.setAutoCommit(false);    //关闭事务的自动提交
    A
    B
    savepoint = conn.setSavepoint();
    C
    D
    conn.commit();
}catch(){
    if(savepoint != null){    //savepoint不为空,说明已经执行到了C或者D
        conn.rollback(savepoint);    //回滚到保存点
    }else{
        conn.rollback();     //savepoing为空,说明在执行A或者B的时候就抛了异常
    }
}

 

二、Spring事务的API
    在 Spring 中,事务是通过 TransactionDefinition 接口来定义的。
    Spring为了管理事务,设计了三个顶级接口,Spring的事务管理就是围绕这三个顶级接口而展开的。 
    我们重点解析事务详情,事务详情也叫事务定义,或者事务属性。
    事务详情中定义两组常量,分别用来描述 隔离级别 和 传播行为
 
    隔离级别:
        隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
        isolation_default = -1,默认值,使用数据库的隔离级别。
        isolation_read_uncommitted = 1, 读未提交
        isolation_read_committed = 2, 读已提交
        isolation_repeatable_read = 4, 可重复读
        isolation_serializable = 8, 串行化
        1、2、4、8这四个常量值是定义在java.sql.Connection接口中
 
    传播行为:
        所谓事务的传播行为是指,如果在当前事务开始之前,一个事务上下文已经存在,此时有若干选项可以指定当前事务性方法的执行行为。
        假设现在有A、B两个操作。     
        propagation_required = 0,默认值,如果A有事务,B就使用A的事务。如果A没事务,B创建一个新事务。
        propagation_supports = 1,如果A有事务,B就使用A的事务。如果A没事务,B以非事务执行。
        propagation_mandatory = 2,如果A有事务,B就事务A的事务。如果A没事务,B会抛出异常。
        propagation_requires_new = 3,如果A有事务,将A的事务挂起,B创建一个新事务执行,如果A没事务,B也创建一个新事务执行。
        propagation_not_supported = 4,如果A有事务,将A的事务挂起,B以非事务执行。如果A没事务,B也以非事务执行。
        propagation_never = 5,如果A有事务,B将抛异常。如果A没事务,B以非事务执行。
        propagation_nested = 6,如果A有事务,B的事务将嵌套在A的事务里面执行。B的事务执行完之后,再出来继续执行A的事务,所以A事务中得有个保存点。
 
    事务超时:
        所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
 
    事务的只读属性:
        事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。
        如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
 
    事务的回滚规则:
        通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。
        这通常也是大多数开发者希望的处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时依然提交事务,或者在抛出某些已检查异常时回滚事务。
 
 
三、手动编程管理事务(基于底层API)
        根据PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口,我们完全可以通过编程的方式来进行事务管理。
        下面给出一个小demo:
        数据库: 
  

        1. dao层

package cn.african.dao;
public interface BankDao {
    boolean transfer(Long fromId, Long toId, double amount);
}


package cn.african.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class BankDaoImpl extends JdbcDaoSupport implements BankDao {

    @Override
    public boolean transfer(Long fromId, Long toId, double amount) {
        int f = this.getJdbcTemplate().update("update account set money = money - ? where id = ?", amount, fromId);
        //异常
        int i = 1 / 0;
        int t = this.getJdbcTemplate().update("update account set money = money + ? where id = ?", amount, toId);
        if(f > 0 && t > 0) {
            return true;
        }
        return false;
    }
}

        2. service层

package cn.african.service;
public interface BankService {    
    public boolean transfer(Long fromId, Long toId, double amount);
}


package cn.african.service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import cn.african.dao.BankDao;

public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    //事务详情
    private TransactionDefinition txDefinition;
    //事务管理器
    private PlatformTransactionManager txManager;

    @Override
    public boolean transfer(Long fromId, Long toId, double amount) {
        TransactionStatus txStatus = txManager.getTransaction(txDefinition);
        boolean result = false;
        try {
            result = bankDao.transfer(fromId, toId, amount);
            txManager.commit(txStatus);
        } catch (Exception e) {
            result = false;
            txManager.rollback(txStatus);
            System.out.println("转账失败");
        }
        return result;
    }

    
    public void setBankDao(BankDao bankDao) {
        this.bankDao = bankDao;
    }
    public void setTxDefinition(TransactionDefinition txDefinition) {
        this.txDefinition = txDefinition;
    }
    public void setTxManager(PlatformTransactionManager txManager) {
        this.txManager = txManager;
    }
}

    3. applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/txdemo" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    
    <bean id="bankDao" class="cn.african.dao.BankDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="bankService" class="cn.african.service.BankServiceImpl">
        <property name="bankDao" ref="bankDao" />
        <property name="txManager" ref="txManager" />
        <!-- 定义事务详情 -->
        <property name="txDefinition">
            <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
                <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED" />
            </bean>
        </property>
    </bean>
</beans>

        4. 测试类

package cn.african.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.african.service.BankService;

public class TestApp {
    
    @Test
    public void demo01() throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        BankService bankService = context.getBean("bankService", BankService.class);
        bankService.transfer(1L, 2L, 1000D);
        context.getClass().getMethod("close").invoke(context);
    }
}
        测试之后可以看到,数据库的数据并没有发生改变。

 

四、手动编程管理事务(基于TransactionTemplate)
        通过前面的示例可以发现,这种事务管理方式很容易理解,但令人头疼的是,事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。
        幸好,Spring 也意识到了这些,并提供了简化的方法,这就是 Spring 在数据访问层非常常见的模板回调模式。
        我们再实现一个基于TransactionTemplate的demo:
        1. dao层(如上)
        2. service层
package cn.african.service;
public interface BankService {    
    public boolean transfer(Long fromId, Long toId, double amount);
}


package cn.african.service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import cn.african.dao.BankDao;

public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionTemplate transactionTemplate;

    @Override
    public boolean transfer(Long fromId, Long toId, double amount) {
        
        return transactionTemplate.execute(new TransactionCallback<Boolean>() {
            boolean result = false;
            @Override
            public Boolean doInTransaction(TransactionStatus status) {
                try {
                    bankDao.transfer(fromId, toId, amount);
                    result = true;
                } catch (Exception e) {
                    //设置回滚
                    status.setRollbackOnly();
                    System.out.println("转账失败");
                }
                return result;
            }
        });
    }

    public void setBankDao(BankDao bankDao) {
        this.bankDao = bankDao;
    }
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
}

    3. applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/txdemo" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    
    <bean id="bankDao" class="cn.african.dao.BankDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="bankService" class="cn.african.service.BankServiceImpl">
        <property name="bankDao" ref="bankDao"></property>
        <property name="transactionTemplate" ref="txTemplate"></property>
    </bean>
    
    <bean id="txTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"></property>
    </bean>
    
</beans>
    4. 测试类(如上)

 

五、声明式事务管理
    Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
    声明式事务管理有四种方式:
    1. 基于TransactionInterceptor 类来进行声明式事务管理
    2. 基于TransactionProxyFactoryBean来进行声明式事务管理
    3. 基于 <tx> 命名空间进行声明式事务管理
    4. 基于 @Transactional 注解来进行声明式事务管理
    
    
    5.1  基于TransactionInterceptor 类来进行声明式事务管理
    首先我们看一下这种方式的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ee19_spring_day03" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    
    <bean id="bankDao" class="cn.african.dao.BankDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!--这个事务拦截器就是切面-->
    <bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager" ref="txManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="transfer">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
    
    <bean id="bankServiceTarget" class="cn.african.service.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <bean id="bankService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--目标类-->
        <property name="target" ref="bankServiceTarget"/>
        <!--配置切面-->
        <property name="interceptorNames">
            <list>
                <idref bean="txInterceptor"/>
            </list>
        </property>
    </bean>    
</beans>
    首先,我们配置了一个 TransactionInterceptor 来定义相关的事务规则,他有两个主要的属性:一个是 transactionManager,用来指定一个事务管理器,并将具体事务相关的操作委托给它;
    另一个是 Properties 类型的 transactionAttributes 属性,它主要用来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。
    指定事务属性的具体的书写规则如下: 
传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常] 
    其中传播行为是必须的,其他都是可选的。
    超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
    不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。
    导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。
    给一个书写例子:    
<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20,+AException,+BException,-CException</prop>
    使用 PROPAGATION_REQUIRED 事务传播行为,事务的隔离级别是 ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出 AException 或者 BException 类型的异常,则仍然提交,当抛出 CException 类型的异常时必须回滚事务。
    这里没有指定"readOnly",表示事务不是只读的。
    由于Spring是通过感知异常来决定是否回滚,所以此时在Service中,我们不能再去捕获异常,这个异常必须要让Spring知道。所以Service实现类改成了:
package cn.african.service;
import cn.african.dao.BankDao;

public class BankServiceImpl implements BankService {
    private BankDao bankDao;

    @Override
    public boolean transfer(Long fromId, Long toId, double amount) {
        //这种方式,在这里不能自己去捕获异常,这个异常必须由spring感知
        return bankDao.transfer(fromId, toId, amount);
    }

    public void setBankDao(BankDao bankDao) {
        this.bankDao = bankDao;
    }
}

 

    5.2   基于TransactionProxyFactoryBean来进行声明式事务管理
    前面的声明式事务虽然好,但是却存在一个非常恼人的问题:配置文件太多。我们必须针对每一个目标对象配置一个 ProxyFactoryBean;
    为了缓解这个问题,Spring 为我们提供了 TransactionProxyFactoryBean,用于将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一。   
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ee19_spring_day03" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    
    <bean id="bankDao" class="cn.african.dao.BankDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <bean id="bankServiceTarget" class="cn.african.service.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <bean id="bankService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target" ref="bankServiceTarget"/>
        <property name="transactionManager" ref="txManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="transfer">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>

 

  
    5.3   基于 <tx> 命名空间进行声明式事务管理
    前面两种声明式事务配置方式奠定了 Spring 声明式事务管理的基石。在此基础上,Spring 2.x 引入了 <tx> 命名空间,结合使用 <aop> 命名空间,配置变得更加简单和灵活。
 另外,得益于 <aop> 命名空间的切点表达式支持,声明式事务也变得更加强大。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ee19_spring_day03" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    
    <bean id="bankDao" class="cn.african.dao.BankDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="bankService" class="cn.african.service.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    <!--如果事务的默认配置就能满足需求,上面元素的配置可以简化为以下方式-->
    <!-- <tx:advice id="txAdvice" transaction-manager="txManager" /> -->
    
    <aop:config>
        <aop:pointcut expression="execution(* cn.african.service..*.*(..))" id="txPointcut"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
    
</beans>
    由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。
    另外,如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。
 
    5.4. 基于 @Transactional 注解来进行声明式事务管理
    除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上。
    当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
   删除通知配置和aop编程的配置,添加注解驱动:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ee19_spring_day03" />
        <property name="user" value="root" />
        <property name="password" value="123456" />
    </bean>
    
    <bean id="bankDao" class="cn.african.dao.BankDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="bankService" class="cn.african.service.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    <!-- 使用事务注解时,必须要开启注解驱动 -->
    <tx:annotation-driven transaction-manager="txManager"/>
    
</beans>
        transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。
        虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
        另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
 
结束语
        1.  基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务管理是 Spring 提供的最原始的方式,通常我们不会这么写,但是了解这种方式对理解 Spring 事务管理的本质有很大作用。
        2.  基于 TransactionTemplate 的编程式事务管理是对上一种方式的封装,使得编码更简单、清晰。
        3.  基于 TransactionInterceptor 的声明式事务是 Spring 声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解 Spring 声明式事务有很大作用。
        4.  基于 TransactionProxyFactoryBean 的声明式事务是上中方式的改进版本,简化的配置文件的书写,这是 Spring 早期推荐的声明式事务管理方式,但是在 Spring 2.0 中已经不推荐了。
        5.  基于 <tx> 和 <aop> 命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活。
        6.  基于 @Transactional 的方式将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理 Bean 的配置,然后在需要实施事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。