spring-projects/spring-framework
Data access
Active contributors: Juergen Hoeller, Sam Brannen
Spring's data access story spans multiple modules and two programming models (imperative and reactive). This page is a unified tour of the modules and how they fit together.
The data-access modules at a glance
| Module | Purpose |
|---|---|
| spring-jdbc | JdbcTemplate — SQL with template-method boilerplate gone |
| spring-tx | PlatformTransactionManager, @Transactional, propagation |
| spring-orm | JPA / Hibernate integration |
| spring-r2dbc | Reactive DatabaseClient, R2dbcTransactionManager |
| spring-oxm | Object/XML marshalling (incidental for some payload formats) |
| spring-jms | JMS messaging (transactional with the same tx infrastructure) |
Common abstractions
DataAccessException hierarchy
Every data-access module uses a common exception hierarchy rooted in org.springframework.dao.DataAccessException (declared in spring-tx). Vendor-specific errors are translated:
- JDBC —
SQLException→BadSqlGrammarException,DuplicateKeyException, … - JPA —
PersistenceException/ Hibernate exceptions →DataIntegrityViolationException, … - R2DBC —
R2dbcException→ similar translations
This means application code can catch DataIntegrityViolationException regardless of which underlying technology you're using.
Transaction management
Both imperative and reactive data access plug into the same conceptual transaction model:
| Concept | Imperative | Reactive |
|---|---|---|
| Transaction manager interface | PlatformTransactionManager |
ReactiveTransactionManager |
| State-binding mechanism | ThreadLocal (TransactionSynchronizationManager) |
Reactor Context |
| Annotation | @Transactional |
@Transactional (same annotation) |
| Programmatic API | TransactionTemplate |
TransactionalOperator |
A method that returns Mono<T>/Flux<T> and is annotated @Transactional is automatically routed through the reactive infrastructure, provided a ReactiveTransactionManager is present.
The imperative path
graph LR
APP[Service @Transactional] -->|"AOP proxy"| TX[TransactionInterceptor]
TX -->|"begin"| PTM[PlatformTransactionManager: DataSourceTransactionManager / JpaTransactionManager]
PTM -->|"binds Connection / EntityManager"| TSM[TransactionSynchronizationManager]
APP -->|"calls"| REPO[Repository]
REPO -->|"JdbcTemplate or EntityManager"| DATA[Database]
REPO -->|"acquires connection from"| TSM
APP --> TX_END[return — commit or rollback]
TX_END --> PTMKey observation: the data-access utility (JdbcTemplate, EntityManagerFactoryUtils) doesn't open its own connection when a transaction is active. It pulls the bound connection from TransactionSynchronizationManager. That's how @Transactional glue happens without anyone passing connections explicitly.
The reactive path
graph LR
APP[Reactive service @Transactional] -->|"reactive interceptor"| RTI[ReactiveTransactionInterceptor]
RTI -->|"begin"| RTM[R2dbcTransactionManager]
RTM -->|"binds Connection in"| CTX[Reactor Context]
APP -->|"DatabaseClient.sql(…)…"| DC[DatabaseClient]
DC -->|"acquires connection from"| CTX
DC -->|"streams Mono/Flux"| APPThe crucial difference: state isn't in ThreadLocal (which doesn't survive operator boundaries). It's stored in Reactor's Context, which propagates through the reactive chain.
JDBC templates
JdbcTemplate (positional parameters) and NamedParameterJdbcTemplate (:name placeholders) are the workhorse APIs:
List<User> users = jdbc.query(
"SELECT id, name FROM users WHERE active = ?",
new BeanPropertyRowMapper<>(User.class),
true
);Common helpers:
RowMapper<T>— One row → one objectResultSetExtractor<T>— WholeResultSet→ one object (e.g., for joins)RowCallbackHandler— Side-effecting per-row processingBeanPropertyRowMapper— Reflective mapping; convenient but slower than hand-writtenDataClassRowMapper— Records / Kotlin data classes / immutable objects with constructor binding
For inserts that need generated keys, use KeyHolder with update(PreparedStatementCreator, KeyHolder).
JPA via spring-orm
spring-orm provides:
LocalContainerEntityManagerFactoryBeanto bootstrap JPA withoutpersistence.xmlJpaTransactionManager(aPlatformTransactionManager)@PersistenceContextinjection processor- Exception translation via
PersistenceExceptionTranslationPostProcessor - Vendor adapters for Hibernate and EclipseLink
Most apps don't write JPA boilerplate directly — they use Spring Data JPA, which generates repository implementations on top of these primitives. But understanding the underlying wiring is essential when something goes wrong.
R2DBC
spring-r2dbc mirrors spring-jdbc for the reactive world:
client.sql("SELECT id, name FROM users WHERE active = :active")
.bind("active", true)
.map(row -> new User(row.get("id", Long.class), row.get("name", String.class)))
.all(); // Flux<User>DatabaseClient is the central type. Like JdbcTemplate, it handles connection acquisition and release; like NamedParameterJdbcTemplate, it supports named parameters with dialect-specific bind markers.
When to use what
| Scenario | Recommendation |
|---|---|
| Imperative app, simple SQL, no ORM | JdbcTemplate (spring-jdbc) |
| Imperative app, domain model, joins, lazy loading | JPA (spring-orm) + Spring Data JPA |
| Reactive WebFlux app, simple SQL | DatabaseClient (spring-r2dbc) |
| Reactive app with rich domain model | Spring Data R2DBC repositories |
| Mostly reads, lots of complex SQL | JdbcTemplate even in JPA apps |
Notable patterns
Exception translation
Combining @Repository with a PersistenceExceptionTranslationPostProcessor in your context lets non-Spring repositories (e.g., a Hibernate session-using DAO) automatically get their exceptions translated to DataAccessException.
Connection pooling
Spring doesn't ship a connection pool. Use HikariCP (most common), Tomcat JDBC, or a vendor pool. Wire the pool's DataSource and pass it to DataSourceTransactionManager.
Read-only transactions
@Transactional(readOnly = true) is honored differently by each tx manager:
DataSourceTransactionManager— SetsConnection.setReadOnly(true); the JDBC driver may optimize.JpaTransactionManager— Disables Hibernate dirty checking; significant performance win for read-heavy methods.R2dbcTransactionManager— Same intent on the R2DBC layer.
Embedded databases for tests
EmbeddedDatabaseBuilder bootstraps H2/HSQLDB/Derby with init scripts:
DataSource ds = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
.addScript("data.sql")
.build();Heavily used by spring-test's integration tests.
See also
- spring-jdbc, spring-tx, spring-orm, spring-r2dbc — the participating modules
- aop —
@Transactionalis implemented as AOP advice - reactive-stack — end-to-end reactive picture
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.