You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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拦截器。每次调用该方法时,拦截器都会执行以下完整流程:
    1. 通过TransactionSynchronizationManager检查当前线程是否存在事务上下文;
    2. 若不存在事务,从数据库连接池获取空闲连接,开启数据库事务(设置连接的autoCommit为false);
    3. 执行目标方法(JPA的save操作);
    4. 提交事务(或异常时回滚);
    5. 释放数据库连接回连接池,清除线程的事务上下文。
      这一系列IO密集型操作在单线程循环中重复50万次,累积的开销是性能暴跌的核心原因。
  • 类级事务的拦截器复用事务上下文:当@Transactional标注在类上时,拦截器会复用当前线程已存在的事务上下文(第一次调用创建的),后续调用仅执行目标方法,无需重复执行事务的创建、提交和连接操作,直到整个外层流程结束才统一提交事务,因此开销极小。

内容的提问来源于stack exchange,提问作者오태현

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 10:47:05