Spring事务管理
标签:Spring

事务管理

使用场景举例

在了解@Transactional怎么用之前我们必须要先知道@Transactional有什么用。下面举个栗子:比如一个部门里面有很多成员,这两者分别保存在部门表和成员表里面,在删除某个部门的时候,假设我们默认删除对应的成员。但是在执行的时候可能会出现这种情况,我们先删除部门,再删除成员,但是部门删除成功了,删除成员的时候出异常了。这时候我们希望如果成员删除失败了,之前删除的部门也取消删除。这种场景就可以使用@Transactional事务回滚。

checked异常和unchecked异常

这里之所以让大家清楚checked异常和unchecked异常概念,是因为:
Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

checked异常:
表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。
必须在代码中显式地处理。比如try-catch块处理,或者给所在的方法加上throws说明,将异常抛到调用栈的上一层。
继承自java.lang.Exception(java.lang.RuntimeException除外)。

unchecked异常:
表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。
不需要在代码中显式地捕获unchecked异常做处理。
继承自java.lang.RuntimeException(而java.lang.RuntimeException继承自java.lang.Exception)。

看下面的异常结构图或许层次感更加深些:

这里写图片描述

上面内容来自:Spring中@Transactional事务回滚(含实例详细讲解,附源码)

1. 事务管理相关的API

PlatformTransactionManager 平台事务管理器

**TransactionDefinition 事务定义信息 (配置信息 来自xml配置文件 和 注解)**包括 (隔离、传播、超时、只读)

TransactionStatus 事务具体运行状态

2. PlatformTransactionManager 接口详解

2.1 不同平台事务管理器实现

Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现 ,针对不同的持久层技术,选用对应事务管理器。

事务 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或MyBatis 进行持久化数据时使用
org.springframework.orm.hibernate3.HibernateTransactionManager 使用Hibernate3.0版本进行持久化数据时使用
org.springframework.orm.jpa.JpaTransactionManager 使用JPA进行持久化时使用
org.springframework.jdo.JdoTransactionManager 当持久化机制是Jdo时使用
org.springframework.transaction.jta.JtaTransactionManager 使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用

2.2 事务隔离级别

事务(Transaction)是由一系列对系统中数据进行访问与更新的操作组成的一个程序执行逻辑单元。

事务具有四个特征:

原子性(Atomicity)

事务的原子性是指事务必须是在一个原子的操作序列单元,事务中包含的各项操作在一次执行过程中,只允许出现下面两种状态:

  1. 要么都成功
  2. 要么都失败

一致性(Consistency)

事务的一致性指的是事务的执行不能破坏数据库数据的完整性和一致性。一个事务在执行之前和执行之后,数据库都必须处于一致性状态。因此数据库只包含成功事务提交的结果时,就能说数据库处于一致性状态。如果数据库在运行过程中出现故障尚未完成就被中断,那么就说数据库处于一种不正确的状态,或者不一致的状态。

隔离性(Isolation)

事务的隔离性指在并发环境中,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行的各个事务之间不能相互干扰。

持久性:

事务的持久性指一个事务一旦提交了,那么它对数据库所做的操作都应该被永久的保留下来


隔离性引发并发问题:


隔离级别 含义
DEFAULT 使用后端数据库默认的隔离级别(spring中的的选择项)
READ_UNCOMMITED 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
READ_COMMITTED 允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生
REPEATABLE_READ 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。
SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。

2.3 事务的传播行为

为什么要有事务的传播行为:

事务传播行为类型 说明
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

执行过程:

区别

3. 编程式的事务管理

创建数据库及表account:

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
INSERT INTO `account` VALUES ('1', 'aaa', '1000');
INSERT INTO `account` VALUES ('2', 'bbb', '1000');
INSERT INTO `account` VALUES ('3', 'ccc', '1000');

AccountDAO:

/**
 * 账户管理数据层
 */
public interface AccountDAO {
    void outMoney(String outAccount, double money);

    void inMoney(String inAccount, double money);

    double getMoney(String name);
}

AccountDAOImpl:

import org.springframework.jdbc.core.support.JdbcDaoSupport;

/**
 * Created By liuyao on 2018/5/14 21:34.
 */
public class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO {
    @Override
    public void outMoney(String outAccount, double money) {
        String sql = "UPDATE account SET money=money-? WHERE name = ?";
        this.getJdbcTemplate().update(sql, money, outAccount);
    }

    @Override
    public void inMoney(String inAccount, double money) {
        String sql = "UPDATE account SET money=money+? WHERE name=?";
        this.getJdbcTemplate().update(sql, money, inAccount);
    }

    @Override
    public double getMoney(String name) {
        String sql = "select money from account where name = ? ";
        return this.getJdbcTemplate().queryForObject(sql, Double.class, name);
    }
}

AccountService:

/**
 * 账户管理
 */
public interface AccountService {
     void transfer(String outAccount, String inAccount, double money);

     double getMoney(String name);
}

AccountServiceImpl:

public class AccountServiceImpl implements AccountService {
    private AccountDAO accountDAO;

    @Override
    public void transfer(String outAccount, String inAccount, double money) {
        accountDAO.outMoney(outAccount, money);
        accountDAO.inMoney(inAccount, money);
    }

    @Override
    public double getMoney(String name) {
        return accountDAO.getMoney(name);
    }

    public void setAccountDAO(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }
}

ApplicationContext.xml:

<bean id="mydataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql:///springjdbc"></property>
    <property name="username" value="root"></property>
    <property name="password" value=""></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<bean id="accountDAO" class="com.liuyao.tracsaction.programing.AccountDAOImpl">
    <!--将连接池注入给DAO,JdbcTemplate会自动创建JDBCTemplate对象-->
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<bean id="accountService" class="com.liuyao.tracsaction.programing.AccountServiceImpl">
    <property name="accountDAO" ref="accountDAO"></property>
</bean>

正常情况下:

发生异常后,事务不回滚:


配置事务管理器及模板:

<bean id="mydataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql:///springjdbc"></property>
    <property name="username" value="root"></property>
    <property name="password" value=""></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<bean id="accountDAO" class="com.liuyao.tracsaction.programing.AccountDAOImpl">
    <!--将连接池注入给DAO,JdbcTemplate会自动创建JDBCTemplate对象-->
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<bean id="accountService" class="com.liuyao.tracsaction.programing.AccountServiceImpl">
    <property name="accountDAO" ref="accountDAO"></property>
    <property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>

<!--事务管理模板-->
<bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
    <property name="transactionManager" ref="transactionManager"></property>
</bean>

<!--事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <!--将连接池 注入给事务管理器-->
    <property name="dataSource" ref="mydataSource"></property>
</bean>

在相应的操作前后添加事务处理:

package com.liuyao.tracsaction.programing;

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * Created By liuyao on 2018/5/14 21:31.
 */
public class AccountServiceImpl implements AccountService {
    private AccountDAO accountDAO;

    private TransactionTemplate transactionTemplate;

    @Override
    public void transfer(final String outAccount, final String inAccount, final double money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                accountDAO.outMoney(outAccount, money);
                int d = 1 / 0;
                accountDAO.inMoney(inAccount, money);
            }
        });

    }

    @Override
    public double getMoney(String name) {
        return accountDAO.getMoney(name);
    }

    public void setAccountDAO(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }

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

测试结果:


原理分析:

4. 使用XML配置声明式事务(原始方式)

使用原始的TransactionProxyFactoryBean

只需要修改ApplicationContext.xml

<bean id="mydataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql:///springjdbc"></property>
    <property name="username" value="root"></property>
    <property name="password" value=""></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<bean id="accountDAO" class="com.liuyao.transaction.transactionproxyfactorybean.AccountDAOImpl">
    <!--将连接池注入给DAO,JdbcTemplate会自动创建JDBCTemplate对象-->
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<bean id="accountService" class="com.liuyao.transaction.transactionproxyfactorybean.AccountServiceImpl">
    <property name="accountDAO" ref="accountDAO"></property>
</bean>

<!--事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="mydataSource"></property>
</bean>

<!--为AccountService创建代理-->
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" id="accountServiceProxy">
    <!--目标-->
    <property name="target" ref="accountService"></property>
    <!--针对接口代理-->
    <property name="proxyInterfaces"
              value="com.liuyao.transaction.transactionproxyfactorybean.AccountService"></property>
    <!--增强 事务管理-->
    <property name="transactionManager" ref="transactionManager"></property>
    <!--事务管理属性-->
    <property name="transactionAttributes">
        <!--针对目标对象 每个方法,设置隔离级别,传播行为,是否只读-->
        <props>
            <!--key就是方法名-->
            <!-- value prop格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception -->
            <prop key="transfer">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

测试结果:

上面这种方法通过TransactionProxyFactoryBean创建代理对象,进行事务管理,缺点是需要为每个Bean都创建单独的代理对象

5. 使用XML配置声明式事务 基于tx/aop

在applicationContext.xml 引入 tx 和 aop 两个名词空间

POM

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
	<version>4.3.16.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
	<version>4.3.16.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>4.3.16.RELEASE</version>
</dependency>

ApplicationContext.xml

 <bean id="mydataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///springjdbc"></property>
        <property name="username" value="root"></property>
        <property name="password" value=""></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="mydataSource"></property>
    </bean>

    <bean id="accountDAO" class="com.liuyao.transaction.txconfig.AccountDAOImpl">
        <!--将连接池注入给DAO,JdbcTemplate会自动创建JDBCTemplate对象-->
        <property name="dataSource" ref="mydataSource"></property>
    </bean>

    <bean id="accountService" class="com.liuyao.transaction.txconfig.AccountServiceImpl">
        <property name="accountDAO" ref="accountDAO"></property>
    </bean>
    <!--事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="mydataSource"></property>
    </bean>
    <!--使用tx定义事务管理增强-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--事务的管理属性-->
        <tx:attributes>
            <!--
				name="transfer" 事务管理方法名
				isolation="DEFAULT" 默认隔离级别
				propagation="REQUIRED"  默认传播行为
				read-only="false"  是否只读
				no-rollback-for=""  发生异常不会滚  类似+Exception
				rollback-for=""  发生异常回滚 类似-Exception
				timeout="-1"  不超时
			-->
            <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" read-only="false" no-rollback-for="" rollback-for=""/>
        </tx:attributes>
    </tx:advice>

    <!--使用AOP自动代理-->
    <aop:config>
        <!--定义切点-->
        <aop:pointcut id="mypointcut"
                      expression="execution(* com.liuyao.transaction.txconfig.AccountService+.*(..))"></aop:pointcut>
        <!--定义切面-->
        <aop:advisor pointcut-ref="mypointcut" advice-ref="txAdvice"></aop:advisor>
    </aop:config>

测试结果:

6. 使用注解配置声明式事务

第一步 :在需要管理事务 类或者方法 上添加 @Transactional

    @Override
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
    public void transfer(final String outAccount, final String inAccount, final double money) {

        accountDAO.outMoney(outAccount, money);
        int d = 1 / 0;
        accountDAO.inMoney(inAccount, money);

    }

第二步: 在applicationContext.xml 配置

  <!--事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="mydataSource"></property>
    </bean>

    <!--注解事务管理-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

测试结果可以,不贴图了。


小结 :

  • 17 min read

CONTRIBUTORS


  • 17 min read