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]

Reply via email to