如何持久化@ManyToMany关系 - 重复条目或分离的实体

我想用 ManyToMany 关系来持久化我的实体。但我在持久化过程中遇到了一些问题。

我的实体 :

@Entity
@Table(name = "USER")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userId;

    @Column(name = "NAME", unique = true, nullable = false)
    String userName;

    @Column(name = "FORNAME")
    String userForname;

    @Column(name = "EMAIL")
    String userEmail;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "USER_USER_ROLES", joinColumns = @JoinColumn(name = "ID_USER"), inverseJoinColumns = @JoinColumn(name = "ID_ROLE"))
    List<UserRoles> userRoles = new ArrayList<UserRoles>();

    // getter et setter
}

@Entity
@Table(name = "USER_ROLES")
public class UserRoles implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userRolesId;

    @Column(unique = true, nullable = false, name = "ROLE_NAME")
    String roleName; 

    // getter et setter
}

服务代码 :

User user = new User();
UserRoles role;
try {
    role = userRolesServices.getUserRoleByName("ROLE_USER"); // find jpql - transaction
} catch (RuntimeException e) {
    LOGGER.debug("No Roles found");
    role = new UserRoles("ROLE_USER"); // create new
}
user.addUserRole(role);
user.setUserName(urlId);
user.setUserForname(fullName);
user.setUserEmail(email);
userServices.createUser(user); // em.persist(user) - transaction

第一次,当我尝试使用UserRoles“ROLE_USER”持久化User时,没问题。插入用户和用户角色以及联接表。

我的问题是当我尝试使用相同的UserRoles持久化第二个用户时。我通过找到UserRoles来检查它是否存在(userRolesServices.getUserRoleByName(...))。如果存在 ->将此用户角色添加到用户列表(id + 角色名称),否则我将创建一个新用户(仅角色名称)。

当我尝试持久化第二个用户时,我得到以下异常:“分离实体持久化:.....UserRoles“(也许是因为getUserRoleByName在另一个事务中执行)

如果我不使用getUserRoleByName(只有*new UserRoles(“ROLE_USER”);*),我得到以下例外:“...约束暴力:“ROLE_NAME”的重复条目...”

那么,如何正确地持久化一个具有@ManyToMany关系实体呢?


答案 1

对于上述问题,我会说你的实体关系级联是错误的。请考虑以下情况:一个用户可以有多个角色,但系统中可以存在固定数量的角色。所以从实体级联ALL没有任何意义,因为生命周期的生命周期不应该依赖于实体的生命周期。例如,当我们删除时,不应删除。UserUserRolesUserUserUserRoles

分离的实体以持久化异常仅在您传递已将主键设置为持久化的对象时才会发生。

删除级联,您的问题现在将得到解决,您只需要决定如何插入用户角色。据我所知,应该有单独的功能来做到这一点。

也不要使用,使用。 允许重复项。ArrayListHashSetArrayList


答案 2

如果有人对我和作者有相同类型的问题,我会提供我的答案。

基本上,我面临的是当我有一个表是某种恒定值时的情况。另一个会改变,但它应该将()映射到这些常量many to many

确切的问题是,它是.USERSROLES

Diagram

Roles将在系统启动时被知道并添加,因此它们永远不会被删除。即使没有用户会有一些,它仍然应该在系统中Role

使用 JPA 的类实现:

用户

@Entity
@Table(name = "USERS")
public class User{

    @Id
    private String login;
    private String name;
    private String password;

    @ManyToMany(cascade = {CascadeType.MERGE})
    private Set<Role> roles = new HashSet<>();

角色

@Entity
@Table(name = "ROLE")
public class Role {

    @Id
    @Enumerated(value = EnumType.STRING)
    private RoleEnum name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

用法

此设置将很容易地添加/删除到 。只需传递一个数组,f.e.: 和 .删除适用于传递空列表。RoleUseruser.getRoles().add(new Role("ADMIN"));mergeuser

如果您忘记在将其添加到用户之前添加,则很可能会收到如下错误:Role

javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: com.storage.entities.Role@246de37e.

内容和原因

如果选择在两个方向上映射关系,则必须将一个方向定义为所有者,另一个方向必须使用 mappedBy 属性来定义其映射 (...)

  • cascade = {CascadeType.MERGE}为正确的级联 JPA 文档添加

级联了 EntityManager.merge() 操作。如果在父项上调用 merge(),则子项也将被合并。这通常应用于依赖关系。请注意,这只会影响合并的级联,关系引用本身将始终被合并。


推荐