一、问题现象
service中仅仅使用visionGatewayRepository.findAllByCompanyId(UserUtils.getCompanyId(), pageable);查询了列表,然后对列表的数据进行了修改,直接通过Controller返回了前端,但是发现数据库的数据被修改了。
二、问题分析
原因解释
当您从数据库查询实体对象时,JPA 会将这些对象纳入持久化上下文(Persistence Context)管理。在默认情况下:
- 实体处于托管状态:通过 JPA Repository 查询返回的实体对象是"托管(managed)"状态的
- 自动脏检查:当事务提交时,JPA 会自动检测这些托管实体的变化
- 自动同步到数据库:如果发现实体属性有变化,JPA 会自动生成 UPDATE 语句将变更同步到数据库
为什么没有调用 save() 也会更新
这是因为在以下情况下会自动触发更新:
- 您的方法有
@Transactional
注解(Spring Boot 默认很多方法都有事务) - 您修改了从 Repository 查询返回的实体对象
- 方法执行完毕,事务提交时 JPA 检测到实体变化并自动同步
解决方案
如果您不希望自动更新,可以考虑以下方法:
- 手动分离实体: java复制
EntityManager entityManager; // 注入 entityManager.detach(entity); // 将实体从持久化上下文分离
- 使用 DTO 返回:
不直接返回实体对象,而是转换为 DTO 对象返回 - 查询时指定只读: java复制
@Transactional(readOnly = true) public Entity getEntity(Long id) { return repository.findById(id).orElse(null); }
- 使用 Projection:
只查询需要的字段而不是整个实体
最佳实践
- 明确区分读写操作,写操作显式调用 save()
- 在只读场景使用
@Transactional(readOnly = true)
- 对外暴露接口时使用 DTO 而不是直接暴露实体
这种自动更新机制是 JPA 的一个便利特性,但需要开发者明确了解其行为以避免意外更新。
三、解决方法
给查询函数前面,添加@Transactional(readOnly = true)
