Как обрабатывать двунаправленные отношения при построении спящих объектов?

Я хочу смоделировать отношения между двумя объектами, группой и учетной записью в 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 в конструкторе не очень хорошая идея, потому что

  1. List из Groups лениво выбирается, поэтому вызов методаaddToAccount нужна открытая транзакция. Так что конструкторGroup может вызываться только внутри открытой транзакции. На мой взгляд, это очень раздражающее ограничение.

  2. 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)

Другие вопросы по тегам