Spring中事务管理的四种方法(银行转账为例)

前言

本文配套示例代码下载地址(完整可运行,含sql文件,下载后请修改数据库配置):点击这里下载

一、事务的作用

将若干的数据库操作作为一个整体控制,一起成功或一起失败。

原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

一致性:指事务前后数据的完整性必须保持一致。

隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。

持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,即时数据库发生故障也不应该对其有任何影响。

二、Spring事务管理高层抽象主要包括3个接口

--Platform TransactionManager 事务管理器(提交、回滚事务)

Spring为不同的持久化框架提供了不同的Platform TransactionManager接口实现。如:

使用Spring JDBC或iBatis进行持久化数据时使用DataSourceTransactionManager

使用Hibernate3.0版本进行持久化数据时使用HibernateTransactionManager

--TransactionDefinition 事务定义信息(隔离、传播、超时、只读)

脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。

不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。

幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。

事务隔离级别:(五种)

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

其中,MySQL默认采用REPEATABLE_READ隔离级别;Oracle默认采用READ_COMMITTED隔离级别

事务传播行为:(七种)

  • REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
  • MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
  • REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
  • NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
  • NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。拥有多个可以回滚的保存点,内部回滚不会对外部事务产生影响。只对DataSourceTransactionManager有效

--TransactionStatus 事务具体运行状态

三、Spring提供了以下方法控制事务

a.编程式事务管理(基于Java编程控制,很少使用)--见demo1包

利用TransactionTemplate将多个DAO操作封装起来

*b.声明式事务管理(基于Spring的AOP配置控制)

-基于TransactionProxyFactoryBean的方式.(很少使用)--见demo2包

需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean进行增强.

-基于XML配置(经常使用)--见demo3包

一旦配置好之后,类上不需要添加任何东西。

如果Action作为目标对象切入事务,需要在<aop:config>元素里添加proxy-target-class="true"属性。原因是通知Spring框架采用CGLIB技术生成具有事务管理功能的Action类。

-基于注解(配置简单,经常使用)--见demo4包

在applicationContext.xml中开启事务注解配置。(applicationContext.xml中只需定义Bean并追加以下元素)

<bean id="txManager" class="...">
 <property name="sessionFactory">
 </property>
<tx:annotation-driven transaction-manager="txManager"/>

在目标组件类中使用@Transactional,该标记可定义在类前或方法前。

四、示例(银行转账)

--编程式

/**
 * @Description:转账案例的DAO层接口
 *
 */
public interface AccountDao {
 /**
 * @param out
 * :转出账号
 * @param money
 * :转账金额
 */
 public void outMoney(String out, Double money); 

 /**
 *
 * @param in
 * :转入账号
 * @param money
 * :转账金额
 */
 public void inMoney(String in, Double money);
} 
/**
 * @Description:转账案例的DAO层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
 /**
 * @param out
 * :转出账号
 * @param money
 * :转账金额
 */
 @Override
 public void outMoney(String out, Double money) {
 String sql = "update account set money = money-? where name = ?";
 this.getJdbcTemplate().update(sql, money, out);
 }
 /**
 * @param in
 * :转入账号
 * @param money
 * :转账金额
 */
 @Override
 public void inMoney(String in, Double money) {
 String sql = "update account set money = money+? where name = ?";
 this.getJdbcTemplate().update(sql, money, in);
 }
} 
/**
 * @Description:转账案例的业务接口
 *
 */
public interface AccountService {
 /**
 * @param out :转出账号
 * @param in :转入账号
 * @param money :转账金额
 */
 public void transfer(String out,String in,Double money);
} 
/**
 * @Description:转账案例的业务层实现类
 */
public class AccountServiceImpl implements AccountService {
 // 注入转账的DAO
 private AccountDao accountDao; 

 // 注入事务管理的模板
 private TransactionTemplate transactionTemplate; 

 /**
 * @param out
 * :转出账号
 * @param in
 * :转入账号
 * @param money
 * :转账金额
 */
 @Override
 public void transfer(final String out, final String in, final Double money) { 

 // 未经事务控制的业务处理操作,如果过程中出异常,则导致前面的操作能完成,后面的不能,即转账成功但未收到转账款
 // accountDao.outMoney(out, money);
 // int i = 1/0;
 // accountDao.inMoney(in, money); 

 transactionTemplate.execute(new TransactionCallbackWithoutResult() { 

 @Override
 protected void doInTransactionWithoutResult(
  TransactionStatus transactionStatus) {
 accountDao.outMoney(out, money);
 // int i = 1 / 0;//事务控制,即出现异常,该段内代码都执行失效
 accountDao.inMoney(in, money);
 }
 });
 } 

 public void setAccountDao(AccountDao accountDao) {
 this.accountDao = accountDao;
 } 

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

applicationContext1.xml

<!-- 引入外部的属性文件 -->
 <context:property-placeholder location="classpath:jdbc.properties"/> 

 <!-- 配置c3p0连接池 -->
 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="${jdbc.driverClass}" />
 <property name="jdbcUrl" value="${jdbc.url}" />
 <property name="user" value="${jdbc.username}" />
 <property name="password" value="${jdbc.password}" />
 </bean> 

 <!-- 配置业务层类 -->
 <bean id="accountService" class="com.zs.spring.demo1.AccountServiceImpl">
 <property name="accountDao" ref="accountDao" />
 <!-- 注入事务管理的模板 -->
 <property name="transactionTemplate" ref="transactionTemplate" />
 </bean> 

 <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
 <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
 <property name="dataSource" ref="dataSource" />
 </bean> 

 <!-- 配置DAO类(未简化) -->
 <!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
 <property name="dataSource" ref="dataSource" />
 </bean>
 <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
 <property name="jdbcTemplate" ref="jdbcTemplate" />
 </bean> --> 

 <!-- ==================================1.编程式的事务管理=============================================== -->
 <!-- 配置事务管理器 -->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <property name="dataSource" ref="dataSource" />
 </bean> 

 <!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类 -->
 <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
 <property name="transactionManager" ref="transactionManager"/>
 </bean> 

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext1.xml")
public class TransactionTest {
 @Resource(name = "accountService")
 private AccountService accountService; 

 @Test
 public void demo1() {
 accountService.transfer("aaa", "bbb", 200d);
 }
} 

--基于TransactionProxyFactoryBean的方式

public class AccountServiceImpl implements AccountService {
 // 注入转账的DAO
 private AccountDao accountDao; 

 /**
 * @param out
 *  :转出账号
 * @param in
 *  :转入账号
 * @param money
 *  :转账金额
 */
 @Override
 public void transfer(String out, String in, Double money) {
 accountDao.outMoney(out, money);
 // int i = 1/0;
 accountDao.inMoney(in, money);
 } 

 public void setAccountDao(AccountDao accountDao) {
 this.accountDao = accountDao;
 }
} 

applicationContext2.xml

<!-- 引入外部的属性文件 -->
 <context:property-placeholder location="classpath:jdbc.properties"/> 

 <!-- 配置c3p0连接池 -->
 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="${jdbc.driverClass}" />
 <property name="jdbcUrl" value="${jdbc.url}" />
 <property name="user" value="${jdbc.username}" />
 <property name="password" value="${jdbc.password}" />
 </bean> 

 <!-- 配置业务层类 -->
 <bean id="accountService" class="com.zs.spring.demo2.AccountServiceImpl">
 <property name="accountDao" ref="accountDao" />
 </bean> 

 <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
 <bean id="accountDao" class="com.zs.spring.demo2.AccountDaoImpl">
 <property name="dataSource" ref="dataSource" />
 </bean> 

 <!-- ==================================2.使用XML配置声明式的事务管理(原始方式)=============================================== --> 

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

 <!-- 配置业务层的代理 -->
 <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
 <!-- 配置目标对象 -->
 <property name="target" ref="accountService" />
 <!-- 注入事务管理器 -->
 <property name="transactionManager" ref="transactionManager"></property>
 <!-- 注入事务的属性 -->
 <property name="transactionAttributes">
  <props>
  <!--
   prop的格式:
   * PROPAGATION :事务的传播行为
   * ISOTATION :事务的隔离级别
   * readOnly :只读
   * -EXCEPTION :发生哪些异常回滚事务
   * +EXCEPTION :发生哪些异常不回滚事务
   -->
  <prop key="transfer">PROPAGATION_REQUIRED</prop>
  <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
  <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
  </props>
 </property>
 </bean> 

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class TransactionTest {
 /**
 * 一定要注入代理类:因为代理类进行增强的操作
 */
 // @Resource(name="accountService")
 @Resource(name = "accountServiceProxy")
 private AccountService accountService; 

 @Test
 public void demo1() {
 accountService.transfer("aaa", "bbb", 200d);
 }
} 

--基于XML配置

public class AccountServiceImpl implements AccountService {
 // 注入转账的DAO
 private AccountDao accountDao; 

 /**
 * @param out
 *  :转出账号
 * @param in
 *  :转入账号
 * @param money
 *  :转账金额
 */
 @Override
 public void transfer(String out, String in, Double money) {
 accountDao.outMoney(out, money);
 // int i = 1/0;
 accountDao.inMoney(in, money); 

 } 

 public void setAccountDao(AccountDao accountDao) {
 this.accountDao = accountDao;
 }
} 

applicationContext3.xml

<!-- 引入外部的属性文件 -->
 <context:property-placeholder location="classpath:jdbc.properties"/> 

 <!-- 配置c3p0连接池 -->
 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="${jdbc.driverClass}" />
 <property name="jdbcUrl" value="${jdbc.url}" />
 <property name="user" value="${jdbc.username}" />
 <property name="password" value="${jdbc.password}" />
 </bean> 

 <!-- 配置业务层类 -->
 <bean id="accountService" class="com.zs.spring.demo3.AccountServiceImpl">
 <property name="accountDao" ref="accountDao" />
 </bean> 

 <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
 <bean id="accountDao" class="com.zs.spring.demo3.AccountDaoImpl">
 <property name="dataSource" ref="dataSource" />
 </bean> 

 <!-- ==================================3.使用XML配置声明式的事务管理,基于tx/aop=============================================== --> 

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

 <!-- 配置事务的通知 -->
 <tx:advice id="txAdvice" transaction-manager="transactionManager">
 <tx:attributes>
  <!--
  propagation :事务传播行为
  isolation :事务的隔离级别
  read-only :只读
  rollback-for:发生哪些异常回滚
  no-rollback-for :发生哪些异常不回滚
  timeout :过期信息
  -->
  <tx:method name="transfer" propagation="REQUIRED"/>
 </tx:attributes>
 </tx:advice> 

 <!-- 配置切面 -->
 <aop:config>
 <!-- 配置切入点 -->
 <aop:pointcut expression="execution(* com.zs.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
 <!-- 配置切面 -->
 <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
 </aop:config> 

测试:

/**
 * @Description:Spring的声明式事务管理的方式二:基于AspectJ的XML方式的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class TransactionTest {
 /**
 * 一定要注入代理类:因为代理类进行增强的操作
 */
 @Resource(name = "accountService")
 private AccountService accountService; 

 @Test
 public void demo1() {
 accountService.transfer("aaa", "bbb", 200d);
 }
} 

--基于注解

/**
 * @Transactional中的的属性 propagation :事务的传播行为 isolation :事务的隔离级别 readOnly :只读
 *   rollbackFor :发生哪些异常回滚 noRollbackFor :发生哪些异常不回滚
 *   rollbackForClassName 根据异常类名回滚
 */
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
public class AccountServiceImpl implements AccountService {
 // 注入转账的DAO
 private AccountDao accountDao; 

 /**
 * @param out
 *  :转出账号
 * @param in
 *  :转入账号
 * @param money
 *  :转账金额
 */
 @Override
 public void transfer(String out, String in, Double money) {
 accountDao.outMoney(out, money);
 // int i = 1/0;
 accountDao.inMoney(in, money);
 } 

 public void setAccountDao(AccountDao accountDao) {
 this.accountDao = accountDao;
 }
} 

applicationContext4.xml

<!-- 引入外部的属性文件 -->
 <context:property-placeholder location="classpath:jdbc.properties"/> 

 <!-- 配置c3p0连接池 -->
 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="${jdbc.driverClass}" />
 <property name="jdbcUrl" value="${jdbc.url}" />
 <property name="user" value="${jdbc.username}" />
 <property name="password" value="${jdbc.password}" />
 </bean> 

 <!-- 配置业务层类 -->
 <bean id="accountService" class="com.zs.spring.demo4.AccountServiceImpl">
 <property name="accountDao" ref="accountDao" />
 </bean> 

 <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
 <bean id="accountDao" class="com.zs.spring.demo4.AccountDaoImpl">
 <property name="dataSource" ref="dataSource" />
 </bean> 

 <!-- ==================================4.使用注解配置声明式事务============================================ --> 

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

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

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class TransactionTest {
 /**
 * 一定要注入代理类:因为代理类进行增强的操作
 */
 @Resource(name = "accountService")
 private AccountService accountService; 

 @Test
 public void demo1() {
 accountService.transfer("aaa", "bbb", 200d);
 }
} 

具体代码和数据库文件参考项目完整代码:

http://xiazai.jb51.net/201805/yuanma/Spring-transaction_jb51.rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Spring中的事务隔离级别的介绍

    spring事务: 什么是事务: 事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败. 事务特性(4种): 原子性 (atomicity):强调事务的不可分割. 一致性 (consistency):事务的执行的前后数据的完整性保持一致. 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰 持久性(durability) :事务一旦结束,数据就持久到数据库 解决读问题: 设置事务隔离级别(5种) DEFAULT 这是一个PlatfromTran

  • 浅谈SpringBoot之事务处理机制

    一.Spring的事务机制 所有的数据访问技术都有事务处理机制,这些技术提供了API用来开启事务.提交事务来完成数据操作,或者在发生错误的时候回滚数据. 而Spring的事务机制是用统一的机制来处理不同数据访问技术的事务处理.Spring的事务机制提供了一个PlatformTransactionManager接口,不同的数据访问技术的事务使用不同的接口实现: 在程序中定义事务管理器的代码如下: @Bean public PlatformTransactionManager transaction

  • 在Spring中编写事务的介绍

    理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元:如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元.所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是

  • Spring 事务事件监控及实现原理解析

    前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件.本文首先会使用实例进行讲解Spring事务事件是如何使用的,然后会讲解这种使用方式的实现原理. 1. 示例 对于事务事件,Spring提供了一个注解@TransactionEventListener,将这个注解标注在某个方法上,那么就将这个方法声明为了一个事务事件处理器,而具体的事件类型则是由TransactionalEventListener.phase属性进行定义的.如下是Transac

  • spring事务异常回滚实例解析

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug?我想多了....... 为了打印清楚日志,很多方法我都加tyrcatch,在catch中打印日志.但是这边情况来了,当这个方法异常时候日志是打印了,但是加的事务却没有回滚. 例: 类似这样的方法不会回滚(一个方法出错,另一个方法不会回滚): if(userSave){ try { userDao.save(user); userCapabilityQuotaDao.save(capabilityQuota); } catch (Exce

  • Spring+SpringMVC配置事务管理无效原因及解决办法详解

    一般我们在Spring的配置文件application.xml中对Service层代码配置事务管理,可以对Service的方法进行AOP增强或事务处理如事务回滚,但是遇到一个问题,在Controller类中调用Service层方法,配置的事务管理会失效,查询相关资料发现原因.其实Spring和SpringMVC俩个容器为父子关系,Spring为父容器,而SpringMVC为子容器.也就是说application.xml中应该负责扫描除@Controller的注解如@Service,而Spring

  • Spring中事务用法示例及实现原理详解

    前言 Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现. 关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比较优雅的方式对其进行封装支持.本文首先会通过一个简单的示例来讲解Spring事务是如何使用的,然后会讲解Spring是如何解析xml中的标签,并对事

  • Spring中事务传播行为的介绍

    传播行为定义关于客户端和被调用方法的事务边界.Spring定义了7种截然不同的传播行为. 1,PROPAGATION_MANDATORY,表示该方法必须运行在一个事务中.如果当前没有事务正在发生,则抛出异常. 2,PROPAGATION_NESTED,表示如果当前有一个事务正在运行当中,则该方法应该运行在一个嵌套事务中.被嵌套的事务可以独立于封装事务进行提交或回滚.如果封装事务不存在,则行为就像PROPAGATION_REQUIRED一样. 3,PROPAGATION_NEVER,表示当前方法不

  • Spring对事务管理的支持

    Spring对事务的支持有两种方式,一是自己编写事务,精确控制事务的边界,二是采用声明事务的方式,使用AOP来完成.无论哪种方式,我们都将使用一个Spring事务管理连接器连接特定平台的事务实现. Spring不直接管理事务,相反,它提供很多可供选择的事务管理器,将事务管理的责任委托给JTA(Java Transaction Interface)或相应的持久性机制所提供的某个特定平台事务实现. Spring提供的常用的事务管理器有DataSourceTransactionManager,Hibe

  • SpringDataMongoDB多文档事务的实现

    一.安装MongoDB4.0.3(××) 1.1.官方安装文档 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/ 1.2.tar.gz包下载地址 https://www.mongodb.com/download-center/community?jmp=docs 1.3.复制集官方配置 https://docs.mongodb.com/manual/administration/replica-set-mem

随机推荐