Method 1: Join Table
In a join table, the combination of the foreign keys will be its composite primary key.
Example 1: User and Role
User.java
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "test_users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_id")
private int id;
@Column(name = "username")
@Length(min = 5, message = "*Your user name must have at least 5 characters")
@NotEmpty(message = "*Please provide a user name")
private String username;
@Column(name = "email")
@Email(message = "*Please provide a valid Email")
@NotEmpty(message = "*Please provide an email")
private String email;
@Column(name = "password")
@Length(min = 5, message = "*Your password must have at least 5 characters")
@NotEmpty(message = "*Please provide your password")
private String password;
@Column(name = "firstName")
@NotEmpty(message = "*Please provide your name")
private String firstName;
@Column(name = "lastName")
@NotEmpty(message = "*Please provide your last name")
private String lastName;
@Column(name = "active")
private Boolean active = true;
@ManyToMany(cascade = CascadeType.MERGE)
@JoinTable(
name = "test_users_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role r : roles) {
authorities.add(new SimpleGrantedAuthority(r.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Role.java
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "test_roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "role_id")
private int id;
@Column(name = "name")
private String name;
}
Then, we would have 3 tables, test_users, test_roles
test_users | |
test_roles | |
test_users_roles | role_id, user_id |
Example 2: Student likes courses
Use @ManyToMany only on both tables
@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;
}
More concise version
@Entity
class Student {
@Id
Long id;
@ManyToMany
@JoinTable(
name = "students_like_courses",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
Set<Course> likedCourses;
}
@Entity
class Course {
@Id
Long id;
@ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;
}
Example 3, the relationship itself has an attribute.
@Entity
class Student {
@Id
Long id;
@OneToMany(mappedBy = "course")
Set<CourseRating> ratings;
}
@Entity
class Course {
@Id
Long id;
@OneToMany(mappedBy = "course")
Set<CourseRating> ratings;
}
@Embeddable
class CourseRatingKey implements Serializable {
@Column(name = "student_id")
Long studentId;
@Column(name = "course_id")
Long courseId;
}
@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;
}
Since it’s an entity, it’ll have its own primary key.
@Entity
class Student {
@Id
Long id;
@OneToMany(mappedBy = "student")
Set<CourseRegistration> registrations;
}
@Entity
class Course {
@Id
Long id;
@OneToMany(mappedBy = "course")
Set<CourseRegistration> registrations;
}
@Entity
class CourseRegistration {
@Id
Long id;
@ManyToOne
@JoinColumn(name = "student_id")
Student student;
@ManyToOne
@JoinColumn(name = "course_id")
Course course;
LocalDateTime registeredAt;
int grade;
}