如何持久化@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 = new ArrayList(); // 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 (…)来检查UserRoles是否存在。 如果存在 – >将此UserRoles添加到用户列表(id +角色名称),否则我创建一个新的(仅角色名称)。

当我尝试持久化第二个用户时,我获得以下exception: “分离的实体持久存在:….. UserRoles” (可能因为getUserRoleByName在另一个事务中执行)

如果我不使用getUserRoleByName (仅* new UserRoles(“ROLE_USER”); *),我将获得以下exception: “… ConstraintViolation:’ROLE_NAME’的重复条目……”

那么,如何使用@ManyToMany关系正确地持久化实体?

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

只有在传递已将主键设置为持久的对象时,才会发生分离实体持久exception。

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

也不要使用ArrayList ,使用HashSetArrayList允许重复。

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

基本上我面临的是一种情况,当我有一个表是某种CONSTANT值。 而另一个会改变,但它应该映射( many to many )到那些CONSTANTS

确切的问题是USERS ,它是ROLES

图

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 roles = new HashSet<>(); 

作用

 @Entity @Table(name = "ROLE") public class Role { @Id @Enumerated(value = EnumType.STRING) private RoleEnum name; @ManyToMany(mappedBy = "roles") private Set users = new HashSet<>(); 

用法

此设置将轻松添加/删除User Role 。 只需传递一个数组,fe: user.getRoles().add(new Role("ADMIN"));merge user删除通过传递空列表。

如果您在将Role添加到用户之前忘记添加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. 

什么和为什么

  • 如JPA Docs中所述, mappedBy属性被添加到mappedBy

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

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

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

(可能因为getUserRoleByName是在另一个事务中执行的)

这似乎是问题,在同一个事务/实体管理器中进行查询。 否则使用find()在当前事务中重新找到它。