Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

NPI – JPA Buddy – JPA (partner)
announcement - icon

JPA is huge! It covers nearly every aspect of communication between relational databases and the Java application and is deeply integrated into all major frameworks.

If you're using IntelliJ, JPA Buddy is super helpful. The plugin gently guides you through the subtleties of the most popular JPA implementations, visually reminds you of JPA features, generates code that follows best practices, and integrates intelligent inspections to improve your existing persistence code.

More concretely, it provides powerful tooling to generate Spring Data JPA repositories and methods, Flyway Versioned Migrations, Liquibase Differential Changelogs, DDL and SQL statements, DTO objects, and MapStruct interfaces.

Oh, and it actually generates JPA entities from an existing database and gradually update the data model as the database evolves! Yeah.

>> Become a lot more productive with JPA Buddy

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

November Discount Launch 2022 – TEMP TOP (NPI)
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

1. Overview

Using an Object Relational Mapping tool, like Hibernate, makes it easy to read our data into objects, but can make forming our queries difficult with complex data models.

The many-to-many relationship is always challenging, but it can be more challenging when we wish to acquire related entities based on some property of the relation itself.

In this tutorial, we are going to look at how to solve this problem using Hibernate's @WhereJoinTable annotation.

2. Basic @ManyToMany Relation

Let's start with a simple @ManyToMany relationship. We'll need domain model entities, a relation entity, and some sample test data.

2.1. Domain Model

Let's imagine we have two simple entities, User and Group, which are associated as @ManyToMany:

@Entity(name = "users")
public class User {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @ManyToMany
    private List<Group> groups = new ArrayList<>();

    // standard getters and setters

}
@Entity
public class Group {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "groups")
    private List<User> users = new ArrayList<>();

    // standard getters and setters

}

As we can see, our User entity can be a member of more than one Group entity. Similarly, a Group entity can contain more than one User entity.

2.2. Relation Entity

For @ManyToMany associations, we need a separate database table called a relation table. The relation table needs to contain at least two columns: The primary keys of the related User and Group entities.

With only the two primary key columns, our Hibernate mapping can represent this relation table.

However, if we need to put additional data in the relation table, we should also define a relation entity for the many-to-many relationship itself.

Let's create UserGroupRelation class to do this:

@Entity(name = "r_user_group")
public class UserGroupRelation implements Serializable {

    @Id
    @Column(name = "user_id", insertable = false, updatable = false)
    private Long userId;

    @Id
    @Column(name = "group_id", insertable = false, updatable = false)
    private Long groupId;

}

Here we've named the entity r_user_group so we can reference it later.

For our extra data, let's say we want to store every User‘s role for each Group. So, we'll create UserGroupRole enumeration:

public enum UserGroupRole {
    MEMBER, MODERATOR
}

Next, we'll add a role property to UserGroupRelation:

@Enumerated(EnumType.STRING)
private UserGroupRole role;

Finally, to configure it properly, we need to add the @JoinTable annotation on User‘s groups collection. Here we'll specify the join table name using r_user_group, the entity name of UserGroupRelation:

@ManyToMany
@JoinTable(
    name = "r_user_group",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "group_id")
)
private List<Group> groups = new ArrayList<>();

2.3. Sample Data

For our integration tests, let's define some sample data:

public void setUp() {
    session = sessionFactory.openSession();
    session.beginTransaction();
    
    user1 = new User("user1");
    user2 = new User("user2");
    user3 = new User("user3");

    group1 = new Group("group1");
    group2 = new Group("group2");

    session.save(group1);
    session.save(group2);

    session.save(user1);
    session.save(user2);
    session.save(user3);

    saveRelation(user1, group1, UserGroupRole.MODERATOR);
    saveRelation(user2, group1, UserGroupRole.MODERATOR);
    saveRelation(user3, group1, UserGroupRole.MEMBER);

    saveRelation(user1, group2, UserGroupRole.MEMBER);
    saveRelation(user2, group2, UserGroupRole.MODERATOR);
}

private void saveRelation(User user, Group group, UserGroupRole role) {

    UserGroupRelation relation = new UserGroupRelation(user.getId(), group.getId(), role);
    
    session.save(relation);
    session.flush();
    session.refresh(user);
    session.refresh(group);
}

As we can see, user1 and user2 are in two groups. Also, we should notice that while user1 is MODERATOR on group1, at the same time it has a MEMBER role on group2.

3. Fetching @ManyToMany Relations

Now we've properly configured our entities, let's fetch the groups of the User entity.

3.1. Simple Fetch

In order to fetch groups, we can simply invoke the getGroups() method of User inside an active Hibernate session:

List<Group> groups = user1.getGroups();

Our output for groups will be:

[Group [name=group1], Group [name=group2]]    

But how can we get the groups of a user whose group role is only MODERATOR?

3.2. Custom Filters on a Relation Entity

We can use the @WhereJoinTable annotation to directly acquire only filtered groups.

Let's define a new property as moderatorGroups and put the @WhereJoinTable annotation on it. When we access the related entities via this property, it will only contain groups of which our user is MODERATOR.

We'll need to add a SQL where clause to filter the groups by the MODERATOR role:

@WhereJoinTable(clause = "role='MODERATOR'")
@ManyToMany
@JoinTable(
    name = "r_user_group",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "group_id")
)
private List<Group> moderatorGroups = new ArrayList<>();

Thus, we can easily get the groups with the specified SQL where clause applied:

List<Group> groups = user1.getModeratorGroups();

Our output will be the groups on which the user has only the role of MODERATOR:

[Group [name=group1]]

4. Conclusion

In this tutorial, we learned how to filter @ManyToMany collections based on a property of the relation table using Hibernate's @WhereJoinTable annotation.

As always, all the code samples given in this tutorial are available over on GitHub.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Persistence footer banner
Comments are closed on this article!