Class JsonTransactionManager
- All Implemented Interfaces:
TransactionManager
TransactionManager for the JSON backend (D-03), giving
@Transactional the same consistent behaviour on datasource.type: json that
DataSourceTransactionManager already gives the two JDBC backends.
Scoped to one requesting identity (a plugin name on the internal path, a canonical data-folder
path on the external one) and shared by every SimpleJsonDataOperator that
JsonStore hands out for that identity. begin() opens (or, by depth, joins) a
transaction on the current thread. The first write reaching any one of that identity's JSON
operators captures a deep-copy snapshot of that operator's cache, lazily -- an operator never
written to during the transaction is neither snapshotted nor restored (see
captureIfAbsent(java.lang.Object, java.util.function.Supplier<java.lang.Object>, java.util.function.Consumer<java.lang.Object>)). rollback() at depth 1 restores every captured snapshot; at
depth greater than 1 it marks the transaction rollback-only instead of tearing it down, exactly
mirroring DataSourceTransactionManager's D-08 semantics so
TransactionInterceptor sees one behaviour regardless of which backend built this
manager. commit() at depth 1 discards the captured snapshots, or -- when the marker is
set -- restores them and throws UnexpectedRollbackException, exactly as the JDBC
manager does.
This is not a durable transaction. It is an in-process rollback of cached state, not a
crash-safe one: a server killed mid-transaction has whatever the flush scheduler last wrote to
disk. Nothing here, and nothing this plan's javadoc, log output, or COMPATIBILITY.md
text produces, should read as a claim that a JSON transaction survives a crash the way a JDBC
transaction's commit/rollback does.
- Since:
- 6.3.0
- Author:
- wisdomme
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classInternal class to hold JSON transaction state -- the JSON analogue ofDataSourceTransactionManager.TransactionContext, minus anything JDBC-specific. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final ThreadLocal<JsonTransactionManager.Context> Instance-scopedThreadLocal, matching the FOUND-04 reasoning already documented onDataSourceTransactionManager.contextHolder: one manager per identity, built once byJsonStore, so two identities can never cross transaction state.private final StringThe requesting identity this manager governs -- a plugin name or a canonical data-folder path, matching whateverJsonStoreused to key the operators it hands out for this manager.private static final Loggerprivate final ThreadLocal<Deque<JsonTransactionManager.Context>> ThreadLocal holding the stack of contextssuspend()has detached and not yetresume(Object)d, most-recently-suspended on top -- the JSON analogue ofDataSourceTransactionManager's field of the same name (02-11, D-09). -
Constructor Summary
ConstructorsConstructorDescriptionJsonTransactionManager(String identity) Constructed only byJsonStore(a different package, hencepublic) when it first resolves a manager for a given identity. -
Method Summary
Modifier and TypeMethodDescriptionvoidbegin()Begins a new transaction.voidCalled by aSimpleJsonDataOperatorwrite path (throughJsonStore, its own package) the first time that operator is touched inside the active transaction on this thread.voidcommit()Commits the current transaction.intGets the current transaction depth (for nested transactions).booleanChecks if there is an active transaction on the current thread.private voidvoidRestores a transaction previously detached byTransactionManager.suspend().voidrollback()Rolls back 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.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface com.ultikits.ultitools.interfaces.TransactionManager
getConnection, getTimeoutDeadlineNanos, setIsolationLevel, setReadOnly
-
Field Details
-
LOGGER
-
identity
The requesting identity this manager governs -- a plugin name or a canonical data-folder path, matching whateverJsonStoreused to key the operators it hands out for this manager. Used only for diagnostic log/exception messages; never compared against anything. -
contextHolder
Instance-scopedThreadLocal, matching the FOUND-04 reasoning already documented onDataSourceTransactionManager.contextHolder: one manager per identity, built once byJsonStore, so two identities can never cross transaction state. -
suspendedStack
ThreadLocal holding the stack of contextssuspend()has detached and not yetresume(Object)d, most-recently-suspended on top -- the JSON analogue ofDataSourceTransactionManager's field of the same name (02-11, 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.
-
-
Constructor Details
-
JsonTransactionManager
Constructed only byJsonStore(a different package, hencepublic) when it first resolves a manager for a given identity.- Parameters:
identity- the requesting identity this manager governs, used only for diagnostics
-
-
Method Details
-
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
-
restore
-
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
-
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
Context(captured restorers included) from this thread and pushes it ontosuspendedStack, leavinghasActiveTransaction()false. Abegin()that runs while the original stays suspended opens a fully independent transaction with its own empty snapshot map -- the suspended one is never touched untilresume(Object)restores it.Like everything else this class does, this is in-process suspension of cached state, not a durable one: nothing here changes what is on disk, and a server killed mid-suspension still has whatever the flush scheduler last wrote.
- 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 (sameContextinstance, same depth, same captured restorers) 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()
-
setTimeout
public void setTimeout(int seconds) Sets the timeout for the current transaction.Note: Not all databases/drivers support transaction timeout.
The JSON backend has no statement or connection layer for a timeout to apply to -- there is nothing here that could time out. Rather than silently accept the value and do nothing (the shape
DataSourceTransactionManager.setTimeout(int)itself uses, since JDBC has no portable timeout mechanism either, but at least genuinely has a connection the value could theoretically apply to), this refuses outright:@Transactional(timeout=)is refused rather than silently ignored on the JSON backend.TransactionInterceptoronly calls this when a positive timeout is actually configured on the annotation, so a@Transactionalmethod that never specifies one is unaffected.- Specified by:
setTimeoutin interfaceTransactionManager- Parameters:
seconds- the timeout in seconds- Throws:
UnsupportedOperationException- always
-
captureIfAbsent
public void captureIfAbsent(Object operatorKey, Supplier<Object> snapshotSupplier, Consumer<Object> restorer) Called by aSimpleJsonDataOperatorwrite path (throughJsonStore, its own package) the first time that operator is touched inside the active transaction on this thread. A no-op when no transaction is active on this thread, or whenoperatorKeywas already captured earlier in this same transaction.Public because the caller lives in a different package (
interfaces.impl.data.json) than this class. The actual deep-copy capture/restore logic stays package-private onSimpleJsonDataOperatoritself, reached only from insideJsonStore:snapshotSupplierandrestorerare lambdas built there, closing over that package-private access, so this manager never touches an operator's cache directly and never needs to.- Parameters:
operatorKey- identifies the operator -- the operator instance itself, in practicesnapshotSupplier- produces the deep-copy snapshot; invoked at most once per operator per transactionrestorer- restoresoperatorKey's cache from the captured snapshot; invoked byrollback(), and bycommit()when the transaction was marked rollback-only
-