Spring中@Transactional注解位置为何影响处理速度?
Spring事务注解位置对性能影响的原因分析
测试现象
当
@Transactional注解声明在类级别时,调用test1与test2方法的处理速度无显著差异;但当注解声明在方法级别时,test1速度骤降,test2仍保持较快速度。此外,无@Transactional的仅打印逻辑速度很快,说明慢因来自@Transactional的影响。本地H2环境测试,即使仅创建Task1并缩小循环范围至10万次,仍能确认循环内逻辑变慢。
相关代码
TestController.java
package com.example.test; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") @RequiredArgsConstructor //@Transactional public class TestController { private final StudentService studentService; @PostMapping("/test1") public void test1() { System.out.println(Thread.currentThread().getName()); long start = System.currentTimeMillis(); for (int i = 1; i <= 500000; i++) { studentService.studentSave("학생_" + i); } long end = System.currentTimeMillis(); System.out.println("실행 시간: " + (end - start) + "ms"); } @PostMapping("/test2") public void test2() { long start = System.currentTimeMillis(); Task task1 = new Task(1, 100000); Task task2 = new Task(100001, 200000); Task task3 = new Task(200001, 300000); Task task4 = new Task(300001, 400000); Task task5 = new Task(400001, 500000); Thread thread1 = new Thread(task1, "thread1"); Thread thread2 = new Thread(task2, "thread2"); Thread thread3 = new Thread(task3, "thread3"); Thread thread4 = new Thread(task4, "thread4"); Thread thread5 = new Thread(task5, "thread5"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); try { thread1.join(); thread2.join(); thread3.join(); thread4.join(); thread5.join(); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("실행 시간: " + (end - start) + "ms"); } class Task implements Runnable { private final int start; private final int end; public Task(int start, int end) { this.start = start; this.end = end; } public void studentSave() { for (int i = start; i <= end; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); studentService.studentSave("학생_" + i); } } @Override public void run() { studentSave(); } } }
StudentService.java
package com.example.test; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class StudentService { private final StudentJpaRepository studentJpaRepository; @Transactional public void studentSave(String studentName) { studentJpaRepository.save(new Student(studentName)); } public void studentAll() { studentJpaRepository.findAll().forEach(System.out::println); } }
现象原因解析
1. 类级事务与方法级事务的事务复用差异
- 类级事务场景:当
@Transactional标注在StudentService类上时,所有方法默认继承事务属性,事务传播行为为REQUIRED(默认值)。在test1的单线程循环中,第一次调用studentSave时会创建一个事务,后续所有的studentSave调用都会复用当前线程已存在的事务,直到整个test1方法执行完毕才会一次性提交事务。这就避免了50万次重复的事务创建、提交开销,所以test1的速度和多线程的test2差异不大。 - 方法级事务场景:当
@Transactional仅标注在studentSave方法上时,test1的Controller方法没有事务上下文,每次调用studentSave都会触发新事务的创建、执行SQL、提交事务、释放数据库连接的完整流程。50万次的重复操作会将事务的重量级开销(连接获取/释放、事务开启/提交)完全放大,导致test1速度骤降。 - 多线程场景的并行优势:test2采用多线程并行执行,每个线程内的循环同样会触发多次独立事务,但多线程可以同时占用数据库连接池中的多个连接,并行处理事务操作,将单线程的串行开销分散为并行处理,因此整体性能不会像单线程那样暴跌。
AOP层面的慢发生点及原因
Spring事务基于动态AOP代理实现,核心慢点在事务拦截器的逻辑执行上:
- 方法级事务的拦截器重复执行全流程:当
@Transactional标注在方法上时,Spring会为studentSave方法生成TransactionInterceptor拦截器。每次调用该方法时,拦截器都会执行以下完整流程:- 通过
TransactionSynchronizationManager检查当前线程是否存在事务上下文; - 若不存在事务,从数据库连接池获取空闲连接,开启数据库事务(设置连接的
autoCommit为false); - 执行目标方法(JPA的save操作);
- 提交事务(或异常时回滚);
- 释放数据库连接回连接池,清除线程的事务上下文。
这一系列IO密集型操作在单线程循环中重复50万次,累积的开销是性能暴跌的核心原因。
- 通过
- 类级事务的拦截器复用事务上下文:当
@Transactional标注在类上时,拦截器会复用当前线程已存在的事务上下文(第一次调用创建的),后续调用仅执行目标方法,无需重复执行事务的创建、提交和连接操作,直到整个外层流程结束才统一提交事务,因此开销极小。
内容的提问来源于stack exchange,提问作者오태현
相关产品推荐
相关产品推荐

