惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
S
SegmentFault 最新的问题
大猫的无限游戏
大猫的无限游戏
The GitHub Blog
The GitHub Blog
M
MIT News - Artificial intelligence
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
Last Week in AI
Last Week in AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
H
Help Net Security
Engineering at Meta
Engineering at Meta
Microsoft Security Blog
Microsoft Security Blog
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
C
Check Point Blog
F
Fortinet All Blogs
腾讯CDC
博客园 - Franky
WordPress大学
WordPress大学
U
Unit 42

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Your @EventListener Fires Before the Transaction Commits⚙️
Kyryl · 2026-06-25 · via DEV Community

Your domain event fires. Your notification service queries the DB for the entity that just got saved. It finds nothing.

You add a log line. It starts working. You remove the log. It breaks again.

That's not a race condition. That's @EventListener.

What's actually happening

Spring's @EventListener fires synchronously, inside the calling thread, before the transaction commits. The DB row exists in Hibernate's session — but it hasn't been flushed and committed yet. Other connections, including the one your listener opens when it calls findById, can't see it.

The log statement "fixes" it because the delay gives Hibernate time to flush. Remove the log, the flush doesn't happen in time, and you're back to an empty Optional.

Here's the broken setup:

@Component
public class OrderEventListener {

    @EventListener // fires MID-TRANSACTION, before commit
    public void onOrderCreated(OrderCreatedEvent event) {
        // Transaction not committed yet.
        // Other DB connections see nothing.
        Order order = orderRepository
                .findById(event.getOrderId())
                .orElseThrow(); // ← throws here, row doesn't exist yet

        notificationService.notifyCustomer(order);
    }
}

The problem: @EventListener fires mid-transaction, before commit

The obvious fix and what it costs you

Spring ships @TransactionalEventListener for exactly this. Set phase = TransactionPhase.AFTER_COMMIT and the listener fires after the transaction commits. The row is visible. findById returns the order. Problem solved.

@Component
public class OrderEventListener {

    @TransactionalEventListener(
        phase = TransactionPhase.AFTER_COMMIT
    )
    public void onOrderCreated(OrderCreatedEvent event) {
        // Transaction committed. All connections see the row.
        Order order = orderRepository
                .findById(event.getOrderId())
                .orElseThrow(); // ← works fine

        notificationService.notifyCustomer(order);
    }
}

The fix: @TransactionalEventListener fires after commit

But the trade-off is real. Your listener is now decoupled from the transaction. If the listener fails — notification service is down, the email throws, the external API times out — the transaction already committed. The event is gone. Nothing retries it. Nothing tells you it was dropped.

@EventListener: stale reads.
@TransactionalEventListener(AFTER_COMMIT): silent data loss on listener failure.

Neither is great.

The edge case that bites in tests

There's a second problem with @TransactionalEventListener that most teams hit in tests or Kafka consumers: if there's no active transaction, the listener silently does nothing.

Call the service from a unit test without @Transactional. Publish a Kafka message that triggers the same service method without a transaction boundary. The listener won't fire. No warning. No exception. The event just disappears.

Fix: fallbackExecution = true.

@TransactionalEventListener(
    phase = TransactionPhase.AFTER_COMMIT,
    fallbackExecution = true  // fires even with no active transaction
)
public void onOrderCreated(OrderCreatedEvent event) {
    // Now works from Kafka consumers, tests, scheduled tasks
    // that don't have an active @Transactional context.
    // Without this: event silently dropped. Nothing tells you.
}

This restores synchronous execution when there's no transaction — which gives you back the mid-transaction timing problem you started with. You're going in circles.

When AFTER_COMMIT is fine and when it isn't

The real question is: what happens if the listener never fires?

If the answer is "stale cache for 60 seconds" or "audit log has a gap" — AFTER_COMMIT is fine. The business isn't broken.

If the answer is "customer didn't get charged", "duplicate order created", or "inventory not decremented" — you need the outbox pattern. Write the event as a row in an outbox table inside the same transaction. A separate process (a scheduler or Debezium reading the WAL) picks it up and publishes it after commit. Now the event delivery is reliable and tied to the transaction at the DB level, not the application level.

The outbox is more infrastructure. But it's the correct choice when losing an event corrupts state.

The trade-off, summarised

Approach Stale reads Silent loss on failure Works outside @Transactional
@EventListener Yes No Yes
@TransactionalEventListener(AFTER_COMMIT) No Yes No (silent drop)
@TransactionalEventListener(AFTER_COMMIT, fallbackExecution = true) Mixed Yes Yes
Outbox pattern No No Yes

@EventListener vs @TransactionalEventListener — almost identical names, completely different behavior. Most teams find this difference via a production incident, not the docs.

How do you handle post-commit side effects in your services?