SpringBoot基础之声明式事务和切面事务和编程式事务

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

前言

事务是一个需要同时保证原子性、隔离性、一致性和持久性的一个或多个数据库操作

本文会说明,springBoot中两种事务的实现方式,编程式事务配置和声明式事务配置还有切面事务,当然在此之前会说一些基础的东西:事务的四大特征,事务的隔离级别,事务的传播行为

事务的四大特征(ACID)

  1. 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部执行,要么均不执行.
  2. 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致.
  3. 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的.
  4. 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失.

开启事务

1
css复制代码@EnableTransactionManagement

SpringBoot声明式事务

声明式事务@Transactional可以使用在类上,也可以使用在public方法上.
如果是使用在类上,则是对所有的public方法都开启事务,如果类和方法上都有则方法上的事务生效

可以在类上使用

1
2
3
java复制代码@Transactional(rollbackFor=Exception.class)
public class TransactionServiceImpl implements TransactionService {
}

更多的是在方法上使用

1
2
3
4
less复制代码@Override
@Transactional(rollbackFor=Exception.class)
public void t1(Student one) {
}

@Transactional的参数

在使用@Transactional的时候会用到它的一些参数

参数 作用
value 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
transactionManager 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
propagation 事务的传播行为,默认值为 Propagation.REQUIRED
isolation 事务的隔离级别,默认值为 Isolation.DEFAULT
timeout 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务.
readOnly 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true.
rollbackFor 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
rollbackForClassName 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
noRollbackFor 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.
noRollbackForClassName 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.

valuetransactionManager相同
rollbackForrollbackForClassName相同
noRollbackFornoRollbackForClassName相同

事务的隔离级别(isolation)

isolation中会用到隔离级别

参数 事务隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED 读未提交(read-uncommitted)
READ_COMMITTED 不可重复读(read-committed)
REPEATABLE_READ 可重复读(repeatable-read)
SERIALIZABLE 串行化(serializable)

事务的传播行为(propagation)

propagation会用到这个传播行文

传播行为 解释
REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务.
SUPPORTS 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行.
MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常.
REQUIRES_NEW 重新创建一个新的事务,如果当前存在事务,暂停当前的事务.
NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务.
NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常.
NESTED 和REQUIRED效果一样.

SpringBoo编程式事务

在需要的地方注入TransactionTemplate

1
2
java复制代码@Autowired 
private TransactionTemplate transactionTemplate;

然后在代码中使用

1
2
3
4
5
6
7
8
typescript复制代码@Override
public final void save2() {
transactionTemplate.execute((status)->{
mapper.saveStudent(newOne());
mapper.saveStudent(newOne());
return Boolean.TRUE;
});
}

这样两个mapper.saveStudent(newOne());就在一个事务中执行了

SpringBoo切面编程式事务

此种方式基于AOP功能,所以需要添加

1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

写配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
java复制代码@Aspect
@Configuration
public class MyTransactionConfig {

/**
* 配置方法过期时间,默认-1,永不超时
*/
private final static int TX_METHOD_TIME_OUT = 10;

/**
* 全局事务位置配置 在哪些地方需要进行事务处理
* 配置切入点表达式
*/
private static final String POITCUT_EXPRESSION = "execution(* zdc.enterprise.service.impl.*.*(..))";

@Autowired
private PlatformTransactionManager platformTransactionManager;


@Bean
public TransactionInterceptor txadvice() {

/*只读事物、不做更新删除等*/
/*事务管理规则*/
RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
/*设置当前事务是否为只读事务,true为只读*/
readOnlyRule.setReadOnly(true);
/* transactiondefinition 定义事务的隔离级别;
*如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。*/
readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);

/*增删改事务规则*/
RuleBasedTransactionAttribute requireRule = new RuleBasedTransactionAttribute();
/*抛出异常后执行切点回滚 建议自定义异常*/
requireRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
/*PROPAGATION_REQUIRED:事务隔离性为1,若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 */
requireRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/*设置事务失效时间,超过10秒*/
requireRule.setTimeout(TX_METHOD_TIME_OUT);

/** 配置事务管理规则
nameMap声明具备需要管理事务的方法名.
这里使用addTransactionalMethod 使用setNameMap
*/
Map<String, TransactionAttribute> nameMap = new HashMap<>();
nameMap.put("add*", requireRule);
nameMap.put("save*", requireRule);
nameMap.put("insert*", requireRule);
nameMap.put("update*", requireRule);
nameMap.put("delete*", requireRule);
nameMap.put("remove*", requireRule);

/*进行批量操作时*/
nameMap.put("batch*", requireRule);
nameMap.put("get*", readOnlyRule);
nameMap.put("query*", readOnlyRule);
nameMap.put("find*", readOnlyRule);
nameMap.put("select*", readOnlyRule);
nameMap.put("count*", readOnlyRule);

NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.setNameMap(nameMap);

TransactionInterceptor transactionInterceptor = new TransactionInterceptor(platformTransactionManager, source);

return transactionInterceptor;
}

/**
* 设置切面=切点pointcut+通知TxAdvice
* @return
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(POITCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txadvice());
}
}

有了这个切面配置类,就不要用在类或者每个方法上使用@Transactional了,当然方法名前缀要能和设置的匹配上. RuleBasedTransactionAttribute的参数大致和@Transactional的参数相同,里面有详细的注释,就不过多解释了

END

切面事务粒度最粗,如果方法名不匹配容易漏方法,声明式事务粒度中等,但是遇到大事务就会出现问题,编程式事务粒度最细,可以考虑在特殊的时候使用它.

1
2
3
4
arduino复制代码    作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%