JamieRhys
JamieRhys

Reputation: 344

Creating a many to many relationship in spring boot

I'm trying to create a many to many relationship but I don't think I'm quite understanding the process here. This is what I have so far:

Asset.java

@Entity
@Table(name = "table_assets")
public class Asset {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "asset_id")
    private long assetId;

    ...
    @ManyToMany
    @JoinTable(
        name = "previous_users",
        joinColumns = @JoinColumns(name = "contact_id_mapping", referencedColumnName = "contact_id"),
        inverseJoinColumns = @JoinColumn(name = "asset_id", referencedColumnName = "asset_id")
    )
    @Column(name = "asset_previous_users")
    private Set<Contact> previousUsers;
    ...
}

Contact.java

@Entity
@Table(name = "table_contacts")
public class Contact {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     @ManyToMany(mappedBy = "previous_users")
     private long contactId;
     ...
}

When I try to build the project, I get the following error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: com.jre.assetregister.database.entities.Contact.contactId

Not sure what I'm doing wrong here as this is the first time of trying to do this type of relationship.

Thanks.

Upvotes: 3

Views: 14546

Answers (1)

Senthil
Senthil

Reputation: 2246

When you use @ManyToMany Annotation, you can use the Set / List

example :

@ManyToMany
    Set<Course> likedCourses;

But you can't use the non Collection data structure like Long / Other Scalar types

Note: Many to Many can be accomplished in three ways

  1. Modeling a Many-to-Many Relationship : A relationship is a connection between two types of entities. In the case of a many-to-many relationship, both sides can relate to multiple instances of the other side.

NOTE: we need to create a separate table to hold the foreign keys for holding the many to many association in all these approaches

Example :

@Entity
class Student {

    @Id
    Long id;

    @ManyToMany
    Set<Course> likedCourses;

    // additional properties
    // standard constructors, getters, and setters
}

@Entity
class Course {

    @Id
    Long id;

    @ManyToMany
    Set<Student> likes;

    // additional properties
    // standard constructors, getters, and setters
}

OR We can do this with the @JoinTable annotation in the Student class.

@ManyToMany
@JoinTable(
  name = "course_like", 
  joinColumns = @JoinColumn(name = "student_id"), 
  inverseJoinColumns = @JoinColumn(name = "course_id"))
Set<Course> likedCourses;

On the target side, we only have to provide the name of the field, which maps the relationship.

@ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;
  1. Many-to-Many Using a Composite Key

Let's say we want to let students rate the courses. A student can rate any number of courses, and any number of students can rate the same course. Therefore, it's also a many-to-many relationship.

What makes this example a bit more complicated is that there is more to the rating relationship than the fact that it exists. We need to store the rating score the student gave on the course.

Where can we store this information? We can't put it in the Student entity since a student can give different ratings to different courses. Similarly, storing it in the Course entity wouldn't be a good solution either.

Creating a Composite Key in JPA. (Used to create the LOOKUP table which holds the many to many association in a separate table).

@Embeddable
class CourseRatingKey implements Serializable {

    @Column(name = "student_id")
    Long studentId;

    @Column(name = "course_id")
    Long courseId;

    // standard constructors, getters, and setters
    // hashcode and equals implementation
}

Using a Composite Key in JPA

@Entity
class CourseRating {

    @EmbeddedId
    CourseRatingKey id;

    @ManyToOne
    @MapsId("studentId")
    @JoinColumn(name = "student_id")
    Student student;

    @ManyToOne
    @MapsId("courseId")
    @JoinColumn(name = "course_id")
    Course course;

    int rating;
    
    // standard constructors, getters, and setters
}

After this, we can configure the inverse references in the Student and Course entities as before:

class Student {

    // ...

    @OneToMany(mappedBy = "student")
    Set<CourseRating> ratings;

    // ...
}

class Course {

    // ...

    @OneToMany(mappedBy = "course")
    Set<CourseRating> ratings;

    // ...
}
  1. Many-to-Many With a New Entity Let's say we want to let students register for courses. Also, we need to store the point when a student registered for a specific course. On top of that, we want to store what grade she received in the course.

In an ideal world, we could solve this with the previous solution, where we had an entity with a composite key. However, the world is far from ideal, and students don't always accomplish a course on the first try.

In this case, there are multiple connections between the same student-course pairs, or multiple rows, with the same student_id-course_id pairs. We can't model it using any of the previous solutions because all primary keys must be unique. So, we need to use a separate primary key.

Therefore, we can introduce an entity, which will hold the attributes of the registration:

Since the course_registration became a regular table, we can create a plain old JPA entity modeling it:

@Entity class CourseRegistration {

@Id
Long id;

@ManyToOne
@JoinColumn(name = "student_id")
Student student;

@ManyToOne
@JoinColumn(name = "course_id")
Course course;

LocalDateTime registeredAt;

int grade;

// additional properties
// standard constructors, getters, and setters

} We also need to configure the relationships in the Student and Course classes:

class Student {

    // ...

    @OneToMany(mappedBy = "student")
    Set<CourseRegistration> registrations;

    // ...
}

class Course {

    // ...

    @OneToMany(mappedBy = "courses")
    Set<CourseRegistration> registrations;

    // ...
}

Ref: https://www.baeldung.com/jpa-many-to-many

Upvotes: 4

Related Questions