3

I am trying to have a list in a model using @DBRef but I can't get it to work. This is my User model:

@Data
@Document
public class User {

    @Id
    @JsonSerialize(using = ToStringSerializer.class)
    private ObjectId id;

    @Indexed(unique = true)
    @NotBlank
    private String email;

    @NotBlank
    private String name;

    @NotBlank
    private String password;

    @DBRef
    private List<Server> servers;
}

Server model:

@Data
@Document
public class Server {

    @Id
    @JsonSerialize(using = ToStringSerializer.class)
    private ObjectId id;

    @NotBlank
    private String name;

    @NotBlank
    private String host;
}

The structure is very simple, every user can have multiple servers. But when I add servers to the user the server is created, but the servers array contains one null entry("servers" : [ null ]). So the server isn't added to the user. This is how I create a server and add it to an user:

@PostMapping
public Mono create(@Valid @RequestBody Server server, Mono<Authentication> authentication) {
    return this.serverRepository.save(server).then(authentication.flatMap(value -> {
        User user = (User) value.getDetails();
        user.getServers().add(server);

        return userRepository.save(user);
    })).map(value -> server);
}

So I simply create and save a server, add the server the user and then save the user. But it doesn't work. I keep having an array with one null entry.

I've seen this page: http://www.baeldung.com/cascading-with-dbref-and-lifecycle-events-in-spring-data-mongodb. But it is for saving the child document, not for linking it. Also it is for a single document, not for an array or list.

Why is my list not being saved correctly?

All my libraries are coming from spring boot version 2.0.0.M6.

UPDATE When removing @DBRef from the user's servers property the servers are getting saved, but they of course get double created, in the server collection and in every user.servers. So the error has something to do with references.

Jan Wytze
  • 3,307
  • 5
  • 31
  • 51
  • can you put Authentication class also, so that we can try the code as it is from our end – pvpkiran Nov 22 '17 at 17:14
  • the above code works for me – pvpkiran Nov 22 '17 at 17:43
  • @pvpkiran The Authentication interface is from spring(`org.springframework.security.core.Authentication`) – Jan Wytze Nov 22 '17 at 18:33
  • @pvpkiran When I edit other properties from the user object it works fine, it is only the Server list that doesn't work. – Jan Wytze Nov 22 '17 at 18:37
  • can you check if `user.getServers()` returns you a list ? – pvpkiran Nov 22 '17 at 18:43
  • For me it works. In database i can see it is stored like this `"servers" : [ { "$ref" : "server", "$id" : ObjectId("5a15b629ad242a61ae690858") } ],` – pvpkiran Nov 22 '17 at 18:47
  • @pvpkiran `getServers()` is not a list but null, I added a check before the add that creates an empty list, but still not stored... The code I posted is almost literately what you need to replicate it... I have all mongodb settings on default. In stead on `Authentication` I replaces it with `userService.findById(new ObjectId("existing id"))` but still the servers isn't saved correctly... What version are you using? – Jan Wytze Nov 22 '17 at 19:02
  • @pvpkiran I created a fully fresh spring boot webflux project and did the same as in this question, but it still doesn't work... How did you get it to work? – Jan Wytze Nov 22 '17 at 19:43

3 Answers3

2

After some googling I found the answer...
https://jira.spring.io/browse/DATAMONGO-1583
https://jira.spring.io/browse/DATAMONGO-1584

Reactive mongo doesn't support this.

Jan Wytze
  • 3,307
  • 5
  • 31
  • 51
  • This is strange. Because. The way I tested is instead of sending it over a rest call, I hardcoded the value(User, Server and Authentication) and triggered the function using the rest call(with no arguments). But I have kept the reactive piece of code as it is. And that works. Not sure how that changes the behaviour – pvpkiran Nov 22 '17 at 21:35
0

Actually there is a way to resolve DbRefs without to using the blocking driver. Yes - the references are resolved in a blocking fashion, but does not require a second connection. In order to achieve this we have to write our own DbRefResolver: NbDbRefResolver.java. In the provided resolver there is a flag: RESOLVE_DB_REFS_BY_ID_ONLY. If is switched on will not going to resolve the DbRefs from the database, but instead will resolve them to fake objects with id only. It is up to implementation to fill the references later in non-blocking fashion.

If the flag RESOLVE_DB_REFS_BY_ID_ONLY is set to false it will eagerly resolve the references by using the non-blocking driver, but will block the execution until the references are resolved. Here is how to register the DbRefResolver in the app: DbConfig.kt

Files attached are provided here: https://jira.spring.io/browse/DATAMONGO-1584

Triphon Penakov
  • 374
  • 3
  • 11
0

Me did it like that for roles :

  @Unwrapped(onEmpty = Unwrapped.OnEmpty.USE_NULL)
  private Collection<Role> roles;

you can check the doc (2021) here : https://spring.io/blog/2021/04/20/what-s-new-in-spring-data-2021-0

walidum
  • 153
  • 1
  • 9