如何不使用FetchType.EAGER实现JPA多对多关联下给用户分配角色
问题根源与解决方案
先修正你的两处核心配置错误
- 关联属性名不匹配:
Role类中mappedBy = "roleList",但User类里的多对多集合属性名写的是roleUserList,两者不一致会导致关联映射失效 - 级联配置不合理:
User侧多对多配置了CascadeType.PERSIST,会在保存用户时尝试持久化已经存在的预存角色,触发detached实体异常
最终实体类修正方案
Role.class
// 保持LAZY加载,不需要修改反向集合 @ManyToMany(mappedBy = "roleList", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY) private List<User> userList = new ArrayList<>(); // 不需要保留addPerson方法,或者保留也不要在业务代码中调用,避免触发userList懒加载
User.class
// 移除CascadeType.PERSIST,不需要级联新增/修改预存的角色 @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) // 属性名和Role的mappedBy保持一致 private List<Role> roleList = new ArrayList<>(); // addRole仅操作维护端(User侧)集合即可,不需要修改Role的userList,避免触发懒加载 public void addRole(Role role) { roleList.add(role); }
业务代码实现
给业务方法加上@Transactional注解,保证查询角色和保存用户在同一个Session中:
@Transactional public User addNewUser(String username) { User user = new User(username); Role role = serviceJpa.findRoleByRoleName("ROLE_USER"); user.addRole(role); // 直接保存用户即可,User是关联的维护端,会自动插入user_role中间表的关联记录 return serviceJpa.saveUser(user); }
原异常原因说明
LazyInitializationException:第一种实现中调用role.addUser(user)时,方法内部会访问Role的userList集合,LAZY模式下如果Session已关闭就无法初始化集合触发异常,且Role是关联的被维护端,修改它的集合不会更新中间表,完全不需要操作这个集合PersistentObjectException:第二种实现中User侧配置了CascadeType.PERSIST,保存用户时Hibernate会尝试级联持久化已经存在的Role对象,Role从数据库查询后如果不在同一个事务中就是detached状态,persist操作不允许处理detached实体,因此报错
内容的提问来源于stack exchange,提问作者Michael Joseph Jackson
相关产品推荐
相关产品推荐

