Long answer I'm afraid - this is a big topic to unpack generally. But the tl;dr is you can, but you probably shouldn't.
You certainly can...
So the question is: is there any hack to use existing mappers along with reactive Fluxes and Monos? Maybe some callback-like solution or something?
Many reactive frameworks, reactor included, have mechanisms designed to do just this. You keep your traditional blocking libraries in the reactive chain, but you make sure they're executed in a traditional threadpool so they don't tie up the event loop threads (these must never block, that's critical.) In Webflux / reactor, you'd use Schedulers.boundedElastic() which is meant for just this purpose - it'll maintain an elastic pool of workers which will scale up and down as necessary, processing items as and when they appear.
As an aside, this is pretty much what your example there is doing - recall that supplyAsync() uses the common ForkJoin pool, so all it's doing is delegating your task to a threadpool-backed executor. Unlike reactors boundedElastic() however, the fork join pool won't scale up and down based on your requirements, so it's not as suited for the task.
...but you shouldn't.
Possible certainly doesn't mean you should. By executing your tasks in this way, you're losing all the benefits of the reactive framework for this particular piece of code - most crucially, you retain the context switching you're trying to avoid (the key driver of using a reactive stack in the first place.)
If you're palming your entire DB layer off onto a blocking threadpool, then there's going to be little to no gain in using a reactive framework to begin with.
Would you ever?
That doesn't mean to say you should never use it however. For big, application critical components such as your DB layer it's a complete no-no in my book. But the above approach can work well if you have a kind of auxiliary service to your application that's not often used and performance isn't critical - say you had an old blocking DB connection that was only ever used for a piece of seldom-used error reporting functionality for instance, then using an elastic scheduler would (IMHO) be perfectly acceptable.
So what should you do?
You'll need to move to R2DBC - that much is pretty clear to me. But you can actually still do that within the norm / safety of a blocking envrionment, at least initially.
When migrating older apps (assuming you've done the due diligence and it's worth doing) my preference is actually to take a "bottom-up" approach, migrating all your components to the reactive equivalent, but then calling block() at the point where you interface your reactive library (R2DBC in this case) to your older, blocking code. This gives you a few advantages:
- You can then run thorough tests with the new library in place, but all in your old, blocking environment - nothing else needs to change;
- It allows you to easily revert if there's an issue without changing heaps of other code;
- You can follow an incremental approach, migrating all your blocking libraries in this way one-by-one until you have a full suite of reactive libraries "ready-to-go", tried and tested;
- When you come to making the "final plunge" to switch to a fully reactive stack, you're not changing much at all - you're just switching the environment and removing some blocking calls. All the individual parts are then tried and tested.
The above isn't to say the task will be quick or easy of course - quite the opposite, especially if you're using some relatively young libraries which might have issues. But it does give you a sane approach to migrating where you can test the system bit by bit, and hopefully gain confidence at each step that things are happening exactly as they should.