Since SpringBoot 2 the Spring WebFlux was introduced so we can create reactive web applications. This was great and it was working fine with NoSql databases but when it came to relational databases this was an issue. The JDBC database operations are blocking by nature and this will stop you to create a totally non-blocking application. But in order to have an asynchronous and non-blocking application, we will need to cover every layer of the application. The hero that solved this was the R2DBC – Reactive Relational Database Connectivity that gives a possibility to make none-blocking calls to Relational Databases.
The combination of WebFlux and R2DBC is enough to cover every layer in our application that we are going to build. As a relational database, we are going to use H2. So on to the coding!
Go to the spring initializr page from where we are going to build our application and select the following configuration:
- Group: com.north47 (or your package name)
- Artifact: spring-r2dbc
- Dependencies: Spring Reactive Web, Spring Data R2DBC [Experimental], H2 Database, Lombok
(You won’t be able to see the Lombok on this picture, but there it is! If for some reason the Lombok is causing you issues you might need to install a plugin. To do this in Intellij go to File -> Settings -> Plugins search for Lombok, install it and restart your IDE. If you can’t manage to do it just go the old way remove the annotations @Data, @AllArgsConstructor, @NoArgsConstructor in the Book.java class and just create your own setters, getters and constructors).
Now click on Generate, unzip the application and open it via your IDE.
Let’s first create a SQL script that will create our table. Go to src -> main -> resources and right-click on it and select New -> File. Name the file: schema.sql and enter there the following code:
CREATE TABLE BOOK (
ID INTEGER IDENTITY PRIMARY KEY ,
NAME VARCHAR(255) NOT NULL,
AUTHOR VARCHAR (255) NOT NULL
);
This will create a table with name ‘Book’ and the following columns: ID, NAME and AUTHOR.
We will create an additional script that will put us some data in our database. Repeat the following procedure from previous and this time give a name to the file: data.sql and add the following code:
INSERT INTO BOOK (ID,NAME,AUTHOR) VALUES (1,'Angels and Demons','Dan Brown');
INSERT INTO BOOK (ID,NAME, AUTHOR) VALUES (2,'The Matarese Circle', 'Robert Ludlum');
INSERT INTO BOOK (ID,NAME,AUTHOR) VALUES (3,'Name of the Rose', 'Umberto Eco');
This will put some data into our database.
In resources delete the application.properties file and let’s create a new file where we are going to add the following:
logging:
level:
org.springframework.data.r2dbc: DEBUG
spring:
r2dbc:
url: r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
name: sa
password:
Now that we have defined the r2dbc URL and enabled DEBUG logging level for r2dbc let’s go to create our java classes.
Create a new package domain under the ‘com.north47.springr2dbc’ and create a new class Book. This will be our database model:
package com.north47.springr2dbc.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
@Table("book")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
@Id
private Long id;
@Column(value = "name")
private String name;
@Column(value = "author")
private String author;
}
Now to create our repository first create a new package named ‘repository’ under ‘com.north47.springrdbc’. In there create an interface named BookRepository. This interface will extend the R2dbRepository:
package com.north47.springr2dbc.repository;
import com.north47.springr2dbc.domain.Book;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
public interface BookRepository extends R2dbcRepository<Book, Long> {
}
As you may notice we are not extending the JpaRepository as usual. The R2dbcRepository will provide us with methods that can work with objects like Flux, Mono etc…
After this, we will create endpoints from where we can access the previously inserted data or create new, modify it or delete it.
Create a new package ‘resource’ under the ‘com.north47.springr2dbc’ package and in there we will create our BookResource:
package com.north47.springr2dbc.resource;
import com.north47.springr2dbc.domain.Book;
import com.north47.springr2dbc.repository.BookRepository;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping(value = "/books")
public class BookResource {
private final BookRepository bookRepository;
public BookResource(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Book> getAllBooks() {
return bookRepository.findAll();
}
@GetMapping(value = "/{id}")
public Mono<Book> findById(@PathVariable Long id) {
return bookRepository.findById(id);
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<Book> save(@RequestBody Book book) {
return bookRepository.save(book);
}
@DeleteMapping(value = "/{id}")
public Mono<Void> delete(@PathVariable Long id) {
return bookRepository.deleteById(id);
}
}
And there we have endpoints from where we can access our data and modify it.
On to the postman so we can test our application, but of course, first, let’s start it. When you run the application you can see in the console that your server is started:
Netty started on port(s): 8080
Also since we enabled DEBUG log level you should be able to see al the SQL queries that are executed from the scripts that we wrote previously.
In postman set a GET method and the url: localhost:8080/books. In the Headers add key: ‘Content-Type’, value:’application-json’.
Press that send button and there it is you will get the data:
data:{"id":1,"name":"Angels and Demons","author":"Dan Brown"}
data:{"id":2,"name":"The Matarese Circle","author":"Robert Ludlum"}
data:{"id":3,"name":"Name of the Rose","author":"Umberto Eco"}
You can test also the other endpoints, for example, getting a book by id just by changing the URL to localhost:8080/books/1. The result will be:
{
"id": 1,
"name": "Angels and Demons",
"author": "Dan Brown"
}
Now you can test the other endpoints by creating a new Book by sending a POST request to the localhost:8080/books or delete a book by sending a DELETE to localhost:8080/books/{id}.
Here you can find the whole code:
Hope you enjoyed it!
Great article !
If you have experience with reactive web in Spring could you consider making a future post about Reactive VS Blocking web and give some pros and cons from your experience ?
Glad that you liked it. I haven’t thought about it, but maybe in the future will write about it or some similar topic.
Nice Article.
Easy to understand.
I tried executing this code in my machine. It gives me the below error,
2020-06-28 20:45:55.821 DEBUG 13728 — [ctor-http-nio-2] o.s.d.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT BOOK.* FROM BOOK]
2020-06-28 20:45:55.871 ERROR 13728 — [ctor-http-nio-2] a.w.r.e.AbstractErrorWebExceptionHandler : [b7134843-1] 500 Server Error for HTTP GET “/books”
org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [SELECT BOOK.* FROM BOOK]; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [42102] [42S02] Table “BOOK” not found; SQL statement:
SELECT BOOK.* FROM BOOK [42102-200]
It is not picking up the schema.sql and data.sql.
can you please help me on this.
Hi filip.trajkovski,
It worked for me. I missed dependencies in pom.xml.
Thanks for a very good article.
Hi Vijaysays,
Please What is the detail of your POM.XML?
Hello Paul, you can use this one: https://gitlab.com/47northlabs/public/spring-r2dbc/-/blob/master/pom.xml. Also you can find the whole project there. Hope that helps!