DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Intro to Spring Data MongoDB Reactive and How to Move It to the Cloud
  • Manage Hierarchical Data in MongoDB With Spring
  • Spring Data: Data Auditing Using JaVers and MongoDB
  • CRUD Operations on Deeply Nested Comments: Scalable Spring Boot and Spring Data approach

Trending

  • Kubeflow: Driving Scalable and Intelligent Machine Learning Systems
  • Revolutionizing Financial Monitoring: Building a Team Dashboard With OpenObserve
  • Building Enterprise-Ready Landing Zones: Beyond the Initial Setup
  • How Large Tech Companies Architect Resilient Systems for Millions of Users
  1. DZone
  2. Data Engineering
  3. Databases
  4. R2DBC: Reactive Programming With Spring, Part 4

R2DBC: Reactive Programming With Spring, Part 4

The fourth part of the blog series contains an introduction to Spring Data R2DBC.

By 
Anna Eriksson user avatar
Anna Eriksson
·
Jun. 27, 21 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
26.6K Views

Join the DZone community and get the full member experience.

Join For Free

This is part 4 of my blog series on reactive programming, which will give an introduction to R2DBC and describe how we can use Spring Data R2DBC to create a fully reactive application.

1. What is R2DBC?

If you are not already familiar with reactive programming and Reactive Streams, I recommend you to first read my introduction on reactive programming which describes the motivation behind this programming paradigm.

When developing a reactive application that should include access to a relational database, JDBC is not a good fit, since it is a blocking API.

R2DBC stands for Reactive Relational Database Connectivity and is intended to provide a way to work with SQL databases using a fully reactive, non-blocking API. It is based on the Reactive Streams specification and is primarily an SPI (Service Provider Interface) for database driver implementors and client library authors — meaning it is not intended to be used directly in application code.

At this moment, driver implementations exist for Oracle, Microsoft SQL Server, MySQL, PostgreSQL, H2, MariaDB, and Google Cloud Spanner.

2. Spring Data R2DBC

Spring Data offers an R2DBC client: Spring Data R2DBC.

This is not a full ORM like JPA — it does not offer features such as caching or lazy loading. But it does provide object mapping functionality and a Repository abstraction.

To demonstrate how it can be used, let’s revisit the StudentController example from my previous blog on WebFlux:

Java
 
@RestController
@RequestMapping("/students")
public class StudentController {

    @Autowired
    private StudentService studentService;


    public StudentController() {
    }

    @GetMapping("/{id}")
    public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) {
        return studentService.findStudentById(id)
                .map(ResponseEntity::ok)
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @GetMapping
    public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) {
        return studentService.findStudentsByName(name);
    }

    @PostMapping
    public Mono<Student> addNewStudent(@RequestBody Student student) {
        return studentService.addNewStudent(student);
    }

    @PutMapping("/{id}")
    public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) {
        return studentService.updateStudent(id, student)
                .map(ResponseEntity::ok)
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {
        return studentService.findStudentById(id)
                .flatMap(s ->
                        studentService.deleteStudent(s)
                                .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                )
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }
}


This controller holds some different methods for performing actions on students. We can see that it is using a StudentService to perform these actions. Now we will look into this functionality behind the REST controller and how we can implement database access using R2DBC.

2.1 Implementation Example

2.1.1 Dependencies

First, we need to add a couple of new dependencies to our project:

XML
 
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
        </dependency>

        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        ...
</dependencies>


We need to include the spring-boot-starter-data-r2dbc to enable spring-data-r2dbc. For this example, we will use a PostgreSQL database, and so we need to add the r2dbc-postgresql to get the r2dbc driver implementation needed.

2.1.2 Database Config

We can either add our database connection details in application.properties:

Properties files
 
spring.r2dbc.url=r2dbc:postgresql://localhost/studentdb
spring.r2dbc.username=user
spring.r2dbc.password=secret


or use a Java-based configuration:

Java
 
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static io.r2dbc.spi.ConnectionFactoryOptions.*;

@Configuration
public class R2DBCConfig {

    @Bean
    public ConnectionFactory connectionFactory() {
        return ConnectionFactories.get(
                ConnectionFactoryOptions.builder()
                        .option(DRIVER, "postgresql")
                        .option(HOST, "localhost")
                        .option(USER, "user")
                        .option(PASSWORD, "secret")
                        .option(DATABASE, "studentdb")
                        .build());
    }

}


2.1.3 StudentService

Now let’s take a look at the StudentService that the StudentController is using:

Java
 
@Service
public class StudentService {

    @Autowired
    private StudentRepository studentRepository;

    public StudentService() {
    }

    public Flux<Student> findStudentsByName(String name) {
        return (name != null) ? studentRepository.findByName(name) : studentRepository.findAll();
    }

    public Mono<Student> findStudentById(long id) {
        return studentRepository.findById(id);
    }

    public Mono<Student> addNewStudent(Student student) {
        return studentRepository.save(student);
    }

    public Mono<Student> updateStudent(long id, Student student) {
        return studentRepository.findById(id)
                .flatMap(s -> {
                    student.setId(s.getId());
                    return studentRepository.save(student);
                });

    }

    public Mono<Void> deleteStudent(Student student) {
        return studentRepository.delete(student);
    }

}


As you can see, it uses a StudentRepository to perform the different database operations on students. So now let’s take a look at this repository.

2.1.4 StudentRepository

The StudentRepository is an implementation of ReactiveCrudRepository. This is an interface from Spring Data R2DBC for generic CRUD operations using Project Reactor types. Since ReactiveCrudRepository already holds definitions for most of the repository methods we use in the StudentService (findAll, findById, save and delete) all we need to declare is the following:

Java
 
public interface StudentRepository extends ReactiveCrudRepository<Student, Long> {

    public Flux<Student> findByName(String name);

}


More complex queries could be defined as well by adding a @Query annotation to a method and specifying the actual SQL.

Besides the ReactiveCrudRepository, there is also an extension called ReactiveSortingRepository which provides additional methods to retrieve entities sorted.

2.1.5 Student

Finally, let’s look at the implementation of Student:

Java
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table
public class Student {

    @Id
    private Long id;
    private String name;
    private String address;

}


A few things to note:

  • The id of an entity should be annotated with Spring Data’s @Id annotation.
  • The @Table annotation is not necessary but adding it lets the classpath scanner find and pre-process the entities to extract the related metadata. If you don’t add it this will instead happen the first time you store an entity which could have a slightly negative impact on performance.
  • Lombok is recommended to be used to avoid boilerplate code.
  • There are also some other recommendations to ensure you get optimal performance, you can find the details in the reference documentation.

2.1.6 Other Options for Queries

Instead of using a repository, you could execute an SQL statement directly using a DatabaseClient.

For example, to retrieve all students:

Java
 
public Flux<Student> findAll() {
        DatabaseClient client = DatabaseClient.create(connectionFactory);
        return client.sql("select * from student")
                .map(row -> new Student(row.get("id", Long.class),
                        row.get("name", String.class),
                        row.get("address", String.class))).all();
 }

It is also possible to use the R2dbcEntityTemplate to perform operations on entities.

For example:

Java
 
@Autowired
private R2dbcEntityTemplate template;

public Flux<Student> findAll() {
    return template.select(Student.class).all();
}

public Mono<Void> delete(Student student) {
    return template.delete(student).then();
}


2.2 Other Features

2.2.1 Optimistic Locking

Quite similar to JPA, it is possible to apply a @Version annotation at field level, to ensure that updates are only applied to rows with a matching version — if the version is not matching an OptimisticLockingFailureException is thrown.

2.2.2 Transactions

Spring supports reactive transaction management through the ReactiveTransactionManager SPI. The @Transactional annotation can be applied on reactive methods returning Publisher types and programmatic transaction management can be applied using the TransactionalOperator.

2.2.3 Reactive Libraries

Just like WebFlux, Spring Data R2DBC requires Project Reactor as a core dependency but is interoperable with other reactive libraries that implement the Reactive Streams specification. Repositories exist for RxJava2 and RxJava3 as well (view package summary).

2.2.4 Connection Pooling

For connection pooling, there is a library called r2dbc-pool available. For details on how to use it, take a look here.

3. Production Readiness

R2DBC is still a fairly new technology. The latest release versions as of now:

  • R2DBC specification: 0.8.5
  • Spring Data R2DBC: 1.3.1
  • r2dbc-postgresql: 0.8.8
  • r2dbc-pool: 0.8.7

Before deciding to go to production with this for your application, it is of course recommended taking a closer look at the current state of the database driver and pooling implementations compared to your requirements. There are some open issues that might prevent you from taking this step as of now, but improvements are ongoing.

4. To Summarize...

This blog post demonstrated how Spring Data R2DBC can be used in a WebFlux application. And by that, we have created a fully reactive application and also come to an end of this series on reactive programming.

Another very interesting initiative worth mentioning is Project Loom. This is an OpenJDK project that started already in 2017 aiming to deliver lightweight concurrency, including a new type of Java threads that do not directly correspond to dedicated OS threads. These types of virtual threads would be much cheaper to create and block.

As you might recall from my first blog post the key drivers behind the reactive programming model are that we:

  • Move away from the thread per request model and can handle more requests with a low number of threads.
  • Prevent threads from blocking while waiting for I/O operations to complete.
  • Make it easy to do parallel calls.
  • Support 'back pressure,' giving the client a possibility to inform the server on how much load it can handle.

Project Loom seems very promising when it comes to helping out with the first two items in this list — this would then be taken care of by the JVM itself without any additional framework needed.

It is not yet decided when the changes will be introduced in an official Java release, but early access binaries are available for download.

References

R2DBC

Spring Data R2DBC Reference Documentation

r2dbc-postgresql

r2dbc-pool

Project Loom

Going inside Java’s Project Loom and virtual threads

Spring Framework Database connection Reactive programming Relational database Spring Data

Published at DZone with permission of Anna Eriksson. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Intro to Spring Data MongoDB Reactive and How to Move It to the Cloud
  • Manage Hierarchical Data in MongoDB With Spring
  • Spring Data: Data Auditing Using JaVers and MongoDB
  • CRUD Operations on Deeply Nested Comments: Scalable Spring Boot and Spring Data approach

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!