- Shipped
- August 27, 2026 at 9:09 AM UTC
- Author
- Kamo
- Commit
- 31379b6
It was a method on ExchangeQueryService annotated @Transactional(REQUIRES_NEW), called from two private helpers on the same class. Spring's transaction advice lives on a proxy and a self-invocation never reaches it, so the annotation did nothing at all: the row joined the caller's transaction. That is precisely backwards for what it records. A FAILED outbound query would have rolled back the record of its own failure — and "we asked and they refused" is the row somebody investigating a gap in a patient's history actually needs. A ledger of only the successful queries answers the easy question and loses the hard one. Worse still, discover() and queryDocuments() are readOnly = true, so the write had no business being in their transaction at all. Extracted to ExchangeRequestRecorder, a bean of its own, exactly as BulkExportJobState was earlier for the same reason. A unit test cannot catch this — constructing the service directly means there is no proxy either, which is why it passed while being wrong. So the guard is structural: the recorder's test asserts the method is PUBLIC (a non-public method is never proxied, making its annotation inert) and that its propagation is REQUIRES_NEW. Both halves are invisible at a call site and both have to hold.