skip to content

What is R2dbcEntityTemplate and when would you use it instead of a repository?

level: middleimportance: should knowfreq 48%

answer

  1. Middle layer: mapping + fluent API
  2. select/insert/update/delete + Query/Criteria DSL
  3. Best for dynamic/conditional queries
  4. Built on DatabaseClient underneath
  5. Relational Criteria, not JPA Criteria

basics

~10 s

R2dbcEntityTemplate is a mid-level helper that maps entities to/from rows using a fluent, type-safe API (select/insert/update/delete with Query and Criteria). Use it for dynamic queries a derived repository method cannot express.

solid answer

~40 s

R2dbcEntityTemplate sits between repositories and the raw DatabaseClient. It still does entity mapping (row to object and back) but exposes a fluent, programmatic API: template.select(User.class).from("users").matching(query(where("active").is(true))).all(), plus insert(), update(), and delete(). You build queries with the Query and Criteria DSL, so it shines for dynamic/conditional filters, projections, and cases where you want mapping without declaring a fixed derived method. It returns Mono/Flux like repositories and shares the same ConnectionFactory and mapping metadata. Repositories are best for stable, named queries; R2dbcEntityTemplate is best when query shape varies at runtime. Below it, DatabaseClient gives raw SQL with manual mapping. Boot auto-configures an R2dbcEntityTemplate bean you can inject directly.

code

java · 20 lines
java
@Service
public class UserSearch {
    private final R2dbcEntityTemplate template;

    UserSearch(R2dbcEntityTemplate template) { this.template = template; }

    Flux<User> search(Boolean active, Integer minAge) {
        Criteria c = Criteria.empty();
        if (active != null)  c = c.and("active").is(active);
        if (minAge != null)  c = c.and("age").greaterThanOrEquals(minAge);

        return template.select(User.class)
                       .matching(Query.query(c).sort(Sort.by("email")))
                       .all();
    }

    Mono<User> create(User u) {
        return template.insert(u); // returns entity with generated id
    }
}

go deeper

for a junior

Know it exists as a fluent alternative to derived methods.

for a middle

Can build a dynamic query with Query/Criteria and choose it over a repository method.

for a senior

Uses it inside custom repository fragments and knows it wraps DatabaseClient.

for a principal

Decides layering conventions across the codebase (repository vs template vs client) for maintainability.

Spring Data R2DBC offers three access layers of decreasing abstraction; **R2dbcEntityTemplate** is the middle one. **1. Repositories** (`R2dbcRepository`) — declarative, generated interfaces. Great for fixed queries. **2. R2dbcEntityTemplate** — a programmatic, still-entity-aware API. It performs object/row mapping using the same mapping metadata as repositories, but you compose the query yourself. **3. DatabaseClient** — raw SQL, no entity mapping (you bind params and map rows by hand). `R2dbcEntityTemplate` is actually built on top of a `DatabaseClient` and exposes it via `getDatabaseClient()`. **The fluent API.** `R2dbcEntityTemplate` provides entry points that read like sentences: - `select(Class<T>)` → `.from(table)` → `.matching(Query)` → `.all()` / `.one()` / `.first()` / `.count()` / `.exists()`. - `insert(entity)` returns `Mono<T>` with generated keys populated. - `update(...)` with `Query` + `Update.update("col", value)` returning `Mono<Long>` rows affected. - `delete(...)` returning `Mono<Long>`. **Query and Criteria DSL.** You build predicates with static imports: `Query.query(Criteria.where("status").is("ACTIVE").and("age").greaterThan(18))`. This is the key advantage over derived methods: you can **assemble filters conditionally at runtime** (add a criterion only if a filter param is present) without writing an exponential number of `findBy...` methods or hand-writing SQL. **Projections and column control.** `Query` supports `.columns(...)`, `.sort(...)`, `.limit(...)`, `.offset(...)`, giving fine control that derived methods lack. **When to prefer it.** - Dynamic search/filter endpoints where the WHERE clause varies. - You want entity mapping (so you avoid manual row mapping) but a single repository method cannot express the query. - Custom repository fragments: inject `R2dbcEntityTemplate` into a `...RepositoryImpl` to back a custom interface method. **When NOT to.** For a stable named query, a derived method or `@Query` is clearer. For complex vendor SQL, joins, CTEs, or set operations, drop to `DatabaseClient`. **Gotchas.** It is still non-blocking — everything returns `Mono`/`Flux` and is lazy. The `Criteria` you use is `org.springframework.data.relational.core.query.Criteria`, not the JPA `Criteria`. And like all of R2DBC there is no relationship handling — a `select` returns flat rows mapped to one entity type.

  • How does R2dbcEntityTemplate relate to DatabaseClient?
    It is built on top of a DatabaseClient and exposes it via getDatabaseClient(). The template adds entity mapping and the Query/Criteria DSL; DatabaseClient underneath handles the actual SQL execution and binding.
  • Why choose R2dbcEntityTemplate over adding another derived repository method?
    When the query shape is dynamic — filters present or absent at runtime — derived methods would explode combinatorially. The Criteria DSL lets you build the predicate conditionally while still getting entity mapping for free.

saying these in an interview costs you the question

  • Thinking the Criteria class is javax/jakarta persistence Criteria (it is Spring Data relational Criteria)
  • Assuming the template can eager/lazy load associations
  • Believing the template blocks or returns Lists

context