Нарушение спящего режима с помощью orphanRemoval
У меня возникают проблемы с установкой JPA/Hibernate (3.5.3), где у меня есть объект, класс "Учетная запись", в котором есть список дочерних объектов, "Контакт". Я пытаюсь добавить/удалить экземпляры Contact в 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) );
Java:
@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, но в реализации чего-то не хватает.
Ответы
Ответ 1
Некоторые замечания:
- Поскольку у вас есть двунаправленная связь, вам нужно добавить атрибут
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
важная часть состоит в том, что поле @ManyToOne
должно иметь флаг optional
, установленный на 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
будет удален, как и ожидалось. Протестировано с Hibernate 3.5.3-Final.