JBoss EAPSpring Framework交易企業系統升級

JBoss EAP 10: MDB-to-database failures, redelivery, and duplicate effects

··4 min read
Back to the JBoss EAP learning path

Two deliveries do not necessarily create two database rows. But onMessage returning normally does not by itself explain database commit or message completion. Trace responsibility from MDB through service to database, then use a local model to inspect failures before DB commit and after commit but before acknowledgment is recorded.

Download lesson 10: H2 transaction and synthetic delivery model

The kit executes real Spring JDBC/H2 transactions. Delivery counts, acknowledgment, and quarantine are synthetic test state: there is no MDB, RAR, JMS client, or broker. The lab reproduces database-side outcomes, not EAP CMT/XA or IBM MQ delivery semantics.

Find the owner at every boundary

text
IBM MQ → Jakarta resource adapter → EAP EJB container
       → MDB.onMessage → managed service → enlisted DB resource

Spring bean proxy and EJB container interception are different mechanisms.

The EJB container owns the MDB lifecycle. For an EJB service, inspect its transaction attributes. For a Spring service, inspect bean acquisition, manager selection, and resource participation. Manually constructing a Spring object with @Transactional does not establish integration with EAP transactions; lesson 9 demonstrated why metadata alone is insufficient.

For container-managed MDB message listeners, Jakarta Enterprise Beans permits REQUIRED or NOT_SUPPORTED transaction attributes. Those options do not establish effective descriptors, adapter capabilities, or enlistment in a specific installation. Spring annotations and EJB transaction attributes are not interchangeable mechanisms.

Separate local commits from one coordinated XA transaction

text
Local model in this kit:
  DB transaction commits → model records acknowledgment
                         ↑ failure window

Target CMT/XA investigation:
  transaction coordinator → enlisted MQ + DB resources
  verify actual outcome and recovery, not this model alone

The local model deliberately separates DB commit from acknowledgment to expose that failure window. If the target MDB, MQ, and DB participate in one coordinated transaction, validate that model’s commit and recovery behavior instead. The exercise does not recommend replacing an existing enterprise transaction architecture.

Keep the business write and processing marker together

java
return transaction.execute(status -> {
    // First detect an existing business key and compare its payload.
    jdbc.update("INSERT INTO processed_events(business_key,title) VALUES (?,?)", key, title);
    jdbc.update("INSERT INTO orders(business_key,title) VALUES (?,?)", key, title);
    if (failBeforeCommit) throw new IllegalStateException("synthetic failure");
    return Result.APPLIED;
});

This excerpt shows the core of OrderProcessor; the full source also validates input and checks duplicates. A processed-event marker and the business order are written within one H2 transaction. Failure rolls both back, avoiding a marker that incorrectly suggests an unfinished business effect already happened.

A repeated key returns DUPLICATE only when its title also matches; changed content is a conflict. This is a sequential teaching implementation. SELECT followed by INSERT is not a complete concurrent idempotency algorithm. Primary keys prevent duplicate rows, but race handling, retries, isolation, and retention remain untested.

Run six cases and inspect where failure occurs

bash
mvn test
mvn package
ScenarioDatabaseModel outcome
Normal processingOne order and markerAcknowledged
Fail before commit, then retryZero, then one eachSecond attempt succeeds
Commit without recorded ackOne each remainsReplay returns DUPLICATE
Same key, changed contentOriginal data remainsConflict
Three synthetic failuresZero eachModel quarantine
Missing keyZero eachRejected
Six JUnit tests: real H2, modeled delivery and acknowledgment.

The third case creates another OrderProcessor over the same H2 database, showing that duplicate detection reads stored state rather than service-object memory. Both attempts remain in one JVM and in-memory database; no process kill, power-loss durability, or XA recovery is tested.

Repeated failure needs an explicit handling policy

The fifth case deliberately fixes three attempts, and quarantine is only a boolean. IBM documents backout count, BOTHRESH, and BOQNAME mechanisms separately. Verify the actual adapter, destination permissions, and failure-to-move behavior for the target combination. Three model attempts are not a production configuration recommendation.

Moving failed messages also requires preserving the business key, original payload, error classification, and traceable replay process. An empty queue or swallowed exception can hide unfinished work. Catching an exception and logging it does not repair a transaction already marked for rollback.

Connect the model to target evidence

TARGET-ENVIRONMENT.md lists adapter, destination, transaction manager, resource participation, and eight target scenarios. Collect actual DB state, message identity/count, and transaction/error logs before feeding lesson 7’s comparator. Never substitute these synthetic results for enterprise observations.

Check yourself: does one DB row and an empty queue prove exactly-once processing? No. Delivery, consumption, side effects, and recovery still need correlated evidence. This lab establishes H2 outcomes for six local scenarios and identifies what to observe next in the real container.

Review lesson 09: service proxies and transactionsLesson 07: compare four evidence surfaces

Sources