- Shipped
- September 2, 2026 at 5:13 PM UTC
- Author
- Kamo
- Commit
- 24550bf
link() is @Transactional(REQUIRES_NEW), so its transaction commits in the proxy after the method body returns. A plain save() of an entity whose id is generated in memory only queues the INSERT, so the database's verdict arrived at that commit — outside the try/catch in upsert(), and outside link()'s own catch too. Association is best-effort by design, which means that log line was the only evidence a failure ever happened, and it could not fire. The effect was not subtle. Through the DATE_CREATED outage VOIPService logged 11k warnings and EmailService logged none, from the same broken write: a household phone number matches several leads, so the second owner's lookup auto-flushed the first one's insert and raised it inside the catch, while a mail address matches one lead and nothing followed it. The service most people used reported a total outage as "0 linked" — indistinguishable from a quiet day. saveAndFlush puts the failure back inside the code written to handle it, and it is also what makes the DataIntegrityViolationException branch reachable rather than dead code. That branch now reads the winner back on the OWNER pair, the one the unique constraint is actually built on: a patient's row has no LEAD_UID, so the lead-keyed lookup searched for null and dropped a row another writer had committed perfectly well — a loss that only ever happens under a race.