@Transtional
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
value:指定事务管理器
ropagation:事务传播行为
Isolation:事务隔离级别
timeout:事务超时时间,默认不超时(受连接池配置影响)
readOnly:是否只读
rollbackFor/rollbackForClassName:指定回滚异常
noRollbackFor/noRollbackForClassName:指定不回滚异常
propagation 事务传播行为枚举类:
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
//如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
@Transactional(propagation = Propagation.REQUIRED)
//创建一个新的事务,如果当前存在事务,则把当前事务挂起
@Transactional(propagation = Propagation.REQUIRES_NEW)
//如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
@Transactional(propagation = Propagation.SUPPORTS)
//以非事务方式运行,如果当前存在事务,则把当前事务挂起。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
//如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
@Transactional(propagation = Propagation.MANDATORY)
//如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
@Transactional(propagation = Propagation.NESTED)
//以非事务方式运行,如果当前存在事务,则抛出异常。
@Transactional(propagation = Propagation.NEVER)
isolation(隔离级别)
解决事务并发执行的问题,微观上,2个执行时间相近的事务相互影响的问题。
- 4种标准的隔离级别
隔离性在数据库的并发访问时得以体现,随着数据库并发事务处理能力的大大增强,数据库资源的利用率也会大大提高,从而提高了数据库系统的事务吞吐量,可以支持更多的用户并发访问。但并发事务处理也会带来一些问题,主要包含两方面的问题,
- 并发修改相同的数据 通过锁解决,1个用户在修改数据时其他用户想要修改必须等待
- 并发读写相同的数据如:脏读、不可重复读、幻读。这些问题是和隔离性相伴相生的。下面一一解释其含义。
- 脏读B事务正在对一条记录做修改,在这个事务提交前,这条记录的数据就处于不一致状态;这时,另一个事务A也来读取同一条记录,如果不加控制,第二个事务读取了这些未提交的(“脏”)数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫作"脏读"(Dirty Reads)。
- 不可重复读一个事务中多次执行相同的查询SQL,不同时刻读取的数据不同。要么发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫作“ 不可重复读”(Non-Repeatable Reads)。
- 幻读一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”(Phantom Reads)。
上述三个问题通过事务的隔离级别解决。
隔离级别 | 含义 | 特点 | 存在的问题 |
---|---|---|---|
READ-UNCOMMITTED | 读未提交 | 一个事务能够读取到另一个事务没有提交的数据 | 脏读、不可重复读、幻读 |
READ-COMMITTED | 读已提交 | 只能读到另外一个事务提交后的数据。 | 不可重复读、幻读 |
REPEATABLE-READ | 可重复读 | 同一个事务下,前后两次结果一致 | 不同数据库不同,有的数据库会有幻读 |
SERIALIZABLE | 序列化读 | 串行 | 性能太差 |
Oracle只支持READ-COMMITTED和SERIALIZABLE 2种隔离级别,不支持脏读,但是提出了一个只读的状态。
MySQL支持4种。
基本使用
1.在启动类加上@EnableTransactionManagement注解启用springboot事务
启动类加上@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)注解解决同一个类中没有事务的方法调用其他方法@Transactional()
不生效的问题(((XXXServiceImpl) AopContext.currentProxy()).save(User);) 该注解还可以解决类似的注解问题如@Async
2.在方法上加上事务注解
@Transactional(propagation = Propagation.MANDATORY)
public void aa(){
}
手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
手动提交和回滚事务
第一种:利用sqlSession
@Resource
private SqlSessionTemplate sqlSessionTemplate;
@Test
@SneakyThrows
public void test3(){
SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession();
Connection connection = sqlSession.getConnection();
try {
connection.setAutoCommit(false);
ReimbUnified unified = new ReimbUnified();
unified.setId("1");
unified.setBillId("1");
unified.setBillCode("1");
unified.setBudgetId("1");
unified.setIsDeleted(false);
unified.setInvoiceAmount(BigDecimal.TEN);
unified.setReimbAmount(BigDecimal.TEN);
ReimbUnifiedMapper unifiedMapper = sqlSession.getMapper(ReimbUnifiedMapper.class);
unifiedMapper.insert(unified);
connection.commit();
}catch (Exception e){
connection.rollback();
}finally {
if (connection!=null){
connection.close();
}
}
}
}
第二种:利用TransactionTemplate
@Service
public class UserServiceImpl extends ServiceImpl<BaseMapper<User>,User> implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private TransactionTemplate transactionTemplate;
@Override
public void aa(){
transactionTemplate.execute(status->{
User user = new User();
user.setId(1L);
userMapper.insert(user);
System.out.println(status.isNewTransaction());
return null;
});
}
}
第三种:利用dataSourceTransactionManager
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
public void aa(){
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
try{
// 业务代码
// 手动提交事务
dataSourceTransactionManager.commit(transaction);
} catch(Exception e) {
// 手动回滚事务
dataSourceTransactionManager.rollback(transaction);
}
}
springboot 异步事务Transactional
使用场景:
我们在处理业务时会有这样的需求:我们需要在业务中需要调用远程的RPC接口,或者调用MQ发送消息,如果一切正常那自然皆大欢喜;但是,业务中出现了异常,导致了事务回滚,但是调用远程的RPC接口,或者调用MQ的动作是无法取消的,这可能就会出现严重的问题。
解决:在事务完成且提交后调用接口
第一种方式:
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* @author wjj
*/
public class TransactionUtils {
public static void doAfterTransaction(DoTransactionCompletion doTransactionCompletion) {
if (TransactionSynchronizationManager.isActualTransactionActive()){
TransactionSynchronizationManager.registerSynchronization(doTransactionCompletion);
}
}
@Transactional
public void aa(){
TransactionUtils.doAfterTransaction(new DoTransactionCompletion(()->{
//send MQ ... RPC
}));
}
}
class DoTransactionCompletion implements TransactionSynchronization {
private Runnable runnable;
public DoTransactionCompletion(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void afterCompletion(int status) {
if (status ==TransactionSynchronization.STATUS_COMMITTED){
this.runnable.run();
}
}
}
第二种方式:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
@Override
public void afterCompletion(int status) {
}
});
使用注意事项
使用注意事项(防止事务失效)
1、一个有@Transactional的方法被没有@Transactional方法调用时,会导致Transactional作用失效
原理:@Transactional是基于动态代理对象来实现的,而在类内部的方法的调用是通过this关键字来实现的,没有经过动态代理对象,所以事务回滚失效。
2、就是在@Transactional方法内部捕获了异常,没有在catch代码块里面重新抛出异常,事务也不会回滚。
原理:看看spring的源码:
TransactionAspectSupport类里面的invokeWithinTransaction方法
3、对非public方法进行事务注解,@Transactional 将会失效。
原因:是应为在Spring AOP代理时,事务拦截器在目标方法前后进行拦截,DynamicAdvisedInterceptor的intercept 方法会获取Transactional注解的事务配置信息,
因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,这个方法会获取Transactional 注解的事务配置信息。他会首先校验事务方法的修饰符是不是public,不是 public则不会获取@Transactional 的属性配置信息。
4、在一个类中A方法被事务注释,B方法也被事务注释。
现象:执行B方法是报错,但是异常被A catch 住,此时事务也会失效。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY