Search code examples
javamicronautmicronaut-data

How to create a TextFree search in mongoDB with Micronaut


I am using reactive MongoDb, and trying to implement Free Text search based on the weight

implementation("io.micronaut.mongodb:micronaut-mongo-reactive")

on below POJO

public class Product {
    @BsonProperty("_id")
    @BsonId
    private ObjectId id;
    private String name;
    private float price;
    private String description;
}

Tried this simple example

public Flowable<List<Product>> findByFreeText(String text) {
    LOG.info(String.format("Listener --> Listening value = %s", text));
    Flowable.fromPublisher(this.repository.getCollection("product", List.class)
            .find(new Document("$text", new Document("$search", text)
                    .append("$caseSensitive", false)
                    .append("$diacriticSensitive", false)))).subscribe(item -> {
                System.out.println(item);
            }, error -> {
                System.out.println(error);
            });
    return Flowable.just(List.of(new Product()));
}

I don't think this is the correct way of implementing the Free Text Search.


Solution

  • At first you don't need to have Flowable with List of Product because Flowable can manage more then one value unlike Single. So, it is enough to have Flowable<Product>. Then you can simply return the Flowable instance from find method.

    Text search can be then implemented like this:

    public Flowable<Product> findByFreeText(final String query) {
        return Flowable.fromPublisher(repository.getCollection("product", Product.class)
            .find(new Document("$text",
                new Document("$search", query)
                    .append("$caseSensitive", false)
                    .append("$diacriticSensitive", false)
            )));
    }
    

    Then it is up to the consumer of the method how it subscribes to the result Flowable. In controller you can directly return the Flowable instance. If you need to consume it somewhere in your code you can do subscribe() or blockingSubscribe() and so on.

    And you can of course test it by JUnit like this:

    @MicronautTest
    class SomeServiceTest {
        @Inject
        SomeService service;
    
        @Test
        void findByFreeText() {
            service.findByFreeText("test")
                .test()
                .awaitCount(1)
                .assertNoErrors()
                .assertValue(p -> p.getName().contains("test"));
        }
    }
    

    Update: you can debug communication with MongoDB by setting this in logback.xml (Micronaut is using Logback as a default logging framework) logging config file:

    <configuration>
        ....
        <logger name="org.mongodb" level="debug"/>
    </configuration>
    

    Then you will see this in the log file:

    16:20:21.257 [Thread-5] DEBUG org.mongodb.driver.protocol.command - Sending command '{"find": "product", "filter": {"$text": {"$search": "test", "$caseSensitive": false, "$diacriticSensitive": false}}, "batchSize": 2147483647, "$db": "some-database"}' with request id 6 to database some-database on connection [connectionId{localValue:3, serverValue:1634}] to server localhost:27017
    16:20:21.258 [Thread-8] DEBUG org.mongodb.driver.protocol.command - 16:20:21.258 [Thread-7] DEBUG org.mongodb.driver.protocol.command - Execution of command with request id 6 completed successfully in 2.11 ms on connection [connectionId{localValue:3, serverValue:1634}] to server localhost:27017
    

    Then you can copy the command from log and try it in MongoDB CLI or you can install MongoDB Compass where you can play with that more and see whether the command is correct or not.