@Retryable结合事务上下文的并发创建实体问题咨询
问题解答
1. 是否需要添加@Transactional(propagation = Propagation.REQUIRES_NEW)?
必须添加,原因如下:
- 你的方法被外层带@Transactional的方法调用时,默认会沿用外层事务上下文。如果不加REQUIRES_NEW,并发插入抛出
DataIntegrityViolationException后,整个外层事务会被标记为回滚状态,后续重试操作会因事务已回滚无法正常执行,也无法查询到其他线程已创建的实体。 - 配置
REQUIRES_NEW后,每次调用(包括重试)都会开启独立的新事务:- 第一次插入失败时,仅回滚当前小事务,不会影响外层事务;
- 重试时,新事务能独立查询数据库,获取到其他线程已创建完成的实体。
2. 是否需要捕获DataIntegrityViolationException并在catch块中获取已创建实体?
不需要,且该操作会与@Retryable逻辑冲突,原因如下:
- @Retryable的触发条件是指定异常向外抛出,若在方法内部捕获该异常,@Retryable无法感知到异常,也就不会执行重试逻辑。
- 正确做法是移除try-catch块,让异常直接抛出触发重试:
- 第一次插入失败,异常抛出触发重试;
- 重试时方法重新执行,第一步
findEntityByUuid(entityUuid)就能查询到其他线程已创建的实体,直接返回即可,无需再执行插入逻辑。
调整后的示例代码
@Retryable( retryFor = {DataIntegrityViolationException.class}, maxAttempts = 2, backoff = @Backoff(delay = 100) ) @Transactional(propagation = Propagation.REQUIRES_NEW) public T getOrCreateEntity(UUID entityUuid) { T entity = findEntityByUuid(entityUuid); if (entity != null) { return entity; } log.info("Creating entity with UUID {}", entityUuid); T newEntity = createNewEntity(entityUuid); return repository.save(newEntity); }
若坚持保留catch块(比如不想依赖重试机制,直接在异常时查询返回),则可以去掉@Retryable注解,因为此时异常已被内部处理,重试不会生效。
内容的提问来源于stack exchange,提问作者magnetiktank
相关产品推荐
相关产品推荐

