Spring Data JPA 分离实体。

5

我开始使用Spring Data JPA构建一个基于Spring Boot的应用程序,以设置用户和角色之间的多对多关系。

在User类中,此关系定义如下:

@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name="user_role", joinColumns = {@JoinColumn(name="user_id")}, inverseJoinColumns = {@JoinColumn(name="role_id")})
private Set<UserRole> roles;

我使用以下方法创建角色:

@Transactional
private void generateSeedRoles() {
    UserRole adminRole = new UserRole(RoleEnum.ADMIN.toString());
    userRoleRepository.save(adminRole);

    UserRole userRole = new UserRole(RoleEnum.USER.toString());
    userRoleRepository.save(userRole);
}

分配角色给用户后失败:
@Transactional
private void generateSeedUsers() {
    UserRole adminRole = userRoleRepository.findUserRoleByRoleName("ADMIN");

    User user = User.createUser("user1", "user1@user.com", "pass");
    user.setRoles(new HashSet<UserRole>(Arrays.asList(adminRole)));

    userRepository.save(user);
}

下面的异常被抛出(为了可读性而格式化):
org.springframework.dao.InvalidDataAccessApiUsageException: 
detached entity passed to persist: co.feeb.models.UserRole; 
nested exception is org.hibernate.PersistentObjectException: 
detached entity passed to persist: co.feeb.models.UserRole

然而,如果在建立关联之前保存用户,则可以正常工作:
@Transactional
private void generateSeedUsers() {
    UserRole adminRole = userRoleRepository.findUserRoleByRoleName("ADMIN");

    User user = User.createUser("user1", "user1@user.com", "pass");
    //Save user
    userRepository.save(user);

    //Build relationship and update user
    user.setRoles(new HashSet<UserRole>(Arrays.asList(adminRole)));
    userRepository.save(user);
}

我认为需要两次保存/更新用户似乎有些不合理。是否有任何方法可以将接收到的角色分配给新用户,而无需先保存用户?


您所发布的代码似乎是正确的,除了一个问题:默认情况下,@Transactional 不适用于私有方法。您可以查看我的示例,它可以正常工作。实际上,我猜测您没有包含 CascadeType.PERSIST,因此您的用户没有获取任何角色,因为它既没有合并也没有分离。 - manish
1个回答

14
当你保存实体时,Spring会在内部检查实体是否为新实体(我忘记确切的机制),并发出persist(如果是新实体)或merge(如果是现有实体)。由于你已经使用Cascade.ALL定义了User到UserRole的关系,任何来自User的persist / merge操作也将级联到UserRole。考虑到Spring如何实现保存以及上述级联行为,以下是需要考虑的一些情况:
- 如果User和UserRole都是新的 - 当保存User时,你将获得一个persist操作,并对UserRole进行级联persist,这很好,因为UserRole是新的。 - 如果User是新的,但UserRole是现有的 - 再次保存User时,你将获得一个persist操作,并对UserRole进行级联persist,这将导致错误,因为UserRole已经存在! - 如果你可以保证向User添加到UserRole的所有UserRole始终存在,则可以从cascade选项中简单地删除Cascade.Persist。否则,在上述两种情况下保存User时,我认为你将不得不使用merge - 通过自定义存储库和entityManager。
关于你使用Cascade.ALL,可能在这种情况下是没有意义的,因为你有一个@ManyToMany关系,从User到UserRole的级联ALL可能会产生一些不良影响(例如,Cascade.Remove将在每次删除User时删除UserRole)。

3
将 CascadeType.ALL 改为 {CascadeType.MERGE, CascadeType.DETACH},它就可以工作了!非常感谢! 这些是所需的CascadeTypes吗?还是我漏掉了什么? - dominik
需要哪种级联取决于您的需求/业务逻辑,但在这种情况下,合并和分离似乎是适当的级联。如果您觉得这个答案有帮助,请随意接受答案。谢谢。 - ikumen
成功了!是否存在一种设置“ALL except PERSIST”级联类型的方法?CascadeType.ALL - CascadeType.PERSIST这将是很好的!xD - Lucas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接