16

JPA/Hibernate (3.5.3) のセットアップで問題が発生しました。このエンティティには、子エンティティのリストである「連絡先」インスタンスを持つ「アカウント」クラスがあります。Contact のインスタンスを Account の List<Contact> プロパティに追加/削除できるようにしようとしています。

セットに新しいインスタンスを追加し、saveOrUpdate(account) を呼び出すと、すべてが適切に保持されます。その後、連絡先をリストから削除して再度 saveOrUpdate を呼び出すと、SQL Hibernate によって account_id 列が null に設定され、データベースの制約に違反しているように見えます。

私は何を間違っていますか?

以下のコードは明らかに単純化された要約ですが、別のコードで同じ結果が見られるので、問題をカバーしていると思います。これは本当に単純なことです。

SQL:

CREATE TABLE account ( INT account_id );
CREATE TABLE contact ( INT contact_id, INT account_id REFERENCES account (account_id) );

ジャワ:

@Entity
class Account {
  @Id
  @Column
  public Long id;

  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinColumn(name = "account_id")
  public List<Contact> contacts;
}

@Entity
class Contact {
  @Id
  @Column
  public Long id;

  @ManyToOne(optional = false)
  @JoinColumn(name = "account_id", nullable = false)
  public Account account;
}

Account account = new Account();
Contact contact = new Contact();

account.contacts.add(contact);
saveOrUpdate(account);

// some time later, like another servlet request....

account.contacts.remove(contact);
saveOrUpdate(account);

結果:

UPDATE contact SET account_id = null WHERE contact_id = ?

編集#1:

これは実際にはバグである可能性があります http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091

編集#2:

うまくいくように見える解決策がありますが、Hibernate API を使用する必要があります

class Account {
    @SuppressWarnings("deprecation")
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "account")
    @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    @JoinColumn(name = "account_id", nullable = false)
    private Set<Contact> contacts = new HashSet<Contact>();
}

class Contact {
    @ManyToOne(optional = false)
    @JoinColumn(name = "account_id", nullable = false)
    private Account account;
}

Hibernate CascadeType.DELETE_ORPHAN は非推奨であるため、JPA2 バージョンに取って代わられたと想定する必要がありますが、実装には何かが欠けています。

4

1 に答える 1

21

いくつかの意見:

  • 双方向の関連付けがあるためmappedBy、関連付けの所有側を宣言する属性を追加する必要があります。
  • また、双方向の関連付けを使用する場合は、リンクの両側を管理する必要があることを忘れないでください。これには、防御的な方法を使用することをお勧めします(以下を参照)。
  • そして、を実装する必要がequalsありhashCodeますContact

したがって、ではAccount、次のようにマッピングを変更します。

@Entity
public class Account {
    @Id @GeneratedValue
    public Long id;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true)
    public List<Contact> contacts = new ArrayList<Contact>();

    public void addToContacts(Contact contact) {
        this.contacts.add(contact);
        contact.setAccount(this);
    }

    public void removeFromContacts(Contact contact) {
        this.contacts.remove(contact);
        contact.setAccount(null);
    }

    // getters, setters
}

Contactで重要なのは、フィールド@ManyToOneoptionalフラグを次のように設定する必要があることfalseです。

@Entity
public class Contact {
    @Id @GeneratedValue
    public Long id;

    @ManyToOne(optional = false)
    public Account account;

    // getters, setters, equals, hashCode

}

これらの変更により、以下が正しく機能します。

Account account = new Account();
Contact contact = new Contact();

account.addToContact(contact);
em.persist(account);
em.flush();

assertNotNull(account.getId());
assertNotNull(account.getContacts().get(0).getId());
assertEquals(1, account.getContacts().size());

account.removeFromContact(contact);
em.merge(account);
em.flush();
assertEquals(0, account.getContacts().size());

そして、Contact期待どおり、孤立したものは削除されます。Hibernate3.5.3-Finalでテスト済み。

于 2010-06-18T15:48:33.333 に答える