Search code examples
hibernatejpaspring-data-jpamany-to-many

How to avoid unnecessary queries when adding an entry to a ManyToMany collection in JPA?


I have the following two entities linked by a ManyToMany relationship

data class GroupEntity(
    @Id
    val id: String,
    val name: String
) {
    @ManyToMany
    val users: MutableSet<UserEntity> = HashSet()
}

@Entity
data class UserEntity(
    @Id
    val id: UUID,
    val name: String,

    @ElementCollection
    val interests: Set<InterestEmbeddable> = HashSet(),

    @ElementCollection
    val badges: Set<BadgeEmbeddable> = HashSet()
) {
    @ManyToMany(mappedBy = "users")
    val groups: MutableSet<GroupEntity> = HashSet()
}

Each of those entities have a corresponding JpaRepository. Whenever I try to add a user to a group, then behind the scenes hibernate does many queries to the database. It actually loads every interests and badges from every user of the group I'm trying to add my user to.

var group = this.groupRepository.getById(groupId);
var user = this.userRepository.getById(userId);

group.getUsers().add(user);
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?
Hibernate: select interests0_.user_entity_id as user_en1_47_0_, interests0_.name as name2_47_0_, interests0_.value as value3_47_0_ from user_entity_interests interests0_ where interests0_.user_entity_id=?
Hibernate: select badges0_.user_entity_id as user_en1_46_0_, badges0_.badge_id as badge_2_46_0_ from user_entity_badges badges0_ where badges0_.user_entity_id=?

As you can imagine this is causing me performance issues. I'm having the same issue when trying to remove a user by the way.

How can I avoid those extra queries ?

I've been reading through Best pratice to avoid selecting all rows when adding to owning side collection of a @ManyToMany but I'm not really satisfied with the answer provided there. I would like to avoid having to materialize the join table as an entity if possible.


Solution

  • @AlanHay's comment directed me towards the solution: I had my interests and badges collections as part of my equals / hashcode and so when doing this it triggered the loading of all elements.