DEV Community

Hasebul Hassan Chowdhury
Hasebul Hassan Chowdhury

Posted on

Unidirectional associations for one-to-many

What is a bidirectional relationship in relational database model? let's take a quick look -
Suppose you two table Post and Post_Comment. Now you want to find out which comment are associated with which post or for a post how many comments are associated with that? How you can do that?
Image description

Here is the basic Post entity table

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String title;

    public Post() {

    }
// ignoring setter and getter
}
Enter fullscreen mode Exit fullscreen mode

Here is the basic Post comment entity table

@Entity
public class PostComment {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column
    private String description;

    public PostComment() {

    }
// ignoring setter and getter
Enter fullscreen mode Exit fullscreen mode

Currently, there is no relationship between the Post and PostComment entities. Now, let's establish a unidirectional relationship between them.

Key Cases to Consider:
Post as the Owner (One-To-Many):
This means the Post entity will manage the relationship. Each Post can have multiple PostComment entries.

PostComment as the Owner (Many-To-One):
Here, the PostComment entity will manage the relationship. Each comment will be linked to a single Post.

Let’s start by implementing Case 1: Post as the Owner.

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String title;

    @OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<PostComment> postComments = new ArrayList<>();

    public Post() {

    }

    public void addComment(PostComment comment) {
        postComments.add(comment);
    }

    public void removeComment(PostComment comment) {
        postComments.remove(comment);
    }
// ignoring setter and getter
}
Enter fullscreen mode Exit fullscreen mode

Now what we have done here?
We added a list and telling Post that it can have zero, one or more than one post comment by using @OneToMany. There can be mutiple comment associated with a single Post. we are cascade = CascadeType.PERSIST, orphanRemoval = true in these two later.
@OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<PostComment> postComments = new ArrayList<>();

we also added two method which will make our life easier to add/remove comment

        public void addComment(PostComment comment) {
        postComments.add(comment);
    }

    public void removeComment(PostComment comment) {
        postComments.remove(comment);
    }
Enter fullscreen mode Exit fullscreen mode

Okay, we are almost done with our entity design (Not fully done). Let's take a look how we can save/persist a post with a comment.

@Service
public class PostService {

    @Autowired
    EntityManager eManager;

    @Transactional
    public void addPost1() {
        Post post = new Post();
        post.setTitle("Hibernate in action is quite good");

        PostComment comment = new PostComment();
        comment.setDescription("Recommended");

        post.addComment(comment);

        eManager.persist(post);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we are creating a post and then setting the title and adding a comment. Then calling eManager.persist(post) which will save the post and its associated comment. Easy, right!!!. Lets take a look at the database (H2) :

Image description

We can see a Post table which have only one row with id and title where id is 1 and title is "Hibernate in action is quite good".
Now lets look at post comment table :

Image description
We have a row with an id of 1 and a description as Recommended. Great! But what is the POST_POST_COMMENTS table? We didn’t explicitly define it in our codebase.

Here’s the explanation:
When we set up a one-to-many relationship between Post and Comment, each comment is linked to a Post using a post_id. This creates a mapping between post_id and post_comment_id. But where is this mapping stored?

Hibernate automatically creates a join table, combining the names of the two entities/tables (POST and POST_COMMENTS). This table holds two columns:

The primary key (POST_ID) from the Post table.
The primary key (POST_COMMENT_ID) from the Post_Comments table.
In this way, Hibernate manages the relationship without requiring us to define this table explicitly in the code..

Join Table

Can we improve that? An extra table is an extra burden.yes we can. lets see how we can do that?

@OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> postComments = new ArrayList<>();

Enter fullscreen mode Exit fullscreen mode

@JoinColumn(name = "post_id") This join column annotation tells that we don't need any extra table for mapping. Instead, POST_COMMENT table will handle it. There will be an extra column named "post_id" in POST_COMMENT table, which will store the associated post id for that comment, but good things is JPA/hibernate will do that on its own. We don't need to change our post comment entity.

POST_COMMENT table with POST_ID

lets add another comment in post with id 1.

@Transactional
    public void addCommentOnPost() {
        Post post = eManager.find(Post.class, 1);

        PostComment comment = new PostComment();
        comment.setDescription("this book is also for beginner");
        post.addComment(comment);

        eManager.persist(post);
    }
Enter fullscreen mode Exit fullscreen mode

Image description

Now lets try to fetch those comments.

@Transactional
    public void findCommentByPostId() {
        Post post = eManager.find(Post.class, 1);
        System.out.println("Number of comment " + post.getPostComments().size());
        System.out.println(post.getPostComments());
    }
/*
Number of comment 2
[
PostComment [id=1, description=Recommended], 
PostComment [id=2, description=this book is also for beginner]
]
*/
Enter fullscreen mode Exit fullscreen mode

How JPA/Hibernate fetches comments:
When you call post.getPostComments(), JPA/Hibernate queries the POST_COMMENT table. It checks the post_id column for entries that match the post_id (in our case, post_id=1). It then retrieves all matching rows and maps them to PostComment objects. That's how both comments associated with the post are fetched and returned

orphanRemoval = true
This ensures that if a PostComment is removed from the postComments list, it is automatically deleted from the database. Essentially:

  1. If a comment is no longer associated with a Post, it becomes an orphan and is removed from the database.
Post post = entityManager.find(Post.class, 1);
PostComment comment = post.getPostComments().get(0);

post.getPostComments().remove(comment);
Enter fullscreen mode Exit fullscreen mode

Without orphanRemoval = true, the PostComment would still exist in the database even if it is removed from the list, potentially leading to orphaned rows.

CascadeType.PERSIST
This means that whenever you persist a Post entity, all associated PostComment entities will also be automatically persisted. In other words:

  1. You don't need to explicitly save the comments.
  2. As long as they are added to the postComments list and the Post is persisted, Hibernate will ensure the comments are saved to the database.
Post post = new Post();
PostComment comment = new PostComment();
comment.setDescription("A new comment");

post.getPostComments().add(comment);

entityManager.persist(post);

Enter fullscreen mode Exit fullscreen mode

Without CascadeType.PERSIST, you would need to explicitly persist each PostComment

Top comments (0)