Annotation Interface LongComputation


@Retention(RUNTIME) @Target({METHOD,CONSTRUCTOR,TYPE}) public @interface LongComputation
Marks work that runs long enough that no transaction may be open while it runs.

What it is for

Gemma sets hibernate.connection.handling_mode = DELAYED_ACQUISITION_AND_HOLD (HibernateConfig), so a transaction holds its pooled connection from first statement to commit — including through any stretch that issues no statements at all. The pool is configured to recycle connections every 30 minutes (gemma.db.hikari.maxLifetime). A computation that runs inside a transaction for longer than that is holding a connection the pool expects to have turned over, and it is holding it for no reason: the computation does not use it.

Measured on production 2026-09-17: corrMat -force on GSE260875 spent 44 minutes in quantile normalization inside one @Transactional(readOnly = true), then failed on its next statement. Reads and writes on that path are cleanly separable from the arithmetic between them, which is what makes holding the connection across it a defect rather than a cost.

The shape that is correct

Read in a short transaction, compute with none open, write in a short transaction. The orchestrating class declares @Transactional(propagation = Propagation.NEVER) — the idiom already used by PreprocessorServiceImpl, DifferentialExpressionAnalyzerServiceImpl, OutlierFlaggingServiceImpl, DataUpdaterImpl and a dozen others — and each step it calls carries its own annotation. Because Spring AOP does not intercept private or self-invoked methods, the steps have to live on a bean the orchestrator calls through its proxy; *HelperServiceImpl is the precedent.

Where to put it

On the method or constructor that is the computation, or on the class when every entry point into it is one (LeastSquaresFit has eight constructors and all of them run the fit). Do not put it on an orchestrator that merely reaches a computation — that is the thing the rule is looking for, not a declaration.

TransactionSpanningComputeRuleTest fails the build when a @Transactional method can reach one of these. Retention is RUNTIME because ArchUnit reads compiled bytecode.

See Also:
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    Optional note on what makes this one long — a measured duration, a complexity, the dataset that showed it.
  • Element Details

    • value

      String value
      Optional note on what makes this one long — a measured duration, a complexity, the dataset that showed it. Read by whoever hits the rule and has to decide where the transaction boundary belongs.
      Default:
      ""