What is R2dbcEntityTemplate and when would you use it instead of a repository?
answer
- Middle layer: mapping + fluent API
- select/insert/update/delete + Query/Criteria DSL
- Best for dynamic/conditional queries
- Built on DatabaseClient underneath
- Relational Criteria, not JPA Criteria
basics
~10 sR2dbcEntityTemplate 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 sR2dbcEntityTemplate 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@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
Know it exists as a fluent alternative to derived methods.
Can build a dynamic query with Query/Criteria and choose it over a repository method.
Uses it inside custom repository fragments and knows it wraps DatabaseClient.
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