简体   繁体   中英

Java JPA manytomany join tables

I have one table for person "id, gender, first_name,...".

The persons who should meet are saved in a second table named linked. A person can meet with another person only once. This table has the columns id1, id2, status and comment. Id1 and id2 are the IDs from the table person and here foreign keys, and both together are the primary key of the table linked.

I would like to join the linked data in the Java class Person.

But I don't know how to create the join, because the ID of the person can be contained in ID1 or in ID2.

Example:

  • Person with ID 1 has joined with persons with IDs 2 and 3.
  • Person with ID 2 has also met with person 3
Person
|ID|GENDER|FIRSTNAME|
|1 | m    | name1   |
|2 | w    | name2   |
|3 | m    | name3   |

Linked
|ID1|ID2|status|
|1  | 2 | xy   |
|1  | 3 | abc  |
|2  | 3 | xyz  |

For the person 1 I want IDs 2 and 3. For the person 2 I want the IDs 1 and 3.

SQL like:

select * from linked where id1=2 or id2=2

Result:
|ID1|ID2|status|
|1  | 2 | xy   |
|2  | 3 | xyz  |
Class Person

@ManyToMany
@JoinTable(name="linked",
            joinColumns={@JoinColumn
private List<Linked> linked;

You should have three tables:

person ( id , gender , first_name )

meet ( id , status , comment )

linked ( meet_id , person_id )

and then you can use ManyToMany like this:

  1. Person:
    @ManyToMany(mappedBy = "persons")
    Set<Meet> meets;
  1. Meet:
@JoinTable(
  name = "linked", 
  joinColumns = @JoinColumn(name = "meet_id"), 
  inverseJoinColumns = @JoinColumn(name = "person_id"))
  Set<Person> persons;

By this way, if later a meet can have multiple person , you can just add more record to the linked table, instead of add more columns.

This relation looks like many to many recursive relationship where many persons can meets other persons.

多对多递归关系

The easiest way to implement this relation is using @ManyToMany with @JoinTable annotations but the problem with this implementation is you can't attaching status and comment attributes to the generated table.

To implement this relationship clearly you must follow the following steps:-

1- create a composite key class represents the composition of ID1 and ID2 keys

@Embeddable
public class MeetId implements Serializable {

    @Column(name = "ID1")
    private int firstPersonId;

    @Column(name = "ID2")
    private int secondPersonId;

    public MeetId() {}

    public MeetId(int firstPersonId, int secondPersonId) {
        this.firstPersonId = firstPersonId;
        this.secondPersonId = secondPersonId;
    }

    public int getFirstPersonId() {
        return firstPersonId;
    }

    public void setFirstPersonId(int firstPersonId) {
        this.firstPersonId = firstPersonId;
    }

    public int getSecondPersonId() {
        return secondPersonId;
    }

    public void setSecondPersonId(int secondPersonId) {
        this.secondPersonId = secondPersonId;
    }
}

2- create meet class to represent meets relation.

@Entity
@Table(name = "meet")
public class Meet {

    @EmbeddedId
    private MeetId id = new MeetId();

    @MapsId("firstPersonId")
    @ManyToOne
    @JoinColumn(name = "ID1")
    private Person id1;

    @MapsId("secondPersonId")
    @ManyToOne
    @JoinColumn(name = "ID2")
    private Person id2;

    private String status;

    private String comment;

    public Meet() {}

    public Meet(Person id1, Person id2) {
        this.id1 = id1;
        this.id2 = id2;
    }

    public Meet(Person id1, Person id2, String status) {
        this.id1 = id1;
        this.id2 = id2;
        this.status = status;
    }

    public Meet(Person id1, Person id2, String status, String comment) {
        this.id1 = id1;
        this.id2 = id2;
        this.status = status;
        this.comment = comment;
    }

    public Person getId1() {
        return id1;
    }

    public void setId1(Person id1) {
        this.id1 = id1;
    }

    public Person getId2() {
        return id2;
    }

    public void setId2(Person id2) {
        this.id2 = id2;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }
}

3- create person entity and make a relation between it and meet entity

@Entity
@Table(name = "person")
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "first_name")
    private String firstName;

    private char gender;

    @OneToMany(mappedBy = "id1", cascade = CascadeType.ALL)
    private List<Meet> meets;

    public Person() {}

    public Person(String firstName, char gender) {
        this(0, firstName, gender);
    }

    public Person(int id, String firstName, char gender) {
        this.id = id;
        this.firstName = firstName;
        this.gender = gender;
        this.meets = new LinkedList<>();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public List<Meet> getMeets() {
        return meets;
    }

    public void setMeets(List<Meet> meets) {
        this.meets = meets;
    }

    public void addMeet(Person person, String status, String comment) {
        meets.add(new Meet(this, person, status, comment));
    }

    public boolean removeMeet(Person person) {
        return meets.stream()
                .filter(meet -> meet.getId2().getId() == person.getId())
                .findFirst()
                .map(meets::remove)
                .orElse(false);
    }
}

After performing the above steps, you will have correctly presented the relationship in jpa.

Now let's create the CRUD methods:-

1- to store persons

public void addPerson(Person person) {
    entityManager.getTransaction().begin();
    entityManager.persist(person);
    entityManager.getTransaction().commit();
}

2- to add meet

public void addMeet(int personId1, int personId2, String status, String comment) {
    entityManager.getTransaction().begin();

    Person person1 =  entityManager.find(Person.class, personId1);
    Person person2 =  entityManager.find(Person.class, personId2);
    person1.addMeet(person2, status, comment);

    entityManager.getTransaction().commit();
}

3- to find all meets by id where ID1=id or ID2=id Using CriteriaQuery

private static List<Meet> findAllMeetsWhereID1EqualsOrID2Equals(int personId) {
    CriteriaBuilder cBuilder = entityManager.getCriteriaBuilder();

    CriteriaQuery<Meet> linkedCriteria = cBuilder.createQuery(Meet.class);
    Root<Meet> linkedRoot = linkedCriteria.from(Meet.class);

    linkedCriteria.select(linkedRoot).where(cBuilder.or(
            cBuilder.equal(linkedRoot.get("id1"), personId),
            cBuilder.equal(linkedRoot.get("id2"), personId)
    ));
    return entityManager.createQuery(linkedCriteria).getResultList();
}

4- to find all meets by id where ID1=id or ID2=id using createNativeQuery method

private static List<Meet> findAllMeetsWhereID1EqualsOrID2Equals(int personId) {
    String sql = "select * from meet where ID1=:id or ID2=:id";
    Query query = entityManager.createNativeQuery(sql, Meet.class);
    query.setParameter("id", personId);
    return query.getResultList();
}

Check this link to get more information's about Many to Many using a composite key

First of all, thank you very much for the answers. They have definitely improved my understanding of the subject. However, I have not described my problem correctly I just realized.

Maybe I have a data modelling problem. Actually I want to map the following:

  • The data model is needed for a software in which people are to be paired up.
  • That means, for a person a suitable partner is searched (this is done by a third person, the admin of the software).
  • If a suitable partner is found, these persons are in the status "meet".
  • A person can only be in the phase "meet" with one person
  • After some days/weeks the admin gets a feedback from the persons: -- they have broken off the relationship (then the status "meet" must be removed for these persons -- they want to keep the relationship and are thus paired (these persons are no longer available for the software and are historical data)

For further meets it is relevant to know with which persons a person already had a "meet" phase.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM