对 Spring 事务管理机制及其配置方式的总结

Spring 事务管理有一部分内容与数据库的事务管理机制相关。关于数据库事务管理机制可以参考数据库相关内容,比如 MySQL 的建议参考《高性能 MySQL》等书籍。这里我们重点介绍 Spring 中事务的配置和使用的方式。

1、数据库事务相关

梳理下基本的知识点:

  1. 事务的 ACID 特性:原子性隔离性一致性持久性

  2. 事务的四种隔离级别:未提交读提交读可重复读序列化

  3. 数据库事务类型有本地事务和分布式事务:

    1. 本地事务:就是普通事务,能保证单台数据库上的操作的 ACID,被限定在一台数据库上;
    2. 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的 ACID,使事务可以跨越多台数据库;
  4. Java 事务类型有 JDBC 事务和 JTA 事务:

    1. JDBC 事务:就是数据库事务类型中的本地事务,通过 Connection 对象的控制来管理事务;
    2. JTA 事务:JTA 指 Java 事务 API(Java Transaction API),是 Java EE 数据库事务规范,JTA 只提供了事务管理接口。由应用程序服务器厂商(如 WebSphere Application Server)提供实现,JTA 事务比 JDBC 更强大,支持分布式事务。
  5. Java EE 事务类型有本地事务和全局事务:

    1. 本地事务:使用 JDBC 编程实现事务;
    2. 全局事务:由应用程序服务器提供,使用 JTA 事务;
  6. 按是否通过编程实现事务有声明式事务和编程式事务;

    1. 声明式事务:通过注解或 XML 配置文件指定事务信息;
    2. 编程式事务:通过编写代码实现事务。

2、Spring 事务管理框架

使用 Spring 事务管理框架需要使用下面的依赖:

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-txartifactId>
    <version>${spring.version}version>
dependency>

下面,我们对 Spring 事务管理框架的一些比较重要的类进行梳理,以此来简单了解下 Spring 事务整体的结构以及各部分之间的关系。

1. PlatformTransactionManager

该类提供了事务策略,它是一个接口,定义如下:

public interface PlatformTransactionManager {
    // 传入 TransactionDefinition 得到 TransactionStatus
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
    void commit(TransactionStatus var1) throws TransactionException;
    void rollback(TransactionStatus var1) throws TransactionException;
}

针对每种具体的事务类型或者数据库类型,有不同的实现方式,实际的开发过程中要根据持久层的实现方式选择不同的事务管理器。

这里要注意:

  1. 每个方法中都会抛出一个异常 TransactionException,它是非受检异常,也就是继承自 RuntimeException。
  2. getTransaction() 方法会根据传入的 TransactionDefinition 返回一个TransactionStatus。TransactionStatus 代表一个事务,包含了事务的状态的信息。
  3. TransactionDefinition 接口则是用来指定事务的传播行为、隔离级别、超时和是否只读等信息的。
  4. 最新版本的 Spring 事务管理器实现被分别集成到了不同的包中,需要根据使用的持久层引入不同的依赖。

2 TransactionDefinition

该接口中定义了一组常量,我们将这些常量按照它们的名称(名称体现出功能)划分成下面几组:

组别 1:事务的传播行为

    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;

事务的传播行为主要用来解决业务层方法之间的调用的问题。就是说业务层的两个方法中分别具有不同的事务,此时该使用哪个事务或者事务该如何表现,就由传播行为来指定。

下面是事务的传播行为的基本总结:

事务的传播行为可以分为 3 组,每组我们只要记住被标记为红色的一个就可以了,因为该组中的行为传播行为是相似的。

组别 2:事务的隔离级别

    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;

下面是关于事务的隔离级别的总结:

组别 3:事务的超时信息

	int TIMEOUT_DEFAULT = -1;

此外该接口中还定义了一组方法,主要用来获取事务信息。

3 TransactionStatus

该接口中定义了一些用于获取事务管理器状态的方法。

3、使用 Spring 事务管理

3.1 环境搭建

要测试 Spring 的事务管理,首先我们要搭建一个简单的开发环境。我们按照下面的步骤来进行。

这里我们使用 DBCP 连接池,所以要加入下面的依赖:

<dependency>
    <groupId>commons-dbcpgroupId>
    <artifactId>commons-dbcpartifactId>
    <version>1.4version>
dependency>

我们使用 MySQL 进行测试,所以又需要使用 MySQL 连接驱动:

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>5.1.38version>
dependency>

不论是使用声明式事务管理还是编程式事务管理,我们都需要先配置数据库源:


<context:property-placeholder location="classpath:tx/dbcp.properties" />


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${username}" />
    <property name="password" value="${password}" />
bean>

显然,这里用到了占位符。这些实际的数据是写在配置文件 dbcp.properties 里面的:

jdbc.driverClassName=org.gjt.mm.mysql.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_is_comming
username=root
password=qweasdzxc

针对 Spring JDBC 的事务,我们需要引入 Spring-jdbc 依赖:

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-jdbcartifactId>
    <version>${spring.version}version>
dependency>

这样我们就可以定义事务管理器:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
bean>

完成了上面的配置,我们就可以在代码中使用SPring事务来进行管理了。这里我们用一个简单的例子来测试我们的开发环境:

    public static void main(String...args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("tx/TxConfig.xml");
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) context.getBean("txManager");
        TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
        DataSource dataSource = (DataSource) context.getBean("dataSource");
        try {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            String sql = "update tx_change set money = money + 200 where id = 0";
            String sql2 = "update tx_change set money = money - 100 where id = 0";
            jdbcTemplate.update(sql);
            int x = 1/0;
            jdbcTemplate.update(sql2);
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            e.printStackTrace();
            transactionManager.rollback(transactionStatus);
        }
    }

这里我们从上下文中获取数据源并使用 JDBC 模板来执行 SQL,在 SQL 执行完毕之后中间产生一个异常,并在 catch 语句中捕获异常并进行事务的回滚。最终因为事务被回滚,导致 SQL 没有成功执行。

以上 Spring 事务的实现方式是使用编程的方式来实现的,也就是说它用编写 Java 样板代码的方式来实现。

3.2 声明式事务管理

从上面的编程式事务管理中可以看出,使用 Spring 事务进行管理的时候有许多样板代码,这些方法在执行真正的逻辑之前和之后被调用,我们可以使用 AOP 来简化模板。

所谓声明式事务管理其实也就是使用 AOP 的思想,从一定程度上讲,也就是在复习 Spring AOP 相关的东西。

1. 使用 TransactionProxyFactoryBean 实现声明式事务管理

这种方式使用 TransactionProxyFactoryBean 来对我们的业务层的方法做代理,并在其中定义事务的行为以及对事务进行控制。

这里我们先定义 DAO 层:

public interface TXDAO {
    void addMoney();
    void minusMoney();
}

public class TXDAOImpl extends JdbcDaoSupport implements TXDAO {

    @Override
    public void addMoney() {
        String sql = "update tx_change set money = money + 200 where id = 0";
        getJdbcTemplate().update(sql);
    }

    @Override
    public void minusMoney() {
        String sql = "update tx_change set money = money - 100 where id = 0";
        getJdbcTemplate().update(sql);
    }

这里我们先定义了接口 TXDAO,其中有 addMoney()minusMoney() 两个方法。它们都用来对表 tx_change 的字段 money 进行修改。

然后,我们定义业务层的类,它们调用 DAO 层的方法:

public interface TXTestService {
    void updateMoney();
}

public class TXTestServiceImpl implements TXTestService {
    @Autowired
    private TXDAO txdao;

    @Override
    public void updateMoney() {
        txdao.addMoney();
        int x = 1 / 0;
        txdao.minusMoney();
    }
}

这里我们定义了业务层接口 TXTestService,并实现了它。在实现类中调用了 TXDAO 中的两个方法,并在两次调用之间出现一个异常。在使用事务管理的时候,我们将 updateMoney() 方法整体作为一个事务进行管理。也就是如果方法中出现了异常,那么方法整体中的事务都会被回滚。

接下来就是在 XML 中配置这些 Bean,当然也可以在 Java 代码中进行配置:

    
    <context:property-placeholder location="classpath:tx/dbcp.properties" />

    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="root" />
        <property name="password" value="${password}" />
    bean>

    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="txDAO" class="me.shouheng.spring.tx.dao.TXDAOImpl">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="txTestService" class="me.shouheng.spring.tx.service.TXTestServiceImpl"/>

    
    <bean id="txTestServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target" ref="txTestService"/>
        <property name="transactionManager" ref="txManager"/>
        <property name="transactionAttributes">
            <props>
  <prop key="*">PROPAGATION_REQUIREDprop>
            props>
        property>
    bean>

这里在之前的基础之上先定义了 DAO 并将数据源作为字段注入,然后将 DAO 注入到 Service 中。我们重点关注一下这里的 TransactionProxyFactoryBean 的配置:

它首先用 target 属性指定了要被代理的对象,然后用 transactionManager 指定了事务管理对象,最后用 transactionAttributes 指定事务的属性。在该属性中我们是使用 props 标签来指定的,你可以看下它的 Setter 方法的定义。

最后说一下每个 prop 标签的含义,这里的 key 用来指定方法,我们用 * 就表示所有的方法,而其中的 PROPAGATION_REQUIRED 用来指定事务的传播行为,你也可以在这里指定隔离级别等信息。它的整体含义就是,对目标对象中的所有方法使用 PROPAGATION_REQUIRED 指定的传播行为。除了指定传播行为、隔离级别、是否只读等信息,我们还可以指定出现那些异常时仍然提交事务和出现哪些异常的时候回滚事务。

完成了上面的配置,我们就可以使用单元测试来测试一下我们的事务:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx/TxConfig2.xml")
public class TXTest2 {

    @Resource(name = "txTestServiceProxy")
    private TXTestService txTestService;

    @Test
    public void test() {
        txTestService.updateMoney();
    }
}

使用 Spring 的单元测试需要引入的两个包:

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-testartifactId>
    <version>${spring.version}version>
dependency>
<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <scope>compilescope>
dependency>

上面的测试即调用了我们的业务层的方法,根据实际的测试结果确实可以对事务进行正确的管理。
这里还要注意一下,我们向 TXTest2 中注入 TXTestService 的时候使用的是 txTestServiceProxy,也就是用代理增强之后的 Service。而且这里的 Service 必须定义成接口,才能成功地将代理之后的值注入进去。

2. 基于 AspectJ 的 XML 事务管理方式

除了使用上面的代理方式,我们还可以使用基于 AspectJ 的 XML 事务管理方式。我们需要做以下配置:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <context:property-placeholder location="classpath:tx/dbcp.properties" />

    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="root" />
        <property name="password" value="${password}" />
    bean>

    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="txDAO" class="me.shouheng.spring.tx.dao.TXDAOImpl">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="txTestService" class="me.shouheng.spring.tx.service.TXTestServiceImpl"/>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
        tx:attributes>
    tx:advice>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* me.shouheng.spring.tx.service.TXTestService+.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    aop:config>

beans>

在 XML 中我们需要定义一个通知,然后定义 AOP 并且指定切点。这里我们在定义通知的时候,引用了之前定义的事务管理器,并且在 标签中指定了事务的属性。在它的子标签中我们指定了对别管理的任意方法(由 * 指定)使用的传播行为 (REQUIRED) 和隔离级别 (DEFAULT)。

使用这种配置方式大部分的工作都在这里配置即可。注意一下我们定义的切点是接口 TXTestService 的字类的所有方法,也就是说其中的任意方法都会使用事务进行管理。这样我们就可以直接使用 txTestService 这个 Bean 来访问了,因为它的每个方法都被 Spring 的事务接管了。

所以,我们可以直接在 Tes t中进行如下的测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx/TxConfig3.xml")
public class TXTest3 {

    @Resource(name = "txTestService")
    private TXTestService txTestService;

    @Test
    public void test() {
        txTestService.updateMoney();
    }
}

显然,这里我们将 txTestService 这个 Bean 注入到了测试环境中,并调用了它的 updateMoney() 方法。在该方法中抛出了异常,所以整个方法的逻辑都会在出现异常的时候被回滚。

3 Spring 的基于注解的事务管理

使用这种方式的配置更加简单,我们只需要做如下的配置即可。

首先在 XML 配置文件中,我们启用事务的注解驱动,并且指定事务管理器:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <context:property-placeholder location="classpath:tx/dbcp.properties" />

    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="root" />
        <property name="password" value="${password}" />
    bean>

    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="txDAO" class="me.shouheng.spring.tx.dao.TXDAOImpl">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="txTestService" class="me.shouheng.spring.tx.service.TXTestServiceImpl"/>

	
    <tx:annotation-driven transaction-manager="txManager"/>

beans>

然后我们在需要使用事务管理的业务层类上面加上 @Transactional 注解即可:

@Transactional
public class TXTestServiceImpl implements TXTestService {

    @Autowired
    private TXDAO txdao;

    @Override
    public void updateMoney() {
        txdao.addMoney();
        int x = 1 / 0;
        txdao.minusMoney();
    }
}

你可以查看一下该注解的定义,其中有用来定义事务的隔离级别和传播行为等的字段。你可以使用它们来定义事务的行为。

接下来就是测试代码,和之前的例子的测试是一样的,只要将使用的配置文件换成我们上面定义的配置文件即可。

总结:

好了,上面就是 Spring 的事务管理相关的知识和使用的基本方法。要掌握 Spring 的事务管理不仅需要 Spring 的知识,同时还需要一些数据库方面的知识。

在 Spring 中使用事务有编程式和声明式两种方式,通常倾向于使用声明式的事务管理,因为它的代码更加简单,对代码的侵入量比较小。

Spring 事务管理:

  1. 编程式事务管理:很少使用
  2. 声明式事务管理:
    1. 基于 TransactionProxyFactoryBean 的方式:很少使用,需要为每个管理类配置一个 TransactionProxyFactoryBean 进行增强
    2. 基于 AspectJ 的 XML 事务管理方式:经常使用,一旦配置号就不需要添加任何东西
    3. 基于注解进行事务管理:经处使用,配置简单,需要在业务层的类上面添加一个 @Transactional 注解。

你可能感兴趣的:(Spring,事务,数据库,服务端,SSM,SpringBoot)