Spring Boot中@Transactional未更新缓存获取的实体至数据库问题
问题解答
首先明确:通过@Transactional实现无需手动保存实体即可更新数据库是完全可行的,但你的代码因实体状态问题导致自动同步失效。
核心原因
你从Redis缓存中获取的User实体是序列化后存储的,此时它已经脱离了Hibernate的持久化上下文,属于游离状态(Detached Entity)。Hibernate只会自动追踪**持久化状态(Managed Entity)**的实体修改,游离实体的变更不会被自动同步到数据库,哪怕标注了@Transactional也无效。
解决方法
方法1:重新加载持久化状态的User实体
从数据库重新查询User,获取处于持久化上下文的实例,再修改其子实体。事务提交时Hibernate会自动检测到变更并同步到DB:
@Transactional public ResponseEntity<Object> updateRating(@NonNull final String sessionId, @NonNull final UserRatingUpdateRequestDTO userRatingUpdateRequestDTO) { final UserSession fetchedSession = userSessionService.findUserSessionBySessionId(sessionId); // 从DB重新加载User,纳入持久化上下文 final User managedUser = userRepository.findById(fetchedSession.getUser().getId()) .orElseThrow(() -> new RuntimeException("User not found")); final LocalDate currentDate = DateTimeUtils.dateTimeInIST().toLocalDate(); logger.info("Updating Rating for user : {}", managedUser.getId()); if (userRatingUpdateRequestDTO.isHasRated()) { managedUser.getUserRating().setHasGivenRating(true); } else { int weeksToAdd = 5; managedUser.getUserRating().setDate(currentDate.plusWeeks(weeksToAdd)); } // 事务提交后清理缓存,避免脏读 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { cacheInvalidatorService.evictUserSession(sessionId); cacheInvalidatorService.deleteUserDetails(managedUser.getUsername()); } }); return ResponseEntity.ok().body("rating updated successfully"); }
方法2:手动关联游离实体到持久化上下文(可选)
如果不想重新查询,可以用entityManager.merge(user)将游离实体转换为持久化状态,但merge会返回新的持久化实例,后续修改要基于这个实例:
@Autowired private EntityManager entityManager; @Transactional public ResponseEntity<Object> updateRating(@NonNull final String sessionId, @NonNull final UserRatingUpdateRequestDTO userRatingUpdateRequestDTO) { final UserSession fetchedSession = userSessionService.findUserSessionBySessionId(sessionId); User detachedUser = fetchedSession.getUser(); // 转换为持久化状态 User managedUser = entityManager.merge(detachedUser); final LocalDate currentDate = DateTimeUtils.dateTimeInIST().toLocalDate(); logger.info("Updating Rating for user : {}", managedUser.getId()); if (userRatingUpdateRequestDTO.isHasRated()) { managedUser.getUserRating().setHasGivenRating(true); } else { int weeksToAdd = 5; managedUser.getUserRating().setDate(currentDate.plusWeeks(weeksToAdd)); } TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { cacheInvalidatorService.evictUserSession(sessionId); cacheInvalidatorService.deleteUserDetails(managedUser.getUsername()); } }); return ResponseEntity.ok().body("rating updated successfully"); }
额外注意事项
- 确保
UserRating与User的关联关系配置正确(比如@OneToOne或@ManyToOne的cascade属性),否则子实体的变更可能无法被正确追踪。 - 缓存清理必须放在事务提交后执行,否则可能出现缓存与数据库不一致的情况。
内容的提问来源于stack exchange,提问作者pennie turci
相关产品推荐
相关产品推荐

