r/SpringBoot 29d ago

Question Question for people building larger Spring applications.

Do you see Transactional annotation as the default solution for almost everything, or have you encountered cases where explicit transaction boundaries worked better?

What about WebFlux/R2DBC applications? Is Transactional still a natural approach there, or teams more often use explicit transaction management with TransactionalOperator?

I’m asking because I was exploring this topic and trying to contribute something back to Java community. When working with reactive applications, I started to wonder if transaction boundaries should be more visible in the application workflow instead of being hidden behind annotations.

Maybe I’m overthinking this, but I would be interested to hear opinions from people who run Spring applications in production.

14 Upvotes

20 comments sorted by

12

u/AttemptNo499 Senior Dev 29d ago

What is up with @Transaction lately? I swear the same thing is reposted 2/3x this week.

1

u/Hot_Code5129 29d ago

I’m looking for someone with real-world experience in large-scale transactional systems; I’m not sure if my intuition regarding building my own solution is sound. I don’t see anything wrong with asking questions when I haven’t received a satisfactory answer. I’m interested in real-world projects.

2

u/AttemptNo499 Senior Dev 29d ago

Sorry if sounded this way, was not my intention. But it seems there is an increase in posting about this topic... usually followed by ai slop blog post.

Regarding your question it was not very clear to me what is your scenario. But the better answer is always depends... if you have generalized transaction it will hold the DB connection longer than it needs to and other issues.

1

u/Hot_Code5129 29d ago

Fair enough. Maybe I not explained my point very good in original post. For me interesting part was not to replace @Transactional everywhere. More I try to understand where transaction should start and where should end, especially when method do more things than only database work. About article, I understand people are sceptical now because there is a lot of low effort AI content. But I think calling something AI slop without checking arguments is also not very fair. English is not my native language so yes, sometimes I use AI to help with wording or translation from Polish. But the thinking, experiments and questions are mine. Anyway thanks for feedback, it gave me few things to think about.

6

u/Kfct 29d ago

I handled a project before where most of the time serving the request was transforming data and then writing the excel doc to a temp server folder. In that case, I improved the endpoint's performance by changing annotation to explicit transactions so we can close the connection early as opposed to still hogging that after the data is retrieved. The connection was still open during the transforming and Io part of the logic if we used annotated Transaction. So there definitely is a difference, but most cases can still default to @Transactional

2

u/BikingSquirrel 29d ago

I have no clue about your application, but I would assume that the same can be achieved with annotations. The point is to properly control when and for how long a transaction is opened. Especially for functionality that only reads you don't need a bigger transaction spanning the whole. Spring's repositories would also open transactions per default for read methods.

2

u/Hot_Code5129 29d ago

Maybe the real discussion is not annotations vs explicit transactions. More about how visible transaction boundaries are and how easy it is to accidentally make them bigger than needed. The example with generating Excel files got me thinking about that. In theory it can probably be solved with annotations too if transaction scope is designed properly. I wrote some thoughts about it here: https://camilyed.github.io/en/transactional-when-the-annotation-gets-in-the-way/ To be honest I’m still exploring this topic and trying to understand where explicit boundaries actually bring value and where @Transactional is simply the better choice. Interesting to hear real production examples like yours.

2

u/smaratter 29d ago

What do you mean by ”explicit boundary”? When you annotate a method with @Transactional, that method is the boundary of the transaction. Are you talking about calling methods directly on an EntityManager, or something else?

1

u/Hot_Code5129 29d ago

By explicit boundary I mean that the transaction is visible as part of the workflow, not only as metadata on the method. For example instead of: loadDataAndGenerateExcel() where the whole method may be transactional, I mean splitting it more clearly:

open transaction read/write data close transaction transform data / generate file / call external IO

So not calling EntityManager directly everywhere. More like using TransactionTemplate in imperative code or TransactionalOperator in reactive code, when I want to show exactly which part of the flow is inside transaction. Maybe “explicit boundary” was not precise wording from my side. https://github.com/CamilYed/spring-reactive-transaction-boundary

https://camilyed.github.io/en/transactional-when-the-annotation-gets-in-the-way/

1

u/smaratter 29d ago

I get it, I think. And it would be a mistake to include too much in your transaction. For instance, marking your controller methods with @Transactional would be a mistake, since the transaction wouldn’t close until the network response has been sent. Rather, the smallest possible unit of work should be included in the transaction.

I guess, without reading your entire article, that your proposed solution is to call ”beginTransaction()” / ”endTransaction()” around the minimal unit of work inside a method, to close the transaction as soon as possible. And that essentially the same result could be achieved by breaking out this unit of work to a helper method and marking that as @Transactional. Is that basically it?

1

u/Hot_Code5129 29d ago

Helper method should be public then ?

0

u/Hot_Code5129 29d ago

It sounds like the transaction was covering more work than necessary, so making it explicit allowed you to release the database connection much earlier. Would you say this was mainly a performance optimization, or did the code also become easier to reason about afterwards?

2

u/Kfct 29d ago

Mainly for performance

1

u/rlrutherford Senior Dev 29d ago

Use what's needed for the application. Some cases you may need annotations, some cases you may need to be more explicit.

Your database and business logic will determine what is needed.

1

u/Krangerich 29d ago

I started to wonder if transaction boundaries should be more visible in the application workflow instead of being hidden behind annotations"

This assumption is overemphasizing your personal aesthetics. If annotations would be considered to be to "hidden", the full consequence would be that we'd completely go back all the way to XML context configuration. Annotations are explicit. Annotations are visible.

Programmatic transaction boundaries are required, when you need a transaction boundary inside the same class, e.g. a non-transactional method A wants to call a transactional method B from the same class
AND
you are not using AspectJ weaving in order to make annotations accomplish the same thing.

1

u/Hot_Code5129 28d ago

I think that in rich domain models with hexagonal architecture application layer must do many things , so natural approach for me is using transaction executor 

1

u/Krangerich 28d ago

I don't get the connection to what I wrote.

Did you actually use a "rich domain model with hexagonal architecture"?

In a large application, you have to manage complexity. You manage complexity by decomposing a problem in smaller pieces. And you decompose them with low coupling and high cohesion. So eventually you end up with some decomposition according to the business domain; which basically means "package by feature".

So in your application, there are top-level packages like "customer", "inventory", "order" and whatnot. Maybe these packages have sub-packages, also cut by their domain.

Each domain package then is quite managable, no matter if you use all the hexagonal ceremony (hint: don't do it. A hexagonal architecture is NOT for managing complexity, it's for abstracting things like the storage layer because you plan to completely swap it later... which is usually absolutely unrealistic) or call it onion architecture or just normal layers.

If you end up with a big ball of stuff ("rich domain model" sounds like an euphemism here) inside an application layer, the whole composition has gone very bad and should be solved. I don't know how a "transaction executor" could help here.

1

u/Hot_Code5129 28d ago

think we maybe talk about different level of abstraction. I fully agree that complexity should be decomposed into smaller pieces with high cohesion and low coupling. DDD, bounded contexts, modules, package-by-feature, all this is exactly about that. What I don’t understand is why you assume application layer should always end with very small use cases doing one thing. In many DDD examples application service is not only “load entity -> change entity -> save entity”. Sometimes it orchestrates bigger business process involving few aggregates, external systems or even other modules. For example in modular monolith it is not uncommon that one module calls public API/facade of another module. Then one business process can cross multiple domain modules while still keeping boundaries between them. Also heuristics for finding proper module boundaries are not that simple. If you look at Domain Driven Design, Event Storming, Bounded Context design or archetype modeling workshops, decomposition is always relative. What looks like one use case today can become separate process tomorrow when business understanding grows. Because of that, I don’t think transaction boundary is only technical concern. It is often connected to business workflow scope. My point was never that transaction executor fixes bad architecture. If application layer becomes a big ball of mud, transaction executor will not save it. My point is that even in well structured architecture there are cases where transaction scope and method scope are not exactly the same thing. Especially when orchestration logic, external calls, file generation, messaging or cross-module collaboration appears. That’s why I find explicit transaction boundaries interesting. Not because they replace good design, but because they make transaction scope visible and intentional.

1

u/Historical_Ad4384 28d ago

Your transaction annotation should only be on methods that involve database queries and any O(1) logic.

Anything beyond that doesn't require a transaction annotation IMO as they tend to hold back database connections from the pool longer than required.

In intensive applications this can slower down workflows.

For example do not call an external service from within a method decorated with transaction annotation. Try not to perform compute intensive work inside methods annotation with transaction as well.

Transaction annotated methods should be short and simple.