From 28cdc0ddc0f259ae00b1657b9251a0f029d33087 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Fri, 27 May 2022 23:55:58 -0300 Subject: [PATCH] BAEL-5370 - MongoDB Composite Key First Draft. --- .../SpringBootCompositeKeyApplication.java | 15 ++ .../composite/key/dao/CustomerRepository.java | 12 ++ .../composite/key/dao/SaleRepository.java | 12 ++ .../composite/key/dao/TicketRepository.java | 10 ++ .../boot/composite/key/data/Customer.java | 49 ++++++ .../boot/composite/key/data/Sale.java | 38 +++++ .../boot/composite/key/data/Ticket.java | 31 ++++ .../boot/composite/key/data/TicketId.java | 56 +++++++ .../key/service/CustomerService.java | 66 +++++++++ .../composite/key/web/CustomerController.java | 88 +++++++++++ .../boot.composite.key/app.properties | 1 + .../key/CustomerServiceIntegrationTest.java | 140 ++++++++++++++++++ 12 files changed, 518 insertions(+) create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties create mode 100644 persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java new file mode 100644 index 0000000000..e9f8eab1dd --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/SpringBootCompositeKeyApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.boot.composite.key; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +@SpringBootApplication +@PropertySource("classpath:boot.composite.key/app.properties") +@EnableMongoRepositories(basePackages = { "com.baeldung.boot.composite.key" }) +public class SpringBootCompositeKeyApplication { + public static void main(String... args) { + SpringApplication.run(SpringBootCompositeKeyApplication.class, args); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java new file mode 100644 index 0000000000..6953a21ddd --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/CustomerRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.composite.key.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.composite.key.data.Customer; + +public interface CustomerRepository extends MongoRepository { + + Optional findByStoreIdAndNumber(Long storeId, Long number); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java new file mode 100644 index 0000000000..3caa33d465 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/SaleRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.composite.key.dao; + +import java.util.Optional; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.composite.key.data.Sale; +import com.baeldung.boot.composite.key.data.TicketId; + +public interface SaleRepository extends MongoRepository { + Optional findByTicketId(TicketId ticketId); +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java new file mode 100644 index 0000000000..b02ea461d2 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/dao/TicketRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.boot.composite.key.dao; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; + +public interface TicketRepository extends MongoRepository { + +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java new file mode 100644 index 0000000000..d4bb1ef40c --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Customer.java @@ -0,0 +1,49 @@ +package com.baeldung.boot.composite.key.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@CompoundIndex(name = "customer_idx", def = "{ 'storeId': 1, 'number': 1 }", unique = true) +public class Customer { + @Id + private String id; + + private Long storeId; + private Long number; + + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public Long getNumber() { + return number; + } + + public void setNumber(Long number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java new file mode 100644 index 0000000000..a81c91674c --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Sale.java @@ -0,0 +1,38 @@ +package com.baeldung.boot.composite.key.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@CompoundIndex(name = "sale_idx", def = "{ 'ticketId': 1 }", unique = true) +public class Sale { + @Id + private String id; + + private TicketId ticketId; + private Double value; + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + + public TicketId getTicketId() { + return ticketId; + } + + public void setTicketId(TicketId ticketId) { + this.ticketId = ticketId; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java new file mode 100644 index 0000000000..7a8ebe1ab3 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/Ticket.java @@ -0,0 +1,31 @@ +package com.baeldung.boot.composite.key.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class Ticket { + @Id + private TicketId id; + + private String event; + + public Ticket() { + } + + public TicketId getId() { + return id; + } + + public void setId(TicketId id) { + this.id = id; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java new file mode 100644 index 0000000000..76fbf81391 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/data/TicketId.java @@ -0,0 +1,56 @@ +package com.baeldung.boot.composite.key.data; + +public class TicketId { + private String venue; + private String date; + + public TicketId() { + } + + public String getVenue() { + return venue; + } + + public void setVenue(String venue) { + this.venue = venue; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((date == null) ? 0 : date.hashCode()); + result = prime * result + ((venue == null) ? 0 : venue.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TicketId other = (TicketId) obj; + if (date == null) { + if (other.date != null) + return false; + } else if (!date.equals(other.date)) + return false; + if (venue == null) { + if (other.venue != null) + return false; + } else if (!venue.equals(other.venue)) + return false; + return true; + } +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java new file mode 100644 index 0000000000..476209bce9 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/service/CustomerService.java @@ -0,0 +1,66 @@ +package com.baeldung.boot.composite.key.service; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.baeldung.boot.composite.key.dao.CustomerRepository; +import com.baeldung.boot.composite.key.dao.SaleRepository; +import com.baeldung.boot.composite.key.dao.TicketRepository; +import com.baeldung.boot.composite.key.data.Customer; +import com.baeldung.boot.composite.key.data.Sale; +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; + +@Service +public class CustomerService { + @Autowired + private CustomerRepository customerRepository; + + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private SaleRepository saleRepository; + + public Optional find(TicketId id) { + return ticketRepository.findById(id); + } + + public Ticket insert(Ticket ticket) { + return ticketRepository.insert(ticket); + } + + public Ticket save(Ticket ticket) { + return ticketRepository.save(ticket); + } + + public Optional findCustomerById(String id) { + return customerRepository.findById(id); + } + + public Optional findCustomerByIndex(Long storeId, Long number) { + return customerRepository.findByStoreIdAndNumber(storeId, number); + } + + public Customer insert(Customer customer) { + return customerRepository.insert(customer); + } + + public Customer save(Customer customer) { + return customerRepository.save(customer); + } + + public Sale insert(Sale sale) { + return saleRepository.insert(sale); + } + + public Optional findSaleByTicketId(TicketId ticketId) { + return saleRepository.findByTicketId(ticketId); + } + + public Optional findSaleById(String id) { + return saleRepository.findById(id); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java new file mode 100644 index 0000000000..83afe0ae42 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/java/com/baeldung/boot/composite/key/web/CustomerController.java @@ -0,0 +1,88 @@ +package com.baeldung.boot.composite.key.web; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.boot.composite.key.data.Customer; +import com.baeldung.boot.composite.key.data.Sale; +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; +import com.baeldung.boot.composite.key.service.CustomerService; + +@RestController +@RequestMapping("/customer") +public class CustomerController { + @Autowired + private CustomerService customerService; + + // @Autowired + // private TicketRepository ticketRepository; + // + // @GetMapping("/ticket") + // public Optional getTicket(TicketId id) { + // return ticketRepository.findById(id); + // } + // + // @PostMapping("/ticket") + // public Ticket postTicket(@RequestBody Ticket ticket) { + // return ticketRepository.insert(ticket); + // } + + @GetMapping("/ticket") + public Optional getTicket(TicketId id) { + return customerService.find(id); + } + + @PostMapping("/ticket") + public Ticket postTicket(@RequestBody Ticket ticket) { + return customerService.insert(ticket); + } + + @PutMapping("/ticket") + public Ticket putTicket(@RequestBody Ticket ticket) { + return customerService.save(ticket); + } + + @GetMapping("/{id}") + public Optional getCustomer(@PathVariable String id) { + return customerService.findCustomerById(id); + } + + @GetMapping("/{storeId}/{number}") + public Optional getCustomerByIndex(@PathVariable Long storeId, @PathVariable Long number) { + return customerService.findCustomerByIndex(storeId, number); + } + + @PostMapping + public Customer postCustomer(@RequestBody Customer customer) { + return customerService.insert(customer); + } + + @PutMapping + public Customer putCustomer(@RequestBody Customer customer) { + return customerService.save(customer); + } + + @PostMapping("/sale") + public Sale postSale(@RequestBody Sale sale) { + return customerService.insert(sale); + } + + @GetMapping("/sale/{id}") + public Optional getSale(@PathVariable String id) { + return customerService.findSaleById(id); + } + + @GetMapping("/sale") + public Optional getSale(TicketId ticketId) { + return customerService.findSaleByTicketId(ticketId); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties new file mode 100644 index 0000000000..a73a94d850 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/main/resources/boot.composite.key/app.properties @@ -0,0 +1 @@ +spring.data.mongodb.auto-index-creation=true diff --git a/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java new file mode 100644 index 0000000000..4f779ebf02 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-2/src/test/java/com/baeldung/boot/composite/key/CustomerServiceIntegrationTest.java @@ -0,0 +1,140 @@ +package com.baeldung.boot.composite.key; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.boot.composite.key.data.Customer; +import com.baeldung.boot.composite.key.data.Ticket; +import com.baeldung.boot.composite.key.data.TicketId; +import com.baeldung.boot.composite.key.service.CustomerService; + +@SpringBootTest +@DirtiesContext +@RunWith(SpringRunner.class) +public class CustomerServiceIntegrationTest { + @Autowired + private CustomerService service; + + private static Ticket ticket; + private static TicketId ticketId; + + @BeforeClass + public static void setup() { + ticket = new Ticket(); + ticket.setEvent("Event A"); + + ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("Venue A"); + ticket.setId(ticketId); + } + + @Test + public void givenCompositeId_whenObjectSaved_thenIdMatches() { + Ticket savedTicket = service.insert(ticket); + assertEquals(savedTicket.getId(), ticket.getId()); + } + + @Test + public void givenCompositeId_whenSearchingByIdObject_thenFound() { + Optional optionalTicket = service.find(ticketId); + + assertThat(optionalTicket.isPresent()); + Ticket savedTicket = optionalTicket.get(); + + assertEquals(savedTicket.getId(), ticketId); + } + + @Test + public void givenCompoundUniqueIndex_whenSearchingByGeneratedId_thenFound() { + Customer customer = new Customer(); + customer.setName("Name"); + customer.setNumber(0l); + customer.setStoreId(0l); + + Customer savedCustomer = service.insert(customer); + + Optional optional = service.findCustomerById(savedCustomer.getId()); + + assertThat(optional.isPresent()); + } + + @Test + public void givenCompositeId_whenDupeInsert_thenExceptionIsThrown() { + Ticket ticket = new Ticket(); + ticket.setEvent("C"); + + TicketId ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("V"); + ticket.setId(ticketId); + + assertThrows(DuplicateKeyException.class, () -> { + service.insert(ticket); + service.insert(ticket); + }); + } + + @Test + public void givenCompositeId_whenDupeSave_thenObjectUpdated() { + TicketId ticketId = new TicketId(); + ticketId.setDate("2020-01-01"); + ticketId.setVenue("Venue"); + + Ticket ticketA = new Ticket(); + ticketA.setEvent("A"); + ticketA.setId(ticketId); + + service.save(ticketA); + + Ticket ticketB = new Ticket(); + ticketB.setEvent("B"); + ticketB.setId(ticketId); + + Ticket savedTicket = service.save(ticketB); + assertEquals(savedTicket.getEvent(), ticketB.getEvent()); + } + + @Test + public void givenCompoundUniqueIndex_whenDupeInsert_thenExceptionIsThrown() { + Customer customer = new Customer(); + customer.setName("Name"); + customer.setNumber(1l); + customer.setStoreId(2l); + + assertThrows(DuplicateKeyException.class, () -> { + service.insert(customer); + service.insert(customer); + }); + } + + @Test + public void givenCompoundUniqueIndex_whenDupeSave_thenExceptionIsThrown() { + Customer customerA = new Customer(); + customerA.setName("Name A"); + customerA.setNumber(1l); + customerA.setStoreId(2l); + + Customer customerB = new Customer(); + customerB.setName("Name B"); + customerB.setNumber(1l); + customerB.setStoreId(2l); + + assertThrows(DuplicateKeyException.class, () -> { + service.save(customerA); + service.save(customerB); + }); + } +}