Spring reactive: Chaining repository results - spring

Repository repo
Repository otherRepo
foreach entity : repo.FindAll() {
entityFind = otherRepo.FindById(entity.Prop)
if (entityFind != null) {
return entityFind
}
}
How could I do this using the spring reactive?
I could use blockFirst() to search in otherRepo but it would break the reaction chain
I also have tried use a handle() to control the flow but I don't get to break the flow when I find an item
Any idea?
Thanks

If you have repos like this, for each record of repo1, if you need to find a record from repo2, you could probably join the tables using spring data JPQL & use your custom method instead as your current approach could have performance impact.
As you seem to be interested only in the first record, Just to give you an idea, We can achieve something like this.
return Flux.fromIterable(repo.findAll()) //assuming it returns a list
.map(entity -> otherRepo.findById(entity.property)) // for each entity we query the other repo
.filter(Objects::nonNull) // replace it with Optional::isPresent if it is optional
.next(); //converts the flux to mono with the first record

The answer from vins is assuming non-reactive repository, so here it is in a fully reactive style:
return repo.findAll() //assuming reactive repository, which returns Flux<Entity>
.flatMap(entity -> otherRepo.findById(entity.property)) //findById returns an empty Mono if id not found, which basically gets ignored by flatMap
.next(); //first record is turned into a Mono, and the Flux is cancelled
Note that as you've stated, this can lead to unnecessary requests being made to Cassandra (and then cancelled by the next()). This is due to flatMap allowing several concurrent requests (256 by default). You can either reduce the parallelism of flatMap (by providing a second parameter, an int) or use concatMap to perform findById queries serially.

Related

How to check if exists when using CrudRepository#getOne()

I have a Document entity. To edit a document you must acquire a DocumentLock.
Request API to edit a document looks like below. edit=true allows to fetch the record from database with 'FOR UPDATE`.
GET /api/document/123?edit=true
In Back end side, we do like this (of course over simplified),
Document document = documentRepository.getOne(documentId) //<--- (1)
if(document == null){ //<--- (2) This is always false
//Throw exception
}
DocumentLock dl = DocumentLock.builder()
.lockedBy(user)
.lockedAt(NOW)
.document(document)
.build()
documentLockRepository.save(dl);
We are using getOne() because we do not need to fetch whole document from database, we are just creating a relation with DocumentLock object.
Question is, how to ensure the document actually exists? Because getOne() will always return the proxy and I do not see any obvious way to check if the record exists in databse.
Versions:
spring-data-jpa: 2.2.6.RELEASE
hibernate: 5.4.12
Update: removed additional questions.
Use findById or existsById. Yes they do access the database. That is the point of it, isn't it?
getOne is explicitly for use cases where you already know the entity exists and only need the id wrapped in something that looks like the entity.
If your repository returning value or its transactional management is confusing, you should check your repository codes and entity class design even session configurations that used for querying or even your Datastore design because everything looks fine in this code fragment

spring reactive: check if user exists or not

I am using spring reactive and need to check weather user with particular
data exists or not and currently I am not able to solve that problem.
Considering the scenerio
In my document I need to check if username or email already exists or not
In RDBS I can do it as
select count(id)>0 where username='abc' or email='abc#idx.com'
while using spring reactive which returns either mono or flux the simple most query becomes
{$or:[{"username":"abc"},{"email":"abc#idx.com"}]} which will return flux but I need boolean to verify from db
On solution is that I can get Flux<User> and the iterate it using form loop but then using ' result.block()' whick will block some other threads and therefore not a clean solution.
Is there any clean solution or any Idea how to solve this.
Thanks
Edit One possible solution can be creating unique indexing in monogdb, that I am using right now. But if there is any other solution please let me know
Using Spring Data MongoDB, you can use something like below:
public interface ReactiveUserRepository extends ReactiveSortingRepository<User, Long> {
Mono<User> findByUsernameAndEmail(String username, String email);
}
Find a single entity for the given criteria. It completes with IncorrectResultSizeDataAccessException on non-unique results.
You can refer to the complete syntax here:
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/

Deadlock on Linq to Entity Framework Core 2.0 Table

I'm getting what I think to be a deadlock when trying to run a bunch of linq queries in parallel.
I am running a Task.WhenAll() on this method:
public async Task<MetabuildScan> GetLatestMetabuildScanAsync(string buildId)
{
var metabuildScanStatuses = new[] { "Completed", "Referenced" };
// Get the latest metabuild scan for this image build
var latestScan = await (from scan in _qsaContext.MetabuildScans
where scan.SoftwareImageBuildId == buildId
&& metabuildScanStatuses.Contains(scan.SIScanStatus)
orderby scan.SIScanStartedOn descending
select scan).FirstOrDefaultAsync();
// If there is a related scan, then use that one, else, use the one we just got
var latestCompletedScanId = latestScan?.RelatedScanId ?? latestScan?.Id;
return await _qsaContext.MetabuildScans
.FirstOrDefaultAsync(scan => scan.Id == latestCompletedScanId);
}
I am getting a System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
_qsaContext was created using Entity-Framework Core.
At first, I thought the FirstOrDefaultAsync would fix my issue (I had a non-asynchronous FirstOrDefault in there at first), but it didn't.
I'm wondering what the best solution to get around this deadlock would be. The table I am selecting from is a large table, so I can't pull the whole table into memory.
As usual, wrap your query in try/catch and repeat your transaction.
Entity Framework DbContext is not thread safe. You can't run parallel queries against it like you are attempting to do.
If you need to have a shared context for your queries that you'll need to await each one individually and sequentially.
If they don't need a shared context but do need to run in parallel then you'll need to have a separate context for each query.
If using a DI framework, perhaps you can look into making your DbContext Transient (instead of what I'm assuming is Scoped) and injecting that into a query class that will call your query methods.

How to get Flux<T> from Mono<K> in Spring Reactive API?

I have two independent collections in NoSQL document db Photo and Property where Photo has propertyId parameter meaning that I can find all photos that belong to a given property like a house. Normally without reactive I would simply do:
Property property = ....
List<Photo> = photoService.findByPropertyId(property.getId());
Just two lines. How to do above in Reactive Programming when I have
`Mono<Property> and I want to find Flux<Photo>
without using block()?` Assume aphotoService.findByPropertyId return List and in reactive case it returns Flux.
You should use flatMapMany, which triggers an async processing from the Mono's value which can emit multiple elements:
Flux<Photo> photoFlux = propertyMono
.flatMapMany(prop -> photoService.findByPropertyId(prop.getId()));

Spring JSON merge

I'm updating a record from a form over AJAX. I have a JSON object that maps to my entity, and my controller method is:
#RequestMapping(value = "/vendors", method = RequestMethod.POST)
public ResponseEntity<String> saveVendorsJson(#RequestParam String vendor) {
Vendor v = Vendor.fromJsonToVendor(vendor);
if (v.merge() == null) {
v.persist();
}
return new ResponseEntity<String>(HttpStatus.OK);
}
I expected from the documentation that v.merge() will return null if it didn't find an existing record by the object's 'id' field to merge with, and in that case I want to persist it as a new Vendor object.
What's happening is, despite my JSON having an 'id' field value that matches an existing record, I'm ALWAYS inserting a new record with my updated goods from the browser.
I'm aware I'm having the POST method pull double-duty here, which isn't strictly RESTful. In theory, this is simpler for me (though of course that's turning out not to be the case).
I believe this is a Hibernate thing. Hibernate will not "merge" if it doesn't know it already exists. I think what I have done in the past is to do a lookup, then a persist. I think if you try to just merge something coming in off the wire you would get a Primary Key Collision, or something similar. I believe Hibernate has some sort of "dirty" flag internal to indicate if you are creating or editing an existing object.
There also used to be a way in raw-Hibernate to do a soft-lookup, basically tell Hibernate "look, I have this object, I don't want to do a SELECT blah-blah-blah, I just want to update some fields". It would load the object into Cache and allow you to do the update without doing the SELECT first. There is also an updateOrSave() in Spring, but that actually does the SELECT first.

Resources