jdaugherty commented on code in PR #16532:
URL: https://github.com/apache/grails-core/pull/16532#discussion_r4326580352
##########
grails-data-hibernate7/docs/src/docs/asciidoc/domainClasses/gormAssociation/manyToMany.adoc:
##########
@@ -40,6 +40,34 @@ class Author {
GORM creates a join table `author_books` with foreign-key columns for both
sides.
+WARNING: One side must declare `belongsTo`. If neither side does, neither side
writes the join table, so the relationship is not stored. GORM logs a warning
at startup for such a mapping.
+
+This also applies to a many-to-many between a class and itself, where both
properties are on the same class. Without `belongsTo`, the relationship is not
stored and GORM logs the same warning at startup:
+
+[source,groovy]
+----
+class User {
+ String name
+ static hasMany = [following: User, followers: User]
+ static mappedBy = [following: 'followers', followers: 'following']
+ static belongsTo = User // <1>
+}
+----
+<1> Without this line, `user.addToFollowing(other); user.save()` writes
nothing, and both `following` and `followers` read back empty.
+
+Adding `belongsTo` to an existing mapping of this kind changes the columns of
both join tables. They hold no rows, because nothing was stored without it, so
drop them and let GORM recreate them, or add a migration. `dbCreate: 'update'`
does not remove the old columns, and the first save then fails.
Review Comment:
Hibernate 5 stores this shape without `belongsTo`: its second pass makes
every circular many-to-many non-inverse (`GrailsDomainBinder.java:534-536` in
`grails-data-hibernate5`), which is why #16656 keeps the circular exclusion
there. An application that moves from Hibernate 5 to Hibernate 7, the Grails 7
→ 8 path in section 26 of the upgrade guide and the only path once Hibernate 5
is gone, arrives with rows in both tables and meets this warning for the first
time. Probe on this merge with the same `following`/`followers` class,
`addToFollowing` then `save()`:
```
H5, no belongsTo: following=[followed] followers=[follower]
PROBE_CIRC_FOLLOWERS [PROBE_CIRC_ID NOT NULL, FOLLOWERS__ID NOT NULL]
rows=[[2, 1]]
PROBE_CIRC_FOLLOWING [FOLLOWERS__ID NOT NULL, FOLLOWING__ID NOT NULL]
rows=[[1, 2]]
H7, no belongsTo: following=[] followers=[]
PROBE_CIRC_FOLLOWERS [FOLLOWERS_ID NOT NULL, PROBE_CIRC_ID] rows=[]
PROBE_CIRC_FOLLOWING [FOLLOWERS_ID, FOLLOWING_ID NOT NULL] rows=[]
H7, belongsTo: following=[followed] followers=[follower]
PROBE_CIRC_OWNED_FOLLOWERS [FOLLOWERS_ID NOT NULL, FOLLOWING_ID NOT NULL]
rows=[[1, 2]]
PROBE_CIRC_OWNED_FOLLOWING [FOLLOWERS_ID NOT NULL, PROBE_CIRC_OWNED_ID NOT
NULL] rows=[[1, 2]]
```
For that application the tables are not empty, and "drop them and let GORM
recreate them" loses the relationship. Its columns also differ from Hibernate
7's (`followers__id` against `followers_id`), so those tables need a migration
with or without `belongsTo`. Please qualify the sentence, for example:
> If Hibernate 7 created the schema, the join tables hold no rows, because
nothing was stored without `belongsTo`, so drop them and let GORM recreate
them. A schema that Hibernate 5 created does hold rows for this mapping,
because Hibernate 5 stored it without `belongsTo`; migrate those rows into the
new columns instead of dropping the tables. `dbCreate: 'update'` does not
remove the old columns, and the first save then fails.
##########
grails-data-hibernate7/docs/src/docs/asciidoc/domainClasses/gormAssociation/manyToMany.adoc:
##########
@@ -40,6 +40,34 @@ class Author {
GORM creates a join table `author_books` with foreign-key columns for both
sides.
+WARNING: One side must declare `belongsTo`. If neither side does, neither side
writes the join table, so the relationship is not stored. GORM logs a warning
at startup for such a mapping.
+
+This also applies to a many-to-many between a class and itself, where both
properties are on the same class. Without `belongsTo`, the relationship is not
stored and GORM logs the same warning at startup:
+
+[source,groovy]
+----
+class User {
+ String name
+ static hasMany = [following: User, followers: User]
+ static mappedBy = [following: 'followers', followers: 'following']
+ static belongsTo = User // <1>
+}
+----
+<1> Without this line, `user.addToFollowing(other); user.save()` writes
nothing, and both `following` and `followers` read back empty.
+
+Adding `belongsTo` to an existing mapping of this kind changes the columns of
both join tables. They hold no rows, because nothing was stored without it, so
drop them and let GORM recreate them, or add a migration. `dbCreate: 'update'`
does not remove the old columns, and the first save then fails.
+
+With `belongsTo`, each of the two properties writes its own join table, so use
`addToFollowing` and `removeFromFollowing`, which update both sides. Assigning
`user.following = [other] as Set` stores the row for `following` only. Before
deleting a `User`, remove it from both sides:
+
+[source,groovy]
+----
+user.following.toList().each { user.removeFromFollowing(it) }
+user.followers.toList().each { it.removeFromFollowing(user) }
+user.delete()
+----
+
+A many-to-many with a `Map` on either side needs no `belongsTo`, because a
`Map` side always writes its join table, so GORM logs no warning for it.
Review Comment:
Non-blocking. This sentence sits between the circular delete snippet and
"Saving a Many-to-Many", so it reads as part of the circular case. It qualifies
the WARNING at line 43, so I'd put it right after that admonition, before "This
also applies to a many-to-many between a class and itself".
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]