Spring 5 WebFlux and JDBC: To Block or Not to Block
With Spring 5's awaited promise of more reactive support, let's examine both how we can make JDBC more asynchronous and the problems that brings.
Join the DZone community and get the full member experience.
Join For FreeThe 5th version of the Spring Framework brings a huge step forward in Functional and Reactive Programming support. You don’t need ApplicationContext or dozens of annotations to have the simplest REST API up and running. Spring 5 will offer lightweight Web Functions and reactive Web Flux support that can help this transition happen. Those new features make Java and Spring 5 good candidates for building reactive web applications
To be reactive, according to The Reactive Manifesto, you have to be Responsive, Resilient, Elastic, and Message Driven. The last criteria in this list caused big movement into the asynchronous way of communications. This includes asynchronous RPC and messaging libraries, database drivers, and more. RDBMSs are quite powerful and useful. The official instruments for database access on JVM provided by database vendors are drivers implementing JDBC API. But JDBC is designed to be blocking and consumes threads per database call. You will not find in the API itself methods or interfaces allowing you to get query results in another thread. There is also an opinion that a transactional database is not a fit for the reactive concept:
The concept of a transaction doesn’t naturally fit the reactive world, as it’s about blocking a resource, which is exactly what you want to avoid here
I would partially agree. However, it depends on how exactly you will be using your relational database. Moreover, in the new era of highly scalable SQL databases (such as Amazon’s Aurora or Google’s Cloud Spanner) reactivity is a fit to achieve high throughput by using them together. The question is what shall we do when we are building applications on top of a SQL database with reactivity in mind. One option would be to use alternative SQL clients that are fully non-blocking. Some examples include here and here.
Of course, none of these drivers is officially supported by database vendors yet. Also, functionality is way much less attractive when compared to mature JDBC-based abstractions such as Hibernate or jOOQ.
An alternative idea came to me from the Scala world. We can dispatch blocking calls into an isolated Thread Pool to avoid mixing blocking and non-blocking calls together. This will allow us to control the overall number of threads and will let the CPU serve non-blocking tasks in the main execution context, applying various optimisations.
Assuming that we have a JDBC-based implementation, such as Spring Data JPA, which is indeed blocking.
import org.springframework.data.repository.CrudRepository;
public interface AddressRepository extends CrudRepository<Address, Long> {}
We can make its execution asynchronous and dispatch it to a dedicated thread pool.
@Service
public class AddressService {
private final AddressRepository repository;
private final Scheduler scheduler;
public AddressRouter(AddressRepository repository, @Qualifier("jdbcScheduler") Scheduler scheduler) {
this.repository = repository;
this.scheduler = scheduler;
}
public Mono<Iterable<Address>> findAll() {
return async(() -> repository.findAll());
}
private <T> Mono<T> async(Callable<T> callable) {
return Mono.fromCallable(callable).publishOn(scheduler);
}
}
Our Scheduler for JDBC should be configured by using a dedicated Thread Pool with a size count equal to the number of connections.
@Configuration
public class SchedulerConfiguration {
private final Integer connectionPoolSize;
public SchedulerConfiguration(@Value("${spring.datasource.maximum-pool-size}") Integer connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
@Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(Executors.newFixedThreadPool(connectionPoolSize));
}
}
However, there are difficulties with this approach. The main one is transaction management. In JDBC, transactions are possible only within a single java.sql.Connection. To make several operations in one transaction, they have to share a connection. If we want to make some calculations in between them, we have to keep the connection. This is not very effective, as we keep a limited number of connections idle while doing calculations in between.
This idea of an asynchronous JDBC wrapper is not new and is already implemented in the Scala library Slick 3. Finally, non-blocking JDBC may come along on the Java roadmap. As it was announced at JavaOne in September 2016, and it is possible that we will see it in Java 10.
Published at DZone with permission of Grygoriy Gonchar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments