Proper way to chain multiple related db ( repository calls ) - spring-boot

Using Spring WebFlux and ReactiveCrudRepository
Assume we have
UserRepository, UserDetailsRepository, UserTransactionRepository
I want to find the user, find their details and their transaction and return a single DTO object.
Separate calls would look lik
userRepo.findById
userDetailsRepo.findByUserId
userTransactionRepo.findByUserId
How id properly chain them?
userRepo.findById()
.map( u -> userDetails.findByUserId( u ))
.map( ud -> userTrans.findByUserId( u))
.map( data -> process....);
?

In ReactiveCrudRepository, the findById method returns Mono from the emitted entity. You must use flatMap().
return userRepo.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException("User not found")))
.zipWhen(u -> userDetails.findById(u))
.zipWhen(ud -> userTrans.findById(ud))
.map(tuple -> mapper(tuple.getT1().getT1(), tuple.getT1().getT2(), tuple.getT2()));
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/reactive/ReactiveCrudRepository.html

Related

Filter Map keys into a set

I have a map Map<String,EnrollmentData> which maps student ID to his data.
The student id need to be filtered on certain EnrollmentData attributes ,and returned as a Set.
Map<String, EnrollmentData> studentData = .........;
if(MapUtils.isNotEmpty(studentData )){
Set<String> idSet = studentData .entrySet().stream()
.filter(x -> x.getValue().equals(...) )
.collect(Collectors.toSet( x -> x.getKey()));
}
However,this gives me a compilation error in the toSet [ Collectors is not applicable for the arguments (( x) -> {}) ] .
What needs to be done here.
After the filtering, you have a Stream<Map.Entry<String, EnrollmentData>>. Collecting with toSet() (which accepts no arguments) would collect Entry<String, EnrollmentData>s, but you want to map each element to their key prior to collecting instead.
You must first map the elements of the resulting stream to the Entry's key:
.filter(yourFilterFunction)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());

How to compare if source list does not contain any object which is present in destination list using Java 8?

List<EPosition> has S and P role.
For some employee, P role exists but now S does not exists.
So Source will only provide P but not S row in list.
In Database, WE have both P as well as S.
We want to make all S row as NULL (do not delete). So how to compare two lists
List<EPosition> src; //Fetch from Soap
List<EPosition> db;//Fetch from DB
for (EPosition d: db){
for (Eposition s: src){
if (s.ID = d.ID){
//Make it null
}
}
}
merge later
Problem statement: From the problem statement, what I understood is you have two lists (say A and B), where A contains some objects which are also present in B and you want to set this value to null in A using java streams.
Solution: So to do that you need to use the map on stream where each object is mapped to null if you find the object in B else object itself. To find whether the object is present in B there are two ways :
a. use contains method of the list (this will use equals method of the object in your case Eposition)
dbs = dbs.stream()
.map(db -> return src.contains(db) ? null :db)
.collect(Collectors.toList());
b. simply iterate over second list using stream and find a match of the id using anyMatch function.
dbs = dbs.stream()
.map(db -> return src.stream().anyMatch(sid -> sid.ID.equals(did.ID) ? null :db)
.collect(Collectors.toList());

Resolve, Incompatible types error, java 8

I am trying to implement one logic using java 8 streams().
List<Persons> persons = logs.stream().map(l -> {
return rules.stream().map(rule -> generator.apply(rule)).collect(Collectors.toList());
}).collect(Collectors.toList());
But I am getting:
Incompatible type: Required List but collect was inferred to R, no instance of type
List of List of Persons
If
l -> {return rules.stream().map(rule -> generator.apply(rule)).collect(Collectors.toList());}
produces a List<Person>, the outer Stream pipeline would produce a List<List<Person>>.
You need flatMap if you want a List<Person>:
List<Persons> persons =
logs.stream()
.flatMap(l -> rules.stream().flatMap(rule -> generator.apply(rule).stream()))
.collect(Collectors.toList());
Explanation:
rules.stream().flatMap(rule -> generator.apply(rule).stream()) creates a Stream<String> and flat maps it to a Stream<Persons>.
.flatMap(l -> rules.stream().flatMap(rule -> generator.apply(rule).stream())) flat maps the elements of the outer Stream to a Stream<Persons>, which can be collected to a List<Persons>.
BTW, it's not clear how the input logs are related to the output, since you are ignoring the elements of the logs.stream() Stream in your mapping.

Fastest way to convert key value pairs to grouped by key objects map using java 8 stream

Model:
public class AgencyMapping {
private Integer agencyId;
private String scoreKey;
}
public class AgencyInfo {
private Integer agencyId;
private Set<String> scoreKeys;
}
My code:
List<AgencyMapping> agencyMappings;
Map<Integer, AgencyInfo> agencyInfoByAgencyId = agencyMappings.stream()
.collect(groupingBy(AgencyMapping::getAgencyId,
collectingAndThen(toSet(), e -> e.stream().map(AgencyMapping::getScoreKey).collect(toSet()))))
.entrySet().stream().map(e -> new AgencyInfo(e.getKey(), e.getValue()))
.collect(Collectors.toMap(AgencyInfo::getAgencyId, identity()));
Is there a way to get the same result and use more simpler code and faster?
You can simplify the call to collectingAndThen(toSet(), e -> e.stream().map(AgencyMapping::getScoreKey).collect(toSet())))) with a call to mapping(AgencyMapping::getScoreKey, toSet()).
Map<Integer, AgencyInfo> resultSet = agencyMappings.stream()
.collect(groupingBy(AgencyMapping::getAgencyId,
mapping(AgencyMapping::getScoreKey, toSet())))
.entrySet()
.stream()
.map(e -> new AgencyInfo(e.getKey(), e.getValue()))
.collect(toMap(AgencyInfo::getAgencyId, identity()));
A different way to see it using a toMap collector:
Map<Integer, AgencyInfo> resultSet = agencyMappings.stream()
.collect(toMap(AgencyMapping::getAgencyId, // key extractor
e -> new HashSet<>(singleton(e.getScoreKey())), // value extractor
(left, right) -> { // a merge function, used to resolve collisions between values associated with the same key
left.addAll(right);
return left;
}))
.entrySet()
.stream()
.map(e -> new AgencyInfo(e.getKey(), e.getValue()))
.collect(toMap(AgencyInfo::getAgencyId, identity()));
The latter example is arguably more complicated than the former. Nevertheless, your approach is pretty much the way to go apart from using mapping as opposed to collectingAndThen as mentioned above.
Apart from that, I don't see anything else you can simplify with the code shown.
As for faster code, if you're suggesting that your current approach is slow in performance then you may want to read the answers here that speak about when you should consider going parallel.
You are collecting to an intermediate map, then streaming the entries of this map to create AgencyInfo instances, which are finally collected to another map.
Instead of all this, you could use Collectors.toMap to collect directly to a map, mapping each AgencyMapping object to the desired AgencyInfo and merging the scoreKeys as needed:
Map<Integer, AgencyInfo> agencyInfoByAgencyId = agencyMappings.stream()
.collect(Collectors.toMap(
AgencyMapping::getAgencyId,
mapping -> new AgencyInfo(
mapping.getAgencyId(),
new HashSet<>(Set.of(mapping.getScoreKey()))),
(left, right) -> {
left.getScoreKeys().addAll(right.getScoreKeys());
return left;
}));
This works by grouping the AgencyMapping elements of the stream by AgencyMapping::getAgencyId, but storing AgencyInfo objects in the map instead. We get these AgencyInfo instances from manually mapping each original AgencyMapping object. Finally, we're merging AgencyInfo instances that are already in the map by means of a merge function that folds left scoreKeys from one AgencyInfo to another.
I'm using Java 9's Set.of to create a singleton set. If you don't have Java 9, you can replace it with Collections.singleton.

Filter on map of map

I have below map of map and want to filter it based on a value. The result should be assigned back to same map. Please let know what is the best approach for this.
Map<String, Map<String, Employee>> employeeMap;
<
dep1, <"empid11", employee11> <"empid12",employee12>
dep2, <"empid21", employee21> <"empid22",employee22>
>
Filter: employee.getState="MI"
I tried like below but i was not able to access the employee object
currentMap = currentMap.entrySet().stream()
**.filter(p->p.getValue().getState().equals("MI"))**
.collect(Collectors.toMap(p -> p.getKey(),p->p.getValue()));
If you want to modify the map in place (and that it allows so), you can use forEach to iterate over the entries of the map, and then use removeIf for each values of the inner maps to remove the employees that satisfy the predicate:
employeeMap.forEach((k, v) -> v.values().removeIf(e -> e.getState().equals("MI")));
Otherwise, what you can do is to use the toMap collector, where the function to map the values takes care of removing the concerned employees by iterating over the entry set of the inner maps:
Map<String, Map<String, Employee>> employeeMap =
employeeMap.entrySet()
.stream()
.collect(toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream().filter(emp -> !emp.getValue().getState().equals("MI")).collect(toMap(Map.Entry::getKey, Map.Entry::getValue))));

Resources