jamesfredley commented on issue #14935:
URL: https://github.com/apache/grails-core/issues/14935#issuecomment-3930840467

   scenario (from `exampleAppSimple2` / `exampleAppGrails700M5`):
   1. Create + update entity - DB version=1
   2. Load `ds1` (v=1) and `ds2` (v=1) in separate sessions
   3. Update via `ds1` - DB version=2
   4. Try update via `ds2` - correctly gets `OptimisticLockingFailureException` 
(v=1 vs DB v=2)
   5. Call `ds2.merge()` expecting an attached copy with DB's version=2 and 
their `name` change applied
   6. Gets `ds2Copy.version == 1` (detached version) - eventual 
`OptimisticLockingException` at flush
   ## Why This Is Expected
   GORM's `performMerge()` is a straight pass-through to Hibernate's 
`session.merge()` with no custom version handling:
   ```groovy
   // AbstractHibernateGormInstanceApi.groovy
   protected D performMerge(final D target, final boolean flush) {
       hibernateTemplate.execute { Session session ->
           Object merged = session.merge(target)
           session.lock(merged, LockMode.NONE)
           if (flush) { flushSession(session) }
           return (D) merged
       }
   }
   ```
   Hibernate 5.6's `DefaultMergeEventListener.entityIsDetached()` does exactly 
this:
   ```java
   // 1. Load entity from DB
   final Object result = source.get(entityName, clonedIdentifier);  // version=2
   // 2. Check version mismatch
   else if (isVersionChanged(entity, source, persister, target)) {
       throw new StaleObjectStateException(entityName, id);  // Fires here
   }
   // 3. Only if versions match: copy state
   copyValues(persister, entity, target, source, copyCache);
   ```
   
   
   `merge()` 
   - Copies **ALL state** from detached to managed (including version)
   - Uses the version specifically for **conflict detection**
   - Throws immediately if versions don't match
   
   The correct pattern for "load fresh + apply my changes" is:
   ```groovy
   Example.withNewTransaction {
       Example fresh = Example.get(ds2.id)  // version=2 from DB
       fresh.name = ds2.name                // apply only desired changes
       fresh.save()                         // succeeds
   }
   ```


-- 
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