源码地址
springboot2教程系列
springboot的事务也主要分为两大类,一是xml声明式事务,二是注解事务,注解事务也可以实现类似声明式事务的方法,关于注解声明式事务,目前网上搜索不到合适的资料,所以在这里,我将自己查找和总结的几个方法写到这里,大家共同探讨
org.springframework.boot
spring-boot-starter-aop
mysql
mysql-connector-java
5.1.47
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
com.alibaba
druid
1.1.12
可以使用 @ImportResource(“classpath:transaction.xml”) 引入该xml的配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" >property>
bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="query*" propagation="SUPPORTS" read-only="true" >tx:method>
<tx:method name="get*" propagation="SUPPORTS" read-only="true" >tx:method>
<tx:method name="select*" propagation="SUPPORTS" read-only="true" >tx:method>
<tx:method name="insert*" propagation="REQUIRED" read-only="true" >tx:method>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" >tx:method>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="allManagerMethod" expression="execution (* cn.myframe..service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="allManagerMethod" order="0" />
aop:config>
beans>
@SpringBootApplication
@ImportResource("classpath:transaction.xml")
public class TxApplication {
public static void main(String[] args){
SpringApplication app = new SpringApplication(TxApplication.class);
app.run(args);
}
}
@Configuration
public class DruidConfig {
@Bean("dataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource getDataSource(){
return new DruidDataSource();
}
}
需要在进行事物管理的方法上添加注解@Transactional
,或者偷懒的话直接在类上面添加该注解,使得所有的方法都进行事物的管理,但是依然需要在需要事务管理的类上都添加,工作量比较大
@Transactional
public void insert(BusReceiverEntity receiverEntity) {
receiverDao.insert(receiverEntity);
throw new NullPointerException();
}
@Transactional 注解的属性介绍
事务的传播行为,默认值为 Propagation.REQUIRED。
事务的隔离级别,默认值为 Isolation.DEFAULT。
可选的值有:
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
@Configuration
public class TxAnoConfig {
private static final int TX_METHOD_TIMEOUT = 5;
private static final String AOP_POINTCUT_EXPRESSION = "execution (* cn.myframe..service.*.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/*只读事务,不做更新操作*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED );
/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(
Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("delete*", requiredTx);
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
source.setNameMap( txMap );
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
return txAdvice;
}
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
或者
@Component
public class TxOtherConfig {
public static final String transactionExecution = "execution (* cn.myframe..service.*.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor transactionInterceptor() {
Properties attributes = new Properties();
attributes.setProperty("get*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("add*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("insert*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, attributes);
return txAdvice;
}
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor(){
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(transactionExecution);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
Properties attributes = new Properties();
attributes.setProperty("get*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("add*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("insert*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
attributes.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, attributes);
advisor.setAdvice(txAdvice);
return advisor;
}
}
@Transactional(propagation = Propagation.REQUIRED)
其中,Propagation有7个常量值,常用的有REQUIRED和SUPPORTS,下面是各种值的解释:
REQUIRED
类似操作。@Transactional(propagation = Propagation.REQUIRED)
public void outer(){
}
outer方法在不同事务的传播等级下的回滚(outer方法发生异常)情况如下
传播等级 | REQUIRED | SUPPORTS | MANDATORY | REQUIRES_NEW | NOT_SUPPORTED | NEVER | NESTED |
---|---|---|---|---|---|---|---|
是否回滚 | 是 | 否 | 在进入方法前报错 | 是 | 否 | 否 | 是 |
class Out{
@Transactional(propagation = Propagation.NESTED)
public void outer(){
In.inner();
......
throw new NullPointerException();
}
}
class In{
@Transactional(propagation = Propagation.REQUIRED)
public void inner(){
......
}
}
在outer方法以非事务方式执行
inner回滚情况跟情况一类似
而且inner的回滚不会导致outer的回滚,无论什么情况outer都不会回滚
在outer方法以事务方式执行,且outer发生异常的情况下
outer都会回滚
inner回滚如下表
inner的传播等级 | REQUIRED | SUPPORTS | MANDATORY | REQUIRES_NEW | NOT_SUPPORTED | NEVER | NESTED |
---|---|---|---|---|---|---|---|
是否回滚 | 是 | 是 | 是 | 否 | 否 | 执行方法前报错 | 是 |
在outer方法以事务方式执行,且inner发生异常的情况下
outer都会回滚,因为inner方法抛出异常会导致outer也抛出异常触发回滚
inner因为NOT_SUPPORTED才非事务执行所以不回滚,NEVER执行方法时会出现异常,其它的都会回滚
总结:
以事务执行的情况下以发生异常必定会回滚,非事务执行不回滚
同一个事务的情况下,任何一个方法发生异常,都会导致同一事务的所有方法回滚
不同事务的方法发生异常,不会互相影响
在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用
,是不会被 Spring 的事务拦截器拦截,就像上面的 outer
方法直接调用了同一个类中的 inner
方法,inner
方法不会被 Spring 的事务拦截器拦截。可以使用 AspectJ 取代 Spring AOP 代理来解决这个问题,但是这里暂不讨论。
在应用系统调用声明了 @Transactional
的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional
的属性配置信息,这个代理对象决定该声明 @Transactional
的目标方法是否由拦截器 TransactionInterceptor
来使用拦截,在 TransactionInterceptor
拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager
操作数据源 DataSource
提交或回滚事务。
Spring AOP 代理有 CglibAopProxy
和 JdkDynamicAopProxy
两种,以 CglibAopProxy
为例,对于 CglibAopProxy
,需要调用其内部类的 DynamicAdvisedInterceptor
的 intercept 方法。对于 JdkDynamicAopProxy
,需要调用其 invoke 方法。
正如上文提到的,事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager
来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager
的具体实现类来实现,如事务管理器 DataSourceTransactionManager
。不同的事务管理器管理不同的数据资源 DataSource
,比如 DataSourceTransactionManager
管理 JDBC 的 Connection
。
使用事务注解@Transactional 之前,应该先了解它的相关属性,避免在实际项目中踩中各种各样的坑点。
例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到SQLException(检测异常)而进行事务回滚!!
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//然后遇到故障
throw new SQLException("发生异常了..");
}
原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException及其子类)和程序错误(Error)才会进行事务回滚,显然SQLException并不属于这个范围。如果想针对检测异常进行事务回滚,可以在@Transactional 注解里使用
rollbackFor 属性明确指定异常。例如下面这样,就可以正常回滚:
@Transactional(rollbackFor = Exception.class)
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//然后遇到故障
throw new SQLException("发生异常了..");
}
这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。例如:下面这段代码直接导致增加余额的事务回滚没有生效。
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//谨慎:尽量不要在业务层捕捉异常并处理
try {
throw new SQLException("发生异常了..");
} catch (Exception e) {
e.printStackTrace();
}
}
不要小瞧了这些细节,往前暴露异常很大程度上很能够帮我们快速定位问题,而不是经常在项目上线后出现问题,却无法刨根知道哪里报错。
推荐做法:在业务层统一抛出异常,然后在控制层统一处理。
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//推荐:在业务层将异常抛出
throw new RuntimeException("发生异常了..");
}