Open-Source Wikis

/

Spring Framework

/

Features

/

Data access

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:

  • JDBCSQLExceptionBadSqlGrammarException, DuplicateKeyException, …
  • JPAPersistenceException / Hibernate exceptions → DataIntegrityViolationException, …
  • R2DBCR2dbcException → 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 --> PTM

Key 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"| APP

The 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 object
  • ResultSetExtractor<T> — Whole ResultSet → one object (e.g., for joins)
  • RowCallbackHandler — Side-effecting per-row processing
  • BeanPropertyRowMapper — Reflective mapping; convenient but slower than hand-written
  • DataClassRowMapper — 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:

  • LocalContainerEntityManagerFactoryBean to bootstrap JPA without persistence.xml
  • JpaTransactionManager (a PlatformTransactionManager)
  • @PersistenceContext injection 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 — Sets Connection.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

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Data access – Spring Framework wiki | Factory