Spring 学习笔记之 @Transactional详解
一、数据库事务基础
数据库事务(Transaction)是数据库管理系统中用于确保数据一致性和完整性的一种机制。它是一组操作的集合,这些操作要么全部成功,要么全部失败,从而保证数据库状态的正确性。
1.1 事务的基本概念
-
定义
事务是用户定义的一个操作序列,这些操作要么全部执行,要么全部不执行。它是数据库运行的基本单位。例如,在银行转账操作中,从一个账户扣除金额和向另一个账户增加金额必须同时成功或同时失败,这就需要通过事务来保证。
- 事务的生命周期
-
开始事务:事务的执行开始,通常由用户或应用程序发起。
-
执行事务:事务中的各个操作依次执行,如插入、更新、删除等。
-
提交事务:如果事务中的所有操作都成功完成,事务被提交,所有操作对数据库的更改将永久生效。
-
回滚事务:如果事务中的某个操作失败,事务将被回滚,所有已经执行的操作都会被撤销,数据库恢复到事务开始前的状态。
1.2 事务的特性(ACID)
事务的特性是通过 ACID 原则来保证的,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
1. 原子性(Atomicity)
-
原子性是指事务中的所有操作要么全部成功,要么全部失败。事务是一个不可分割的最小执行单位。例如,在一个订单系统中,创建订单和扣款操作是一个事务。如果扣款成功但创建订单失败,那么整个事务会回滚,扣款操作也会被撤销,以保证系统的状态不会出现部分操作成功的情况。
2. 一致性(Consistency)
-
一致性是指事务执行前后,数据库从一个一致的状态转换到另一个一致的状态。事务必须保证数据库的完整性约束没有被破坏。例如,在一个库存管理系统中,库存数量不能为负。如果一个事务试图将库存数量减少到负数,那么这个事务应该被回滚,以保证数据库的一致性。
3. 隔离性(Isolation)
-
隔离性是指多个并发事务之间是相互隔离的,一个事务的执行不会受到其他事务的干扰。数据库系统提供了不同的隔离级别来控制事务之间的隔离程度。常见的隔离级别包括:
-
读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务未提交的数据。这种情况下可能会出现脏读(dirty read),即读取到其他事务未提交的错误数据。
-
读已提交(Read Committed):一个事务只能读取到其他事务已经提交的数据,避免了脏读。但可能会出现不可重复读(non-repeatable read),即在同一个事务中,多次读取同一数据可能得到不同的结果。
-
可重复读(Repeatable Read):保证在同一个事务中,多次读取同一数据的结果是一致的。但可能会出现幻读(phantom read),即在同一个事务中,查询满足某个条件的记录时,可能会出现新插入的记录。
-
可串行化(Serializable):最高的隔离级别,事务之间完全隔离,按照串行的顺序执行,避免了脏读、不可重复读和幻读。但这种隔离级别会带来较大的性能开销。
-
4. 持久性(Durability)
-
持久性是指事务一旦提交,其对数据库的更改将永久生效,即使系统发生故障也不会丢失。数据库系统通常通过日志(log)来保证持久性。当事务提交时,数据库会将事务的操作记录到日志中,即使系统崩溃,也可以通过日志恢复数据。
1.3 事务的并发控制
在多用户环境中,多个事务可能会同时对数据库进行操作,这就需要并发控制机制来保证事务的隔离性和一致性。常见的并发控制方法包括:
1. 锁机制
-
共享锁(Shared Lock,S锁):当一个事务对数据加上共享锁后,其他事务可以读取该数据,但不能修改它。多个事务可以同时对同一数据加共享锁。
-
排他锁(Exclusive Lock,X锁):当一个事务对数据加上排他锁后,其他事务不能对该数据加任何锁,即不能读取也不能修改。排他锁用于写操作,保证数据的独占访问。
-
锁的粒度可以是行级锁、表级锁或数据库级锁。行级锁的粒度最小,锁的冲突概率较低,但管理开销较大;表级锁的粒度较大,锁的冲突概率较高,但管理开销较小。
2. 乐观锁和悲观锁
-
悲观锁(Pessimistic Locking):假设冲突很可能会发生,因此在事务开始时就对数据加锁,直到事务结束才释放锁。悲观锁适用于写操作较多的场景,但可能会导致锁的冲突和等待。
-
乐观锁(Optimistic Locking):假设冲突较少发生,因此在事务开始时不加锁,只有在提交时才检查是否有冲突。如果发现冲突,则回滚事务。乐观锁通常通过版本号(Version Number)或时间戳(Timestamp)来实现。乐观锁适用于读操作较多的场景,可以减少锁的开销。
1.4 事务的实现机制
数据库系统通过日志(log)和回滚段(rollback segment)等机制来实现事务的特性。
1. 日志(Log)
-
日志记录了事务对数据库的所有操作,包括修改操作的前值和后值。当事务提交时,数据库会将日志写入磁盘,以保证持久性。如果系统发生故障,可以通过日志恢复数据。日志的写入顺序与事务的执行顺序一致,因此可以保证事务的原子性和持久性。
2. 回滚段(Rollback Segment)
-
回滚段用于存储事务执行过程中数据的旧值。当事务回滚时,数据库可以从回滚段中恢复数据到事务开始前的状态。回滚段的大小和数量会影响事务的性能和并发能力。
1.5 事务的使用
在实际的数据库应用中,事务的使用通常由应用程序通过 SQL 语句来控制。
1. 显式事务
-
显式事务是指用户明确地定义事务的开始和结束。例如,在 SQL 中可以使用以下语句:
-- 开始事务 BEGIN TRANSACTION;-- 执行事务中的操作 INSERT INTO table_name (column1, column2) VALUES (value1, value2); UPDATE table_name SET column1 = value1 WHERE condition;-- 提交事务 COMMIT;
如果事务中的某个操作失败,可以通过以下语句回滚事务:
ROLLBACK;
2. 隐式事务
-
隐式事务是指数据库系统自动为每个单独的 SQL 语句启动一个事务。如果语句成功执行,则事务自动提交;如果语句失败,则事务自动回滚。隐式事务适用于简单的数据库操作,但对于复杂的业务逻辑,显式事务更能保证事务的完整性和一致性。
二、@Transactional介绍
@Transactional
是 Spring 中用于声明式事务管理的核心注解。它允许开发者通过简单的注解方式,将事务管理逻辑与业务逻辑分离,从而简化事务的管理。
2.1 作用
@Transactional
注解用于声明事务的边界。它可以让 Spring 容器在方法执行前后自动管理事务的开启、提交和回滚。具体来说:
-
开启事务:在方法执行前,Spring 会创建一个新的事务(或加入已有的事务)。
-
提交事务:如果方法正常执行完成,Spring 会提交事务。
-
回滚事务:如果方法抛出异常,Spring 会根据配置决定是否回滚事务。
2.2 使用场景
@Transactional
通常用于服务层(@Service
注解的类)/ 数据库访问层(@Repository注解的类) 的方法上,因为事务管理通常与业务逻辑密切相关。例如:
@Service
public class UserService {@Transactionalpublic void updateUser(User user) {// 更新用户信息userRepository.save(user);}
}
2.3. 常见属性
@Transactional代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";String[] label() default {};Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default -1;String timeoutString() default "";boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};
}
@Transactional
注解提供了多个属性,用于配置事务的行为。以下是一些常用的属性:
2.3.1 value/transactionManager
value和transactionManager属性可以用来指定使用的事务管理器。
value: 这个属性用于指定事务管理器的名称。当你的应用中配置了多个事务管理器时,可以通过value属性来指定使用哪一个。如果只有一个事务管理器,可以省略这个属性。
transactionManager: 这个属性与value属性的作用相同,都是用于指定事务管理器的名称。通常情况下,value和transactionManager可以互换使用,但transactionManager属性更明确地表达了其用途。
假设你的应用中配置了两个事务管理器,分别是transactionManagerA和transactionManagerB,你可以这样使用@Transactional注解:
@Service
public class MyService {@Transactional("transactionManagerA")public void methodA() {// 使用 transactionManagerA}@Transactional(transactionManager = "transactionManagerB")public void methodB() {// 使用 transactionManagerB}
}
默认事务管理器: 如果没有指定value或transactionManager,Spring会使用默认的事务管理器。默认的事务管理器通常是第一个被定义的事务管理器。
事务管理器的配置: 确保在Spring配置中正确配置了事务管理器,并且名称与@Transactional注解中的指定名称一致。
通过合理使用value或transactionManager属性,可以灵活地控制不同方法或类使用不同的事务管理器,从而更好地管理事务。
2.3.2 propagation
(事务传播行为)
在 Spring 中,@Transactional
注解的 propagation
属性用于定义事务传播行为,它决定了当一个事务方法被另一个事务方法调用时,事务应该如何处理。
后面我们描述外层的Transaction为父事务,内层被调用的事务为子事务。
1. Propagation.REQUIRED
-
描述:这是
@Transactional
注解的默认传播行为。如果当前存在事务,方法将加入该事务;如果当前没有事务,会创建一个新事务。 -
示例场景:多个业务操作需要在同一个事务中完成,保证数据的一致性。比如在一个订单处理服务中,创建订单和扣减库存的操作需要在同一个事务里,若其中一个操作失败,整个事务回滚。
@Service
public class OrderService {@Autowiredprivate InventoryService inventoryService;@Transactional(propagation = Propagation.REQUIRED)public void createOrder() {// 创建订单的业务逻辑inventoryService.reduceInventory();// 其他业务逻辑}
}@Service
public class InventoryService {@Transactional(propagation = Propagation.REQUIRED)public void reduceInventory() {// 扣减库存的业务逻辑}
}
当调用 OrderService
的 createOrder
方法时,如果当前没有事务,会创建一个新事务。调用 InventoryService
的 reduceInventory
方法时,由于当前存在事务,reduceInventory
方法会加入到这个事务中。若在任何一个方法中出现异常,整个事务会回滚。
结论:
1)Propagation.REQUIRED 子事务任何一个失败回滚,所有事务都会回滚。
2)Propagation.REQUIRED 父事务失败回滚,所有子事务都会回滚。
2. Propagation.NESTED
-
描述:如果当前存在事务,在嵌套事务中执行;如果当前没有事务,和
REQUIRED
一样创建新事务。嵌套事务是当前事务的子事务,有自己的保存点。当嵌套事务回滚时,不会影响外部事务,但外部事务回滚时,嵌套事务也会回滚。 -
示例场景:某些操作可以独立回滚,但又依赖于外部事务的上下文。例如在批量处理数据时,部分数据处理失败可以只回滚这部分操作,而不影响其他数据的处理。
-
注意:需要数据库支持保存点(如 MySQL InnoDB、Oracle)。
@Service
public class BatchService {@Autowiredprivate SubBatchService subBatchService;@Transactional(propagation = Propagation.REQUIRED)public void batchProcess() {try {subBatchService.subProcess();} catch (Exception e) {// 处理子批量处理异常}// 其他批量处理逻辑}
}@Service
public class SubBatchService {@Transactional(propagation = Propagation.NESTED)public void subProcess() {// 子批量处理逻辑throw new RuntimeException("子批量处理异常");}
}
当调用 BatchService
的 batchProcess
方法时会创建一个事务,调用 SubBatchService
的 subProcess
方法时会创建一个嵌套事务。subProcess
抛出异常时,subProcess
中的操作会回滚,但 batchProcess
中的其他操作不受影响。
结论:
1)Propagation.NESTED 子事务失败回滚,不影响父事务的状态。
2)Propagation.NESTED 父事务失败回滚,所有子事务都会回滚。
3. Propagation.REQUIRES_NEW
-
描述:无论当前是否存在事务,都会创建一个新事务,并挂起当前事务(如果存在)。新事务和当前事务相互独立,一个事务的回滚或提交不会影响另一个事务。
-
示例场景:当某个操作需要独立于外部事务时使用,比如记录日志操作,即使主业务事务失败,日志记录也应该保存。
@Service
public class MainService {@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.REQUIRED)public void mainOperation() {try {logService.recordLog();} catch (Exception e) {// 处理日志记录异常}// 主业务逻辑throw new RuntimeException("主业务异常");}
}@Service
public class LogService {@Transactional(propagation = Propagation.REQUIRES_NEW)public void recordLog() {// 记录日志的业务逻辑}
}
调用 MainService
的 mainOperation
方法时会创建一个事务,调用 LogService
的 recordLog
方法时会挂起 mainOperation
的事务,创建一个新事务。mainOperation
抛出异常时,主业务事务回滚,但日志记录事务不受影响。
结论:
1)Propagation.REQUIRES_NEW 子事务失败回滚,不影响父事务的状态。
2)Propagation.REQUIRES_NEW 父事务失败回滚,不影响所有子事务状态。
4.Propagation.SUPPORTS
-
描述:如果当前存在事务,方法将加入该事务;如果当前没有事务,方法将以非事务方式执行。
-
示例场景:某些查询操作可以选择在事务中执行以保证数据的一致性,也可以在非事务环境下执行以提高性能。
@Service
public class QueryService {@Transactional(propagation = Propagation.SUPPORTS)public List<Product> getProducts() {// 查询产品列表的业务逻辑return null;}
}
如果调用 getProducts
方法时存在事务,它会加入该事务;如果不存在事务,它会以非事务方式执行。
5. Propagation.NOT_SUPPORTED
-
描述:方法将以非事务方式执行,如果当前存在事务,会挂起当前事务。
-
示例场景:一些不需要事务管理的操作,如读取配置信息等,可以避免事务带来的开销。
@Service
public class ConfigService {@Transactional(propagation = Propagation.NOT_SUPPORTED)public String getConfig() {// 获取配置信息的业务逻辑return null;}
}
如果在事务环境中调用 getConfig
方法,当前事务会被挂起,getConfig
方法以非事务方式执行。
6. Propagation.MANDATORY
-
描述:如果当前存在事务,方法将加入该事务;如果当前没有事务,会抛出
IllegalTransactionStateException
异常。 -
示例场景:确保方法必须在一个已存在的事务中执行,例如一些数据更新操作依赖于外部事务的上下文。
@Service
public class UpdateService {@Transactional(propagation = Propagation.MANDATORY)public void updateData() {// 更新数据的业务逻辑}
}
如果在没有事务的情况下调用 updateData
方法,会抛出异常。
7. Propagation.NEVER
-
描述:方法以非事务方式执行,如果当前存在事务,会抛出
IllegalTransactionStateException
异常。 -
示例场景:确保方法不应该在事务中执行,例如一些简单的计算操作。
@Service
public class CalculationService {@Transactional(propagation = Propagation.NEVER)public int calculate(int a, int b) {return a + b;}
}
如果在事务环境中调用 calculate
方法,会抛出异常。
2.3.3 isolation
(事务隔离级别)
指定事务的隔离级别,控制当前事务与其他事务之间的隔离程度。默认值为 Isolation.DEFAULT
,即数据库默认的隔离级别。(即数据库隔离性里面的具体分类)
隔离级别 | 描述 |
---|---|
DEFAULT | 数据库默认的隔离级别。 |
READ_UNCOMMITTED | 读未提交,允许并发事务读取未提交的数据(可能出现脏读)。 |
READ_COMMITTED | 读已提交,允许并发事务读取已提交的数据。 |
REPEATABLE_READ | 可重复读,保证在同一个事务中多次读取同一数据的结果是一致的。 |
SERIALIZABLE | 串行化,最高级别的隔离,完全隔离并发事务。 |
2.3.4 timeout
(事务超时时间)
指定事务的超时时间(以秒为单位)。如果事务执行时间超过指定值,事务将被回滚。默认值为 -1
,表示使用数据库默认的超时时间。
2.3.5 readOnly
(只读事务)
指定事务是否为只读事务。如果设置为 true
,则事务不会修改数据,从而可以提高性能。默认值为 false
。
2.3.6 rollbackFor
(回滚异常类)
指定哪些异常会导致事务回滚。默认情况下,Error子类和运行时异常(RuntimeException
及其子类)会导致事务回滚,而检查型异常不会导致事务回滚。可以通过该属性指定额外的异常类型。
2.3.7 noRollbackFor
(不回滚异常类)
指定哪些异常不会导致事务回滚。即使这些异常是运行时异常,事务也不会回滚。
2.4. 使用方式
@Transactional
可以作用于类或方法上:
2.4.1 作用于方法
@Service
public class UserService {@Transactionalpublic void updateUser(User user) {// 更新用户信息userRepository.save(user);}
}
2.4.2 作用于类
如果将 @Transactional
作用于类上,则该类的所有方法都将默认使用相同的事务配置。
@Transactional
@Service
public class UserService {public void updateUser(User user) {// 更新用户信息userRepository.save(user);}public void deleteUser(Long id) {// 删除用户userRepository.deleteById(id);}
}
2.5 @Transactional面试问题
1)@Transactional propagation REQUIRED/NETESD/REQUIRES_NEW的区别?
2)propagation = REQUIRED, 子事务失败的情况。看看下面代码的执行结果是什么?
@Service
public class OrderService {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {// Propagation.REQUIREDorderRepository.createOrder(order);try {// Propagation.REQUIREDinventoryRepository.deductStock(order.getProductId(), order.getQuantity());} catch (Exception e) {log.error("扣减库存失败", e);}}
}@Repository
public class OrderRepository {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {orderMapper.insert(order);}
}@Repository
public class InventoryRepository {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void deductStock(Long productId, Integer quantity) {inventoryMapper.deductStock(productId, quantity);throw new RuntimeException("出现未知错误");}}
OrderRepository.createOrder, InventoryRepository.deductStock, OrderService.createOrder的事务有哪些能成功提交,为什么?
三个事务都不能成功提交,因为InventoyRepository.deductStock事务因为异常会回滚,导致外层事务失败,然后所有事务都会回滚。
详细日志如下:
2025-04-19T10:11:57.650+08:00 INFO 35024 --- [nio-8080-exec-1] com.example.controller.OrderController : 创建订单:Order(id=null, orderNumber=order-004, productId=1001, quantity=100, totalAmount=180, createdAt=2025-04-18T21:50:16)
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
JDBC Connection [HikariProxyConnection@1303670389 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e79d43] will be managed by Spring
==> Preparing: INSERT INTO orders (order_number, product_id, quantity, total_amount) VALUES (?, ?, ?, ?)
==> Parameters: order-004(String), 1001(Long), 100(Integer), 180(BigDecimal)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868] from current transaction
==> Preparing: UPDATE inventory SET stock_quantity = stock_quantity - ? WHERE product_id = ?
==> Parameters: 100(Integer), 1001(Long)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
2025-04-19T10:11:57.754+08:00 ERROR 35024 --- [nio-8080-exec-1] com.example.service.OrderService : 扣减库存失败java.lang.RuntimeException: 出现未知错误
at com.example.repository.InventoryRepository.deductStock(InventoryRepository.java:31) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]
at com.example.repository.InventoryRepository$$SpringCGLIB$$0.deductStock(<generated>) ~[classes/:na]
at com.example.service.OrderService.createOrder(OrderService.java:33) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]
at com.example.service.OrderService$$SpringCGLIB$$0.createOrder(<generated>) ~[classes/:na]
at com.example.controller.OrderController.createOrder(OrderController.java:21) ~[classes/:na]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
2025-04-19T10:11:57.773+08:00 ERROR 35024 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only] with root causeorg.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:938) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:754) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:698) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:416) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]
at com.example.service.OrderService$$SpringCGLIB$$0.createOrder(<generated>) ~[classes/:na]
at com.example.controller.OrderController.createOrder(OrderController.java:21) ~[classes/:na]
3)progation = NESTED, 子事务失败的情况。以下代码的执行结果?
@Service
public class OrderService {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {// Propagation.NESTEDorderRepository.createOrder(order);try {// Propagation.NESTEDinventoryRepository.deductStock(order.getProductId(), order.getQuantity());} catch (Exception e) {log.error("扣减库存失败", e);}}
}@Repository
public class OrderRepository {...@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)public void createOrder(Order order) {orderMapper.insert(order);}
}@Repository
public class InventoryRepository {...@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)public void deductStock(Long productId, Integer quantity) {inventoryMapper.deductStock(productId, quantity);throw new RuntimeException("出现未知错误");}}
OrderRepository.createOrder, InventoryRepository.deductStock, OrderService.createOrder的事务有哪些能成功提交,为什么?
OrderRepository.createOrder 成功提交
InventoryRepository.deductStock 失败回滚
OrderService.createOrder 成功提交
因为NESTED标记的事务失败不会影响外层事务的结果。
相关文章:
Spring 学习笔记之 @Transactional详解
一、数据库事务基础 数据库事务(Transaction)是数据库管理系统中用于确保数据一致性和完整性的一种机制。它是一组操作的集合,这些操作要么全部成功,要么全部失败,从而保证数据库状态的正确性。 1.1 事务的基本概念 定…...
绕过UI的cooke和token的验证
很多的网站都在登录页面加入了识别文字,识别图片,拖动拼图的验证码方式来防止爬虫、恶意注册等,如果是做自动化,需要绕过 验证码才能进入下一步操作。 方案一、测试环境去除验证码 :最轻松的方法,找开发帮忙…...
2024-04-19| Java: Documented注解学习 JavaDoc
在 Java 中,Documented 是一个元注解(meta-annotation),用于标记其他注解,表明这些注解应该被包含在 JavaDoc 文档中。以下是关于 Documented 注解的作用的简要说明: 作用 记录注解信息到 JavaDoc&#x…...
09-DevOps-Jenkins实现CI持续集成
前面已经把harbor搭建好了,也可以向harbor中推送自定义镜像。 原计划是在Jenkins这台服务器上,完成镜像构建,然后把镜像推送的harbor仓库中。现在改变计划了,Jenkins所在的服务器(192.168.1.10)不负责镜像…...
Java中包装类和泛型
包装类和泛型 包装类装箱和拆箱 泛型泛型的概念泛型的使用泛型的上界 通配符通配符概念通配符上界通配符下界 前言 在Java中,由于基本类型不是继承⾃Object,为了在泛型中可以⽀持基本类型,Java给每个基本类型都对应了⼀个包装类型,…...
小程序 GET 接口两种传值方式
前言 一般 GET 接口只有两种URL 参数和路径参数 一:URL 参数(推荐方式) 你希望请求: https://serve.zimeinew.com/wx/products/info?id5124接口应该写成这样,用 req.query.id 取 ?id5124: app.get(&…...
8、表单控制:预言水晶球——React 19 复杂表单处理
一、水晶球的预言本质 "每个表单都是时空裂缝中的预言容器,"占卜课教授特里劳妮凝视着水晶球,"React-Hook-Form与Formik的融合,让数据捕获如同捕捉未来碎片!" ——以魔法部神秘事务司的预言厅为隐喻…...
Android studio开发——room功能实现用户之间消息的发送
文章目录 1. Flask-SocketIO 后端代码后端代码 2. Android Studio Java 客户端代码客户端代码 3. 代码说明 SocketIO基础 1. Flask-SocketIO 后端代码 后端代码 from flask import Flask, request from flask_socketio import SocketIO, emit import uuidapp Flask(__name_…...
【测试文档】项目测试文档,测试管理规程,测试计划,测试文档模版,软件测试报告书(Word)
原件获取列表: 系统测试方案-2.docx B-Web安全服务渗透测试模板.docx 压力测试报告.docx安全测试用例及解析.docx 测试计划.doc 测试需求规范.doc 测试需求指南.docx 测试用例设计白皮.doc 单元测试报告模板.doc 单元测试计划模板.doc 回归测试指南.doc 集成测试报…...
将 DeepSeek 集成到 Spring Boot 项目实现通过 AI 对话方式操作后台数据
文章目录 项目简介GiteeMCP 简介环境要求项目代码核心实现代码MCP 服务端(批量注册 Tool)MCP 客户端(调用 DeepSeek) DeepSeek APIDockersse 连接http 连接 Cherry Studio配置模型配置 MCP调用 MCP 项目简介 在本项目中ÿ…...
接口自动化 ——fixture allure
一.参数化实现数据驱动 上一篇介绍了参数化,这篇 说说用参数化实现数据驱动。在有很多测试用例的时候,可以将测试用例都存储在文件里,进行读写调用。本篇主要介绍 csv 文件和 json 文件。 1.读取 csv 文件数据 首先创建 csv 文件ÿ…...
Datawhale AI春训营学习笔记
数据竞赛Baseline代码全解析:从数据加载到结果输出 一、环境配置与数据加载 1.1 依赖库导入 from netCDF4 import Dataset # 处理气象.nc格式数据 import numpy as np import pandas as pd from sklearn.model_selection import KFold from sklearn.metrics imp…...
关于学习STM32的C语言的知识
数据类型 关键字位数表示范围stdint关键字char8 -128 ~ 127 int8_tunsigned char8 0 ~ 255 uint8_tshort16 -32768 ~ 32767 int16_tunsigned short16 0 ~ 65535 uint16_tint32 -2147483648 ~ 2147483647 int32_t unsigned int32 0 ~ 429496729 uint32_t long32 -2147483648 ~…...
day28 学习笔记
文章目录 前言一、图像添加水印1.ROI操作2.添加水印 二、图像去除噪声1.均值滤波2.方框滤波3.高斯滤波4.中值滤波5.双边滤波6.总结 前言 通过今天的学习,我掌握了OpenCV中有关图像水印以及图像去除噪声的原理以及相关操作 一、图像添加水印 1.ROI操作 ROI操作即之…...
第34讲|遥感大模型对比实战:SAM vs. CLIP vs. iSAM
目录 🔍 一、遥感大模型简要介绍 1️⃣ SAM(Segment Anything Model) 2️⃣ CLIP(Contrastive Language–Image Pretraining) 3️⃣ iSAM(Improved SAM for Remote Sensing) 🧪 二、实战数据集与任务设计 🌟 任务设置: 🧠 三、代码实现片段(以 Python 为…...
EAGLE代码研读+模型复现
要对代码下手了,加油(ง •_•)ง 作者在他们自己的设备上展现了推理的评估结果,受第三方评估认证,EAGLE为目前最快的投机方法(虽然加速度是评估投机解码方法的主要指标,但其他点也值得关注。比如PLD和Lookahead无需额…...
多线程使用——线程安全、线程同步
一、线程安全 (一)什么是线程安全问题 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全的问题。 (二)用程序摹拟线程安全问题 二、线程同步 (一)同步思想概述 解决线…...
基于 Linux 环境的办公系统开发方案
基于 Linux 环境的办公系统开发方案 一、项目概述 1.1 项目背景 在当今数字化办公的时代,高效、稳定且功能丰富的办公系统对于企业和组织的日常运营至关重要。Linux 作为一种开源、稳定且高度可定制的操作系统,拥有庞大的开发者社区和丰富的软件资源&…...
mysql8.0.17以下驱动导致mybatis blob映射String乱码问题分析与解决
mysql8.0.17以下驱动导致blob映射String乱码问题分析与解决 一、问题复现二、问题深究三、解决方法方法1方法2 一、问题复现 1、docker安装mysql8.0,并创建测试数据库及测试数据表 CREATE DATABASE test DEFAULT CHARACTER SET utf8mb4; use test; CREATE TABLE t…...
Unity Nav Mesh导航系统的简单使用
标题 1.下载。2.面板位置3.object面板4.Area面板5.Bake面板6.Agent面板7.Nav Mesh Agent组件8.Nav Mesh Obstacle组件9.简单使用 1.下载。 unity2022以上版本要去packageManager中下载。 2.面板位置 3.object面板 Navigation Static:设置该物体是否被列入静态寻路…...
从零开始学A2A五:A2A 协议的安全性与多模态支持
A2A 协议的安全性与多模态支持 一、A2A 协议安全机制 1. 认证机制 A2A 协议采用多层次认证机制,确保智能体身份的真实性和通信的安全性。 基于 Agent Card 的身份认证: {"agent_id": "secure_agent_001","authentication&…...
PyTorch源码编译报错“fatal error: numpy/arrayobject.h: No such file or directory”
记录一下这个bug的fix过程 一开始以为是版本问题,尝试了几个不同版本都不可以,遂排除版本问题的可能 定位 首先 pip list 看到确实安装了这个库 接着 pip show 查看 numpy 库的安装路径 numpy/arrayobject.h 是 NumPy 的 C-API 头文件,其…...
[Java EE] Spring AOP 和 事务
目录 1. AOP 1.1 AOP 概念 1.2 AOP 核心概念 1.3 AOP 作用 2. AOP 详解 2.1 切点(Pointcut) 2.2 连接点(Join Point) 2.3 通知(Advice) 2.4 切面(Aspect) 2.5 通知类型 2.5.1 Around 环绕通知 2.5.2 Before 前置通知 2.5.3 After 后置通知 2.5.4 AfterReturning …...
2025年KBS SCI1区TOP:增强天鹰算法EBAO,深度解析+性能实测
目录 1.摘要2.天鹰算法AO原理3.改进策略4.结果展示5.参考文献6.代码获取 1.摘要 本文提出了增强二进制天鹰算法(EBAO),针对无线传感器网络(WSNs)中的入侵检测系统(IDSs)。由于WSNs的特点是规模…...
适合IIS部署网页应用的编程语言
直接部署在 IIS 上的语言/技术 PHP 使用 FastCGI 模块直接集成安装 PHP Manager for IIS 可简化配置示例配置:在 web.config 中添加处理程序映射指向 php-cgi.exe Node.js 使用 iisnode 模块实现直接集成允许 Node.js 应用在 IIS 进程中运行支持进程管理、负载均衡…...
43.[前端开发-JavaScript高级]Day08-ES6-模板字符串-展开运算符-ES7~ES11
ES6~ES13新特性(二) 1 模板字符串的详解 字符串模板基本使用 标签模板字符串使用 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content…...
边生成边训练:构建合成数据驱动的在线训练系统设计实战
目录 边生成边训练:构建合成数据驱动的在线训练系统设计实战 一、系统目标与能力总览 ✅ 核心目标: 二、系统架构图(推荐用于PPT展示) 三、关键模块设计解析 ✅ 1. 合成数据生成器模块 ✅ 2. 质量评分器模块 ✅ 3. 在线训…...
AF3 create_alignment_db_sharded脚本main函数解读
AlphaFold3 create_alignment_db_sharded 脚本在源代码的scripts/alignment_db_scripts文件夹下。 该脚本中的 main 函数是整个对齐数据库生成脚本的核心入口,它 orchestrates(编排)了所有流程,从读取链目录到生成 .db 文件、构建索引、处理重复链、写入最终索引文件。 ma…...
52. Java 类和对象 - 什么是隐藏字段?
文章目录 52. Java 类和对象 - 什么是隐藏字段?🎯 参数名称的作用与规则✅ 参数名称的命名规则 🎯 什么是隐藏字段?🚨 问题定义✅ 解决办法:使用 this 关键字 🎯 如何避免隐藏字段带来的困扰&am…...
IntelliJ IDEA右键快捷方式设置方法
IntelliJ IDEA右键快捷方式设置方法 在 IntelliJ IDEA 中设置右键快捷方式快速打开项目或文件(Windows 系统),可以通过以下方法实现: 方法 1:通过注册表添加右键菜单(推荐) 打开注册表编辑器 按…...
深入剖析JavaScript内存泄漏:识别、定位与实战解决
在JavaScript的世界里,开发者通常不必像使用C那样手动管理内存的分配和释放,这得益于JavaScript引擎内置的垃圾回收(Garbage Collection, GC)机制。然而,这并不意味着我们可以完全忽视内存管理。“自动"不等于&qu…...
JVM原理与实战
一、Java虚拟机概述 java程序通过虚拟机实现了java代码的跨平台。 二、java虚拟机运行过程: 类编译器编译java代码为class文件, 类加载器将class文件加载到jvm, 程序计数器控制程序的执行, 虚拟机栈存放局部变量,方法名…...
MCP协议用到的Node.js 和 npm npx
一、Node.js 与 npm、npx 的介绍 Node.js:是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,使您能够在服务器端运行 JavaScript 代码。它广泛用于构建服务器端应用程序和工具。 npm(Node Package Manager):是 Nod…...
如何写 commit message?
前言 每次写 commit message 时,都会纠结用什么动词,格式应该什么样,所以决定总结一下。 查了一下,还是挺复杂的。因为只面向我个人日常使用,所以只进行一些简单的、适合我的总结。 正文 message 分为两部分&#…...
【厦门大学】DeepSeek大模型赋能高校教学和科研
DeepSeek赋能高校教学和科研 引言人工智能发展简史:从图灵测试到大模型时代大模型核心技术解析:构筑智能金字塔DeepSeek赋能高校:打造智能校园生态本地部署方案:安全、高效与定制化兼得教学革新:重塑知识传授与学习体验…...
【专刷】滑动窗口(一)
📝前言说明: 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录,按专题划分每题主要记录:(1)本人解法 本人屎山代码;(2)优质解法 优质代码;ÿ…...
cJSON_Print 和 cJSON_PrintUnformatted的区别
cJSON_Print 和 cJSON_PrintUnformatted 是 cJSON 库中用于将 cJSON 对象转换为 JSON 字符串的两个函数,它们的区别主要在于输出的格式: 1. cJSON_Print 功能:将 cJSON 对象转换为格式化的 JSON 字符串。 特点: 输出的 JSON 字符…...
C 语 言 --- 指 针 4(习 题)
C 语 言 --- 指 针 4(习 题) sizeofstrlen整 型 数 组 - - - int a[ ]字 符 数 组 - - - char arr[ ]字 符 数 组 - - - char arr1[ ]字 符 串 常 量 指 针 - - - char arr[ ]二 维 数 组 - - - char arr[3][4]总结 💻作 者 简 介:…...
可发1区的超级创新思路(python 、MATLAB实现):基于区域注意力双通道MABMA的时间序列预测模型
首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 应用领域:功率预测、故障诊断、流量预测等领域! 目录 首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 应用领域:功率预测、故障诊断、流…...
可发1区的超级创新思路(python 、MATLAB实现):基于多尺度注意力TCN-KAN与小波变换的时间序列预测模型
一、数学模型与原理 1.1 小波变换多尺度分解 输入功率序列 x(t) 经小波变换分解为近似系数 Aj 与细节系数 Dj: 1.2 多尺度TCN特征提取 对每个尺度子序列 {A3,D3,D2,D1} 采用独立TCN: 式中 ∗d 为扩张率 d=2l 的扩张卷积,Wd 为可学习参数。 1.3 多尺度注…...
PyTorch `flatten()` 和 `squeeze()` 区别
PyTorch flatten() 和 squeeze() 区别 在 PyTorch 里,flatten() 和 squeeze(0) 是两个不同的张量操作, 1. flatten() 方法 flatten() 方法用于把一个多维张量展开成一维张量。它会将张量里的所有元素按顺序排列成一个一维序列。 语法 torch.flatten(input, start_dim=...
使用Java基于Geotools的SLD文件编程式创建与磁盘生成实战
前言 在地理信息系统(GIS)领域,地图的可视化呈现至关重要,而样式定义语言(SLD)文件为地图元素的样式配置提供了强大的支持。SLD 能够精确地定义地图图层中各类要素(如点、线、面、文本等&#x…...
opencv练习
1.创建一个 PyQt 应用程序,该应用程序能够: (1)使用 OpenCV 加载一张图像。 (2)在 PyQt 的窗口中显示这张图像。 (3)提供四个按钮(QPushButton)࿱…...
opencv--基础
opencv OpenCV是一个实现数字图像处理和计算机视觉通用算法的开源跨平台库。 链接 opencv中的cv是什么意思 在OpenCV中,"cv" 是 "Computer Vision"(计算机视觉) 的缩写。 opencv的实现语言 opencv的底层实现代码是使…...
基于模板匹配的信用卡号码识别系统
本项目实现了一个基于模板匹配的信用卡号码识别系统。 1. 导入库和设置参数 # -*- coding: utf-8 -*- # 导入工具包 from imutils import contours import numpy as np import argparse import cv2 import myutils import os# 设置参数 ap argparse.ArgumentParser() # 替换…...
Spring Boot中Excel处理完全指南
文章目录 1. Excel处理基础知识1.1 为什么需要在应用中处理Excel文件?1.2 Java中的Excel处理库介绍1.2.1 Apache POI1.2.2 EasyExcel1.2.3 JExcel1.2.4 Apache POI SXSSF1.3 Spring Boot中集成Excel处理2. 在Spring Boot中集成Excel处理库2.1 集成Apache POI2.1.1 添加依赖2.1…...
洛谷P1312 [NOIP 2011 提高组] Mayan 游戏
题目 #算法/进阶搜索 思路: 根据题意,我们可以知道,这题只能枚举,剪枝,因此,我们考虑如何枚举,剪枝. 首先,我们要定义下降函数down(),使得小木块右移时,能够下降到最低处,其次,我们还需要写出判断函数,判断矩阵内是否有小木块没被消除.另外,我们还需要消除函数,将矩阵内三个相连…...
c++ (异常)
1.异常的概念及使用 1.1异常的概念 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理, 异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后 解决问题的任务传…...
MySQL如何实现行行比较
概述 在MySQL中实现行行比较通常涉及比较同一表或不同表中不同行的数据。以下是几种常见的方法及示例: 1. 自连接(Self-Join) 通过将表与自身连接,比较不同行的数据。 场景示例:比较同一用户相邻订单的金额差异。 …...
springboot2.X创建maven多模块工程
因为需要,所以付出。 好长时间没有搭建新的框架了,最近在搭建微服务的多模块maven工程,现在就将创建的过程记录下来,方便自学的小伙伴找寻资料,少走弯路。好了下面直接开干。 开发工具 :idea 、springboo…...