Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Transaction Management with Spring: Simplicity with Sharp Edges

Posted on March 5, 2007 By Luis Fernandez

“Spring makes transactions feel like a checkbox. They are not.”

Transaction Management with Spring looks easy on the surface. Write your service, add a neat annotation or a small config snippet, and boom, atomic work. If you are moving from heavy EJB days to Spring 2, it feels like swapping a truck for a scooter. But scooters still have brakes and sharp parts. Today I want to cover the joy and the papercuts from a practitioner angle, so you can get the speed without eating gravel.

Story one The first taste of simple

We had a small team taking a monolith out of an app server and into Tomcat with Spring transaction management, Hibernate 3, and a plain DataSource. Life improved right away. XML was smaller. Unit tests ran without the container. We tossed the EJB facade layer and kept our services tidy with declarative transactions. The first week was lovely. We flipped a few REQUIRED settings, watched commits happen at the right places, and saw rollbacks wipe out bad writes. Everyone smiled.

Then a bug report landed. A service method called another method in the same class, and the inner one should have started a new transaction. It did not. Money moved but audit rows did not. No exception. No stack trace. Welcome to proxy world. Spring’s default proxying wraps your bean, but self invocation goes straight to the target and skips the proxy. We learned that lesson with a long night, strong coffee, and a whiteboard full of arrows.

Story two When a checked exception sneaks by

On another project we wrapped an external billing call. The call failed with a checked exception. Our service caught it, logged it, returned a friendly error, and the transaction committed. Later we found partial state in the database and an angry customer. By default Spring marks transactions for rollback on runtime exceptions but checked exceptions do not trigger it unless you ask. The fix was a one line rule, but the lesson was bigger. What you catch, rethrow, or wrap shapes database state in ways you may not expect.

Deep dive Propagation and boundaries

The heart of Spring @Transactional and XML based advice is propagation. It decides whether your method joins, starts, or avoids a transaction.

  • REQUIRED is the default. Join if there is a transaction, start one if not. Great for most service methods.
  • REQUIRES_NEW always starts a fresh one. The current one is suspended. Good for audit logs or outbox style writes you must commit even when the outer work fails. Be careful, you lose the outer rollback story.
  • NESTED uses savepoints in the same physical transaction. It can roll back its part without discarding the whole outer unit. This needs DataSourceTransactionManager with a database that supports savepoints. It is not available with JtaTransactionManager.
  • NOT_SUPPORTED runs without a transaction and suspends any current one. Useful for long reads or calls that should not hold locks.
  • MANDATORY, NEVER, and SUPPORTS are more niche. They help enforce calling rules or reuse code in and out of transactions.

Two sharp edges appear again and again:

  • Self invocation. Transaction advice is applied by a proxy. A call from one method to another inside the same class does not pass through the proxy, so your @Transactional on the inner method is ignored. Possible paths: extract the inner method to another bean, enable proxyTargetClass with CGLIB and still avoid self calls, or use AspectJ weaving. The cleanest fix is to refactor so calls cross the bean boundary.
  • Thread boundaries. Transactions are bound to the current thread. Do not spawn threads inside a transactional method and expect the child to join the work. It will not.

Deep dive Exceptions, rollbacks, and readOnly

By default Spring rolls back on RuntimeException and Error. Checked exceptions commit unless you tell Spring otherwise. You have three levers:

  • Declare rollbackFor and noRollbackFor to tune behavior with annotations or XML.
  • Do not swallow exceptions. If you catch one to log, rethrow an appropriate one that triggers the right outcome.
  • Use timeout to avoid hanging forever. Timeouts are honored by DataSourceTransactionManager and can be propagated to Hibernate.

The readOnly flag confuses many people. It is mostly a hint. With JDBC it might set the connection as read only which some drivers respect. With Hibernate, Spring can switch the session flush mode to reduce writes. It does not add a magical lock. Do not rely on readOnly to block changes. Validate that your framework and driver honor it and still keep discipline in your write paths.

Isolation is another lever. READ_COMMITTED is usually fine. REPEATABLE_READ may be needed for tricky reads on MySQL with InnoDB. SERIALIZABLE is rare and heavy. Use optimistic locking with versions when possible, and keep transactions short so locks do not pile up.

Deep dive Picking the right TransactionManager

Spring plugs transactions into different backends through PlatformTransactionManager. Choose the one that matches your stack and resource mix.

  • DataSourceTransactionManager: one JDBC DataSource. Simple, fast, supports NESTED with savepoints. Perfect for a single database on Tomcat.
  • HibernateTransactionManager: matches Hibernate’s session and handles flush mode and timeouts. Good when you manage sessions outside JPA. Pair it with one DataSource.
  • JtaTransactionManager: you need this when you span more than one transactional resource. For example a database and JMS. In a full app server it hooks into the container. On Tomcat you can use a standalone manager like Bitronix or JOTM. Note that NESTED is not part of JTA.

Mixing resources without JTA is a trap. If you write to two databases inside a single transaction with only a local manager, you can get partial commits on failure. If you truly need two phase commit, reach for JTA. If you do not, keep it to one resource and life stays simpler.

Also think about the web tier. The classic Open Session in View pattern can help with lazy reads in views, but it can hide N plus one and cause unexpected flushes at commit time. Keep view logic light and load what you need in the service layer with clear transactional scope.

Reflective close

Spring makes transactions feel friendly. That is good. The same friendliness can hide the edge of a proxy, the surprise of a checked exception, or the weight of the wrong manager. Keep your services small, be explicit about propagation, define rollback rules, and test with the transaction manager you will use in production. When in doubt, favor one resource with REQUIRED, avoid self calls, and keep transactions short. Simplicity wins, as long as you keep an eye on the sharp bits.

Engineering Management Software Architecture Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes