How to handle bidirectional relationships when con

2019-04-10 09:41发布

问题:

I want to model the relationship between two entities, a group and an account with JPA/Hibernate. An account can have several groups, but not vice versa, so we have a OneToMany relationship between account and group. My working colleague suggested to model the entities Account and Group like

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;
    }
}

and

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;
    }
}

My question is now about the usage of the helper method addToAccount in the constructor of Group. According to my working colleague this method is necessary because we need to update the bidirectional relationship between the two entities from both sides to ensure a consistent memory model of the two entities.

However I believe calling the method addToAccount in the constructor is not a good idea, because

  1. The List of Groups is lazily fetched, so calling the method addToAccount needs an open transaction. So the constructor of Group can only be called inside an open transaction. In my opinion this is a very annoying restriction.

  2. The Account object given as argument to the constructor of Group is changed by the constructor. In my opinion, this is an surprising side effect of the Group constructor and should not happen.

My suggestion was to better use a simple constructor like

 public Group(String name, Account account) {
            this.name = name;
            this.account = account;
        }

and deal with the bidirectional relationship manually. But maybe I'm wrong. Is there a common way how one should handle bidirectional relationships when constructing hibernate entities?

回答1:

In our projects we usually try to avoid bidirectional associations.

One reason is that you have a cycle in your model that may create problems if you want to somehow serialize it, for example let's say you want to serialize an Account and your serialization algorithm is not smart enough you end up with an infinite loop (because Group has a reference back to the Account).

Second reason is that I find it clearer having only one way to navigate the model. What I usually do is to remove the OneToMany association in the Account entity and use a repository call when I need to collect all the Groups for a specific Account (but this probably depends on your use case and personal taste).

Third, if you get rid of the addToAccount method and you use field access you can make your classes immutable that is a good thing.



回答2:

In my experience you're doing it exactly as it is commonly done. My question would be more about the structure (I expect there is a lot more going on than the sample provided above) as to why you want to manipulate the Account directly from the Group.

I also question whether this is a OneToMany or a ManyToMany situation (usually multiple accounts can belong to a single group and multiple groups can belong to a single account but it is all in the semantics of your particular accounting scheme...) anyway: you're doing it right, and though I question (in this exact case) why you would want to directly manipulate the account (unless it is Lazily loaded) this is entirely fine.

[You may need to add some Cascade rules so that it persists properly depending on your configuration.]

You should note that by mapping to the Account you have effectively added it to the List of the Account. When the database next queries to create that list it will populate the list by finding the references from the Account entity.

In short->

public void Group.setAccounts(Account a)
{
  this.account = a;
}

is effectively equivalent to what you are doing above. The database will query and populate the List with something akin to:

//Pseudo SQL
    SELECT g.id FROM Group g WHERE g.account_id = :account_id

Thus, aside from the lazy load (something you may or may not want) adding to the groups is unnecessary as the List is defined by the query.

(Don't make it too hard, it look simple. I hope the long explanation gives you an idea of what's happening in the JPA)