Skip to content

#3407 Honor isolation level on read-only transactions - #3874

Open
arimu1 wants to merge 1 commit into
ebean-orm:masterfrom
arimu1:fix/3407-readonly-isolation-level
Open

#3407 Honor isolation level on read-only transactions#3874
arimu1 wants to merge 1 commit into
ebean-orm:masterfrom
arimu1:fix/3407-readonly-isolation-level

Conversation

@arimu1

@arimu1 arimu1 commented Aug 7, 2026

Copy link
Copy Markdown

Summary

Fixes #3407 — when a method (or TxScope) is read-only and specifies an isolation level, Ebean dropped the isolation and always used the connection default (typically READ_COMMITTED).

Root cause

TransactionManager.createTransaction(TxScope) used the read-only path without applying isolation:

if (txScope.isReadonly()) {
  return createReadOnlyTransaction(null, false); // isolation ignored
} else {
  return createTransaction(true, txScope.getIsolationLevel());
}

Fix

After creating the read-only transaction, apply the same setIsolationLevel helper used by normal transactions:

if (txScope.isReadonly()) {
  SpiTransaction transaction = createReadOnlyTransaction(null, false);
  return transactionFactory.setIsolationLevel(transaction, txScope.getIsolationLevel());
}

When isolation is unset (-1), behavior is unchanged.

Tests

  • TestTransactionalReadOnly#test_readonly_honors_isolation — programmatic TxScope.required().setReadOnly(true).setIsolation(SERIALIZABLE)
  • TestTransactionalReadOnly#test_readonly_annotation_honors_isolation@Transactional(readOnly = true, isolation = SERIALIZABLE)
  • Existing read-only metric tests still pass
mvn -pl ebean-test -Dtest=org.tests.transaction.TestTransactionalReadOnly test
# Tests run: 4, Failures: 0 (Temurin 21, H2)

Notes

Isolation is still only as useful as the target database allows (e.g. some platforms map or reject READ_UNCOMMITTED). This change ensures the requested level is applied to the JDBC connection for read-only scopes when the platform supports it.

Read-only TxScope previously ignored isolation when creating
ImplicitReadOnlyTransaction. Apply setIsolationLevel after
createReadOnlyTransaction so @transactional(readOnly=true, isolation=...)
and TxScope setReadOnly+setIsolation take effect.
@rbygrave

rbygrave commented Aug 8, 2026

Copy link
Copy Markdown
Member

Do you have an application hitting this issue or is it more a bug fix for a known issue?

If you have an application hitting this, what database and isolation level is being used? Can you explain the use case? Do you have a workaround?

@arimu1

arimu1 commented Aug 8, 2026

Copy link
Copy Markdown
Author

@rbygrave Honest answer: I don't have a production app hitting this myself — the fix comes from the known issue #3407 reported by @rPraml.

Reporter's use case (from the issue): they hold update locks on certain tables and wanted some UI queries on a read-only connection at READ_UNCOMMITTED so the UI is less blocked. With @Transactional(readOnly = true, isolation = READ_UNCOMMITTED) they still got a read-only txn at the default isolation (READ_COMMITTED) because createReadOnlyTransaction never applied the requested isolation.

Workaround they used: skip the read-only connection (use a normal transaction) when a non-default isolation is required.

This PR: when TxScope requests both read-only and an isolation level, apply setIsolationLevel after creating the read-only transaction (same path as non-read-only scopes). Covered by TestTransactionalReadOnly for TxScope + @Transactional SERIALIZABLE on H2.

If you'd rather keep isolation unsupported on the read-only pool for product reasons (e.g. only useful on DB2), happy to close or narrow — just say the word.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReadOnly transactions does not honor isolation-level

2 participants