Spring Boot Data查询返回旧数据 WebService更新库后首次查询未生效
问题根因
你遇到的现象是典型的*JPA持久化上下文缓存(一级缓存)*或者事务隔离级别导致的读取过时数据问题,核心原因如下:
- 同一事务内的一级缓存未失效:默认Spring声明式事务绑定的持久化上下文会缓存当前事务内查询过的所有实体。你第一次调用
findByCardNumber查询CardMaster实体后,该实体就被缓存到当前持久化上下文,调用外部WebService更新数据库属于事务外的操作,持久化上下文无法感知到数据库的实际变更,第二次调用同一个查询方法时会直接返回缓存中的旧实体,不会发起真实的数据库查询。 - 事务隔离级别为可重复读:如果你的数据库默认隔离级别是可重复读(REPEATABLE READ,MySQL InnoDB默认值),同一个事务内的所有查询会复用第一次查询生成的一致性快照,也会导致看不到其他事务提交的更新。
- 二级缓存未失效:如果你开启了JPA全局二级缓存,外部更新不会自动触发二级缓存的失效,也会返回缓存的旧数据。
解决方案
可根据你的业务场景选择以下任意一种方案:
方案1:主动刷新实体/清空一级缓存
注入EntityManager,在外部更新完成后主动刷新实体,或者清空当前持久化上下文的缓存,修改后的代码如下:
@Autowired private CardMasterService cardMasterService; @Autowired private SoapWebserviceUtil webserviceUtil; // 注入EntityManager @PersistenceContext private EntityManager entityManager; public CardConfidentialDataResponseDto getConfidentialData(CardConfidentialDataRequestDto requestDTO) { CardConfidentialDataResponseDto responseDto; try { CardMaster card = cardMasterService.findByCardNumber(requestDTO.getCardNumber()); DateFormat df = new SimpleDateFormat("YYMM"); String expDate = df.format(card.getExpiryDate()); String cvv2 = callGetClearMethod("GET_CLEAR_CVV2", card.getCardNumber(), expDate); // 新增:主动刷新实体,从数据库拉取最新数据覆盖缓存实体,不需要二次查询 entityManager.refresh(card); // 或者选择清空全部一级缓存,再执行二次查询 // entityManager.clear(); // card = cardMasterService.findByCardNumber(requestDTO.getCardNumber()); Date jalaliiExpDate = PersianDateUtil.convertToJalali(card.getExpiryDate()); String jalaiExpDateStr = df.format(jalaliiExpDate); responseDto = new CardConfidentialDataResponseDto(SUCCESSFUL.toString(), SUCCESSFUL.getDescription(), track1, track2, cvv2, card.getEmbossingName(), jalaiExpDateStr); return responseDto; } catch (Exception e) { logger.error(e.getMessage(), e); responseDto = new CardConfidentialDataResponseDto(INTERNAL_ERROR.toString(), INTERNAL_ERROR.getDescription()); } return responseDto; }
方案2:给查询方法添加绕过缓存的查询提示
在CardMasterRepository的findByCardNumber方法上添加查询Hint,强制每次查询都从数据库拉取最新数据:
@QueryHints(value = { @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHE_MODE, value = "REFRESH"), // 如果开启了二级缓存,添加下面这行跳过二级缓存 @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHEABLE, value = "false") }) Optional<CardMaster> findByCardNumber(String cardNumber);
方案3:调整事务隔离级别/事务传播属性
如果是可重复读隔离级别导致的问题,给当前方法调整事务隔离级别为读已提交:
@Transactional(isolation = Isolation.READ_COMMITTED) public CardConfidentialDataResponseDto getConfidentialData(CardConfidentialDataRequestDto requestDTO) { // 原有业务逻辑 }
也可以把二次查询的逻辑抽成独立方法,标注@Transactional(propagation = Propagation.REQUIRES_NEW),用新事务读取最新的已提交数据。
内容的提问来源于stack exchange,提问作者Mostafa Soltani
相关产品推荐
相关产品推荐

