How to limit number of results in ReactiveMongoRepository - spring

I am looking for a way to pass the limit to the mongo query in ReactiveCrudRepository
I tried adding "First2" to the method name but I'm still getting all the results.
What I'm really looking for is a way to pass the value of the 'limit' to the method, passing it in request as #RequestParam int limit
This is my code for the repository
public interface ReactiveUserRepository
extends ReactiveCrudRepository<User, String> {
#Query("{ 'roles': ?0 }")
Flux<User> findFirst2ByRole(String role);
}
And this is controller method:
#GetMapping(path = "/byrole", produces = "application/stream+json")
Flux<User> getByRole(#RequestParam String role) {
return users.findFirst2ByRole(role).doOnNext(next -> {
System.out.println("Next user=" + next.getAssocId());
}).switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("No users found with role=%s", role))));
}

limitRequest(long) on a Flux may also be worth to have a look at: https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#limitRequest-long-
it can be used as a stricter form of take(long)
As it does cap the request amount that goes to the upstream source. This may prevent the reactive MongoDB driver from requesting/loading a huge batch of data when only a limited number is needed.
When using limitRequest, make sure that the provided long is "> 0" (<0 makes no sense and =0 results in a never completing Flux)

try to use reactor method users.findFirst2ByRole(role).take(2)
as well you can use skip() if needed

Related

Return an item by id

I got this piece of code, I am learning from tutorial. I want to return an element by url which looks like clients/1 instead of clients?id=1. How can I achieve this? Also, can the code below be made easier way?
#GetMapping
public Client getClient(#RequestParam int id) {
Optional<Client> first = clientList.stream().filter(element -> element.getId() == id).findFirst();
return first.get();
}
You may want to use #PathVariable as follows:
#Controller
#RequestMapping("/clients")
public class MyController {
#GetMapping("/{id}")
public Client getClient(#PathVariable int id) {
return clientList.stream().filter(element -> element.getId() == id).findFirst().orElseThrow();
}
Please note, the Optional can be unpacked with orElseThrow method. This will throw a NoSuchElementException in case there is no element found for the id.
Other solution would be to use orElse(new Client(...)) to return a default value if nothing is found.
get() is not really recommended to be used. From the JavaDoc of the get() method:
API Note:
The preferred alternative to this method is orElseThrow().
Even though get() may also throw a NoSuchElementException, similar to orElseThrow, usually the consensus is that get should not be used without isPresent, or should not be used at all. There several other methods to unpack the Optional without forcing you write an if.
The whole idea of the Optional is to overcome this by forcing you to think about the case when there is no value inside.

Mutiny Uni Convert to Primitive Type

Up until now I have done very basic things with smallrye Mutiny in Quarkus. Basically, I have one or two very small web services which only interact with a web application. These services return a Uni<Response>.
Now I'm writing a logging service I want my others to pass information to. In this logging service, I need to return a value to calling services. The logging service will return this value as a Uni<Integer>. What I'm struggling with is how to extract the return value in the calling service as an int.
Here is the function in the logging service
#GET
#Path("/requestid")
#Produces(MediaType.TEXT_PLAIN)
public Uni<Integer> getMaxRequestId(){
return service.getMaxRequestId();
}
public Uni<Integer> getMaxRequestId() {
Integer result = Integer.valueOf(em.createQuery("select MAX(request_id) from service_requests").getFirstResult());
if(result == null) {
result = 0;
}
return Uni.createFrom().item(result += 1);
}
And here is the client side code in the calling service
#Path("/requests")
public class RequestIdResource {
#RestClient
RequestIdServices service;
#GET
#Path("/requestid")
#Produces(MediaType.TEXT_PLAIN)
public Uni<Integer> getMaxRequestId(){
return service.getMaxRequestId();
}
}
public void filter(ContainerRequestContext requestContext) throws IOException {
int requestid = client.getMaxRequestId();
rm.name = ConfigProvider.getConfig().getValue("quarkus.application.name", String.class);
rm.server = requestContext.getUriInfo().getBaseUri().getHost();
rm.text = requestContext.getUriInfo().getPath(true);
rm.requestid = requestid;
}
Basically everything I have tried creates another Uni. Maybe I am simply using the concept all wrong. But how do I get the Integer out of the Uni so I can get the intValue?
You need to invoke a terminal operation, or use the value and continue the chain.
If you want to invoke a terminal operator you can invoke the await operation to make your code blocking and wait for the response.
If you want to merge this reactive invocation with another that is present in your client code, you can join or combine your actual Mutiny stream with the on coming from the response by using the combine method.
If you just want to use the value and do not retrieve it, you can suscribe and get the result.
If you have a multi you can call directly the method toList
Assuming that you want to have some timeouts involved and you want to get the actual Integer, you can go with the await method and a timeout.

spring webflux webclient response convert list of string to string

Response:
[
{
"version": "1.0",
"content": [
"12345",
"67076",
"123462",
"604340",
"1331999",
"1332608",
"1785581",
]
}
]
Code:
Mono<List<String>> mp = webClient.get().uri(accountMgmtURI)
.retrieve()
.bodyToMono(Map.class)
.flatMap(trans-> {
List<String> content= (List<String>) trans.get("content");
System.out.println("content :: "+content);
return Mono.just(content);
});
System.out.println("content :: "+mp.toString());
String sites=mp.toString();
The first issue is that the API you're using is not returning a single object, but an array of objects, indicated by the square brackets ([]).
This means you should at least refactor your code to use bodyToFlux() in stead of bodyToMono():
client
.get()
.retrieve()
// bodyToFlux() in stead of bodyToMono(
.bodyToFlux(Map.class)
// ...
The second issue is that it isn't easy to work with a Map in this case since you would have to cast everything the whole time as you weren't able to pass any generics. Working with a proper class, would make things easier. For example, you could write the following class:
public class VersionContent {
private String version;
private List<String> content;
// TODO: Getters + Setters
}
And change your code to:
client
.get()
.retrieve()
.bodyToFlux(VersionContent.class)
.map(VersionContent::getContent)
.flatMap(Flux::fromIterable)
// ...
This piece of code will retrieve the content for each object, and flatMap it so that each individual value is emitted separately.
Right now, each item within the content array would be published individually. This brings us to the third issue, which is that you're not concatenating your strings.
To concat items, you could use the reduce() operator:
client
.get()
.retrieve()
.bodyToFlux(VersionContent.class)
.map(VersionContent::getContent)
.flatMap(Flux::fromIterable)
// reduce() can be used to merge all individual items to a single item
.reduce((sites, site) -> sites + "|" + site)
// ...
The final issue is that you're using toString(), which won't work. One of the key aspects of reactive programming is that everything happens asynchronously. That means that if you try to do anything with your data in the main thread, nothing will happen.
Additionally, another feature of publishers like Mono and Flux is that they're lazy. Without a proper subscription, nothing will even happen.
The solution is to properly subscribe() to get your value, for example:
client
.get()
.retrieve()
.bodyToFlux(VersionContent.class)
.map(VersionContent::getContent)
.flatMap(Flux::fromIterable)
.reduce((sites, site) -> sites + "|" + site)
.subscribe(System.out::println);
For your example, the code above would print the following to the console:
12345|67076|123462|604390|1331999|1332608|1785581
Be aware, this also means that every operation you want to do with these sites, should be done asynchronously.
If you don't want to work asynchronously, you can use the block() operator like this:
String sites = client
.get()
.retrieve()
.bodyToFlux(VersionContent.class)
.map(VersionContent::getContent)
.flatMap(Flux::fromIterable)
.reduce((sites, site) -> sites + "|" + site)
.block();
Please use ready solutions. You should not use List<String> content= (List<String>) trans.get("content"). Java is strong typing language - so create classes for types. And frameworks as spring works with classes and objects.
At this case:
public class VersionedDataResponse {
private List<VersionedData> versionedDataList;
}
....
public class VersionedData {
private String version;
private List<String> content;
}
And spring will convert it at bodyToMono(VersionedDataResponse.class)

Service method transactionality when not using exceptions as flow control in Spring Boot

I have the following method in an #Service class which has #Transactional defined:
#Override
public Result add(#NonNull final UserSaveRequest request) {
final Result<Email> emailResult = Email.create(request.getEmail());
final Result<UserFirstName> userFirstNameResult = UserFirstName.create(request.getFirstName());
final Result<UserLastName> userLastNameResult = UserLastName.create(request.getLastName());
final Result combinedResult = Result.combine(emailResult, userFirstNameResult, userLastNameResult);
if (combinedResult.isFailure()) {
return Result.fail(combinedResult.getErrorMessage());
}
final Result<User> userResult = User.create(emailResult.getValue(), userFirstNameResult.getValue(), userLastNameResult.getValue());
if (userResult.isFailure()) {
return Result.fail(userResult.getErrorMessage());
}
this.userRepository.save(userResult.getValue());
return Result.ok();
}
Now as you can see I utilize a Result class which can contain a return value or an error message as I don't think using exceptions for flow control is very clean.
The problem I now have is; the complete method is bound in one transaction and if one database call should fail the whole transaction will be rolled back. In my model however, after the this.userRepository.save(userResult.getValue()); call, if something would happen that would force me to return a failed result, I can't undo that save(userResult.getVlaue()); call seeing as I don't use exceptions for flow control.
Is this a problem that has an elegant solution, or is this a place where I need to make a trade-off between using exceptions as flow control and having to mentally keep track of the ordering of my statements in these kind of situations?
Yes, you can trigger rollback manually. Try this:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
More information: https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/data-access.html#transaction-declarative-rolling-back

Is there any way to implement pagination in spring webflux and spring data reactive

I'm trying to understand reactive part of spring 5. I have created simple rest endpoint for finding all entities using spring web-flux and spring data reactive (mongo) but don't see any way how to implement pagination.
Here is my simple example in Kotlin:
#GetMapping("/posts/")
fun getAllPosts() = postRepository.findAll()
Does it mean that reactive endpoint does not require pagination? Is some way to implement pagination from server side using this stack?
The reactive support in Spring Data does not provide means of a Page return type. Still, the Pageable parameter is supported in method signatures passing on limit and offset to the drivers and therefore the store itself, returning a Flux<T> that emits the range requested.
Flux<Person> findByFirstname(String firstname, Pageable pageable);
For more information please have a look at the current Reference Documentation for 2.0.RC2 and the Spring Data Examples.
Flux provides skip and take methods to get pagination support, and you also can use filter and sort to filter and sort the result. The filter and sort below is not a good example, but use skip and Pageable as 2nd parameter are no different.
The following codes work for me.
#GetMapping("")
public Flux<Post> all(
//#RequestParam(value = "q", required = false) String q,
#RequestParam(value = "page", defaultValue = "0") long page,
#RequestParam(value = "size", defaultValue = "10") long size) {
return this.postRepository.findAll()
//.filter(p -> Optional.ofNullable(q).map(key -> p.getTitle().contains(key) || p.getContent().contains(key)).orElse(true))//(replace this with query parameters)
.sort(comparing(Post::getCreatedDate).reversed())
.skip(page * size).take(size);
}
Update: The underlay drivers should be responsible for handling the result in the reactivestreams way.
And as you see in the answer from Christoph, if using a findByXXX method, Spring Data Mongo Reactive provides a variant to accept a pageable argument, but the findAll(reactive version) does not include such a variant, you have to do skip in the later operations if you really need the pagination feature. When switching to Flux instead of List, imagine the data in Flux as living water in the rivers or oil in the pipes, or the tweets in twitter.com.
I have tried to compare the queries using Pageale and not in the following case.
this.postRepository.findByTitleContains("title")
.skip(0)
.limitRequest(10)
.sort((o1, o2) -> o1.getTitle().compareTo(o2.getTitle()))
this.postRepository.findByTitleContains("title", PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "title")))
When enabling logging for logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG and found they print the same log for queries.
find using query: { "title" : { "$regularExpression" : { "pattern" : ".*title.*", "options" : ""}}} fields: Document{{title=1}} for class: class com.example.demo.Post in collection: post
//other logging...
find using query: { "title" : { "$regularExpression" : { "pattern" : ".*title.*", "options" : ""}}} fields: Document{{title=1}} for class: class com.example.demo.Post in collection: post
Keep in mind, all these operations should be DELEGATED to the underlay R2dbc drivers which implemented the reactive streams spec and performed on the DB side, NOT in the memory of your application side.
Check the example codes.
The early sample code I provided above maybe is not a good sample of filter and sort operations(MongoDB itself provides great regularexpression operations for it). But pagination in the reactive variant is not a good match with the concept in the reactive stream spec. When embracing Spring reactive stack, most of the time, we just move our work to a new collection of APIs. In my opinion, the realtime update and elastic response scene could be better match Reactive, eg. using it with SSE, Websocket, RSocket, application/stream+json(missing in the new Spring docs) protocols, etc
This is not efficient but it works for me while I look for another solution
Service
public Page<Level> getPage(int page, int size, Sort.Direction direction, String properties) {
var pageRequest = PageRequest.of(page, size, direction, properties);
var count = levelRepository.count().block();
var levels = levelRepository.findAllLevelsPaged(pageRequest).collectList().block();
return new PageImpl<>(Objects.requireNonNull(levels), pageRequest, Objects.requireNonNull(count));
}
Repo
#Repository
public interface LevelRepository extends ReactiveMongoRepository<Level, String> {
#Query("{ id: { $exists: true }}")
Flux<Level> findAllLevelsPaged(final Pageable page);
}
Ref example

Resources