springboot的事务也主要分为两大类,一是xml声明式事务,二是注解事务,注解事务也可以实现类似声明式事务的方法,关于注解声明式事务,目前网上搜索不到合适的资料,所以在这里,我将自己查找和总结的几个方法写到这里,大家共同探讨
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
xml事务
可以使用 @ImportResource(“classpath:transaction.xml”) 引入该xml的配置
xml的配置
<?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: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();
}
}
注解开启事务
1、Transactional注解事务
需要在进行事物管理的方法上添加注解@Transactional
,或者偷懒的话直接在类上面添加该注解,使得所有的方法都进行事物的管理,但是依然需要在需要事务管理的类上都添加,工作量比较大
@Transactional
public void insert(BusReceiverEntity receiverEntity) {
receiverDao.insert(receiverEntity);
throw new NullPointerException();
}
@Transactional 注解的属性介绍
propagation 属性(以下面有详细解说)
事务的传播行为,默认值为 Propagation.REQUIRED。
isolation 属性
事务的隔离级别,默认值为 Isolation.DEFAULT。
可选的值有:
- Isolation.DEFAULT 使用底层数据库默认的隔离级别。
- Isolation.READ_UNCOMMITTED
- Isolation.READ_COMMITTED
- Isolation.REPEATABLE_READ
- Isolation.SERIALIZABLE
timeout 属性
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 属性
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor 属性
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor 属性
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
2、注解声明式事务
@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,下面是各种值的解释:
- PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行
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 事务实现机制
在应用系统调用声明了 @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 之前,应该先了解它的相关属性,避免在实际项目中踩中各种各样的坑点。
常见坑点1:遇到检测异常时,事务默认不回滚。
例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到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("发生异常了..");
}
常见坑点2: 在业务层捕捉异常后,发现事务不生效。
这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,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("发生异常了..");
}