Mastering Spring Data R2DBC: Reactive Database Access in Spring Boot
This guide explains how to set up Spring Data R2DBC with Spring Boot, covering driver selection, dependency configuration, application properties, entity definitions, CRUD services, controllers, repository usage, and optimistic locking for reactive relational database interactions.
R2DBC Overview
Spring Data R2DBC is part of the larger Spring Data family and simplifies building repositories based on the Reactive Relational Database Connectivity (R2DBC) specification, which enables reactive, non‑blocking access to SQL databases. It provides a functional approach via R2dbcEntityTemplate as the entry point.
WebFlux Introduction
Spring WebFlux, added in Spring 5, is a fully non‑blocking web framework that runs on Netty, Undertow, or Servlet 3.1+ containers. It coexists with Spring Web MVC, allowing applications to use either or both frameworks.
Dependency Management
<code><dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
</dependency>
</dependencies></code>Application Configuration
<code>spring:
r2dbc:
url: r2dbc:mysql://localhost:3306/reactive_db
username: root
password: 123123
pool:
initialSize: 100
maxSize: 100
---
logging:
level:
org.springframework.r2dbc: DEBUG # prints executed SQL</code>Entity and Service CRUD Operations
<code>@Table("T_USERS")
public class Users {
@Id
private Long id;
private String name;
private String sex;
private Integer age;
}
@Service
public class UsersService {
@Resource
private R2dbcEntityTemplate template;
@Transactional
public Mono<Users> insertByTemplate(Users users) {
return template.insert(users);
}
public Mono<Integer> removeByTemplate(Long id) {
Query query = Query.query(Criteria.where("id").is(id));
return template.delete(query, Users.class);
}
public Mono<Integer> updateByTemplate(Users users) {
CriteriaDefinition criteria = Criteria.where("id").is(users.getId());
Query query = Query.query(criteria);
Update update = Update.update("name", users.getName());
return template.update(query, update, Users.class);
}
public Mono<Users> selectByTemplate(Long id) {
Query query = Query.query(Criteria.where("id").is(id));
return template.select(query, Users.class).single();
}
public Flux<Users> selectByTemplate(Integer page, Integer size) {
Query query = Query.empty().offset((page - 1) * size).limit(size);
return template.select(query, Users.class);
}
public Mono<Long> selectByTemplateCount() {
return template.select(Users.class).count();
}
public Mono<ResponseEntity<List<Users>>> selectByTemplatePager(Integer page, Integer size) {
Mono<List<Users>> datas = this.selectByTemplate(page, size).collectList();
Mono<Long> count = this.selectByTemplateCount();
return datas.zipWith(count, (list, c) ->
ResponseEntity.ok()
.header("count", c + "")
.header("page", page + "")
.header("size", size + "")
.body(list)
);
}
}</code>Controller Layer
<code>@RestController
public class UsersController {
@Resource
private UsersService us;
@PostMapping("/insert")
public Mono<Users> insertByTemplate(@RequestBody Users users) {
return us.insertByTemplate(users);
}
@GetMapping("/remove/{id}")
public Mono<Integer> removeByTemplate(@PathVariable("id") Long id) {
return us.removeByTemplate(id);
}
@PostMapping("/update")
public Mono<Integer> updateByTemplate(@RequestBody Users users) {
return us.updateByTemplate(users);
}
@GetMapping("/query/{id}")
public Mono<Users> selectByTemplate(@PathVariable("id") Long id) {
return us.selectByTemplate(id).single();
}
@GetMapping("/pager")
public Mono<ResponseEntity<List<Users>>> selectByTemplate(Integer page, Integer size) {
return us.selectByTemplatePager(page, size);
}
@GetMapping("/count")
public Mono<Long> selectByTemplateCount() {
return us.selectByTemplateCount();
}
}</code>R2DBC Repository
Repositories can extend ReactiveCrudRepository or ReactiveSortingRepository . Example methods:
<code>interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
Mono<Integer> deleteByLastname(String lastname);
Mono<Void> deletePersonByLastname(String lastname);
Mono<Boolean> deletePersonByLastname(String lastname);
}</code>Custom Modifying Query
<code>@Modifying
@Query("UPDATE person SET firstname = :firstname where lastname = :lastname")
Mono<Integer> setFixedFirstnameFor(String firstname, String lastname);
</code>Optimistic Locking Support
The @Version annotation adds a version column to the entity, ensuring updates succeed only when the version matches, similar to JPA. If a concurrent update changes the version, an OptimisticLockingFailureException is thrown.
<code>@Table
public class Person {
@Id
Long id;
String firstname;
String lastname;
@Version
Long version;
}
</code>Example that triggers the optimistic‑locking exception:
<code>R2dbcEntityTemplate template = …;
// 1. Insert initial data (version = 0)
Mono<Person> daenerys = template.insert(new Person("Daenerys"));
// 2. Load the inserted row (version = 0)
Person other = template.select(Person.class)
.matching(query(where("id").is(daenerys.getId()))).first().block();
// 3. Update the first instance (version becomes 1)
daenerys.setLastname("Targaryen");
template.update(daenerys);
// 4. Attempt to update the stale instance – triggers OptimisticLockingFailureException
template.update(other).subscribe();
</code>For more details on MySQL R2DBC configuration, see the official GitHub repository.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.