事务管理
事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。
Spring 事务管理分为编码式和声明式的两种方式。
编程式事务指的是通过编码方式实现事务;
声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。
声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。
声明式事务有两种方式
一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional
注解的方式。注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional
注解的事务管理。
1.在 xml 配置文件中添加事务配置信息。
<!-- TransactionManager定义。 -->
<bean id="transactionManager" class="com.xxx.cms.xxx.common.frame.spring.transaction.BaseTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:aspectj-autoproxy proxy-target-class="true" />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" read-only="false" />
<tx:method name="del*" propagation="REQUIRED" read-only="false"/>
<tx:method name="move*" propagation="REQUIRED" read-only="false"/>
<tx:method name="modify*" propagation="REQUIRED" read-only="false"/>
<tx:method name="update*" propagation="REQUIRED" read-only="false"/>
<tx:method name="batch*" propagation="REQUIRED" read-only="false"/>
<tx:method name="sync*" propagation="REQUIRED" read-only="false" />
<tx:method name="save*" propagation="REQUIRED" read-only="false"/>
<tx:method name="accept*" propagation="REQUIRED" read-only="false"/>
<tx:method name="capturePicBy*" propagation="NESTED" read-only="false"/>
<tx:method name="fetch*" propagation="SUPPORTS" read-only="false" />
<tx:method name="get*" propagation="SUPPORTS" read-only="false" />
<tx:method name="find*" propagation="SUPPORTS" read-only="false" />
<tx:method name="check*" propagation="SUPPORTS" read-only="false" />
<tx:method name="search*" propagation="SUPPORTS" read-only="false" />
<tx:method name="*" propagation="SUPPORTS" read-only="false" />
</tx:attributes>
</tx:advice>
<!--把事务控制在Service层 & 代表转义后的&-->
<!--1.先看bean类或方法上是否有{@link org.springframework.transaction.annotation.Transactional}注解,如果有,按照注解的方式生成代理-->
<!--2.再看bean类或方法上是否有{@link NotTransactionProxy}注解,如果有,那么将不会生成事务代理-->
<!--3.最后和{@code data-source-beans.xml}中的配置相比较,按照配置文件中的方法名,生成指定的代理-->
<aop:config>
<aop:pointcut id="pc"
expression="execution( * com.xxx.cms.xxx..*.service..*.*(..)) and
!@annotation(org.springframework.transaction.annotation.Transactional) and
!@annotation(com.xxx.cms.xxx.common.frame.spring.transaction.NotTransactionProxy) and
!@within(com.xxx.cms.xxx.common.frame.spring.transaction.NotTransactionProxy) and
!@within(org.springframework.transaction.annotation.Transactional)" />
<aop:advisor pointcut-ref="pc" advice-ref="txAdvice" />
</aop:config>
使用xml
配置的方式,需要显式的配置aop
,这点比较麻烦
但是可以从全局统一进行事务的管理,比较方便
2.采用@Transactional注解进行声明式事务的管理
相比纯xml配置,使用@Transactional 注解管理事务要简单的多。
@Transactional管理事务的实现步骤分为两步。
<bean id="transactionManager" class="com.xxx.cms.xxx.common.frame.spring.transaction.BaseTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven/>
1.首先配置事务管理器
2.使用<tx:annotation-driven/>
开启对@Transactional
的支持
除了用配置文件的方式,@EnableTransactionManagement
注解也可以启用事务管理功能。
@Transactional的属性
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
@Transactional
1.类级别
:表示该类中的所有公共方法配置相同的事务信息
2.方法级别
:表示调用该方法时会进行相应的事务管理
当类和方法同时存在@Transactional
时,方法级的会覆盖类级别的@Transactional
@Transactional的工作原理
1.调用声明@Transactional
的目标方法时,Spring Framework 默认使用 AOP
代理,在代码运行时生成一个代理对象
。
2.根据@Transactional
的属性配置信息,这个代理对象
决定该声明@Transactional
的目标方法是否由拦截器 TransactionInterceptor
来使用拦截。
3.在 TransactionInterceptor
拦截时,会在在目标方法
开始执行之前
创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器(AbstractPlatformTransactionManager
)操作数据源 DataSource 提交或回滚事务,
@Tansactional失效的情况
前面介绍了声明式事务管理的两种方式,基于注解的事务管理已经成为了主流,但是有时候使用了@Transactional
也不一定会生效
在以下几种情况下,@Transactional
会失效
1.在接口上使用@Transactional
;
spring使用的代理有两种 CglibAopProxy
和 JdkDynamicAopProxy
两种。
如果使用了Cglib
代理,则spring无法为接口生成代理对象,就无法在目标方法执行之前创建并加入事务。
2.@Transactional
应用在非 public
修饰的方法上
之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor
(事务拦截器)在目标方法执行前后进行拦截,Spring代理工厂在启动时,会扫描所有类和方法,并会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional
的属性配置信息。
3.@Transactional
注解属性 propagation
设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。
这3种都是因为以非事务的方式运行,所以事务管理失效。
4. @Transactional
注解属性rollbackFor
设置错误
Spring默认抛出了未检查unchecked异常
(继承自 RuntimeException 的异常)或者 Error才回滚
事务;其他异常不会触发回滚事务,若想spring为抛出的其他异常进行回滚,需要在rollback中进行指定。
5.同一个类中方法调用,导致@Transactional
失效
比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
6. 异常被你try…catch…了,导致@Transactional
失效
如果你在目标方法中try…catch…了异常,那么spring是感知不到的,也就不会进行回滚
7.数据库引擎不支持事务
事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了