Open BenNeighbour opened 3 years ago
Could I get some help please?
@kaqqao ?
Also my build.gradle
dependency for this is: implementation "io.leangen.graphql:graphql-spqr-spring-boot-starter:0.0.6"
Pheeew, this is a rather involved scenario that I really can't answer it without investigation. There's nothing in SPQR that deals with DataLoader in any meaningful way. That is to say, you should be in the exact same situation if you were using DataLoader without SPQR. So whatever is happening likely isn't really SPQR specific. That said, the Java impl of DataLoader has been a bit of mine field... so it may well be a bug in there. I think I even vaguely remember it freaking out on nulls. Try searching their issues. Can you maybe try using their latest release? I believe there was a big release recently. If I'm wrong, please try building their master locally and using that. If it breaks compatibility, let me know and I'll have a look what changes are needed in the starter.
@kaqqao Yeah will do. I'm just on their official GitHub Docs trynna find out some useful info about null handing. I did end up fixing it about 2 days ago but I had to do extra iterations on the DB Query's result to bucket it into a list (that had the same number of elements) - because that's what the dataLoader.load(....)
expects.
This would mean that any "nulls" in the releationships would be returned as just empty lists - which is okay but would prefer for it to just be null in some situations.
public static CompletableFuture<List<List<Role>>> loadRoles(List<UUID> ids) {
/* Create a list (with multiple sub-lists) of the same size as the input */
List<List<Role>> bucketedList =
new ArrayList<>(Collections.nCopies(ids.size(), new ArrayList<>()));
/* Batch load all the companies that contain the given ids */
roleDao.findAllByEmployeeIdIn(ids).stream()
/* Convert to some sort of Map */
.flatMap(Collection::stream)
/* Group the hashmap by the company ids */
.collect(Collectors.groupingBy(role -> role.getEmployee().getId()))
/* For each item that maps to an id, group it into the right "bucket" */
.forEach(
(uuid, roles) -> {
bucketedList.set(ids.indexOf(uuid), roles);
});
/* Return the ordered list as a completed future */
return CompletableFuture.completedFuture(bucketedList);
}
You'll have to refer to the context in the first comment (the Entities and their relations) for this specific DataFetcher to make sense. I feel like there's a better way of doing this - without having to create "n" copies (to make sure the size is the same as the input - as that's what the DataLoader expects).
The whole bucketing thing is hacky and I'm not sure how that - in combination with the forEach at the end
- will affect the performance of the function. Thanks for your feedback @kaqqao I really like your framework - helps us do a lot with a little!
I mean, it's A solution - but I wouldn't want to settle on this for both Code Quality and performance
Because it's never "too bad" for @OneToOne
relationships, it only becomes a problem when you have any @OneToMany
or @ManyToOne
's that you wanna eliminate N+1 for (which why wouldn't you), and you have to batch load a list of relations by a list of ids and the return list has to be the same size as the input list. Again this must be an internal thing within java-dataloader
so I'm checking it out now
But won't using their latest release conflict with the peer dependency of your SPQR Starter?
I was hoping it would be compatible enough not to cause issues when you override the version. But after looking into the comments, issues and PRs on the DataLoader project, I'm not exactly hopeful any longer. I personally find DataLoader to be too messy for its worth and tend to stay away from it, so I recommend you consider simpler approaches to batching e.g. looking ahead and prefetching using the LocalContext.
@kaqqao @BaldyLocks @vkachan @csueiras @kicktipp Hi all,
I have set up some batch DataLoaders for each entity (that have relationships with others) in order to solve the N+1 Problem with GraphQL/SPQR - and it works. HOWEVER, when I have a
@OneToOne
or@ManyToMany
relation on some entity that is null, anException while fetching data (/getAllEmployees[6]/company) : null
.Here is the resolver for the company field of each employee:
And the DataFetcher,
And for some context, here are the entities:
For the Employee
For the Company
Any help, comments or clarification would be much, much appreciated - I'm probably missing out something stupid!!