Spring Boot中@Transactional作用及删除操作失效原因解析
我正在使用Java Spring Boot开发,现有如下服务层函数:
@Override public QuestionDTO updateQuestion(Long id, QuestionDTO updateData) { Question question = questionRepository.findById(id).orElseThrow(() -> new RuntimeException()); if(updateData.getText().length() > 0) { question.setText(updateData.getText()); } List<Answer> newAnswers = new ArrayList<>(); // Delete old answers // some filtering logic that would set isPresent variable // for i->0 -> arr.size if(!isPresent) { Long x = existingAnswer.getId(); // the answer with this id needs to be deleted answerRepository.deleteById(x); } // for i->0 -> arr.size { // some logic to update existing answer entity existingAnswer.setIsCorrect(a.getIsCorrect()); answerRepository.save(existingAnswer); justUpdate = true; break; } if(!justUpdate) { a.setQuestion(question); newAnswers.add(a); } LoggingController.getLogger().info("DONE UPDATE! Final array is:"); question.setAnswers(newAnswers); // Save new array of answers for question Question updatedQuestion = questionRepository.save(question); return QuestionMapper.mapToQuestionDTO(updatedQuestion); }
Question和Answer均为实体类,我的PostgreSQL数据库结构如下:
Question表
| id | text |
|---|---|
| 1 | "What is the capital of France?" |
| 2 | "How many countries..." |
Answer表
| id | text | question_id (fk) |
|---|---|---|
| 1 | "Answer A" | 1 |
| 2 | "Answer B" | 1 |
当我在该函数上添加@Transactional注解时,所有数据库操作均可正常执行;未添加该注解时,删除操作失效,但其他操作正常。请问删除操作失效的原因是什么?@Transactional注解的具体作用又是什么?
一、删除操作失效的原因
持久化上下文缓存不一致
没加@Transactional时,answerRepository.deleteById(x)会单独开启一个独立事务执行删除,执行完立即提交到数据库。但此时你之前查询出来的question对象还在当前线程的Hibernate Session(持久化上下文)缓存中,它关联的旧Answer集合并未更新。后续执行question.setAnswers(newAnswers)并调用questionRepository.save(question)时,Hibernate会根据Session缓存里的question状态生成SQL——它仍认为已删除的Answer和question保持关联,甚至会把已删除的Answer重新关联回去,最终表现为删除操作失效。独立事务的冲突问题
无外层事务包裹时,删除Answer和更新Question是两个完全独立的事务。删除操作本身可能成功,但后续更新Question时,Session缓存的旧关联关系会触发Hibernate的自动同步,将数据库中已删除的Answer记录“恢复”,导致最终数据和预期不符。JPA方法的事务特性限制
deleteById这类Repository方法在无外层事务时,执行完会立即提交但不会清空Session缓存。而save方法会触发Hibernate的脏检查,将缓存中的实体状态同步到数据库,此时缓存里的question仍持有已删除Answer的引用,必然导致数据不一致。
二、@Transactional注解的核心作用
- 统一事务边界:给方法包裹一个完整事务,所有数据库操作(查询、删除、更新、插入)都在同一个事务内执行,保证操作的原子性——要么全部成功,要么全部回滚。
- 管理持久化上下文:事务开启时创建一个Session,方法内所有实体都在该Session缓存中,所有操作同步到缓存后,直到事务提交时才一次性将所有变更刷入数据库,从根源避免缓存与数据库状态不一致的问题。
- 事务传播控制:可通过参数(如
propagation = Propagation.REQUIRES_NEW)配置事务传播行为,适配嵌套事务、独立事务等不同业务场景。 - 自动回滚机制:默认情况下,方法抛出RuntimeException或Error时事务自动回滚;若需针对Checked Exception触发回滚,可通过
rollbackFor参数自定义规则。
内容的提问来源于stack exchange,提问作者Alexandru Kullman

