Understanding Relationships in JPA: Many-to-Many with Simple Primary Key and Extra Column
Discussing “Many-to-Many with Simple Primary Key and Extra Column” and examining how JPA/Hibernate generates the corresponding tables
As mentioned in the introductory article of this series, I’ve faced difficulties in implementing these relationships correctly in my applications.
To enhance my understanding of them, I developed a GitHub repository called spring-data-jpa-relationships, containing simple examples for each relationship type.
In this series of articles, we will demonstrate each JPA relationship type by presenting the necessary code to map the entities and examining how JPA/Hibernate generates tables.
I hope you find these articles and the GitHub repository helpful.
Today, we will talk about “Many-to-Many with Simple Primary Key and Extra Column”. So, let’s get started!
Many-to-Many with Simple Primary Key and Extra Column
In this example, we will associate Reviewer, Article and Comment. Here, a reviewer can comment zero, one or more articles and an article can be commented by zero, one or more reviewers.
Below is the desired database model we aim to achieve.

This is the complete and final code. Next, we will explain it in detail.
@Entity
@Table(name = "articles")
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private Set<Comment> comments = new LinkedHashSet<>();
@Column(nullable = false)
private String title;
// getters and setters
}
@Entity
@Table(name = "reviewers")
public class Reviewer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToMany(mappedBy = "reviewer", cascade = CascadeType.ALL)
private Set<Comment> comments = new LinkedHashSet<>();
@Column(nullable = false)
private String name;
// getters and setters
}
@Entity
@Table(name = "comments")
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne
@JoinColumn(name = "reviewer_id")
private Reviewer reviewer;
@ManyToOne
@JoinColumn(name = "article_id")
private Article article;
@Column(nullable = false)
private String text;
// getters and setters
}We have created three classes: Article, Reviewer, and Comment, and have annotated them with @Entity to indicate that they correspond to database tables.
To customize the table names, we have used the @Table annotation, specifying “articles” for the Article entity, “reviewers” for the Reviewer entity, and “comments” for the Comment entity.
In the Article class, we have added the id field of type Long and the title field of type String. Similarly, we have added the id field of type Long and the name field of type String to the Reviewer class. Finally, we have added the id field of type Long and the text field of type String to the Comment class.
To make sure that the title field in the Article entity, name field in the Reviewer entity, and text field in the Comment entity always have values, we add the @Column(nullable = false) annotation. This tells JPA to define the corresponding database column as NOT NULL.
To designate the id field in the Article, Reviewer, and Comment entities as their primary key, we add the @Id annotation. Additionally, we include the @GeneratedValue(strategy = GenerationType.SEQUENCE) annotation to specify the strategy for generating the ID.
The @OneToMany annotation is used in Article and Reviewer entities to establish a one-to-many relationship with the Comment entity. This means that an Article or a Reviewer has a Set of Comment objects. It’s represented by the comments field in Article and Reviewer entities.
In Article and Reviewer entities, @OneToMany is used with mappedBy attribute to establish the one-to-many relationship with Comment entity. The cascade attribute is used to define the cascade behavior, which in this case is set to CascadeType.ALL. This means that any changes made to Article or Reviewer entity will be cascaded to the Comment entity.
The Comment entity has @ManyToOne relationships with both the Article and Reviewer entities. The @ManyToOne annotation specifies the name of the field in the Article and Reviewer entities that maps this relationship. In this case, the article field in the Comment entity maps the relationship with the Article entity, and the reviewer field in the Comment entity maps the relationship with the Reviewer entity.
The @JoinColumn annotation in the Comment entity specifies the foreign key column name in the comments table that references the primary key column of the Article and Reviewer entities.
Lastly, let’s describe the tables in Postgres terminal.
jparelationshipsdb=# \d articles
Table "public.articles"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | bigint | | not null |
title | character varying(255) | | not null |
Indexes:
"articles_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "comments" CONSTRAINT "fkk4ib6syde10dalk7r7xdl0m5p" FOREIGN KEY (article_id) REFERENCES articles(id)
jparelationshipsdb=# \d reviewers
Table "public.reviewers"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | bigint | | not null |
name | character varying(255) | | not null |
Indexes:
"reviewers_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "comments" CONSTRAINT "fkrdi78u94731rw7i3vqcn23fot" FOREIGN KEY (reviewer_id) REFERENCES reviewers(id)
jparelationshipsdb=# \d comments
Table "public.comments"
Column | Type | Collation | Nullable | Default
-------------+------------------------+-----------+----------+---------
id | bigint | | not null |
text | character varying(255) | | not null |
article_id | bigint | | |
reviewer_id | bigint | | |
Indexes:
"comments_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"fkk4ib6syde10dalk7r7xdl0m5p" FOREIGN KEY (article_id) REFERENCES articles(id)
"fkrdi78u94731rw7i3vqcn23fot" FOREIGN KEY (reviewer_id) REFERENCES reviewers(id)The articles, reviewers and comments tables have the id column as Primary Key. In the comments table, besides the column text that stores what the reviewer wrote, the has the columns article_id and reviewer_id that are Foreign Keys to the column id in articles and reviewers tables, respectively.
Conclusion
In this series of articles, we are exploring the four different types of relationships in JPA: one-to-one, one-to-many / many-to-one, and many-to-many. By presenting simple examples for each type of relationship and providing necessary code to map the entities, we hope to help developers to gain a better understanding of the JPA relationships.
Support and Engagement
If you enjoyed this article and would like to show your support, please consider taking the following actions:
- 👏 Engage by clapping, highlighting, and replying to my story. I’ll be happy to answer any of your questions;
- 🌐 Share my story on Social Media;
- 🔔 Follow me on: Medium | LinkedIn | Twitter | GitHub;
- ✉️ Subscribe to my newsletter, so you don’t miss out on my latest posts.





