Как обрабатывать двунаправленные отношения при построении спящих объектов?
Я хочу смоделировать отношения между двумя объектами, группой и учетной записью в JPA/Hibernate. Учетная запись может иметь несколько групп, но не наоборот, поэтому у нас есть связь OneToMany между учетной записью и группой. Мой рабочий коллега предложил смоделировать объекты Account
а также Group
лайк
public class Account {
private List<Group> groups = new ArrayList<Group>();
public Account() {}
public void setGroups(List<Group> usergroups) {
this.groups = groups;
}
@OneToMany(mappedBy = "account")
public List<Group> getGroups() {
return groups;
}
}
а также
public class Group {
private String name;
private Account account;
public Group() {}
public Group(String name, Account account) {
this.name = name;
addToAccount(account);
}
public void addToAccount(Account account) {
setAccount(account);
List<Group> accountGroups = account.getGroups();
accountGroups.add(this);
}
@ManyToOne
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}
Мой вопрос сейчас об использовании вспомогательного метода addToAccount
в конструкторе Group
, По словам моего коллеги по работе, этот метод необходим, потому что нам необходимо обновить двунаправленные отношения между двумя объектами с обеих сторон, чтобы обеспечить согласованную модель памяти этих двух объектов.
Однако я считаю, что вызов метода addToAccount
в конструкторе не очень хорошая идея, потому что
List
изGroup
s лениво выбирается, поэтому вызов методаaddToAccount
нужна открытая транзакция. Так что конструкторGroup
может вызываться только внутри открытой транзакции. На мой взгляд, это очень раздражающее ограничение.Account
объект, указанный в качестве аргумента для конструктораGroup
изменено конструктором. На мой взгляд, это удивительный побочный эффектGroup
конструктор и не должен случиться.
Мое предложение заключалось в том, чтобы лучше использовать простой конструктор, как
public Group(String name, Account account) {
this.name = name;
this.account = account;
}
и справиться с двусторонними отношениями вручную. Но, возможно, я ошибаюсь. Есть ли общий способ обработки двунаправленных отношений при построении спящих сущностей?
2 ответа
В наших проектах мы обычно стараемся избегать двунаправленных ассоциаций.
Одна из причин заключается в том, что в вашей модели есть цикл, который может создавать проблемы, если вы хотите каким-либо образом его сериализовать, например, допустим, вы хотите сериализовать Account
и ваш алгоритм сериализации не достаточно умен, вы в конечном итоге с бесконечным циклом (потому что Group
имеет ссылку на Account
).
Вторая причина состоит в том, что мне кажется более понятным использование только одного способа навигации по модели. Обычно я удаляю связь OneToMany в учетной записи и использую вызов хранилища, когда мне нужно собрать все Group
с для конкретного Account
(но это, вероятно, зависит от вашего варианта использования и личного вкуса).
В-третьих, если вы избавляетесь от метода addToAccount и используете доступ к полям, вы можете сделать свои классы неизменяемыми, и это хорошо.
По моему опыту, вы делаете это именно так, как это обычно делается. Мой вопрос был бы больше о структуре (я полагаю, что происходит намного больше, чем в приведенном выше примере) о том, почему вы хотите манипулировать Учетной записью непосредственно из Группы.
Я также спрашиваю, является ли это ситуацией OneToMany или ManyToMany (обычно несколько учетных записей могут принадлежать к одной группе, а несколько групп могут принадлежать к одной учетной записи, но это все в семантике вашей конкретной схемы учета...) в любом случае: вы Вы делаете все правильно, и хотя я спрашиваю (в этом конкретном случае), почему вы хотите напрямую манипулировать учетной записью (если она не загружена Lazily), это совершенно нормально.
[Возможно, вам придется добавить некоторые правила каскадирования, чтобы они сохранялись должным образом в зависимости от вашей конфигурации.]
Следует отметить, что, сопоставив учетную запись, вы фактически добавили ее в список учетных записей. Когда база данных в следующий раз запрашивает создание этого списка, она заполняет список, находя ссылки из сущности Account.
Короче ->
public void Group.setAccounts(Account a)
{
this.account = a;
}
эффективно эквивалентно тому, что вы делаете выше. База данных будет запрашивать и заполнять список чем-то похожим на:
//Pseudo SQL
SELECT g.id FROM Group g WHERE g.account_id = :account_id
Таким образом, кроме ленивой загрузки (что-то, что вы можете или не хотите), добавление в группы не требуется, поскольку список определяется запросом.
(Не делайте это слишком сложно, это выглядит просто. Я надеюсь, что длинное объяснение даст вам представление о том, что происходит в JPA)