identifier of an instance of xx.entity was altered from xxKey@249e3cb2 to xxKey@74e8f4a3; nested exc...

博客讲述了使用entityManager保存三张表数据时遇到的报错问题。使用事务保存数据后报错,去掉最后一张表实体联合主键仍有问题。经单元测试,发现问题关键在于一条数据从数据库读取,另一条新插入。将读取数据复制到新实体后问题解决,原因是Hibernate不允许修改Persistent状态实体的id后保存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用entityManager保存数据时报错如下

identifier of an instance of xx.entity was altered from xxKey@249e3cb2 to xxKey@74e8f4a3; nested exception is org.hibernate.HibernateException: identifier of an instance of xxentity was altered from xxKey@249e3cb2 to xxKey@74e8f4a3

一共存了三张表的数据,使用了事务,前面两张表存各存1条数据,最后一张表存两条数据。执行完成后报上面的错误。后来把最后一张表实体上的联合主键去掉不报错,但最后一张表只保存成功1条数据

太奇怪了。怀疑是事务引起,检查了是使用的

 @Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)

写了单元测试,单独插入最后一张表两条数据可以成功插入。

最后找到问题关键,是因为最后一张表的其中一条数据是从数据库里读取出来的,另外一条是新插入的。把读取的数据copy到新new的实体后问题解决。

原因分析 处于Persistent状态的实体的id, hibernate是不允许修改后双保存回去。

 

转载于:https://www.cnblogs.com/DevMuYuer/p/10945103.html

<think>我们正在处理用户关于Spring Data JPA与Hibernate中出现的JpaSystemException异常,具体是标识符(identifier)被修改(altered)的问题。 根据经验,这种异常通常发生在实体对象的持久化状态中,当尝试修改实体的主键(标识符)时,Hibernate会抛出此异常。因为主键一旦设定就不应该改变。 可能的原因: 1. 在实体类中,主键字段(通常用@Id标注)被意外修改。 2. 在实体类中,可能使用了可变类型(如String)作为主键,并在实体处于持久化状态时修改了它的值。 解决方案: 1. 确保在实体对象被持久化(例如,通过save方法保存到数据库)后,不再修改其主键字段。 2. 如果主键是自动生成的(如使用@GeneratedValue),则不应该手动设置或修改它。 3. 检查代码中是否有地方在实体被管理(managed)状态时修改了主键。 另外,注意Hibernate的session状态:当一个实体被关联到Hibernate session(例如,通过getOne或findById方法获取)时,它处于持久化状态。此时修改其主键会导致异常。 示例代码: 假设有一个实体类User: ```java @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // getters and setters } ``` 在以下代码中,如果尝试修改id,就会抛出异常: ```java @Service public class UserService { @Autowired private UserRepository userRepository; public void updateUserName(Long userId, String newName) { User user = userRepository.findById(userId).orElseThrow(); // 错误:尝试修改主键 user.setId(100L); // 这会导致JpaSystemException: identifier altered user.setName(newName); userRepository.save(user); } } ``` 因此,修复方法就是避免修改主键。 如果用户确实需要修改主键,那么正确的做法是先删除旧记录,再插入一条新记录(使用新的主键值)。但注意,这可能会带来关联数据的问题(如外键约束)。 此外,如果用户遇到的是级联操作中相关实体的主键被修改,也需要检查相关实体的主键是否被修改。 根据以上分析,我们给出回答。</think>### Spring Data JPA Hibernate 标识符被修改异常分析与解决 当使用 Spring Data JPA 和 Hibernate 时,遇到 `JpaSystemException: identifier of an instance was altered` 异常,通常表示**实体对象的主键(ID)在持久化状态中被非法修改**。以下是详细分析和解决方案: --- ### 原因分析 1. **主键不可变性** Hibernate 要求实体主键在对象被持久化后(关联到 Session 或 EntityManager)保持不可变。尝试修改主键会触发此异常[^1]。 2. **典型场景** ```java @Entity public class User { @Id // 主键字段 private Long id; // 其他字段... } // 错误代码示例:修改持久化对象的主键 User user = userRepository.findById(1L).get(); user.setId(2L); // 触发异常! userRepository.save(user); ``` 3. **根本原因** Hibernate 使用主键跟踪实体状态。修改主键会破坏 Hibernate 的内部标识机制,导致状态不一致[^1]。 --- ### 解决方案 #### ✅ 方法 1:避免直接修改主键 - **原则**:主键应在对象创建时设置,后续永不修改 - **正确做法**: ```java // 创建新对象代替修改主键 User newUser = new User(); newUser.setId(2L); // 仅允许在瞬态(未持久化)状态下设置ID newUser.setName("New Name"); userRepository.save(newUser); // 删除旧对象(如需替换) userRepository.deleteById(1L); ``` #### ✅ 方法 2:使用业务键代替主键修改 若需"修改ID",实际应通过业务逻辑实现: ```java // 1. 创建新实体 User newUser = copyProperties(oldUser); newUser.setId(newId); // 2. 保存新实体 userRepository.save(newUser); // 3. 删除旧实体 userRepository.delete(oldUser); // 4. 更新关联关系(需事务管理) orderRepository.updateUserId(oldUser.getId(), newUser.getId()); ``` #### ✅ 方法 3:检查级联操作 当主键被级联操作意外修改时: ```java @Entity public class Order { @Id private Long id; @ManyToOne @JoinColumn(name = "user_id") private User user; // 关联实体 } // 错误:修改关联实体的主键 Order order = orderRepository.findById(orderId).get(); order.getUser().setId(newId); // 触发异常! ``` **修复**:修改关联关系而非主键: ```java User newUser = userRepository.findById(newId).get(); order.setUser(newUser); // 正确:替换关联引用 ``` --- ### 预防措施 1. **实体设计规范**: - 主键字段添加 `final` 修饰符(如适用) - 使用 `@GeneratedValue` 自动生成不可变ID ```java @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private final Long id; // final 防止修改 ``` 2. **代码审查重点**: - 检查所有 `setId()` 调用是否发生在持久化对象上 - 使用 `@Immutable` 注解标记不可变实体 ```java @Entity @Immutable // Hibernate 特殊注解 public class AuditLog { ... } ``` 3. **Hibernate 配置增强**: ```properties # 开启调试日志定位非法修改 spring.jpa.properties.hibernate.event.merge.entity_state_logging=true ``` > **关键原则**:数据库主键对应 OOP 中的对象标识,修改它等同于创建新对象。业务上需要"修改ID"的需求,应通过创建新实体+迁移数据实现[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值