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

@Transactional致Rest API性能骤降,构造函数缓存数据方案求助

嘿,我来帮你拆解下这两个头疼的问题——@Transactional拖慢API响应,还有构造函数加载缓存的坑,咱们一步步来解决:

1. 为什么Controller加@Transactional会让API变慢?怎么解决?

首先得明确:把事务注解加在Controller层是反模式!Spring的事务是通过AOP实现的,当你在Controller类上加@Transactional时,整个HTTP请求的生命周期(从接收请求到返回响应)都会被包裹在事务里。这会带来两个致命的性能问题:

  • 数据库连接被长时间占用:事务会持有数据库连接直到响应完全发送,这不仅拖慢当前请求,还会耗尽连接池,导致后续请求排队等待连接,形成恶性循环。
  • 事务范围过大:Controller层的逻辑应该是接收请求、调用业务逻辑、返回响应,事务只需要包裹业务逻辑部分,没必要覆盖整个HTTP流程。

最优解决方案:把事务移到Service层

这是Spring开发的最佳实践,把业务逻辑抽成独立的Service类,在Service的方法上添加事务注解,Controller只做请求转发的工作。示例代码如下:

// 新建Service层类,负责业务逻辑与事务管理
@Service
public class FqdnService {
    private final CNMDBFqdnRepository cnmdbRepository;
    private final CNMTSFqdnRepository cnmtsRepository;

    @Autowired
    public FqdnService(CNMDBFqdnRepository cnmdbRepository, CNMTSFqdnRepository cnmtsRepository) {
        this.cnmdbRepository = cnmdbRepository;
        this.cnmtsRepository = cnmtsRepository;
    }

    @Transactional(readOnly = true, transactionManager = "cnmdbTm")
    public Set<String> getAllFqdn() {
        Set<String> fqdns = new HashSet<>();
        fqdns.addAll(cnmdbRepository.findAllFqdn());
        fqdns.addAll(cnmtsRepository.findAllFqdn());
        return fqdns;
    }
}

// 修改后的Controller,去掉事务注解
@RestController
@RequestMapping("/scheduler/api")
public class RestApiController {
    private final FqdnService fqdnService;

    @Autowired
    public RestApiController(FqdnService fqdnService) {
        this.fqdnService = fqdnService;
    }

    @GetMapping("/fqdns")
    public ResponseEntity<Set<String>> getFqdns() {
        // 直接调用Service方法,事务由Service管理
        return ResponseEntity.ok(fqdnService.getAllFqdn());
    }
}

这样一来,事务只包裹数据库查询的业务逻辑,请求响应完成后连接会立刻释放,响应速度会明显提升。

2. 构造函数中初始化HashMap缓存的问题与优化

你现在在构造函数里加载数据库数据到缓存,这里藏着几个坑:

  • 服务启动慢:如果数据量较大,构造函数会阻塞Bean初始化,拖慢整个服务的启动时间。
  • 缓存无法更新:一旦服务启动,缓存里的数据就固定了,数据库里的数据发生变化后,缓存不会同步,除非重启服务。
  • 事务上下文问题:构造函数执行时,Spring的事务上下文还没完全建立,如果Repository的查询方法依赖事务,可能会出现查询失败或性能问题。

优化方案1:改用@PostConstruct初始化缓存

把缓存初始化逻辑移到@PostConstruct标注的方法里,Spring会在Bean完成依赖注入、事务上下文就绪后再执行这个方法,避免构造函数的问题:

@RestController
@RequestMapping("/scheduler/api")
public class RestApiController {
    private final CNMDBFqdnRepository cnmdbRepository;
    private final CNMTSFqdnRepository cnmtsRepository;
    private Set<String> cache;

    @Autowired
    public RestApiController(CNMDBFqdnRepository cnmdbRepository, CNMTSFqdnRepository cnmtsRepository) {
        this.cnmdbRepository = cnmdbRepository;
        this.cnmtsRepository = cnmtsRepository;
    }

    @PostConstruct
    @Transactional(readOnly = true, transactionManager = "cnmdbTm")
    public void initCache() {
        cache = new HashSet<>();
        cache.addAll(cnmdbRepository.findAllFqdn());
        cache.addAll(cnmtsRepository.findAllFqdn());
    }

    @GetMapping("/cached-fqdns")
    public ResponseEntity<Set<String>> getCachedFqdns() {
        return ResponseEntity.ok(cache);
    }
}

优化方案2:添加缓存刷新机制

如果数据库数据会动态变化,你需要手动提供缓存刷新的方式,比如新增一个API接口,或者用定时任务定期更新:

// 新增刷新缓存的API
@GetMapping("/refresh-cache")
@Transactional(readOnly = true, transactionManager = "cnmdbTm")
public ResponseEntity<Void> refreshCache() {
    cache.clear();
    cache.addAll(cnmdbRepository.findAllFqdn());
    cache.addAll(cnmtsRepository.findAllFqdn());
    return ResponseEntity.noContent().build();
}

// 或者用定时任务定期刷新(需要启动类加@EnableScheduling)
@Scheduled(fixedRate = 3600000) // 每1小时刷新一次
public void scheduledRefreshCache() {
    refreshCache();
}

优化方案3:用Spring Cache框架替代手动HashMap

如果你的缓存需求比较复杂(比如自动过期、缓存击穿防护),推荐用Spring Cache配合Caffeine或Redis,不用手动管理HashMap,更灵活可靠:

@Service
public class FqdnService {
    private final CNMDBFqdnRepository cnmdbRepository;
    private final CNMTSFqdnRepository cnmtsRepository;

    @Autowired
    public FqdnService(CNMDBFqdnRepository cnmdbRepository, CNMTSFqdnRepository cnmtsRepository) {
        this.cnmdbRepository = cnmdbRepository;
        this.cnmtsRepository = cnmtsRepository;
    }

    @Cacheable(value = "fqdns", key = "'all'")
    @Transactional(readOnly = true, transactionManager = "cnmdbTm")
    public Set<String> getAllFqdn() {
        Set<String> fqdns = new HashSet<>();
        fqdns.addAll(cnmdbRepository.findAllFqdn());
        fqdns.addAll(cnmtsRepository.findAllFqdn());
        return fqdns;
    }

    @CacheEvict(value = "fqdns", key = "'all'")
    public void refreshFqdnCache() {
        // 清空缓存,下次调用getAllFqdn会自动重新加载数据
    }
}
总结

两步走就能解决你的问题:

  1. 把@Transactional从Controller移到Service层,缩小事务范围,释放数据库连接。
  2. 把构造函数里的缓存初始化改成@PostConstruct,加上刷新机制,或者直接用Spring Cache管理缓存。

内容的提问来源于stack exchange,提问作者Vladislav Kysliy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:29:17