Max Gabderakhmanov
Max Gabderakhmanov

Reputation: 922

Multiple @JoinTable in Spring Boot (JPA)

I have following code (Spring Boot) that works perfectly:

@Getter
@Setter
@ManyToMany(fetch = FetchType.LAZY)
@Column(name = "fight")
@JoinTable(name = "fighter_fight",
        joinColumns = {@JoinColumn(name = "fighter_id")},
        inverseJoinColumns = {@JoinColumn(name = "fight_id")})
private Set<Fight> fights;

it's creating this table:

enter image description here

I need to have three columns. Two fighter_id let's say called first_fighter_id and second_fighter_id and fight_id. When I'm trying to add one more line with like this

 joinColumns = {@JoinColumn(name = "fighter_id")},

I'm getting an error ("Duplicate attribute joinColumns"). How can I have two columns in a table with the same object (Fighter in my case)?

Upvotes: 0

Views: 5742

Answers (1)

K.Nicholas
K.Nicholas

Reputation: 11551

First you should consider whether you are describing the correct schema. If you only have fighter1 and fighter2 then you probably are talking about two ManyToOne relationships like so:

enter image description here

That should be pretty easy to model. If you think you actually have a ManyToMany relationship, perhaps for WWE or something, and you want an additional attribute in the relationship then you need a relationship with an attribute, like so:

enter image description here

This is not an uncommon schema requirement and is realized with two ManyToOne relationships to a new entity, like so:

enter image description here

Simple JPA entities for this require an Embeddable entity to be used as an EmbeddableId like so:

@Entity
@Data
public class Fight {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}
@Entity
@Data
public class Fighter {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}
@Entity
@Data
public class Fights {
    @EmbeddedId
    private FightsPK id;        
    @MapsId("fighterId")
    @ManyToOne
    private Fighter fighter;    
    @MapsId("fightId")
    @ManyToOne
    private Fight fight;        
    private String fId;
}
@Embeddable
@Data
public class FightsPK implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long fighterId;
    private Long fightId;
}

And using it gets a little awkward but is doable:

private void create() {
    Fighter f1 = new Fighter();
    fighterRepo.save(f1);
    Fighter f2 = new Fighter();
    fighterRepo.save(f2);

    Fight f = new Fight();
    fightRepo.save(f);

    Fights f1s = new Fights();
    f1s.setId(new FightsPK());
    f1s.setFighter(f1);
    f1s.setFight(f);
    f1s.setFId("f1");
    fightsRepo.save(f1s);

    Fights f2s = new Fights();
    f2s.setId(new FightsPK());
    f2s.setFighter(f2);
    f2s.setFight(f);
    f2s.setFId("f2");
    fightsRepo.save(f2s);
}

The fId field is a String so you can put anything in there if, for example, a fight organizer needs to put their own designation in the field. You might want to reconsider whether you need the initially described relationship. With only two fighters you can put additional fields in the Fight for fighter1 and fighter1 designations.

Upvotes: 2

Related Questions