@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会自动重新加载数据 } }
总结
两步走就能解决你的问题:
- 把
@Transactional从Controller移到Service层,缩小事务范围,释放数据库连接。 - 把构造函数里的缓存初始化改成
@PostConstruct,加上刷新机制,或者直接用Spring Cache管理缓存。
内容的提问来源于stack exchange,提问作者Vladislav Kysliy
相关产品推荐
相关产品推荐

