Three self-invoked transactions that were doing nothing

FixDocsService
Shipped
September 4, 2026 at 6:30 PM UTC
Author
Kamo
Commit
9e9f460

@Transactional and @Async are PROXIES. A call from inside the same bean never reaches one, so all three of these annotations were inert. Found by audit, not by a failure — which is the point: none of them reported anything. 1. **************** — the worst of the three. It runs a @Modifying bulk delete, which throws TransactionRequiredException without a transaction; the caller catches and logs a warning. So stale translations were NEVER deleted, and the careers site would serve machine translations of superseded advert text in 21 locales — the exact thing the method exists to prevent, failing the exact way KB translation failed silently for months. That incident is cited in the javadoc of the very repository method being called here. 2. CareersRetentionSweep.purgeOne — deletes the Img row and the application row, which must both happen or neither. Without a transaction a failure between them either strands a résumé pointing at nothing or drops an applicant's file while their record survives, on a nightly job whose whole purpose is honouring a retention promise. Both are now on their own beans (CareersTranslationWriter, CareersApplicationPurger) reached only through an injected reference, which is the pattern LegalPackageDeliveryWorker already uses here. 3. **************** — the annotation is REMOVED rather than made real, because making it real would have introduced a bug. Sending mail is not something a rollback undoes: a per-organization transaction would make a failure on the ninth subscriber roll back the lastNotifiedAt of the eight already emailed, and the next hourly run would mail all eight again. Per subscriber commit — what was actually running — is correct. The javadoc now says so instead of claiming a guarantee that never existed. Also corrects that class's replica note: it said DaemonService runs replicas: 1, but this sweep runs in DocsService. Scaling THIS deployment double-sends to strangers, and the comment pointed at the wrong service to check. CareersSelfInvocationTest scans the package for the pattern so it cannot come back. Plain @Transactional is deliberately not flagged — its default propagation is REQUIRED, so a nested self-call correctly joins the caller's transaction, and the seven such calls here were each verified to sit inside an already transactional entry point.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing