欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring事物管理及@Transactional注解用法

程序员文章站 2022-05-23 18:31:12
...

一、什么是事物

       在我们开发过程中,有很多需要事物的业务场景。比如:在我们转账的场景中,用户A需要转账500元给用户B。在用户A账户中扣除500元后,发生了异常,这时候用户B增加500元的语句可能就不会执行。就会导致转账的丢失。此时就需要事物管理将扣除500元的操作进行回滚。也就是说事物内的语句,要么全部执行成功,要么全部执行失败。

二、事物管理的几种方式

spring中的事物管理主要分为两大类:编程式事物管理和声明式事物管理

        其中编程式事物管理就是在我们编程的过程中,通过代码执行commit或rollback语句,来实现事物的提交和回滚。这种方式我们在此不做详细讲解。

另外一种就是声明式事物管理。声明式事物管理主要分为XML配置声明和@Transactional注解声明方式。

        Spring 的声明式事务管理是建立在 Spring AOP 机制之上的,其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

        声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中作相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。总的来说,声明式事务得益于 Spring IoC容器 和 Spring AOP 机制的支持:IoC容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而由于事务管理本身就是一个典型的横切逻辑(正是 AOP 的用武之地),因此 Spring AOP 机制是声明式事务管理的直接实现者。

        显然,声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染。

三、声明式事物管理的实现方式

1.xml配置方式实现

<!-- 配置事务管理器 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>   //这里的ref映射需要指定数据源
    </bean>
    <!-- 拦截器方式配置事物 -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
<!-- 根据方法名指定事务  -->
            <tx:method name="delete*" rollback-for="Exception"/>
            <tx:method name="save*" rollback-for="Exception"/>
            <tx:method name="insert*" rollback-for="Exception"/>
            <tx:method name="update*" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
    <!-- Spring AOP事务管理 -->
    <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution(* *..service..*(..))"/>
        <aop:advisor pointcut-ref="transactionPointcut"
                     advice-ref="transactionAdvice"/>
    </aop:config>

        配置之后我们在注入@service的业务层就会有事物管理了,但是需要注意的是,我们业务层的方法名必须是配置中的指定前缀才会生效,例如:insertUserInfo等。

        另外事物生效需要异常抛到spring中,如果我们在方法中try catch中捕获异常的话,事物是不会进行回滚的。

[email protected]注解声明方式

第一步需要将xml配置引入,如果你是使用springboot的话,它已经帮你集成了,不需要手动配置。

<tx:annotation-driven />
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />    //这里的ref映射需要指定数据源
</bean>

第二步将@Transactional()注解放到需要的方法上,并且根据需要设置参数。

例如:@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)

具体的参数信息如下:

1.事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。注解中采用的属性名为 isolation

  • Isolation.DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。
  • Isolation.READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
  • Isolation.READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • Isolation.REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • Isolation.SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

 

2.事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。事物传播行为的属性名为 propagation

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED。

      这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 Propagation.NESTED是 Spring 所特有的。以 Propagation.NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。

3.事物回滚

rollbackFor:用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。

noRollbackFor:抛出 no-rollback-for 指定的异常类型,不回滚事务。

例如:@Transactional(rollbackFor=Exception.class)

四、事物管理注意事项

1.通常情况下,方法内抛出异常,则默认回滚事物。如果方法中有try cath进行捕获,则不会回滚事物。但是将异常继续向上抛,则依然会回滚事物。

[email protected]只能在public修饰的方法上使用,否则则不生效。

3.避免 Spring 的 AOP 的自调用问题,在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,也就是说在同一个类中,一个方法调用另一个使用@Transactional修饰的方法,事物将不会生效。

为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。需要将下面的 AspectJ 信息添加到 xml 配置信息中。

<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<bean
    class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"
    factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>

同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。

    <dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>4.3.2.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjrt</artifactId>
		<version>1.8.9</version>
	</dependency>
	<plugin>
		<groupId>org.codehaus.mojo</groupId>
		<artifactId>aspectj-maven-plugin</artifactId>
		<version>1.9</version>
		<configuration>
			<showWeaveInfo>true</showWeaveInfo>
			<aspectLibraries>
				<aspectLibrary>
					<groupId>org.springframework</groupId>
					<artifactId>spring-aspects</artifactId>
				</aspectLibrary>
			</aspectLibraries>
		</configuration>
		<executions>
			<execution>
				<goals>
					<goal>compile</goal>
					<goal>test-compile</goal>
				</goals>
			</execution>
		</executions>
	</plugin>