I'm trying to implement a dynamic search for a huge product collection. The object has several properties including productName
, subCategoryName
, categoryName
, brandName
, etc. The user could search for products using any of these properties. The order is fixed and the first priority for a search string is to find it in productName
and then subCategoryName
and so on.
I used aggregate
to achieve this and then unionWith
to concat records that matched with other properties. It seems to work when fired as a raw query but we also need support for pagination and I'm not being able to achieve that with Spring Data MongoDB
db.product.aggregate(
[
{ $match: { "productName" : { "$regex" : "HYPER", "$options" : "i"},
"companyNo" : { "$in" : [10000009]}, "status" : { "$in" : ["ACTIVE", "IN_ACTIVE", "OUT_OF_STOCK"]} }},
{ $unionWith: { coll: "product", pipeline: [{ $match: { "subCategoryName" : { "$regex" : "HYPER", "$options" : "i"},
"companyNo" : { "$in" : [10000009]}, "status" : { "$in" : ["ACTIVE", "IN_ACTIVE", "OUT_OF_STOCK"]}} }] } },
{ $unionWith: { coll: "product", pipeline: [{ $match: { "categoryName" : { "$regex" : "HYPER", "$options" : "i"},
"companyNo" : { "$in" : [10000009]}, "status" : { "$in" : ["ACTIVE", "IN_ACTIVE", "OUT_OF_STOCK"]}} }] } },
{ $unionWith: { coll: "product", pipeline: [{ $match: { "brandName" : { "$regex" : "HYPER", "$options" : "i"},
"companyNo" : { "$in" : [10000009]}, "status" : { "$in" : ["ACTIVE", "IN_ACTIVE", "OUT_OF_STOCK"]}} }] } },
]
)
Also, this query only works if we pass the substring of the exact name. For example, the NIVEA BODY LOTION EXPRESS HYDRATION 200 ML HYPERmart product will be returned if I search with NIVEA BODY LOTION but it won't return anything if I search with HYDRATION LOTION
A Sample Product:
{
"_id" : ObjectId("6278c1c2f2570d6f199435b2"),
"companyNo" : 10000009,
"categoryName" : "BEAUTY and PERSONAL CARE",
"brandName" : "HYPERMART",
"productName" : "NIVEA BODY LOTION EXPRESS HYDRATION 200 ML HYPERmart",
"productImageUrl" : "https://shop-now-bucket.s3.ap-south-1.amazonaws.com/shop-now-bucket/qa/10000009/product/BEAUTY%20%26%20PERSONAL%20CARE/HYPERMART/NIVEA%20BODY%20LOTION%20EXPRESS%20HYDRATION%20200%20ML/temp1652081080302.jpeg",
"compressProductImageUrl" : "https://shop-now-bucket.s3.ap-south-1.amazonaws.com/shop-now-bucket/qa/10000009/product/BEAUTY%20%26%20PERSONAL%20CARE/HYPERMART/NIVEA%20BODY%20LOTION%20EXPRESS%20HYDRATION%20200%20ML/temp1652081080302.jpeg",
"productPrice" : 249.0,
"status" : "ACTIVE",
"subCategoryName" : "BODY LOTION & BODY CREAM",
"defaultDiscount" : 0.0,
"discount" : 7.0,
"description" : "Give your skin fast-absorbing moisturisation and make it noticeably smoother for 48-hours with Nivea Express Hydration Body Lotion. The formula with Sea Minerals and Hydra IQ supplies your skin with moisture all day. The new improved formula contains Deep Moisture Serum to lock in deep moisture leaving you with soft and supple skin.",
"afterDiscountPrice" : 231.57,
"taxPercentage" : 1.0,
"availableQuantity" : NumberLong(100),
"packingCharges" : 0.0,
"available" : true,
"featureProduct" : false,
"wholesaleProduct" : false,
"rewards" : NumberLong(0),
"createAt" : ISODate("2022-05-09T07:24:40.286Z"),
"createdBy" : "companyAdmin_@[email protected]",
"isBulkUpload" : true,
"buyPrice" : 0.0,
"privateProduct" : false,
"comboProduct" : false,
"subscribable" : false,
"discountAdded" : false,
"_class" : "com.apptmart.product.entity.Product"
}
I'm new to MongoDB. any references will be appretiated.
Here is my working example in Spring Boot.
https://github.com/ConsciousObserver/MongoAggregationTest
You can invoke the /product
REST service using following command
http://localhost:8080/products?productName=product&brandName=BRAND1&categoryName=CATEGORY2&subCategoryName=SUB_CATEGORY3&pageNumber=0&pageSize=10
Implementation supports following
productName
(Searches by words, needs text search index)brandName
, categoryName
and subCategoryName
pageNumber
and pageSize
All of it is implemented using Spring Data APIs. I generally avoid writing native queries in code, as they are not validated at compile time.
All classes are added to one Java file, it's just a sample so it's better to keep everything in one place.
Adding code below in case GitHub repository goes down.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>MongoAggregationTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MongoAggregationTest</name>
<description>MongoAggregationTest</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
MongoAggregationTestApplication.java
package com.example;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.bson.BsonDocument;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.LimitOperation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.aggregation.SkipOperation;
import org.springframework.data.mongodb.core.index.TextIndexDefinition;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
import org.springframework.data.mongodb.core.index.TextIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@SpringBootApplication
@Slf4j
public class MongoAggregationTestApplication {
public static void main(String[] args) {
SpringApplication.run(MongoAggregationTestApplication.class, args);
}
private final MongoTemplate mongoTemplate;
@PostConstruct
void prepareData() {
boolean collectionExists = mongoTemplate.collectionExists(Product.COLLECTION_NAME);
log.info("####### product collection exists: {}", collectionExists);
if (!collectionExists) {
throw new RuntimeException(
String.format("Required collection {%s} does not exist", Product.COLLECTION_NAME));
}
//Adding index manually ------------- This is required for text search on productName
TextIndexDefinition textIndex = new TextIndexDefinitionBuilder().onField("productName", 1F).build();
mongoTemplate.indexOps(Product.class).ensureIndex(textIndex);
boolean samplesAlreadyAdded = mongoTemplate
.exists(new Query().addCriteria(Criteria.where("brandName").exists(true)), Product.class);
//Uncomment to delete all rows from product collection
//mongoTemplate.getCollection(Product.COLLECTION_NAME).deleteMany(new BsonDocument());
if (!samplesAlreadyAdded) {
for (int i = 1; i <= 5; i++) {
//adds 3 words in productName
//product name term1
String productName = "product name term" + i;
Product product = new Product(null, "ACTIVE", productName, "BRAND" + i, "CATEGORY" + i,
"SUB_CATEGORY" + 1);
mongoTemplate.save(product);
log.info("Saving sample product to database: {}", product);
}
} else {
log.info("Skipping sample insertion as they're already in DB");
}
}
}
@Slf4j
@RestController
@RequiredArgsConstructor
@Validated
class ProductController {
private final MongoTemplate mongoTemplate;
//JSR 303 validations are returning 500 when validation fails, instead of 400. Will look into it later
/**
* Invoke using follwing command
* <p>
* <code>http://localhost:8080/products?productName=product&brandName=BRAND1&categoryName=CATEGORY2&subCategoryName=SUB_CATEGORY3&pageNumber=0&pageSize=10</code>
*
* @param productName
* @param brandName
* @param categoryName
* @param subCategoryName
* @param pageNumber
* @param pageSize
* @return
*/
@GetMapping("/products")
public List<Product> getProducts(@RequestParam String productName, @RequestParam String brandName,
@RequestParam String categoryName, @RequestParam String subCategoryName,
@RequestParam @Min(0) int pageNumber, @RequestParam @Min(1) @Max(100) int pageSize) {
log.info(
"Request parameters: productName: {}, brandName: {}, categoryName: {}, subCategoryName: {}, pageNumber: {}, pageSize: {}",
productName, brandName, categoryName, subCategoryName, pageNumber, pageSize);
//Query Start
TextCriteria productNameTextCriteria = new TextCriteria().matchingAny(productName).caseSensitive(false);
TextCriteriaHack textCriteriaHack = new TextCriteriaHack();
textCriteriaHack.addCriteria(productNameTextCriteria);
//Needs this hack to combine TextCriteria with Criteria in a single query
//See TextCriteriaHack for details
MatchOperation productNameTextMatch = new MatchOperation(textCriteriaHack);
//Exact match
Criteria brandNameMatch = Criteria.where("brandName").is(brandName);
Criteria categoryNameMatch = Criteria.where("categoryName").is(categoryName);
Criteria subCategoryNameMatch = Criteria.where("subCategoryName").is(subCategoryName);
MatchOperation orMatch = Aggregation
.match(new Criteria().orOperator(brandNameMatch, categoryNameMatch, subCategoryNameMatch));
//Pagination setup
SkipOperation skip = Aggregation.skip((long) pageNumber * pageSize);
LimitOperation limit = Aggregation.limit(pageSize);
Aggregation aggregation = Aggregation.newAggregation(productNameTextMatch, orMatch, skip, limit);
//Query end
//Query execution
AggregationResults<Product> aggregateResults = mongoTemplate.aggregate(aggregation, Product.COLLECTION_NAME,
Product.class);
List<Product> products = new ArrayList<>();
aggregateResults.iterator().forEachRemaining(products::add);
log.info("Found products: {}", products);
return products;
}
}
@Data
@Document(Product.COLLECTION_NAME)
@NoArgsConstructor
@AllArgsConstructor
class Product {
static final String COLLECTION_NAME = "product";
@Id
@Field("_id")
private String id;
@Field("status")
private String status;
@TextIndexed
@Field("productName")
private String productName;
@Field("brandName")
private String brandName;
@Field("categoryName")
private String categoryName;
@Field("subCategoryName")
private String subCategoryName;
}
/**
* https://stackoverflow.com/a/29925876 There is no way to combine
* CriteriaDefinition and Criteria in one query This hack converts
* CriteriaDefinition to Query which can be converted to Criteria
*/
class TextCriteriaHack extends Query implements CriteriaDefinition {
@Override
public org.bson.Document getCriteriaObject() {
return this.getQueryObject();
}
@Override
public String getKey() {
return null;
}
}
Here's the query that's being executed by /products
, I got it from MongoTemplate
logs
[
{
"$match": {
"$text": {
"$search": "name",
"$caseSensitive": false
}
}
},
{
"$match": {
"$or": [
{
"brandName": "BRAND1"
},
{
"categoryName": "CATEGORY2"
},
{
"subCategoryName": "SUB_CATEGORY3"
}
]
}
},
{
"$skip": 0
},
{
"$limit": 1
}
]
Here's log contents, after a few requests have been fired
2022-10-06 04:50:01.209 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : No active profile set, falling back to 1 default profile: "default"
2022-10-06 04:50:01.770 INFO 26472 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data MongoDB repositories in DEFAULT mode.
2022-10-06 04:50:01.780 INFO 26472 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 MongoDB repository interfaces.
2022-10-06 04:50:02.447 INFO 26472 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-10-06 04:50:02.456 INFO 26472 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-10-06 04:50:02.456 INFO 26472 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-10-06 04:50:02.531 INFO 26472 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-10-06 04:50:02.531 INFO 26472 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1277 ms
2022-10-06 04:50:02.679 INFO 26472 --- [ main] org.mongodb.driver.client : MongoClient with metadata {"driver": {"name": "mongo-java-driver|sync|spring-boot", "version": "4.6.1"}, "os": {"type": "Windows", "name": "Windows 10", "architecture": "amd64", "version": "10.0"}, "platform": "Java/OpenLogic-OpenJDK/1.8.0-262-b10"} created with settings MongoClientSettings{readPreference=primary, writeConcern=WriteConcern{w=null, wTimeout=null ms, journal=null}, retryWrites=true, retryReads=true, readConcern=ReadConcern{level=null}, credential=null, streamFactoryFactory=null, commandListeners=[], codecRegistry=ProvidersCodecRegistry{codecProviders=[ValueCodecProvider{}, BsonValueCodecProvider{}, DBRefCodecProvider{}, DBObjectCodecProvider{}, DocumentCodecProvider{}, IterableCodecProvider{}, MapCodecProvider{}, GeoJsonCodecProvider{}, GridFSFileCodecProvider{}, Jsr310CodecProvider{}, JsonObjectCodecProvider{}, BsonCodecProvider{}, EnumCodecProvider{}, com.mongodb.Jep395RecordCodecProvider@22bd2039]}, clusterSettings={hosts=[localhost:27017], srvServiceName=mongodb, mode=SINGLE, requiredClusterType=UNKNOWN, requiredReplicaSetName='null', serverSelector='null', clusterListeners='[]', serverSelectionTimeout='30000 ms', localThreshold='30000 ms'}, socketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=0, receiveBufferSize=0, sendBufferSize=0}, heartbeatSocketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=10000, receiveBufferSize=0, sendBufferSize=0}, connectionPoolSettings=ConnectionPoolSettings{maxSize=100, minSize=0, maxWaitTimeMS=120000, maxConnectionLifeTimeMS=0, maxConnectionIdleTimeMS=0, maintenanceInitialDelayMS=0, maintenanceFrequencyMS=60000, connectionPoolListeners=[], maxConnecting=2}, serverSettings=ServerSettings{heartbeatFrequencyMS=10000, minHeartbeatFrequencyMS=500, serverListeners='[]', serverMonitorListeners='[]'}, sslSettings=SslSettings{enabled=false, invalidHostNameAllowed=false, context=null}, applicationName='null', compressorList=[], uuidRepresentation=JAVA_LEGACY, serverApi=null, autoEncryptionSettings=null, contextProvider=null}
2022-10-06 04:50:02.725 INFO 26472 --- [localhost:27017] org.mongodb.driver.connection : Opened connection [connectionId{localValue:1, serverValue:121}] to localhost:27017
2022-10-06 04:50:02.725 INFO 26472 --- [localhost:27017] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:122}] to localhost:27017
2022-10-06 04:50:02.726 INFO 26472 --- [localhost:27017] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=13, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=48972600}
2022-10-06 04:50:02.922 INFO 26472 --- [ main] org.mongodb.driver.connection : Opened connection [connectionId{localValue:3, serverValue:123}] to localhost:27017
2022-10-06 04:50:02.933 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : ####### product collection exists: true
2022-10-06 04:50:02.957 DEBUG 26472 --- [ main] o.s.data.mongodb.core.MongoTemplate : Executing count: { "brandName" : { "$exists" : true}} in collection: product
2022-10-06 04:50:02.977 DEBUG 26472 --- [ main] o.s.data.mongodb.core.MongoTemplate : Saving Document containing fields: [status, productName, brandName, categoryName, subCategoryName, _class]
2022-10-06 04:50:02.993 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : Saving sample product to database: Product(id=633e1122297cce382aea07d4, status=ACTIVE, productName=product name term1, brandName=BRAND1, categoryName=CATEGORY1, subCategoryName=SUB_CATEGORY1)
2022-10-06 04:50:02.993 DEBUG 26472 --- [ main] o.s.data.mongodb.core.MongoTemplate : Saving Document containing fields: [status, productName, brandName, categoryName, subCategoryName, _class]
2022-10-06 04:50:02.995 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : Saving sample product to database: Product(id=633e1122297cce382aea07d5, status=ACTIVE, productName=product name term2, brandName=BRAND2, categoryName=CATEGORY2, subCategoryName=SUB_CATEGORY1)
2022-10-06 04:50:02.995 DEBUG 26472 --- [ main] o.s.data.mongodb.core.MongoTemplate : Saving Document containing fields: [status, productName, brandName, categoryName, subCategoryName, _class]
2022-10-06 04:50:02.996 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : Saving sample product to database: Product(id=633e1122297cce382aea07d6, status=ACTIVE, productName=product name term3, brandName=BRAND3, categoryName=CATEGORY3, subCategoryName=SUB_CATEGORY1)
2022-10-06 04:50:02.996 DEBUG 26472 --- [ main] o.s.data.mongodb.core.MongoTemplate : Saving Document containing fields: [status, productName, brandName, categoryName, subCategoryName, _class]
2022-10-06 04:50:02.997 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : Saving sample product to database: Product(id=633e1122297cce382aea07d7, status=ACTIVE, productName=product name term4, brandName=BRAND4, categoryName=CATEGORY4, subCategoryName=SUB_CATEGORY1)
2022-10-06 04:50:02.997 DEBUG 26472 --- [ main] o.s.data.mongodb.core.MongoTemplate : Saving Document containing fields: [status, productName, brandName, categoryName, subCategoryName, _class]
2022-10-06 04:50:02.998 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : Saving sample product to database: Product(id=633e1122297cce382aea07d8, status=ACTIVE, productName=product name term5, brandName=BRAND5, categoryName=CATEGORY5, subCategoryName=SUB_CATEGORY1)
2022-10-06 04:50:03.310 INFO 26472 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-10-06 04:50:03.318 INFO 26472 --- [ main] c.e.MongoAggregationTestApplication : Started MongoAggregationTestApplication in 2.446 seconds (JVM running for 2.802)
2022-10-06 04:50:17.447 INFO 26472 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-10-06 04:50:17.447 INFO 26472 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-10-06 04:50:17.448 INFO 26472 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2022-10-06 04:50:17.511 INFO 26472 --- [nio-8080-exec-1] com.example.ProductController : Request parameters: productName: product, brandName: BRAND1, categoryName: CATEGORY2, subCategoryName: SUB_CATEGORY3, pageNumber: 0, pageSize: 10
2022-10-06 04:50:17.517 DEBUG 26472 --- [nio-8080-exec-1] o.s.data.mongodb.core.MongoTemplate : Executing aggregation: [{ "$match" : { "$text" : { "$search" : "product", "$caseSensitive" : false}}}, { "$match" : { "$or" : [{ "brandName" : "BRAND1"}, { "categoryName" : "CATEGORY2"}, { "subCategoryName" : "SUB_CATEGORY3"}]}}, { "$skip" : 0}, { "$limit" : 10}] in collection product
2022-10-06 04:50:17.527 INFO 26472 --- [nio-8080-exec-1] com.example.ProductController : Found products: [Product(id=633e1122297cce382aea07d5, status=ACTIVE, productName=product name term2, brandName=BRAND2, categoryName=CATEGORY2, subCategoryName=SUB_CATEGORY1), Product(id=633e1122297cce382aea07d4, status=ACTIVE, productName=product name term1, brandName=BRAND1, categoryName=CATEGORY1, subCategoryName=SUB_CATEGORY1)]
2022-10-06 04:50:23.235 INFO 26472 --- [nio-8080-exec-2] com.example.ProductController : Request parameters: productName: product, brandName: BRAND1, categoryName: CATEGORY2, subCategoryName: SUB_CATEGORY3, pageNumber: 0, pageSize: 1
2022-10-06 04:50:23.236 DEBUG 26472 --- [nio-8080-exec-2] o.s.data.mongodb.core.MongoTemplate : Executing aggregation: [{ "$match" : { "$text" : { "$search" : "product", "$caseSensitive" : false}}}, { "$match" : { "$or" : [{ "brandName" : "BRAND1"}, { "categoryName" : "CATEGORY2"}, { "subCategoryName" : "SUB_CATEGORY3"}]}}, { "$skip" : 0}, { "$limit" : 1}] in collection product
2022-10-06 04:50:23.238 INFO 26472 --- [nio-8080-exec-2] com.example.ProductController : Found products: [Product(id=633e1122297cce382aea07d5, status=ACTIVE, productName=product name term2, brandName=BRAND2, categoryName=CATEGORY2, subCategoryName=SUB_CATEGORY1)]
2022-10-06 04:50:28.891 INFO 26472 --- [nio-8080-exec-3] com.example.ProductController : Request parameters: productName: product, brandName: BRAND1, categoryName: CATEGORY2, subCategoryName: SUB_CATEGORY3, pageNumber: 0, pageSize: 10
2022-10-06 04:50:28.892 DEBUG 26472 --- [nio-8080-exec-3] o.s.data.mongodb.core.MongoTemplate : Executing aggregation: [{ "$match" : { "$text" : { "$search" : "product", "$caseSensitive" : false}}}, { "$match" : { "$or" : [{ "brandName" : "BRAND1"}, { "categoryName" : "CATEGORY2"}, { "subCategoryName" : "SUB_CATEGORY3"}]}}, { "$skip" : 0}, { "$limit" : 10}] in collection product
2022-10-06 04:50:28.894 INFO 26472 --- [nio-8080-exec-3] com.example.ProductController : Found products: [Product(id=633e1122297cce382aea07d5, status=ACTIVE, productName=product name term2, brandName=BRAND2, categoryName=CATEGORY2, subCategoryName=SUB_CATEGORY1), Product(id=633e1122297cce382aea07d4, status=ACTIVE, productName=product name term1, brandName=BRAND1, categoryName=CATEGORY1, subCategoryName=SUB_CATEGORY1)]
2022-10-06 04:50:33.354 INFO 26472 --- [nio-8080-exec-4] com.example.ProductController : Request parameters: productName: term3, brandName: BRAND1, categoryName: CATEGORY2, subCategoryName: SUB_CATEGORY3, pageNumber: 0, pageSize: 10
2022-10-06 04:50:33.355 DEBUG 26472 --- [nio-8080-exec-4] o.s.data.mongodb.core.MongoTemplate : Executing aggregation: [{ "$match" : { "$text" : { "$search" : "term3", "$caseSensitive" : false}}}, { "$match" : { "$or" : [{ "brandName" : "BRAND1"}, { "categoryName" : "CATEGORY2"}, { "subCategoryName" : "SUB_CATEGORY3"}]}}, { "$skip" : 0}, { "$limit" : 10}] in collection product
2022-10-06 04:50:33.356 INFO 26472 --- [nio-8080-exec-4] com.example.ProductController : Found products: []
2022-10-06 04:50:36.667 INFO 26472 --- [nio-8080-exec-5] com.example.ProductController : Request parameters: productName: term2, brandName: BRAND1, categoryName: CATEGORY2, subCategoryName: SUB_CATEGORY3, pageNumber: 0, pageSize: 10
2022-10-06 04:50:36.667 DEBUG 26472 --- [nio-8080-exec-5] o.s.data.mongodb.core.MongoTemplate : Executing aggregation: [{ "$match" : { "$text" : { "$search" : "term2", "$caseSensitive" : false}}}, { "$match" : { "$or" : [{ "brandName" : "BRAND1"}, { "categoryName" : "CATEGORY2"}, { "subCategoryName" : "SUB_CATEGORY3"}]}}, { "$skip" : 0}, { "$limit" : 10}] in collection product
2022-10-06 04:50:36.669 INFO 26472 --- [nio-8080-exec-5] com.example.ProductController : Found products: [Product(id=633e1122297cce382aea07d5, status=ACTIVE, productName=product name term2, brandName=BRAND2, categoryName=CATEGORY2, subCategoryName=SUB_CATEGORY1)]