JPA保存ID已存在用户时误更新懒加载关联字段问题
JPA/Hibernate 更新用户时意外修改APPUserRoles表的问题解决
问题场景
定义了APPUser实体,通过@OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "username")关联APPUserroles列表;保存新用户时正常,但更新已存在用户时,Hibernate会执行错误的SQL:
update appuserroles set username=null where username=?
导致数据库报错:
com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert NULL value into username column, SLAPPS.dbo.APPUserRoles table. The column does not allow NULL values. UPDATE failed.
核心原因
- 关联关系维护方错误:你使用的是单向
@OneToMany关联,通过@JoinColumn指定由APPUser维护关联关系(即APPUser负责更新APPUserroles的username外键)。当你更新APPUser时,因为新建的APPUser对象中appuserroles为null,Hibernate会认为你要移除该用户所有关联的角色记录,因此尝试将这些记录的username设为null。 - 实体设计缺陷:
APPUserroles的主键是username,这意味着一个用户只能对应一个角色,不符合多角色的业务逻辑;同时主键字段不允许为null,直接触发了数据库报错。
解决方案
方案一:修正实体关联设计(推荐)
这是从根源解决问题的方式,同时修正角色表的设计以支持多角色:
修改
APPUserroles实体
改成复合主键(支持一个用户多个角色),并建立双向关联:@Data @Builder @NoArgsConstructor @AllArgsConstructor @Entity public class APPUserroles { // 复合主键:username + rolename @EmbeddedId private APPUserrolesId id; // 关联APPUser,由该字段维护关联关系 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "username", insertable = false, updatable = false) private APPUser user; } // 复合主键类,需实现Serializable @Embeddable public class APPUserrolesId implements Serializable { private String username; private String rolename; // 必须提供无参构造、equals和hashCode方法 public APPUserrolesId() {} public APPUserrolesId(String username, String rolename) { this.username = username; this.rolename = rolename; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; APPUserrolesId that = (APPUserrolesId) o; return Objects.equals(username, that.username) && Objects.equals(rolename, that.rolename); } @Override public int hashCode() { return Objects.hash(username, rolename); } }修改
APPUser中的关联
使用mappedBy指定由APPUserroles的user字段维护关联,让APPUser不再负责外键更新:@Entity public class APPUser implements UserDetails{ // 其他字段... @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List<APPUserroles> appuserroles; // 其他关联和方法... }
方案二:临时规避(不推荐长期使用)
如果暂时不想修改实体设计,可以在更新时保留原有的角色关联:
public void saveNewUserinDatabase(RegisterRequest request) { String remoteAddr = ""; // 查询数据库中已存在的用户 Optional<APPUser> existingUser = userRepository.findById(request.getUsername()); APPUser appuser = new APPUser( request.getUsername(), request.getVorname(), request.getNachname(), request.getGeburtstag(), request.getEmail(), request.getLocked(), passwordEncoder.encode(request.getPasswort()), "", remoteAddr ); // 若用户已存在,将原有的角色列表设置到新对象中 existingUser.ifPresent(user -> appuser.setAppuserroles(user.getAppuserroles())); userRepository.save(appuser); }
这样Hibernate的脏检查机制会认为角色关联没有变化,不会触发APPUserroles表的更新操作。
内容的提问来源于stack exchange,提问作者Sametcey
相关产品推荐
相关产品推荐

