10

I'm new to MongoDB and Reactor and I'm trying to retrieve a User with its Profiles associated Here's the POJO :

public class User {

    private @Id String id;
    private String login;
    private String hashPassword;
    @Field("profiles") private List<String> profileObjectIds;
    @Transient private List<Profile> profiles; }

public class Profile {

    private @Id String id;
    private @Indexed(unique = true) String name;
    private List<String> roles; }

The problem is, how do I inject the profiles in the User POJO ?

I'm aware I can put a @DBRef and solve the problem but in it's documentation, MongoDB specify manual Ref should be preferred over DB ref.

I'm seeing two solutions :

  1. Fill the pojo when I get it :

    public Mono<User> getUser(String login) {
        return userRepository.findByLogin(login)
        .flatMap(user -> ??? );
    }
    

I should do something with profileRepository.findAllById() but I don't know or to concatene both Publishers given that profiles result depends on user result.

  1. Declare an AbstractMongoEventListener and override onAfterConvert method :

But here I am mistaken since the method end before the result is Published

public void onAfterConvert(AfterConvertEvent<User> event) {
    final User source = event.getSource();
    source.setProfiles(new ArrayList<>());
    profileRepository.findAllById(source.getProfileObjectIds())
    .doOnNext(e -> source.getProfiles().add(e))
    subscribe();
}

4 Answers 4

12

TL;DR

There's no DBRef support in reactive Spring Data MongoDB and I'm not sure there will be.

Explanation

Spring Data projects are organized into Template API, Converter and Mapping Metadata components. The imperative (blocking) implementation of the Template API uses an imperative approach to fetch Documents and convert these into domain objects. MappingMongoConverter in particular handles all the conversion and DBRef resolution. This API works in a synchronous/imperative API and is used for both Template API implementations (imperative and the reactive one).

Reuse of MappingMongoConverter was the logical decision while adding reactive support as we don't have a need to duplicate code. The only limitation is DBRef resolution that does not fit the reactive execution model.

To support reactive DBRefs, the converter needs to be split up into several bits and the whole association handling requires an overhaul.

Reference : https://jira.spring.io/browse/DATAMONGO-2146

Recommendation

Keep references as keys/Id's in your domain model and look up these as needed. zipWith and flatMap are the appropriate operators, depending on what you want to archive (enhance model with references, lookup references only).

On a related note: Reactive Spring Data MongoDB comes partially with a reduced feature set. Contextual SpEL extension is a feature that is not supported as these components assume an imperative programming model and thus synchronous execution.

Sign up to request clarification or add additional context in comments.

1 Comment

The problem in this context is to attach converters to ReactiveMongoTemplate as MappingMongoConverter accepts DefaultDbRefResolver: new MappingMongoConverter(new DefaultDbRefResolver(new SimpleReactiveMongoDatabaseFactory(mongoClient, database)), new MongoMappingContext())
6

For the first point, I finally achieve doing what I wanted :

public Mono<User> getUser(String login) {
   return userRepository.findByLogin(login)
         .flatMap( user ->
              Mono.just(user)
              .zipWith(profileRepository.findAllById(user.getProfileObjectIds())
                  .collectionList(),
                  (u, p) -> {
                       u.setProfiles(p);
                       return u;
                   })
            );
}

Comments

0

Thanks, this helped a lot. Here is my solution:

public MappingMongoConverter mappingMongoConverter(MongoMappingContext mongoMappingContext) {
    MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mongoMappingContext);
    converter.setTypeMapper(new DefaultMongoTypeMapper(null));
    converter.setCustomConversions(mongoCustomConversions());
    return converter;
}

The trick was to use the NoOpDbRefResolver.INSTANCE

Comments

0

In my case, I have managed this problem using the follow approach:

  1. My Entity is:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "post")
public class Post implements Serializable {

    private static final long serialVersionUID = -6281811500337260230L;

    @EqualsAndHashCode.Include
    @Id
    private String id;
    private Date date;
    private String title;
    private String body;
    private AuthorDto author;
    private Comment comment;
    private List<Comment> listComments = new ArrayList<>();
    private List<String> idComments = new ArrayList<>();
}
  1. My controller is:
    @GetMapping(FIND_POST_BY_ID_SHOW_COMMENTS)
    @ResponseStatus(OK)
    public Mono<Post> findPostByIdShowComments(@PathVariable String id) {
        return postService.findPostByIdShowComments(id);
    }
  1. Last, but not, least, my Service (here is the solution):
    public Mono<Post> findPostByIdShowComments(String id) {
        return postRepo
                .findById(id)
                .switchIfEmpty(postNotFoundException())
                .flatMap(postFound -> commentService
                                 .findCommentsByPostId(postFound.getId())
                                 .collectList()
                                 .flatMap(comments -> {
                                     postFound.setListComments(comments);
                                     return Mono.just(postFound);
                                 })
                        );
    }

    public Flux<Comment> findCommentsByPostId(String id) {
        return postRepo
                .findById(id)
                .switchIfEmpty(postNotFoundException())
                .thenMany(commentRepo.findAll())
                .filter(comment1 -> comment1.getIdPost()
                                            .equals(id));

    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.