Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should @JoinTable be specified in both sides of a @ManyToMany relationship?

I've an entity Course and an entity User. There's a many-to-many relation ship between course and user, since a course can have many users and a user can be enrolled in many courses. In both entities I've put the @ManyToMany annotation on the specific field, that is, in Course I have:

@ManyToMany
private List<RegisteredUser> members;

and in User I have:

@ManyToMany
private List<Course> coursesTaken;

Now, I know that this kind of many-to-many relationships are usually represented by a third table. I also know that there's the annotation @JoinTable which allows us to do that. What I don't know is if I should add this annotation @JoinTable over both fields in the two different entities or not. By the way, if I need to add to both, the names need to match right?

like image 678
nbro Avatar asked Apr 22 '16 21:04

nbro


3 Answers

It's actually a good question, and it helps to understand the concept of an "owning" entity because neither side needs a @JoinTable annotation. If you want to prevent both sides from having join tables, a good idea, then you need to have a mappedBy= element on one side. The @JoinTable annotation is used to either specify the table name, or the columns that map the association.

First look at the Javadoc for @JoinTable:

Specifies the mapping of associations. It is applied to the owning side of an association.

Whether or not there is a join table is controlled by the mappedBy="name" element of the @ManyToMany annotation. The Javadoc for mappedBy for the ManyToMany annotation says:

The field that owns the relationship. Required unless the relationship is unidirectional.

For your (bidirectional) example in Hibernate (5.0.9.Final), if there were only two @ManyToMany annotations and no mappedBy= element, the default will have two Entity tables and two Join Tables:

Hibernate: create table Course (id bigint not null, primary key (id))
Hibernate: create table Course_Member (Course_id bigint not null, members_id bigint not null, primary key (Course_id, members_id))
Hibernate: create table Member (id bigint not null, primary key (id))
Hibernate: create table Member_Course (Member_id bigint not null, courses_id bigint not null, primary key (Member_id, courses_id))

While this is saying that each Entity "owns" its ManyToMany relationship, the extra join table is redundant in the typical use case. However, if I decide to have the Member entity "own" the relationship, then I add the mappedBy= element to the Course entity to specify that it doesn't own the relationship:

@ManyToMany(mappedBy="courses")
Set<Member> members;

Adding @JoinTable(name="Member_Course") to the Member entity doesn't change anything: it's only naming the table the same as it would have been named anyway.

Since the Course entity no longer owns its ManyToMany relationship, the extra JoinTable will not be created:

Hibernate: create table Course (id bigint not null, primary key (id))
Hibernate: create table Member (id bigint not null, primary key (id))
Hibernate: create table Member_Course (members_id bigint not null, courses_id bigint not null, primary key (members_id, courses_id))

This is important to the developer because he or she must understand that no relationship is persisted unless it's added to the owning entity, in this case the Member entity. However, since this a bidirectional relationship, the developer should be adding both a Course to Member.courses and a Member to Course.members anyway.

So, if you have a bidirectional ManyToMany relationship, which means you have ManyToMany on both entities involved, then you should add a mappedBy="name" on one of them to avoid having a redundant join table. Since it's bidirectional, I don't think it matters which side you make the owning entity. As always, it's always a good idea to enable the sql logs and see what's going on in the database:

References:

What is the difference between Unidirectional and Bidirectional associations?.

What does relationship owner means in bidirectional relationship?.

What is the “owning side” in an ORM mapping?.

Most efficient way to prevent an infinite recursion in toString()?.

like image 186
K.Nicholas Avatar answered Oct 16 '22 11:10

K.Nicholas


You actually CAN use @JoinTable on both sides and often it makes perfect sense! I am talking out of experience after I had been looking for this solution for weeks.

Even though all throughout the internet, blogs and articles tell a different story - and the Javadoc of JPA is easily misunderstood (or wrong) in this way. I tried it after seeing this uncommented example in a book for professionals - and it worked.

How to do it:

Singer-Instrument-Association: Singer side:

@ManyToMany 
@JoinTable(name = "singer_instrument", joinColumns =
@JoinColumn(name = "SINGER_ID"), inverseJoinColumns = @JoinColumn(name = "INSTRUMENT_ID")) 
public Set<Instrument> instruments;

And exactly the same on the other side! Instrument side:

@ManyToMany
@JoinTable(name = "singer_instrument",
joinColumns = @JoinColumn(name = "INSTRUMENT_ID"),
inverseJoinColumns = @JoinColumn(name = "SINGER_ID"))
public Set<Singer> singers;

So, if you address the same join table, "singer_instrument", with the same name, it work's. If you address one join table "singer_instrument" and one join table "instrument-singer" though, it will indead result in two different join tables in the database.

This makes a lot of sense, because a many-to-many relationship has no owning side - seen from the database perspective. Owning side means the side, that owns the foreign key of the relationship. But neither the table "singer" nor "instrument" have a foreign key referring to each other. The foreign keys are inside the neccessary join table between them.

The advantage of @JoinTable on both sides of the relation: Let's say, a singer starts to learn a new instrument: You can add the instrument to singer (and vise versa, as it is bidirectional) and update/merge the singer. The update will update only the singer and the join table. It won't touch the instrument-table.

Now the other case - a guitar-course has ended, so you want to remove the connection between the guitar and the former course-participants/singers: After removing the instrument "guitar" from the singers (and vise versa!), you update/merge the instrument. The update will update only the instrument and the join table. It won't touch the singer-table.

If you had @JoinTable only on one side, you would always have to update/save/delete this side to safely handle the entries in the join table (the relationships between singers and instruments). In this case, you would have to update each singer, who ended the guitar course. That is not reflecting the type of relationship properly and can cause performance issues and conflicts during data transaction.

like image 8
Julia Hinßen Avatar answered Oct 16 '22 09:10

Julia Hinßen


Nope. Both sides get @ManyToMany, but only one has the @JoinTable

More ManyToMany info here

like image 1
Dean Clark Avatar answered Oct 16 '22 11:10

Dean Clark