Support removing elements from @ManyToMany Set and @ElementCollection (composite FK key delete)

XMLWordPrintableJSON

    • Type: New Feature
    • Resolution: Unresolved
    • Priority: Unknown
    • None
    • Affects Version/s: None
    • Component/s: Mutation
    • None
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      When an application removes a specific element from a @ManyToMany Set (or an @ElementCollection), Hibernate issues a per-row delete on the join/collection table keyed by both FK columns, e.g.:

      DELETE FROM Owner_Tag WHERE Owner_id = ? AND tags_id = ?

      Our translator throws FeatureNotSupportedException in createKeyFilter() because numberOfKeyBindings > 1. The error message ("MongoDB does not support primary key spanning multiple columns") is misleading — this is not an entity table with a composite PK, it is a join table whose rows are identified by two FK columns.

      MongoDB handles the equivalent filter perfectly fine:

      db.Owner_Tag.deleteMany({ Owner_id: {$eq: 1}, tags_id: {$eq: 2} })

      Note: @ManyToMany with List (BAG semantics) is unaffected because Hibernate uses delete-all-then-reinsert for bags (single owner FK key binding).

      In Hibernate 8.0 the failure also occurs at SessionFactory construction time because BasicCollectionPersister.postInstantiate() eagerly builds all collection mutation plans, so any application using @ManyToMany or @ElementCollection will fail to start.

      Failing test:

              @Entity(name = "Owner")
              static class Owner {
                  @Id
                  int id;
      
                  @ManyToMany
                  Set<Tag> tags = new HashSet<>();
      
                  public Owner() {}
      
                  Owner(int id, Set<Tag> tags) {
                      this.id = id;
                      this.tags = tags;
                  }
              }
      
              @Entity(name = "Tag")
              static class Tag {
                  @Id
                  int id;
      
                  String name;
      
                  public Tag() {}
      
                  Tag(int id, String name) {
                      this.id = id;
                      this.name = name;
                  }
              }
      
      
      
        @Test
        void testRemoveElementFromManyToManySet() {
            getSessionFactoryScope().inTransaction(session -> {
                var owner = session.find(Owner.class, 1);
                var tagToRemove = owner.tags.stream().filter(t -> t.id == 1).findFirst().orElseThrow();
                owner.tags.remove(tagToRemove);
            });
            getSessionFactoryScope().inSession(session -> {
                var owner = session.find(Owner.class, 1);
                assertThat(owner.tags).hasSize(1);
            });
        }
      

      Fix: change createKeyFilter() in AbstractMqlTranslator to build a multi-field equality filter (implicit $and) when numberOfKeyBindings > 1, instead of throwing.

            Assignee:
            Unassigned
            Reporter:
            Jeffrey Yemin
            None
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: