8

I've created two JPA entities (Client, InstrumentTraded) using Hibernate as a provider that have a ManyToMany relationship. After letting Hibernate generate the tables for MySQL it appears that the ManyToMany relationship table does not contain primary keys for the two foreign keys. This allows duplicate records in the many-to-many table, which is not the desired result.

Tables generated:

client(id,name)  
instrument_traded(id,name)  
client_instrument_traded(FK client_id, FK instrument_traded_id)

Preferred table:

client_instrument_traded(PK,FK client_id, PK,FK instrument_traded_id)

Entities:

@Entity
public class Client extends AbstractEntity<Integer> {

    private static final long serialVersionUID = 1L;

    @Basic(optional = false)
    @Column(nullable = false, length = 125)
    private String name;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(joinColumns = {
        @JoinColumn}, inverseJoinColumns = {
        @JoinColumn(name = "instrument_traded_id")}, uniqueConstraints =
    @UniqueConstraint(name = "UK_client_instruments_traded_client_id_instrument_traded_id",
    columnNames = {"client_id", "instrument_traded_id"}))
    @ForeignKey(name = "FK_client_instruments_traded_client_id",
    inverseName = "FK_client_instruments_traded_instrument_traded_id")
    private List<InstrumentTraded> instrumentsTraded;

    public Client() {
    }

    public List<InstrumentTraded> getInstrumentsTraded() {
        return instrumentsTraded;
    }

    public void setInstrumentsTraded(List<InstrumentTraded> instrumentsTraded) {
        this.instrumentsTraded = instrumentsTraded;
    }

    ...
}



@Entity
@Table(uniqueConstraints = {
    @UniqueConstraint(name = "UK_instrument_traded_name", columnNames = {"name"})})
public class InstrumentTraded extends AbstractEntity<Integer> {

    private static final long serialVersionUID = 1L;

    @Basic(optional = false)
    @Column(nullable = false, length = 50)
    private String name;

    @ManyToMany(mappedBy = "instrumentsTraded", fetch = FetchType.LAZY)
    private List<Client> clients;

    public InstrumentTraded() {
    }

    public List<Client> getClients() {
        return clients;
    }

    public void setClients(List<Client> clients) {
        this.clients = clients;
    }

    ...

}

After doing some research it looks like the only solution is for mapping a join table with additional columns using @OneToMany @IdClass and a composite primary key class when I don't need additional columns. Is this the only solution besides the one I've included in the code above, which is using a @UniqueConstraint with the two foreign key columns on the @ManyToMany mapping? It seems a bit ridiculous the amount of work needed for a common scenario like this. Thanks!

stites
  • 4,903
  • 5
  • 32
  • 43
dukethrash
  • 1,449
  • 4
  • 15
  • 25
  • http://stackoverflow.com/questions/1212058/how-to-make-a-composite-primary-key-java-persistence-annotation/6344626#6344626 – steveeen Jun 14 '11 at 14:14
  • Also worths to check if you accidently didn't add a record twice, this happened to me, so `list.add(x); list.add(x);` results duplicate in lists. – CsBalazsHungary Oct 30 '14 at 15:58

4 Answers4

20

I had a similar problem. All I did was that I changed the collection type from List to Set:

private List<InstrumentTraded> instrumentsTraded;

to

private Set<InstrumentTraded> instrumentsTraded;

And somehow Hibernate now generates primary keys for the join table.

Antti Sulanto
  • 201
  • 2
  • 4
8

Here's a way to solve the problem:

Client.java:

 @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
 @JoinTable(
        joinColumns = {@JoinColumn(name = "client_id")},
        inverseJoinColumns = {@JoinColumn(name = "instrument_traded_id")},
        uniqueConstraints = {@UniqueConstraint(
            columnNames = {"client_id", "instrument_traded_id"})}
)
 private List<InstrumentTraded> instrumentsTraded;

This is for unidirectional mapping. If you want a bidirectional relationship, change the mapping in InstrumentTraded.class to the same.

Thomas Schmidt
  • 1,248
  • 2
  • 12
  • 37
Shahriar
  • 825
  • 1
  • 15
  • 31
2

Your mapping looks odd (in particular the joinColumn part of the @JoinTable annotation). I would expect something like this:

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
    joinColumns= 
        @JoinColumn(name="CLIENT_ID", referencedColumnName="ID"), 
    inverseJoinColumns=
        @JoinColumn(name="instrument_traded_id", referencedColumnName="ID"),
    uniqueConstraints=
        @UniqueConstraint(
            name="UK_client_instruments_traded_client_id_instrument_traded_id",
            columnNames = {"client_id", "instrument_traded_id"}
        )
)
@ForeignKey(name = "FK_client_instruments_traded_client_id",
inverseName = "FK_client_instruments_traded_instrument_traded_id")
private List<InstrumentTraded> instrumentsTraded;

But unless you want to override the defaults (and I guess you do), I would just skip the @JoinTable.

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • I do want to override the defaults because it pluralizes the the instrument_traded_id (instruments_traded_id). I have a custom naming strategy that adds "_id" if it "_id" doesn't exist on the end of a foreign key. I can remove the joinColumns=@JoinColumn. Do you have a working example of a ManyToMany creating a primary key consisting of two foreign keys in the ManyToMany table? – dukethrash Aug 13 '10 at 14:00
  • @dukethrash: `Do you have a working example of a ManyToMany creating a primary key consisting of two foreign keys in the ManyToMany table?` That's the default behavior if properly annotated. Did you try without the `joinColumn` (or with the above one)? – Pascal Thivent Aug 13 '10 at 14:15
  • Yes I tried yours (with the exception that referencedColumn is actually referencedColumnName) and it is still not generating the primary keys. I've even added the following on InstrumentTraded just to see if that would change anything. @ManyToMany(fetch = FetchType.LAZY, mappedBy = "instrumentsTraded") private List clients; – dukethrash Aug 13 '10 at 14:55
0

Or Split your @ManyToMany into @OneToMany - @ManyToOne relationship See here how you can accomplish this mapping

Community
  • 1
  • 1
Arthur Ronald
  • 33,349
  • 20
  • 110
  • 136