BAEL-5400 - Return Only Specific Fields for a Query in Spring Data MongoDB (#11991)

* feat: field projection spring data mongodb

* fix: use available ports

* fix: typo

* fix: add abstraction

* fix: avoid blank lines
This commit is contained in:
lucaCambi77 2022-04-10 16:19:13 +02:00 committed by GitHub
parent ca824c47d9
commit d0e3eebb12
8 changed files with 686 additions and 1 deletions

View File

@ -0,0 +1,46 @@
package com.baeldung.projection.model;
import java.util.Objects;
public class InStock {
private String wareHouse;
private Integer quantity;
public InStock(String wareHouse, int quantity) {
this.wareHouse = wareHouse;
this.quantity = quantity;
}
public String getWareHouse() {
return wareHouse;
}
public void setWareHouse(String wareHouse) {
this.wareHouse = wareHouse;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
InStock inStock = (InStock) o;
return Objects.equals(wareHouse, inStock.wareHouse) && Objects.equals(quantity, inStock.quantity);
}
@Override
public int hashCode() {
return Objects.hash(wareHouse, quantity);
}
}

View File

@ -0,0 +1,70 @@
package com.baeldung.projection.model;
import java.util.List;
import java.util.Objects;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoId;
@Document(collection = "inventory")
public class Inventory {
@MongoId
private String id;
private String item;
private String status;
private Size size;
private List<InStock> inStock;
public String getId() {
return id;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Size getSize() {
return size;
}
public void setSize(Size size) {
this.size = size;
}
public List<InStock> getInStock() {
return inStock;
}
public void setInStock(List<InStock> inStock) {
this.inStock = inStock;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Inventory inventory = (Inventory) o;
return Objects.equals(id, inventory.id) && Objects.equals(item, inventory.item) && Objects.equals(status, inventory.status);
}
@Override
public int hashCode() {
return Objects.hash(id, item, status);
}
}

View File

@ -0,0 +1,56 @@
package com.baeldung.projection.model;
import java.util.Objects;
public class Size {
private Double height;
private Double width;
private String uom;
public Size(Double height, Double width, String uom) {
this.height = height;
this.width = width;
this.uom = uom;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Double getWidth() {
return width;
}
public void setWidth(Double width) {
this.width = width;
}
public String getUom() {
return uom;
}
public void setUom(String uom) {
this.uom = uom;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Size size = (Size) o;
return Objects.equals(height, size.height) && Objects.equals(width, size.width) && Objects.equals(uom, size.uom);
}
@Override
public int hashCode() {
return Objects.hash(height, width, uom);
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.projection.repository;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import com.baeldung.projection.model.Inventory;
public interface InventoryRepository extends MongoRepository<Inventory, String> {
@Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1 }")
List<Inventory> findByStatusIncludeItemAndStatusFields(String status);
@Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, '_id' : 0 }")
List<Inventory> findByStatusIncludeItemAndStatusExcludeIdFields(String status);
@Query(value = "{ 'status' : ?0 }", fields = "{ 'status' : 0, 'inStock' : 0 }")
List<Inventory> findByStatusIncludeAllButStatusAndStockFields(String status);
@Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, 'size.uom': 1 }")
List<Inventory> findByStatusIncludeEmbeddedFields(String status);
@Query(value = "{ 'status' : ?0 }", fields = "{ 'size.uom': 0 }")
List<Inventory> findByStatusExcludeEmbeddedFields(String status);
@Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, 'inStock.quantity': 1 }")
List<Inventory> findByStatusIncludeEmbeddedFieldsInArray(String status);
@Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, 'inStock': { $slice: -1 } }")
List<Inventory> findByStatusIncludeEmbeddedFieldsLastElementInArray(String status);
}

View File

@ -21,6 +21,7 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.test.context.TestPropertySource;
import org.springframework.util.SocketUtils;
import com.baeldung.logging.model.Book;
import com.mongodb.client.MongoClients;
@ -50,7 +51,7 @@ public class LoggingUnitTest {
@BeforeEach
void setup() throws Exception {
String ip = "localhost";
int port = 27017;
int port = SocketUtils.findAvailableTcpPort();
ImmutableMongodConfig mongodConfig = MongodConfig.builder()
.version(Version.Main.PRODUCTION)

View File

@ -0,0 +1,88 @@
package com.baeldung.projection;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.util.SocketUtils;
import com.baeldung.projection.model.InStock;
import com.baeldung.projection.model.Inventory;
import com.baeldung.projection.model.Size;
import com.mongodb.client.MongoClients;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfig;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
abstract class AbstractTestProjection {
private static final String CONNECTION_STRING = "mongodb://%s:%d";
protected MongodExecutable mongodExecutable;
protected MongoTemplate mongoTemplate;
@AfterEach
void clean() {
mongodExecutable.stop();
}
void setUp() throws IOException {
String ip = "localhost";
int port = SocketUtils.findAvailableTcpPort();
ImmutableMongodConfig mongodbConfig = MongodConfig.builder()
.version(Version.Main.PRODUCTION)
.net(new Net(ip, port, Network.localhostIsIPv6()))
.build();
MongodStarter starter = MongodStarter.getDefaultInstance();
mongodExecutable = starter.prepare(mongodbConfig);
mongodExecutable.start();
mongoTemplate = new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, ip, port)), "test");
}
public List<Inventory> getInventories() {
Inventory journal = new Inventory();
journal.setItem("journal");
journal.setStatus("A");
journal.setSize(new Size(14.0, 21.0, "cm"));
journal.setInStock(Collections.singletonList(new InStock("A", 5)));
Inventory notebook = new Inventory();
notebook.setItem("notebook");
notebook.setStatus("A");
notebook.setSize(new Size(8.5, 11.0, "in"));
notebook.setInStock(Collections.singletonList(new InStock("C", 5)));
Inventory paper = new Inventory();
paper.setItem("paper");
paper.setStatus("D");
paper.setSize(new Size(8.5, 11.0, "in"));
paper.setInStock(Collections.singletonList(new InStock("A", 60)));
return Arrays.asList(journal, notebook, paper);
}
abstract void whenIncludeFields_thenOnlyIncludedFieldsAreNotNull();
abstract void whenIncludeFieldsAndExcludeOtherFields_thenOnlyExcludedFieldsAreNull();
abstract void whenIncludeAllButExcludeSomeFields_thenOnlyExcludedFieldsAreNull();
abstract void whenIncludeEmbeddedFields_thenEmbeddedFieldsAreNotNull();
abstract void whenExcludeEmbeddedFields_thenEmbeddedFieldsAreNull();
abstract void whenIncludeEmbeddedFieldsInArray_thenEmbeddedFieldsInArrayAreNotNull();
abstract void whenIncludeEmbeddedFieldsSliceInArray_thenArrayLengthEqualToSlice();
}

View File

@ -0,0 +1,216 @@
package com.baeldung.projection;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.query.Query;
import com.baeldung.projection.model.InStock;
import com.baeldung.projection.model.Inventory;
import com.baeldung.projection.model.Size;
@SpringBootTest
public class MongoTemplateProjectionUnitTest extends AbstractTestProjection {
@BeforeEach
void setup() throws Exception {
super.setUp();
List<Inventory> inventoryList = getInventories();
mongoTemplate.insert(inventoryList, Inventory.class);
}
@Test
void whenIncludeFields_thenOnlyIncludedFieldsAreNotNull() {
Query query = new Query();
query.fields()
.include("item")
.include("status");
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getId());
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNull(i.getSize());
assertNull(i.getInStock());
});
}
@Test
void whenIncludeFieldsAndExcludeOtherFields_thenOnlyExcludedFieldsAreNull() {
Query query = new Query();
query.fields()
.include("item")
.include("status")
.exclude("_id");
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNull(i.getId());
assertNull(i.getSize());
assertNull(i.getInStock());
});
}
@Test
void whenIncludeAllButExcludeSomeFields_thenOnlyExcludedFieldsAreNull() {
Query query = new Query();
query.fields()
.exclude("status")
.exclude("inStock");
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getId());
assertNotNull(i.getSize());
assertNull(i.getInStock());
assertNull(i.getStatus());
});
}
@Test
void whenIncludeEmbeddedFields_thenEmbeddedFieldsAreNotNull() {
Query query = new Query();
query.fields()
.include("item")
.include("status")
.include("size.uom");
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getSize());
assertNotNull(i.getSize()
.getUom());
assertNull(i.getSize()
.getHeight());
assertNull(i.getSize()
.getWidth());
assertNull(i.getInStock());
});
}
@Test
void whenExcludeEmbeddedFields_thenEmbeddedFieldsAreNull() {
Query query = new Query();
query.fields()
.exclude("size.uom");
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getSize());
assertNull(i.getSize()
.getUom());
assertNotNull(i.getSize()
.getHeight());
assertNotNull(i.getSize()
.getWidth());
assertNotNull(i.getInStock());
});
}
@Test
void whenIncludeEmbeddedFieldsInArray_thenEmbeddedFieldsInArrayAreNotNull() {
Query query = new Query();
query.fields()
.include("item")
.include("status")
.include("inStock.quantity");
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getInStock());
i.getInStock()
.forEach(stock -> {
assertNull(stock.getWareHouse());
assertNotNull(stock.getQuantity());
});
assertNull(i.getSize());
});
}
@Test
void whenIncludeEmbeddedFieldsSliceInArray_thenArrayLengthEqualToSlice() {
Inventory postcard = new Inventory();
postcard.setItem("postcard");
postcard.setStatus("A");
postcard.setSize(new Size(10.0, 15.25, "cm"));
InStock firstInStock = new InStock("B", 15);
InStock lastInStock = new InStock("C", 35);
postcard.setInStock(Arrays.asList(firstInStock, lastInStock));
mongoTemplate.save(postcard);
Query query = new Query();
query.fields()
.include("item")
.include("status")
.slice("inStock", -1);
List<Inventory> inventoryList = mongoTemplate.find(query, Inventory.class);
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getInStock());
assertEquals(1, i.getInStock()
.size());
assertNull(i.getSize());
});
InStock stock = inventoryList.stream()
.filter(i -> i.getItem()
.equals("postcard"))
.map(i -> i.getInStock()
.get(0))
.findFirst()
.orElse(null);
assertEquals(lastInStock, stock);
}
}

View File

@ -0,0 +1,175 @@
package com.baeldung.projection;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.baeldung.projection.model.InStock;
import com.baeldung.projection.model.Inventory;
import com.baeldung.projection.model.Size;
import com.baeldung.projection.repository.InventoryRepository;
@SpringBootTest
public class RepositoryProjectionUnitTest extends AbstractTestProjection {
@Autowired
private InventoryRepository inventoryRepository;
@BeforeEach
void setup() throws Exception {
super.setUp();
List<Inventory> inventoryList = getInventories();
inventoryRepository.saveAll(inventoryList);
}
@Test
void whenIncludeFields_thenOnlyIncludedFieldsAreNotNull() {
List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeItemAndStatusFields("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getId());
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNull(i.getSize());
assertNull(i.getInStock());
});
}
@Test
void whenIncludeFieldsAndExcludeOtherFields_thenOnlyExcludedFieldsAreNull() {
List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeItemAndStatusExcludeIdFields("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNull(i.getId());
assertNull(i.getSize());
assertNull(i.getInStock());
});
}
@Test
void whenIncludeAllButExcludeSomeFields_thenOnlyExcludedFieldsAreNull() {
List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeAllButStatusAndStockFields("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getId());
assertNotNull(i.getSize());
assertNull(i.getInStock());
assertNull(i.getStatus());
});
}
@Test
void whenIncludeEmbeddedFields_thenEmbeddedFieldsAreNotNull() {
List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeEmbeddedFields("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getSize());
assertNotNull(i.getSize()
.getUom());
assertNull(i.getSize()
.getHeight());
assertNull(i.getSize()
.getWidth());
assertNull(i.getInStock());
});
}
@Test
void whenExcludeEmbeddedFields_thenEmbeddedFieldsAreNull() {
List<Inventory> inventoryList = inventoryRepository.findByStatusExcludeEmbeddedFields("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getSize());
assertNull(i.getSize()
.getUom());
assertNotNull(i.getSize()
.getHeight());
assertNotNull(i.getSize()
.getWidth());
assertNotNull(i.getInStock());
});
}
@Test
void whenIncludeEmbeddedFieldsInArray_thenEmbeddedFieldsInArrayAreNotNull() {
List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeEmbeddedFieldsInArray("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getInStock());
i.getInStock()
.forEach(stock -> {
assertNull(stock.getWareHouse());
assertNotNull(stock.getQuantity());
});
assertNull(i.getSize());
});
}
@Test
void whenIncludeEmbeddedFieldsSliceInArray_thenArrayLengthEqualToSlice() {
Inventory postcard = new Inventory();
postcard.setItem("postcard");
postcard.setStatus("A");
postcard.setSize(new Size(10.0, 15.25, "cm"));
InStock firstInStock = new InStock("B", 15);
InStock lastInStock = new InStock("C", 35);
postcard.setInStock(Arrays.asList(firstInStock, lastInStock));
inventoryRepository.save(postcard);
List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeEmbeddedFieldsLastElementInArray("A");
assertTrue(inventoryList.size() > 0);
inventoryList.forEach(i -> {
assertNotNull(i.getItem());
assertNotNull(i.getStatus());
assertNotNull(i.getId());
assertNotNull(i.getInStock());
assertEquals(1, i.getInStock()
.size());
assertNull(i.getSize());
});
InStock stock = inventoryList.stream()
.filter(i -> i.getItem()
.equals("postcard"))
.map(i -> i.getInStock()
.get(0))
.findFirst()
.orElse(null);
assertEquals(lastInStock, stock);
}
}