JPA持久化多对多关系

8
我有一个标准的情况,其中我有一个用户表,用户id作为主键,还有一个角色表,角色id作为主键。两个表通过多对多关系相关联(即,用户可以有多个角色,一个角色可以分配给多个用户),因此我有一个连接表称为users_has_roles。users_has_roles中只有两列,users_user_id和roles_role_id。
我已经生成了实体类(见下文),并且我没有问题将数据持久化到用户和角色表中,但是我无法将任何内容持久化到users_has_roles连接表中,因此当前没有用户被分配角色。在我发疯之前,有人可以告诉我如何添加一个具有相应roles_role_id的users_user_id到users_has_roles表中,以便我的用户可以拥有角色吗?
我的Users.java实体类:
@Entity
@Table(name = "users")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "Users.findAll", query = "SELECT u FROM Users u"),
@NamedQuery(name = "Users.findByUserId", query = "SELECT u FROM Users u WHERE u.userId = :userId"),
@NamedQuery(name = "Users.findByUsername", query = "SELECT u FROM Users u WHERE u.username = :username"),
@NamedQuery(name = "Users.findByPassword", query = "SELECT u FROM Users u WHERE u.password = :password")})
public class Users implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 60)
@Column(name = "user_id")
private String userId;
@Basic(optional = false)
@NotNull
@Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")
@Size(min = 1, max = 45)
@Column(name = "username")
private String username;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 120)
@Column(name = "password")
private String password;
@JoinTable(name = "users_has_roles", joinColumns = {
    @JoinColumn(name = "users_user_id", referencedColumnName = "user_id")}, inverseJoinColumns = {
    @JoinColumn(name = "roles_role_id", referencedColumnName = "role_id")})
@ManyToMany
private Collection<Roles> rolesCollection;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "usersUserId")
private Collection<UserAccount> userAccountCollection;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "usersUserId")
private Collection<UserDetails> userDetailsCollection;

...

All the getter and setter methods etc.

我的Roles.java实体类:

@Entity
@Table(name = "roles")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "Roles.findAll", query = "SELECT r FROM Roles r"),
@NamedQuery(name = "Roles.findByRoleId", query = "SELECT r FROM Roles r WHERE r.roleId = :roleId"),
@NamedQuery(name = "Roles.findByRoleName", query = "SELECT r FROM Roles r WHERE r.roleName = :roleName"),
@NamedQuery(name = "Roles.findByRolePermission", query = "SELECT r FROM Roles r WHERE r.rolePermission = :rolePermission"),
@NamedQuery(name = "Roles.findByRoleDescription", query = "SELECT r FROM Roles r WHERE r.roleDescription = :roleDescription")})
public class Roles implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 60)
@Column(name = "role_id")
private String roleId;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 45)
@Column(name = "role_name")
private String roleName;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 45)
@Column(name = "role_permission")
private String rolePermission;
@Size(max = 45)
@Column(name = "role_description")
private String roleDescription;
@ManyToMany(mappedBy = "rolesCollection")
private Collection<Users> usersCollection;

...

All the getter and setter methods etc.

Thanks

---- UPDATE ----

// New Users
Users currentUser = new Users();
currentUser.setUserId(userId);
currentUser.setUsername(email);
currentUser.setPassword(password);
getUsersFacade().create(currentUser);
2个回答

9

首先感谢Mikko帮我找到了答案。我想发表一个可能直接对其他人有帮助的答案。此外,这是基于一个顿悟时刻,所以它可能不是技术上正确的,但这就是我的看法。

我面临的最大问题是,在MySQL中,我可以将桥接表视为单独的表!(抱歉,我不能发布我的EER图像,但我目前似乎没有足够的权限)。所以我认为Java也会将桥接表视为表!但实际上不是这样的。那个桥接表在Java中并不存在一个常规的表,而是由你与之关联的相反表的集合类型来表示。

对我来说最容易理解的方法是完全忘记桥接表,专注于两个“真实”的表,并将其中的数据相关联。以下代码不是最佳实践,因为我只是设置了role_id,但这很好地说明了我的观点。

List<Roles> userRoleList = new ArrayList<Roles>();

Users currentUser = new Users();
currentUser.setUserId(userId);
currentUser.setUsername(email);
currentUser.setPassword(password);

Roles userRole = new Roles();
userRole.setRoleId("2");

userRoleList.add(userRole);
currentUser.setRolesCollection(userRoleList);

getUsersFacade().create(currentUser);

希望这能帮助到其他遇到多对多关系问题的人。
(注:我已编辑原始代码,使用List而非Collection以方便使用,但你同样可以使用其他适合你需求的类型。)

6
你的示例在EclipseLink 2.3和MySQL中运行良好。出现问题的可能是你未展示的代码部分,比如向rolesCollection添加元素。典型的错误是仅将元素添加到非拥有方。
要持久化它,你必须关注拥有方(没有mappedBy的那一方)的关系,在内存中保持对象图与数据库一致,你应该始终修改关系的两个方向。
我尝试了以下内容:
    // method to add element to rolesCollection
    public void addRoles(Roles r) {
        rolesCollection.add(r);
    }

    //first created new instances of Users and Roles
    // then:
    tx.begin();
    users.addRoles(r);
    //it is not needed for persisting, but here you can also
    //add user to roles.
    em.persist(users);
    em.persist(r);
    tx.commit();

1
嗨,米科,我仍然很困惑!我不明白上面的代码是如何工作的。如果我需要修改关系的两侧,我是否需要向rolesCollection和usersCollection都添加元素?看起来你只是在rolesCollection中添加了。另外,我还不清楚我要向各自的Collections中添加什么。rolesCollection是否需要users_user_id和roles_role_id两个属性? - tarka
事情是这样的:为了使它被持久化到数据库中,最低要求是修改拥有者方(没有mappedBy的那个)。坏处是,与mappedBy相关的集合会失去同步。因此,通常最好同时修改双方,这意味着向rolesCollection和rolesCollection两个角色都添加元素。 - Mikko Maunu
我就是理解不了这个!我不想添加一个新角色,我只想添加一个新用户,然后将该用户与已经存在的角色关联起来。如果我不添加 role_id,那么如何将新用户与现有角色关联起来呢?抱歉,我真的很蠢! - tarka
1
您无需添加新角色,流程与新/现有角色类似。您不需要使用ID将实体连接到彼此,而是通过将它们添加到集合并合并/持久化更改来将实体连接到其他实体。1)创建新用户2)从数据库中获取现有角色3)按照之前的说明将它们连接起来。4)合并/持久化更改。 - Mikko Maunu
抱歉,我真的看不到它,也许是因为今天是周日!你能不能在我失去意志之前给我展示一下?注意:我似乎没有足够的权限将其移动到聊天中。 - tarka
显示剩余2条评论

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