web-dev-qa-db-ja.com

@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を持つ2番目のユーザーを永続化しようとしたときです。 UserRolesが存在するかどうかを確認します(userRolesServices.getUserRoleByName(...))。存在する場合->このUserRolesをユーザーリスト(ID +ロール名)に追加します。そうでない場合は、新しいユーザーロールを作成します(ロール名のみ)。

2番目のユーザーを永続化しようとすると、次の例外が発生します:"永続化するデタッチされたエンティティ:..... UserRoles"(おそらくgetUserRoleByNameが別のトランザクションで実行されるため)

getUserRoleByName(* new UserRoles( "ROLE_USER"); *のみ)を使用しない場合、次の例外が発生します:"。 ..ConstraintViolation: 'ROLE_NAME'の重複したエントリ... "

それで、@ ManyToMany関係でエンティティを適切に永続化する方法は?

15
Aure77

上記の問題について、私はあなたの実体関連カスケードが間違っていると言うでしょう。これを考慮してください:ユーザーは複数の役割を持つことができますが、システムに存在できる役割の数は固定されている可能性があります。したがって、UserエンティティのライフサイクルはUserRolesエンティティのライフサイクルに依存してはならないため、UserエンティティからのCASCADEALLは意味がありません。例えば。 Userを削除しても、UserRolesは削除されません。

永続化するデタッチエンティティ例外は、主キーがすでに永続化するように設定されているオブジェクトを渡した場合にのみ発生します。

カスケードを削除すると、問題は解決されます。決定する必要があるのは、ユーザーロールをどのように挿入するかだけです。私によると、そうするための別個の機能があるはずです。

また、ArrayListは使用せず、HashSetを使用してください。 ArrayListは重複を許可します。

24
Amit Deshpande

誰かが私と作者に同じタイプの問題を抱えている場合、私は私の答えを提供します。

基本的に私が直面していたのは、ある種の[〜#〜]定数[〜#〜]値である1つのテーブルがある状況でした。もう一方は変更されますが、(_many to many_)をそれらの[〜#〜]定数[〜#〜]にマップする必要があります。

正確な問題はUSERSで、それはROLESです。

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<>();
_

使用法

この設定により、RoleUserに簡単に追加/削除できます。配列を渡すだけです。例:user.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.
_

何と理由

  • JPA Docs で説明されているように、mappedBy属性が子エンティティに追加されます

関係を両方向にマッピングすることを選択した場合、一方の方向を所有者として定義し、もう一方の方向をmappedBy属性を使用してそのマッピングを定義する必要があります。 (...)

  • _cascade = {CascadeType.MERGE}_が適切なカスケードに追加されました JPA Docs

EntityManager.merge()操作をカスケードしました。親でmerge()が呼び出されると、子もマージされます。これは通常、依存関係に使用する必要があります。これはマージのカスケードにのみ影響し、関係参照自体は常にマージされることに注意してください。

8
Atais

(おそらくgetUserRoleByNameが別のトランザクションで実行されるため)

それは問題のように思われます。同じトランザクション/エンティティマネージャでクエリを実行してください。それ以外の場合は、find()を使用して現在のトランザクションで再検索します。

0
James