`
Adan-Chiu
  • 浏览: 21678 次
社区版块
存档分类
最新评论

深入Spring事务管理

 
阅读更多

        互联网系统时时面对着高并发,在互联网系统中同时跑成百上千条进程都是十分常见的,尤其当一些热门网站将刚上市的廉价商品放在线上销售时,狂热的用户几乎在同一时刻打开手机 、电脑、平板设备进行疯狂抢购。这样就会出现多线程访问网站,进而导致数据库在一个多事务访问的环境中,从而引发数据库丢失更新(Lost Update)和数据一致性问题,同时也给服务器带来很大压力,甚至可能发生数据库死锁和瘫痪进而导致数据库宕机。为了解决这些问题,互联网开发者需要了解数据库一些特性,进而规避一些存在的问题,避免数据的不一致,提高系统性能。

      大部分情况下一个数据库事务是要么同时成功,要么同时失败的,但是也存在的不同的要求,例如信用卡还款,有个跑批量的事务,而整个批量事务包含了对多个信用卡的还款业务的处理,我们不能因为一张卡的事务失败了,就把其他卡的事务也会滚,造成多个客户还款失败,即正常还款的用户,也被认为是不正常还款的,这样会引发很严重的金融信誉问题,Spring事务的传播行为带来了比较方便的解决方案。

 
Spring数据库事务管理器的设计
       在Spring中数据库事务是通过PlatformTransactionManager进行管理的。附TransactionTemplate重要源码。
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
	Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
        //使用自定义的事务管理器        
	if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
		return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
	}else {//系统默认管理器
               //获取事务状态
		TransactionStatus status = this.transactionManager.getTransaction(this);
		T result;
		try {
			result = action.doInTransaction(status);//回调接口方法
		}catch (RuntimeException | Error ex) {
			// 回滚异常方法
			rollbackOnException(status, ex);
			throw ex;
		}catch (Throwable ex) {
			// 回滚异常方法
			rollbackOnException(status, ex);
			throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
		}
		this.transactionManager.commit(status);//提交事务
		return result;//返回结果
	}
}
 可以清楚的看到:
  • 事务的创建、提交和回滚是通过PlatformTransactionManager接口来完成的
  • 当事务产生异常时会回滚事务,在默认实现中所有的异常都会回滚。我们可以通过配置去修改在某些异常发生时回滚或者不回滚事务
  • 当无异常时,提交事务

配置事务管理器

       目前我们讨论JDBC和MyBatis,使用最多的事务管理器是DataSourceTransactionManager,其它持久层框架后续介绍。

 

<?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:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">
        
        <context:component-scan base-package="com.wise.tiger"/>
    
        <context:property-placeholder location="classpath:dbcp-config.properties"/>
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" >
		    <property name="driverClassName" value="${driverClassName}" />
		    <property name="url" value="${url}" />
		    <property name="username" value="${jdbc.username}" />
		    <property name="password" value="${password}" />
		    <property name="defaultAutoCommit" value="${defaultAutoCommit}"/>
		    <property name="connectionProperties" value="${connectionProperties}"/>
		</bean>
			
		<!--配置数据源事务管理器 -->
		<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    		<property name="dataSource" ref="dataSource"/>
		</bean>
</beans>
       在spring中可以使用声明式事务和编程式事务,如今编程式事务几乎不用了,因为它会产生冗余,代码可读性较差。声明式事务又可以分为xml配置和注解事务,但xml方式已经不常用了,所以这里只简单交代下它的用法,目前主流方法是注解@Transactional。

 

声明式事务

        声明式事务是一种约定性的事务,在大部分的情况下,使用数据库事务时,大部分场景是在代码中发生了异常时,需要回滚事务,而不发生异常时则是提交事务,从而保证数据库数据的一致性。从这点出发,Spring给了一个约定(类似于AOP开发给的约定),你的业务方法不发生异常,事务管理器就提交事务,发生异常则让事务管理器回滚事务。

       首先声明式事务允许自定义事务接口——TransactionDefinition,它可以由xml或者注解@Transactional进行配置,到了这里我们先谈谈@Transactional的配置项

   
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	@AliasFor("transactionManager")
	String value() default "";

	@AliasFor("value")
	String transactionManager() default "";

	Propagation propagation() default Propagation.REQUIRED;

	Isolation isolation() default Isolation.DEFAULT;

	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	boolean readOnly() default false;

	Class<? extends Throwable>[] rollbackFor() default {};

	String[] rollbackForClassName() default {};
	
	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};
}
 Transactional的配置项
配置项 含义 备注
value 定义事务管理器

spring容器中的一个Bean id,

这个Bean需要实现接口PlatformTransactionManager

transactionManager 同上 同上
isolation 隔离级别 数据库在并发事务时的概念,默认取值为数据库的隔离级别
propagation 传播行为 方法之间调用问题,默认取值为Propagation.REQUIRED
timeout 超时时间 单位为秒,当超时时,会引发异常,默认会导致事务回滚
readOnly 是否开启只读事务 默认值:false
rollbackFor 回滚事务的异常类定义 只有当方法产生所定义的异常时,才会回滚事务
rollbackForClassName 回滚事务的异常类名定义 同rollbackFor,只是使用类名称定义
noRollbackFor 当产生哪些异常不回滚事务 当产生所定义异常时,Spring将继续提交事务
noRollbackForClassName 同noRollbackFor 同noRollbackFor,只是使用类名称定义

只需要在xml配置文件中加入如下配置就可以使用@Transactional配置事务了

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

 使用xml进行配置事务管理器

<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
	<tx:attributes>
		<!-- 根据方法名指定事务的属性  -->
		<tx:method name="bookService" propagation="REQUIRED"/>
		<tx:method name="get*" read-only="true"/>
		<tx:method name="find*" read-only="true"/>
		<tx:method name="*"/>
	</tx:attributes>
</tx:advice>
<!-- 配置事务切入点,以及把事务切入点和事务属性关联起来 -->
<aop:config>
	<aop:pointcut expression="execution(* com.wise.tiger.service.*.*(..))"
		id="txPointcut"/>
	<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

数据库的相关知识(参考另一篇博文:https://adan-chiu.iteye.com/blog/2441253) 

选择隔离级别和传播行为

       选择隔离级别的出发点在于两点:性能和数据一致性

选择隔离级别

        在互联网应用中,不但要考虑数据库数据的一致性,而且还要考虑系统的性能,一般而言,从脏读到序列化,系统性能直线下降。因此设置高的级别,比如序列化,会严重压制并发,从而引发大量的线程挂起,直到获得锁才能进一步操作,而恢复时又需要大量的等待时间。因此在一般的在购物类应用中,通过隔离级别来控制事务一致性的方式又被排除了,而对于脏读又风险过大,在大部分场景下,企业会选择读/写提交的方式设置事务,这样既有助于提高并发,又压制了脏读,但是对于数据一致性问题并没有解决,后面讨论解决。对于一般的应用都可以使用@Transactional方法进行配置。

       隔离级别需要根据并发的大小和性能来做出决定,对于并发不大又要保证数据安全性的可以使用序列化的隔离级别,这样就能保证数据库在多事务环境中的一致性。

@Transactional(isolation = Isolation.SERIALIZABLE)
public void save(Book book) {
	bookDao.insertBook(book);
}

       只是这样的代码会使得数据库的并发能力低下,在抢购商品的场景下出现卡顿的情况,所以在高并发的场景下这样的代码并不适用。

       注解@Transaction的默认隔离级别是Isolation.DEFAULT,其含义是默认的,随数据库的默认值而变化。因为不同的数据库支持的隔离级别是不一样的,MySQL支持全部四种隔离级别,默认为可重复读的隔离级别。而Oracle只支持读/写提交和序列化,默认读/写提交.

 传播行为

        传播行为是指方法之间的调用事务策略问题。在大部分情况下,我们都希望事务能够同时成功或者同时失败。但也会有例外,假如现在需要实现信用卡的还款功能,有一个总的代码调用逻辑————RepaymentBatchService的batch方法,那么它要实现的是记录还款成功的总卡数和对应完成的信息,而每一张卡的还款则是通过RepaymentService的repay方法完成的。

        首先来分析业务。如果只有一条事务,那么当调用RepaymentService的repay方法对某一张信用卡进行还款时,不幸的事发生了,它发生了异常。如果将这条事务回滚,就会造成所有的数据操作都会回滚,那些已经正常还款的用户也会还款失败,这将是一个糟糕的结果。当batch方法调用repay方法时,它会为repay方法创建一个新的事务。当这个方法产生异常时,只会回滚它自身的事务,而不会影响主事务和其它事务,这样就避免了上面的问题

      Spring中传播行为的类型是通过一个枚举去定义的org.springframework.transaction. annotation.Propagation

传播行为 含义 备注
REQUIRED

当方法调用时,如果不存在当前事务,那么就创建事务;如果之前的方法已经存在事务了,那么就沿用之前的事务

spring默认
SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。  
MANDATORY 方法必须在事务内运行

使用当前的事务,

如果当前没有事务,就抛出异常。

REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起 事务管理器会打开新的事务运行该方法
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,直到该方法结束才恢复当前事务 适用于那些不需要事务的SQL
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。  
NESTED 嵌套事务,也就是调用方法如果抛出异常只回滚自己内部执行的SQL,而不回滚主方法的SQL 它的实现存在两种情况:1.如果当前数据库支持保存点(savepoint),那么它就会在当前事务上使用保存点技术;如果发生异常,则将方法内执行的SQL回滚到保存点上,而不是全部回滚;否则就等同于REQUIRES_NEW创建新的事务运行方法代码

 

 一般而言,企业级应用中主要关注的是REQUEIRES_NEW和NESTED

@Transactional的自调用失效问题

       注解@Transactional底层的实现是SpringAOP技术,而SpringAOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的。还有一个更隐秘的,而且在使用过程中极其容易犯错的——自调用。

所谓自调用,就是一个类的方法调用自身另外一个方法的过程

 

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	private BookMapper bookMapper;

	// 传播行为定义为REQUIRED
	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED,timeout = 1)
	public int insertBookList(List<Book> bookList) {
		int count=0;
                bookList.forEach(book-> count += this.insertBook(book));//调用自己的方法,产生自调用问题
		return count;
	}
 
	// 传播行为定义为REQUIRES_NEW,每次调用产生新事务
	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW,timeout = 1)
	public int insertBook(Book book) {
		return bookMapper.insertBook(book);
	}
}
       通过测试,在insertBook上标注的@Transactional失效了,这是一个很容易掉进去的陷阱。
       出现这个问题的根本原因在于AOP的实现原理。@Transactional实现原理是AOP,而AOP的实现原理是动态代理,类中的方法调用自己的另一个方法,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题。为了解决这个问题,一方面我们可以使用两个服务类,另外一方面可以直接从容器中获取service的代理对象。

 

典型错误用法剖析

MVC模型中Controller中调用Service的问题

  • 当一个Controller使用Service方法时,如果这个Service标注有@Transactional,那么它就会启用一个事务,而一个Service方法完成后,它就会释放该事务
  • 当一个Controller声明的方法中,调用了两次Service方法,这两个方法是有各自单独的事务的。如果多次调用,且不在同一个事务中,这会造成不同时提交和回滚不一致的问题

防止过长时间占用事务

  • 大型互联网系统中,一个数据库的链接可能也就是50条左右。
  • @Transactional的Service类中的方法代码,作为一个事务整体,与数据库没有交互的代码,如网络请求,文件上传也会占用事务时间,因为只有方法运行完成后,返回Result后才会关系数据库资源

错误异常捕获语句

  • MVC模型中服务类分为原子服务类和组合服务类
  • ...方法已经存在异常了,由于开发者不了解Spring的事务约定,在两个操作方法里加入自己的try...catch..语句,就可能造成数据库操作发生异常的时候,被代码中的try...catch..所捕获,Spring在事务约定的流程中再也得不到任何异常信息,此时Spring就会提交事务,造成数据不一致
  • 解决方法时将捕获的异常向上抛出

参考资料:

JavaEE互联网轻量级应用整合开发

分享到:
评论

相关推荐

    Spring事务管理Demo

    Spring事务管理的目的是确保数据的一致性和完整性,尤其是在多操作、多资源的环境中。本Demo将深入探讨Spring如何实现事务的管理。 首先,Spring提供了两种主要的事务管理方式:编程式事务管理和声明式事务管理。 ...

    Spring事务管理的jar包

    本篇将深入探讨Spring事务管理的核心概念、工作原理以及如何使用`spring-tx-3.2.0.RELEASE.jar`这个jar包。 首先,我们需要理解什么是事务。在数据库系统中,事务是一组操作,这些操作被视为一个整体,要么全部完成...

    Spring事务管理失效原因汇总

    标题“Spring事务管理失效原因汇总”指出了本文的核心内容是分析在使用Spring框架进行事务管理时可能遇到的问题及其原因。描述部分进一步说明了事务失效的后果往往不明显,容易在测试环节被忽略,但在生产环境中出现...

    spring 事务管理的理解

    Spring 框架是Java开发中...理解并熟练掌握Spring事务管理,对于提升应用程序的稳定性和可靠性至关重要。在实际开发中,结合声明式事务管理、事务传播行为、隔离级别和回滚规则,可以有效地确保数据的完整性和一致性。

    深入理解spring的事务管理机制

    Spring事务管理的核心是基于AOP(面向切面编程)来实现的。 **Spring事务的本质**实际上是依赖于底层数据库提供的事务支持。如果没有数据库层面的支持,Spring无法单独实现事务的功能。在传统的JDBC操作中,如果想...

    spring事务管理5种方法

    本篇文章将深入探讨Spring事务管理的五种方法,旨在帮助开发者更好地理解和运用这一核心特性。 首先,我们来了解什么是事务。在数据库操作中,事务是一组逻辑操作,这些操作要么全部成功,要么全部失败,确保数据的...

    实验 spring 声明事务

    实验 "Spring 声明事务" ...通过这个实验,学生可以深入理解Spring声明式事务管理的工作原理,以及如何在实际项目中配置和使用。这将有助于他们在未来开发中更好地处理事务相关的复杂问题,确保应用程序的数据一致性。

    spring事务管理

    在深入探讨Spring事务管理之前,我们需要先理解什么是事务。事务可以被定义为一系列的操作集合,这些操作作为一个整体被提交或回滚。简单来说,事务就是一个不可分割的工作单位,它包含的一系列操作要么全部成功,...

    spring 自定义事务管理器,编程式事务,声明式事务@Transactional使用

    在Spring框架中,事务管理是核心功能之一,它确保了数据操作的一致性和完整性。本教程将深入探讨如何在Spring中实现自定义事务管理器...这将加深你对Spring事务管理的理解,帮助你在实际项目中更加熟练地运用这些技术。

    spring事务操作试验

    首先,Spring事务管理的核心概念是ACID(原子性、一致性、隔离性和持久性),这是所有事务系统的基础。在Spring中,事务管理分为两种模式:声明式事务管理和编程式事务管理。声明式事务管理通过配置元数据(如XML或...

    Spring的事务管理小案例

    在本文中,我们将深入探讨Spring框架中的事务管理。Spring是一个广泛应用的Java企业级应用开发框架,它提供...如果你想要深入了解,可以参考提供的博客链接或其他相关资料,进一步学习Spring事务管理的细节和最佳实践。

    全面分析_Spring_的编程式事务管理及声明式事务管理

    本教程将深入探讨 Spring 的编程式事务管理和声明式事务管理,帮助你理解这两种方式的差异与应用场景。 首先,编程式事务管理依赖于编程的方式显式地控制事务的开始、提交、回滚等操作。它通过实现 `...

    spring-tx事务管理实例

    本实例将深入探讨Spring事务管理的实现与应用。 首先,Spring事务管理分为编程式事务管理和声明式事务管理两种方式。编程式事务管理是通过调用TransactionTemplate或直接使用PlatformTransactionManager接口来控制...

    spring_事务管理(实例代码)

    Spring事务管理就是围绕这些特性来确保数据的一致性。 四、事务的传播行为 在Spring中,我们可以配置事务的传播行为,比如REQUIRED(默认,如果当前存在事务,则加入当前事务,否则新建一个事务)、PROPAGATION_...

    详细介绍Spring事务管理

    ### Spring事务管理详解 #### 一、Spring事务管理的重要性及必要性 在现代软件开发中,事务管理是一项至关重要的...通过深入了解Spring事务管理的不同方面,开发者可以更好地利用这些特性来构建高质量的应用程序。

    spring事务案例分析.zip

    本主题将深入探讨“Spring事务案例分析.zip”中的关键知识点,包括Spring事务管理及其在实际项目中的应用。 首先,我们来了解什么是Spring事务管理。在分布式系统或数据库操作中,事务管理是确保数据一致性和完整性...

    Spring事务管理和SpringJDBC思维导图

    首先,我们来深入理解Spring事务管理。事务是数据库操作的基本单元,它确保了数据的一致性和完整性。Spring提供了声明式和编程式两种事务管理方式。声明式事务管理是通过配置或注解来实现的,如@Transactional注解,...

    spring学习事务源码

    本文将深入探讨Spring事务管理的源码,理解其背后的实现机制。 首先,Spring事务管理有两种主要模式:编程式事务管理和声明式事务管理。编程式事务管理通过调用`PlatformTransactionManager`接口提供的方法进行显式...

    Spring事务处理-ThreadLocal的使用

    首先,了解Spring事务管理的基本概念。在多线程环境中,事务管理是至关重要的,它负责确保一组数据库操作要么全部成功,要么全部失败。Spring提供了两种主要的事务管理方式:编程式事务管理和声明式事务管理。声明式...

    Spring 事务简单完整例子

    本文将深入探讨在Spring框架中如何管理事务,以“Spring 事务简单完整例子”为出发点,结合标签“spring,事务,jdbc事务”,我们将详细解释Spring事务管理的原理和实践。 首先,Spring提供了两种事务管理方式:编程...

Global site tag (gtag.js) - Google Analytics