question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

SPQR Dataloder doesn't allow null values - Exception while fetching data : null

See original GitHub issue

@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, an Exception while fetching data (/getAllEmployees[6]/company) : null.

Here is the resolver for the company field of each employee:

@GraphQLQuery(name = "company")
public CompletableFuture<Company> getCompany(
        @GraphQLContext @Valid @GraphQLNonNull Employee employee, @GraphQLEnvironment ResolutionEnvironment env) {
  DataLoader<UUID, Company> loader = env.dataFetchingEnvironment.getDataLoader("company");
  return loader.load(employee.getCompany().getId());
}

And the DataFetcher,

@Component
public class DataLoaderFactory implements DataLoaderRegistryFactory {

  private static CompanyDao companyDao;
  private static final BatchLoader<UUID, Company> companyLoader = DataLoaderFactory::companies;
  /* ...... */

  @Autowired
  public DataLoaderFactory(CompanyDao companyDao, /* ...... */) {
    DataLoaderFactory.companyDao = companyDao;
    /* ...... */
  }

  public static CompletableFuture<List<Company>> companies(List<UUID> ids) {
    return CompletableFuture.completedFuture(companyDao.findAllById(ids));
  }

  /* ...... */

  @Override
  @Bean
  public DataLoaderRegistry createDataLoaderRegistry() {
    DataLoaderRegistry allLoaders = new DataLoaderRegistry();
    allLoaders.register("company", new DataLoader<>(companyLoader));
    /* ...... */

    return allLoaders;
  }
}

And for some context, here are the entities:

For the Employee

@Setter
@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {

  private static final long serialVersionUID = 684733882540759135L;

  @Id
  @GeneratedValue
  @Column(columnDefinition = "uuid", updatable = false)
  @GraphQLQuery(name = "id", description = "A Person's Id")
  private UUID id;

  @NotNull(message = "There must be a Person's Name!")
  @GraphQLQuery(name = "fullName", description = "A Person's Name")
  private String fullName;

  @NotNull(message = "A Person must have an Age!")
  @GraphQLQuery(name = "age", description = "A Person's Age")
  private int age;

  @NotNull(message = "A Person must have a Vehicle!")
  @GraphQLQuery(name = "personalVehicle", description = "A Person's Mode of Transport")
  private Vehicle personalVehicle;

  @ManyToOne(targetEntity = Company.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  @GraphQLQuery(name = "company", description = "The Company a Person works for")
  private Company company;

  @GraphQLQuery(name = "roles", description = "The Role a Person plays in their Company")
  @OneToMany(
      targetEntity = Role.class,
      orphanRemoval = true,
      fetch = FetchType.LAZY,
      mappedBy = "employee",
      cascade = CascadeType.ALL)
  private List<Role> roles = new ArrayList<>();

  public enum Vehicle {
    CAR,
    BUS,
    VAN,
    BICYCLE,
    MOTORBIKE,
    SCOOTER
  }
}

For the Company

@Setter
@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Company implements Serializable {

  private static final long serialVersionUID = -6007975840330441233L;

  @Id
  @GeneratedValue
  @Column(columnDefinition = "uuid", updatable = false)
  @GraphQLQuery(name = "id", description = "A Company's Id")
  private UUID id;

  @NotNull(message = "There must be a Company Name!")
  @GraphQLQuery(name = "name", description = "A Company's Name")
  private String name;

  @Min(10000)
  @NotNull(message = "You gotta tell us how rich you are!")
  @GraphQLQuery(name = "balance", description = "A Company's Dollar")
  private BigDecimal balance;

  @NotNull(message = "There must be a Company Type!")
  @GraphQLQuery(name = "type", description = "The type of company")
  private CompanyType type;

  @GraphQLQuery(name = "offices", description = "The Company's offices")
  @OneToMany(
      targetEntity = Office.class,
      orphanRemoval = true,
      fetch = FetchType.LAZY,
      mappedBy = "company",
      cascade = CascadeType.ALL)
  private List<Office> offices;

  public enum CompanyType {
    PRIVATE_LIMITED,
    SOLE_TRADER,
    PUBLIC
  }
}

Any help, comments or clarification would be much, much appreciated - I’m probably missing out something stupid!!

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:9 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
kaqqaocommented, Jul 28, 2021

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.

0reactions
kaqqaocommented, Aug 2, 2021

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

leangen/graphql-spqr - Gitter
I noticed that graphql-java makes DataFetcher s wrap checked exceptions into runtime exceptions even though the surrounding code can handle both just fine....
Read more >
GraphQL Dataloader fails if backend returns null values
Found that the backend server is not even sending 'null', it just ignores the missing record.
Read more >
Error 'Empty Column is found' in Data Loader - Salesforce Help
To resolve this error you will need to ensure that any empty columns are removed from your .csv file. Note: This error does...
Read more >
Using Micronaut and GraphQL with transactions and security ...
DataLoader - Loading initial data to the database [main] DEBUG ... At the moment I'm writing this article graphql-spqr is very poorly ...
Read more >
30 Days With GraphQL - Better Programming
Under-fetching is when you wanted a little bit more but the API ... For example, this is a valid value [1,2,null] , but...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found