diff --git a/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ConcurrentRequestApplication.java b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ConcurrentRequestApplication.java new file mode 100644 index 0000000000..21d031555b --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ConcurrentRequestApplication.java @@ -0,0 +1,11 @@ +package com.baeldung.concurrentrequest; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ConcurrentRequestApplication { + public static void main(String[] args) { + SpringApplication.run(ConcurrentRequestApplication.class, args); + } +} diff --git a/spring-core-5/src/main/java/com/baeldung/concurrentrequest/Product.java b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/Product.java new file mode 100644 index 0000000000..30a47d27bc --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/Product.java @@ -0,0 +1,26 @@ +package com.baeldung.concurrentrequest; + +public class Product { + + private final int id; + private final String name; + private final Stock stock; + + public Product(int id, String name, Stock stock) { + this.id = id; + this.name = name; + this.stock = stock; + } + + public Stock getStock() { + return stock; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ProductController.java b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ProductController.java new file mode 100644 index 0000000000..11f824ddda --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ProductController.java @@ -0,0 +1,30 @@ +package com.baeldung.concurrentrequest; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController() +@RequestMapping("product") +public class ProductController { + + private final ProductService productService; + + public ProductController(ProductService productService) { + this.productService = productService; + } + + @GetMapping("/{id}") + public Product getProductDetails(@PathVariable("id") int productId) { + return productService.getProductById(productId) + .orElse(null); + } + + @GetMapping("{id}/stock") + public Stock getProductStock(@PathVariable("id") int productId) { + return productService.getProductById(productId) + .map(Product::getStock) + .orElse(null); + } +} diff --git a/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ProductService.java b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ProductService.java new file mode 100644 index 0000000000..447c450213 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/ProductService.java @@ -0,0 +1,32 @@ +package com.baeldung.concurrentrequest; + +import static java.lang.Thread.currentThread; +import static java.util.Arrays.asList; + +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Service; + +@Service +public class ProductService { + + // @formatter:off + private final static List productRepository = asList( + new Product(1, "Product 1", new Stock(100)), + new Product(2, "Product 2", new Stock(50)) + ); + // @formatter:on + + public Optional getProductById(int id) { + Optional product = productRepository.stream() + .filter(p -> p.getId() == id) + .findFirst(); + String productName = product.map(Product::getName) + .orElse(null); + + System.out.printf("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().getName(), this, id, productName); + + return product; + } +} diff --git a/spring-core-5/src/main/java/com/baeldung/concurrentrequest/Stock.java b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/Stock.java new file mode 100644 index 0000000000..bb7860f5be --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/concurrentrequest/Stock.java @@ -0,0 +1,13 @@ +package com.baeldung.concurrentrequest; + +public class Stock { + private final int inStockItems; + + public Stock(int inStockItems) { + this.inStockItems = inStockItems; + } + + public int getInStockItems() { + return inStockItems; + } +} diff --git a/spring-core-5/src/test/java/com/baeldung/concurrentrequest/ConcurrentRequestUnitTest.java b/spring-core-5/src/test/java/com/baeldung/concurrentrequest/ConcurrentRequestUnitTest.java new file mode 100644 index 0000000000..76a0d764e1 --- /dev/null +++ b/spring-core-5/src/test/java/com/baeldung/concurrentrequest/ConcurrentRequestUnitTest.java @@ -0,0 +1,53 @@ +package com.baeldung.concurrentrequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; + +@SpringBootTest +@AutoConfigureMockMvc +public class ConcurrentRequestUnitTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ProductController controller; + + @Test + public void givenContextLoads_thenProductControllerIsAvailable() { + assertThat(controller).isNotNull(); + } + + @Test + public void givenMultipleCallsRunInParallel_thenAllCallsReturn200() throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(2); + + executor.submit(() -> performCall("/product/1", status().isOk())); + executor.submit(() -> performCall("/product/2/stock", status().isOk())); + + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } + + private void performCall(String url, ResultMatcher expect) { + try { + this.mockMvc.perform(get(url)) + .andExpect(expect); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +}