Backend Development 9 min read

Master Reactive Database Access with Spring Boot WebFlux and R2DBC

Learn how to integrate Spring Boot WebFlux with R2DBC for reactive database operations, covering R2DBC basics, driver selection, configuration, entity and service implementation, controller endpoints, repository usage, optimistic locking, and practical code examples to build high-performance, non-blocking backend applications.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Reactive Database Access with Spring Boot WebFlux and R2DBC

1. Introduction to R2DBC

Spring Data R2DBC is part of the larger Spring Data suite and simplifies building reactive repositories for relational databases. R2DBC (Reactive Relational Database Connectivity) provides a standard for integrating SQL databases in a reactive, non‑blocking manner. Spring Data R2DBC offers a functional API via R2dbcEntityTemplate as the entry point.

2. WebFlux Overview

Spring WebFlux, added in Spring 5, is a fully non‑blocking reactive 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 modules.

3. Adding Dependencies

<code>&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-webflux&lt;/artifactId&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-r2dbc&lt;/artifactId&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;dev.miku&lt;/groupId&gt;
    &lt;artifactId&gt;r2dbc-mysql&lt;/artifactId&gt;
  &lt;/dependency&gt;
&lt;/dependencies&gt;</code>

4. 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  # output executed SQL</code>

5. Entity and Service Implementation

<code>@Table("T_USERS")
public class Users {
  @Id
  private Long id;
  private String name;
  private String sex;
  private Integer age;
}

@Service
@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> selecyByTemplateCount() {
  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.selecyByTemplateCount();
  return datas.zipWith(count, (list, c) -> {
    return ResponseEntity.ok()
      .header("count", c + "")
      .header("page", page + "")
      .header("size", size + "")
      .body(list);
  });
}</code>

6. Controller Endpoints

<code>@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> selecyByTemplateCount() {
  return us.selecyByTemplateCount();
}</code>

7. R2DBC Repository

Repositories can extend ReactiveCrudRepository or ReactiveSortingRepository . The following table (illustrated below) lists the query methods they provide.

<code>interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
  Mono<Integer> deleteByLastname(String lastname);
  Mono<Void> deletePersonByLastname(String lastname);
  Mono<Boolean> deletePersonByLastname(String lastname);
}</code>

8. Custom Modifying Query

<code>@Modifying
@Query("UPDATE person SET firstname = :firstname where lastname = :lastname")
Mono<Integer> setFixedFirstnameFor(String firstname, String lastname);
</code>

9. Optimistic Lock Support

The @Version annotation adds a version column to the entity, enabling optimistic locking 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‑lock 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 row – version becomes 1
daenerys.setLastname("Targaryen");
template.update(daenerys);
// 4. Attempt to update the stale instance – triggers exception
template.update(other).subscribe();
</code>

10. Conclusion

This article demonstrates how to combine Spring Boot WebFlux with R2DBC to achieve reactive database programming. By leveraging non‑blocking I/O, developers can handle high concurrency and improve application performance and responsiveness. The provided code samples illustrate configuration, entity modeling, service and controller layers, repository usage, and optimistic locking, offering a solid foundation for building scalable, maintainable backend systems.

Backend DevelopmentReactive ProgrammingSpring BootWebFluxr2dbc
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.