Class DataSourceTransactionManager
- All Implemented Interfaces:
JdbcTransactionManager,TransactionManager
TransactionManager, and its JDBC-specific
sub-interface JdbcTransactionManager.
Uses ThreadLocal to associate transactions with threads, ensuring thread safety. Each thread can have at most one active transaction at a time.
- Since:
- 6.2.0
- Author:
- wisdomme
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) static classInternal class to hold transaction state. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final ThreadLocal<DataSourceTransactionManager.TransactionContext> ThreadLocal holding the current transaction context for each thread.private final DataSourceprivate static final Loggerprivate final ThreadLocal<Deque<DataSourceTransactionManager.TransactionContext>> ThreadLocal holding the stack of contextssuspend()has detached and not yetresume(Object)d, most-recently-suspended on top (D-09). -
Constructor Summary
ConstructorsConstructorDescriptionDataSourceTransactionManager(DataSource dataSource) Creates a new DataSourceTransactionManager. -
Method Summary
Modifier and TypeMethodDescriptionvoidbegin()Begins a new transaction.private voidCleans up the transaction context.voidcommit()Commits the current transaction.Gets the current connection, creating a new one if necessary.(package private) DataSourceTransactionManager.TransactionContextGets the current transaction context (for testing).Returns theSystem.nanoTime()value at which the active transaction's timeout budget expires, ornullwhen no timeout is configured for the active transaction -- no active transaction at all, or@Transactional(timeout=0)(the default).intGets the current transaction depth (for nested transactions).booleanChecks if there is an active transaction on the current thread.voidRestores a transaction previously detached byTransactionManager.suspend().voidrollback()Rolls back the current transaction.voidsetIsolationLevel(int level) Sets the isolation level for the current transaction.voidsetReadOnly(boolean readOnly) Sets read-only mode for the current transaction.voidsetTimeout(int seconds) Sets the timeout for the current transaction.suspend()Suspends the current transaction, if any, detaching it from this thread so an independent transaction can begin in its place.
-
Field Details
-
LOGGER
-
dataSource
-
contextHolder
ThreadLocal holding the current transaction context for each thread.Instance-scoped on purpose (FOUND-04) - one
DataSourceTransactionManagerper plugin container, built once inPluginManager.wireAop. Astaticfield here would let two plugin containers' transactions cross: manager A'srollback()would be able to reach aDataSourceTransactionManager.TransactionContextmanager B set up for a completely differentDataSource, on the same thread. Still aThreadLocal- the fix is "no longer static", not "no longer ThreadLocal". -
suspendedStack
ThreadLocal holding the stack of contextssuspend()has detached and not yetresume(Object)d, most-recently-suspended on top (D-09).Deliberately a sibling of
contextHolderrather than folding the active context into the same structure:getTransactionDepth(),hasActiveTransaction(),commit()androllback()all read the single active context and stay completely unchanged by this field's existence. Onlysuspend()/resume(Object)ever touch it. Never left holding an emptyDeque- the last pop callsThreadLocal.remove()so a Bukkit worker thread returned to the pool does not carry a stale frame (T-02-DOS-5).
-
-
Constructor Details
-
DataSourceTransactionManager
Creates a new DataSourceTransactionManager.- Parameters:
dataSource- the data source to use for connections
-
-
Method Details
-
getConnection
Description copied from interface:JdbcTransactionManagerGets the current connection, creating a new one if necessary.If a transaction is active, returns the connection associated with it. Otherwise, returns a new connection from the data source.
- Specified by:
getConnectionin interfaceJdbcTransactionManager- Specified by:
getConnectionin interfaceTransactionManager- Returns:
- the current connection
-
begin
public void begin()Description copied from interface:TransactionManagerBegins a new transaction.Creates a new connection, disables auto-commit, and associates it with the current thread.
- Specified by:
beginin interfaceTransactionManager
-
commit
public void commit()Description copied from interface:TransactionManagerCommits the current transaction.Commits all changes made during the transaction and releases the connection.
- Specified by:
commitin interfaceTransactionManager
-
rollback
public void rollback()Description copied from interface:TransactionManagerRolls back the current transaction.Discards all changes made during the transaction and releases the connection.
- Specified by:
rollbackin interfaceTransactionManager
-
hasActiveTransaction
public boolean hasActiveTransaction()Description copied from interface:TransactionManagerChecks if there is an active transaction on the current thread.- Specified by:
hasActiveTransactionin interfaceTransactionManager- Returns:
- true if a transaction is active
-
setIsolationLevel
public void setIsolationLevel(int level) Description copied from interface:JdbcTransactionManagerSets the isolation level for the current transaction.Must be called after begin() and before any statements are executed.
- Specified by:
setIsolationLevelin interfaceJdbcTransactionManager- Specified by:
setIsolationLevelin interfaceTransactionManager- Parameters:
level- the JDBC isolation level constant
-
setReadOnly
public void setReadOnly(boolean readOnly) Description copied from interface:JdbcTransactionManagerSets read-only mode for the current transaction.- Specified by:
setReadOnlyin interfaceJdbcTransactionManager- Specified by:
setReadOnlyin interfaceTransactionManager- Parameters:
readOnly- true for read-only mode
-
setTimeout
public void setTimeout(int seconds) Sets the timeout for the current transaction.Note: Not all databases/drivers support transaction timeout.
D-10: records a deadline on the active
DataSourceTransactionManager.TransactionContextrather than a wall-clock bound on the transaction as a whole --getTimeoutDeadlineNanos()exposes it so each statement issued afterward can be given the time remaining, not a fresh full allowance.seconds <= 0is inert (no deadline recorded), matching@Transactional(timeout=0)'s default --TransactionInterceptoritself never calls this method for a non-positive value in the first place, so this is a second, defensive guard for any other caller. Called with no active transaction, this logs a warning and records nothing, the same shapecommit()/rollback()already use for that case.- Specified by:
setTimeoutin interfaceTransactionManager- Parameters:
seconds- the timeout in seconds
-
getTimeoutDeadlineNanos
Returns theSystem.nanoTime()value at which the active transaction's timeout budget expires, ornullwhen no timeout is configured for the active transaction -- no active transaction at all, or@Transactional(timeout=0)(the default).D-10: consulted per statement (by
TimeoutAwareQueryRunnerand the two directPreparedStatementcall sitesAbstractRelationalDataOperator.insertAll/updateAllbuild themselves) to compute the time remaining in the budget, not a value captured once -- the caller re-reads this on every statement.The default implementation always returns
null, meaning "never enforced" -- the same posture a backend whosesetTimeout(int)refuses any positive value outright (rather than ever recording a deadline) naturally has, since it never has anything to report here either. A backend that wants per-statement enforcement overrides this together withTransactionManager.setTimeout(int)-- overriding only one half of the pair leaves the other silently inert, the same cautionTransactionManager.suspend()'s javadoc gives for its pair.Reads
DataSourceTransactionManager.TransactionContext.timeoutDeadlineNanosoff the active context --nullwhen no transaction is active, or whensetTimeout(int)was never called (or called only with a non-positive value) for it.suspend()/resume(Object)detach and restore the wholeDataSourceTransactionManager.TransactionContextobject, so a suspended-then-resumed transaction's deadline travels with it automatically -- no separate handling needed here.- Specified by:
getTimeoutDeadlineNanosin interfaceTransactionManager- Returns:
- the deadline, as a
System.nanoTime()value, ornullif none is active
-
getTransactionDepth
public int getTransactionDepth()Description copied from interface:TransactionManagerGets the current transaction depth (for nested transactions).- Specified by:
getTransactionDepthin interfaceTransactionManager- Returns:
- the transaction depth (0 if no transaction)
-
suspend
Suspends the current transaction, if any, detaching it from this thread so an independent transaction can begin in its place. BacksPropagation.REQUIRES_NEWandPropagation.NOT_SUPPORTED(D-09).Deliberately typed to return an opaque
Objectrather than a backend-specific context type: a JDBC-backed manager suspends aConnection-holding context, a JSON snapshot manager (02-05) suspends a set of operator snapshots (D-03) - neither shape belongs on this base interface. Pass whatever this returns toTransactionManager.resume(Object)to restore it; the caller must not otherwise inspect or reuse the returned value.The default implementation does not support suspension: it always returns
null, meaning nothing was suspended. A backend that maintains thread-bound transaction state (JDBC, JSON) must override this together withTransactionManager.resume(Object)- overriding only one of the pair breaks the contract silently.Detaches the current
DataSourceTransactionManager.TransactionContext(connection included) from this thread and pushes it ontosuspendedStack, leavinghasActiveTransaction()false. Abegin()that runs while the original stays suspended opens a fully independent transaction, on its own connection - the suspended one is never touched untilresume(Object)restores it.- Specified by:
suspendin interfaceTransactionManager- Returns:
- an opaque handle to resume later, or
nullif no transaction was active
-
resume
Restores a transaction previously detached byTransactionManager.suspend().nullis a no-op - it meansTransactionManager.suspend()found nothing active in the first place, so there is nothing to restore. Passing a handle thisTransactionManagerdid not itself produce is a programming error.The default implementation is a no-op, pairing with
TransactionManager.suspend()'s default of always returningnull.nullis a no-op -suspend()found nothing active in the first place, so there is nothing onsuspendedStackfor this call to pop. A non-null handle must be exactly what this manager's ownsuspend()most recently returned; restoring it sets it back as the active context (sameDataSourceTransactionManager.TransactionContextinstance, sameConnection, same depth) and pops it offsuspendedStack, removing theThreadLocalentirely once the stack is empty rather than leaving a dangling emptyDequebehind.- Specified by:
resumein interfaceTransactionManager- Parameters:
suspended- the handle returned by a priorTransactionManager.suspend()call, ornull- Throws:
IllegalArgumentException- ifsuspendedis non-null but was not produced by this manager's ownsuspend()
-
cleanup
Cleans up the transaction context. -
getCurrentContext
DataSourceTransactionManager.TransactionContext getCurrentContext()Gets the current transaction context (for testing).Instance method, not static, per the same FOUND-04 fix as
contextHolderitself: twoDataSourceTransactionManagerinstances now have independent state.
-