Chef.Yeon
Code Cook
Chef.Yeon
전체 방문자
오늘
어제
  • 분류 전체보기 (230)
    • 게임 개발 (1)
      • Unity (1)
    • Android (27)
      • Kotlin (19)
      • 우아한테크코스 5기 (4)
    • Language (11)
      • 파이썬 (3)
      • Java (7)
    • DB (2)
      • SQL (16)
    • Spring (25)
    • 코딩테스트 (56)
    • Git (1)
    • TIL (85)
    • DevOps (6)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 안드로이드
  • 우아한테크코스
  • enum
  • elasticsearch
  • MariaDB
  • 레포지토리
  • Android
  • rsocket
  • 다이나믹 프로그래밍
  • Docker
  • 코틀린
  • grafana
  • webflux
  • kibana
  • SQL
  • til
  • 백준
  • spring
  • 프로그래머스
  • java
  • kotlin
  • 프리코스
  • 문자열
  • 에라토스테네스의 체
  • 내림차순
  • 코딩테스트
  • ec2
  • 파이썬
  • 코틀린 인 액션
  • Wil

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Chef.Yeon

Code Cook

Spring

[Spring] Spring WebFlux 간단한 예제

2023. 5. 25. 04:03

Spring WebFlux를 이용하여 간단하게 상품 등록/삭제, 카트에 상품 담기/삭제 기능을 구현해보도록 하겠다.

 

1. 프로젝트 생성

 

2. 환경 구성

build.bradle 에 다음 dependency를 추가해준다.

implementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:3.3.0'
implementation 'org.mongodb:mongodb-driver-sync'
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:3.3.0'

 

프로젝트 구조는 다음과 같다.

3. 코드

Cart.java

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Cart {

    @Id
    private String id;

    private List<CartItem> cartItems;

    public Cart(String id) {
        this(id, new ArrayList<CartItem>());
    }

    public void removeItem(CartItem cartItem) {
        this.cartItems.remove(cartItem);
    }

    public Optional<CartItem> getCartItemByItemId(String itemId) {
        return this.getCartItems().stream()
                .filter(cartItem -> cartItem.getItem().getId().equals(itemId))
                .findFirst();
    }
}

 

Item.java

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Item {

    @Id
    private String id;
    private String name;
    private String origin;
    private int price;

    public Item(String name, String origin, int price) {
        this.name = name;
        this.origin = origin;
        this.price = price;
    }
}

 

CartItem.java

@Getter
@Setter
@ToString
@NoArgsConstructor
public class CartItem {

    private Item item;
    private int quantity;

    public CartItem(Item item) {
        this.item = item;
        this.quantity = 1;
    }

    public void increment() {
        this.quantity += 1;
    }

    public void decrement() {
        this.quantity -= 1;
    }

    public boolean isOne() {
        if (this.quantity == 1) {
            return true;
        }
        return false;
    }
}

 

CarReactiveRepository.java

public interface CartReactiveRepository extends ReactiveCrudRepository<Cart, String> {
}

 

ItemReactiveRepository.java

두 메서드는 테스트 코드에서 사용한 메서드라 작성하지 않아도 된다.

public interface ItemReactiveRepository extends ReactiveCrudRepository<Item, String>, ReactiveQueryByExampleExecutor<Item> {
    Flux<Item> findByNameContaining(String itemName);
    Mono<Item> findByName(String name);
}

 

ShopController.java

@Controller
@RequiredArgsConstructor
public class ShopController {

    private final ItemReactiveRepository itemReactiveRepository;
    private final CartReactiveRepository cartReactiveRepository;
    private final CartService cartService;

    @GetMapping("/")
    public Mono<Rendering> home() {
        return Mono.just(Rendering.view("shop")
                .modelAttribute("items", itemReactiveRepository.findAll())
                .modelAttribute("cart", cartReactiveRepository.findById("My Cart")
                        .defaultIfEmpty(new Cart("My Cart"))).build());
    }

    @GetMapping("/item")
    public Mono<Rendering> itemRegisterPage() {
        return Mono.just(Rendering.view("register").build());
    }

    @PostMapping("/item")
    public Mono<String> registerItem(@RequestBody ItemRequestDto itemRequestDto) {
        return cartService.registerItem(itemRequestDto)
                .thenReturn("redirect:/");
    }

    @PostMapping("/cart/item/{id}")
    public Mono<String> addCart(@PathVariable String id) {
        return cartService.addToCart("My Cart", id)
                .thenReturn("redirect:/");
    }

    @DeleteMapping("/cart/item/deleteAll/{id}")
    public Mono<String> delToCartAll(@PathVariable String id) {
        return cartService.deleteAllItemFromCart("My Cart", id)
                .thenReturn("redirect:/");
    }

    @DeleteMapping("/cart/item/delete/{id}")
    public Mono<String> delToCartItem(@PathVariable String id) {
        return cartService.deleteOneItemFromCart("My Cart", id)
                .thenReturn("redirect:/");
    }

    @DeleteMapping("/item/delete/{id}")
    public Mono<String> deleteItem(@PathVariable String id) {
        return itemReactiveRepository.deleteById(id)
                .thenReturn("redirect:/");
    }
}

 

CartService.java

public interface CartService {
    public Mono<Item> registerItem(ItemRequestDto itemRequestDto);
    public Mono<Cart> delToCartCount(String cartId, String id);
    public Mono<Cart> delToCartAll(String cartId, String id);
    public Mono<Cart> addToCart(String cartId, String id);
}

 

CartServiceImpl.java

생성한 CartService 인터페이스의 구현체인 CartServiceImpl을 생성했다.

@Service
@RequiredArgsConstructor
@Slf4j
public class CartServiceImpl implements CartService {

    private final ItemReactiveRepository itemReactiveRepository;
    private final CartReactiveRepository cartReactiveRepository;

    @Override
    public Mono<Item> registerItem(ItemRequestDto itemRequestDto) {
        Item newItem = new Item(itemRequestDto.getName(), itemRequestDto.getOrigin(), itemRequestDto.getPrice());

        return itemReactiveRepository.save(newItem);
    }

    @Override
    public Mono<Cart> deleteOneItemFromCart(String cartId, String itemId) {
        return cartReactiveRepository.findById(cartId)
                .defaultIfEmpty(new Cart(cartId))
                .flatMap(cart -> {
                    cart.getCartItemByItemId(itemId).ifPresent(cartItem -> {
                        if (cartItem.isOne()) {
                            //카트에 상품 수량이 1이면 상품 삭제
                            cart.removeItem(cartItem);
                        } else {
                            //카트에 상품 수량이 1 이상이면 수량 -1
                            cartItem.decrement();
                        }
                    });
                    return Mono.just(cart).log();
                })
                .flatMap(cart -> cartReactiveRepository.save(cart));
    }

    @Override
    public Mono<Cart> deleteAllItemFromCart(String cartId, String itemId) {
        return cartReactiveRepository.findById(cartId)
                .defaultIfEmpty(new Cart(cartId))
                .flatMap(cart -> {
                    //카트에 상품이 존재한다면 삭제
                    cart.getCartItemByItemId(itemId).ifPresent(cart::removeItem);
                    return Mono.just(cart).log();
                })
                .flatMap(cart -> cartReactiveRepository.save(cart));
    }

    @Override
    public Mono<Cart> addToCart(String cartId, String itemId) {
        return cartReactiveRepository.findById(cartId)
                .defaultIfEmpty(new Cart(cartId))
                .flatMap(cart -> cart.getCartItemByItemId(itemId)
                        .map(cartItem -> {
                            //카트에 상품이 있는 경우 개수만 +1
                            cartItem.increment();
                            return Mono.just(cart).log();
                        }).orElseGet(() -> itemReactiveRepository.findById(itemId)
                                //카트에 상품이 없는 경우 CartItem을 새로 생성해서 카트에 담기
                                .map(CartItem::new)
                                .doOnNext(cartItem -> cart.getCartItems().add(cartItem))
                                .map(cartItem -> cart))).flatMap(cart -> cartReactiveRepository.save(cart));
    }
}

 

shop.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Shop Home</title>
</head>
<body>
<h2>Item List</h2>
<table border="1">
    <thead>
    <tr>
        <td><b>상품번호</b></td>
        <td><b>상품명</b></td>
        <td><b>원산지</b></td>
        <td><b>가격</b></td>
        <td colspan="2"><b></b></td>
    </tr>
    </thead>
    <tbody>
    <tr th:each="item : ${items}">
        <td th:text="${item.id}"></td>
        <td th:text="${item.name}"></td>
        <td th:text="${item.origin}"></td>
        <td th:text="${item.price}"></td>
        <td>
            <form th:method="post" th:action="@{'/cart/item/' + ${item.id}}">
                <input type="submit" value="담기">
            </form>
        </td>
        <td>
        <form th:method="delete" th:action="@{'/item/delete/' + ${item.id}}">
            <input type="submit" value="삭제">
        </form>
    </td>
    </tr>
    </tbody>
</table>

<button style="background-color:blue; color:white; margin-top: 10px" onclick="window.location.href='/item'">
    상품 등록
</button>

<h2>My Cart</h2>
<table border="1">
    <thead>
    <tr>
        <td><b>상품번호</b></td>
        <td><b>상품명</b></td>
        <td><b>원산지</b></td>
        <td><b>가격</b></td>
        <td><b>수량</b></td>
        <td colspan="2"><b></b></td>
    </tr>
    </thead>
    <tbody>
    <tr th:each="cartItem : ${cart.cartItems}">
        <td th:text="${cartItem.item.id}"></td>
        <td th:text="${cartItem.item.name}"></td>
        <td th:text="${cartItem.item.origin}"></td>
        <td th:text="${cartItem.item.price}"></td>
        <td th:text="${cartItem.quantity}"></td>
        <td>
            <form th:method="delete" th:action="@{'/cart/item/delete/' + ${cartItem.item.id}}">
                <input type="submit" value="단일 삭제">
            </form>
        </td>
        <td>
            <form th:method="delete" th:action="@{'/cart/item/deleteAll/' + ${cartItem.item.id}}">
                <input type="submit" value="전체 삭제">
            </form>
        </td>
    </tr>
    </tbody>
</table>
</body>
</html>

 

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Item Register</title>
</head>
<body>
<h3>등록할 상품 정보를 입력해주세요.</h3>
<label for="name">상품명:</label>
<input type="text" id="name" name="name"><br><br>
<label for="origin">원산지:</label>
<input type="text" id="origin" name="origin"><br><br>
<label for="price">가격:</label>
<input type="number" id="price" name="price"><br><br>
<button style="background-color:blue; color:white;" onclick="register()">생성하기</button>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
    function register() {
        var name = $('#name').val();
        var origin = $('#origin').val();
        var price = $('#price').val();

        $.ajax({
            url: '/item',
            method: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                'name': name,
                'origin': origin,
                'price': price
            }),
            success: function(data) {
                alert('상품을 등록하였습니다.');
                window.location.href = '/';
            },
            error: function(data) {
                alert('상품 등록에 실패하였습니다.');
            }
        });
    }
</script>
</body>
</html>

 

 

728x90

'Spring' 카테고리의 다른 글

[Spring] Jacoco 적용하여 코드 커버리지 확인 및 Codecov사용한 PR에 커버리지 코멘트 추가  (0) 2023.05.27
[Spring] SpringBoot Prometheus, Grafana를 사용한 모니터링(Feat. Docker)  (0) 2023.05.25
[Spring] @RequestParam으로 Enum 타입 매개변수 받기  (0) 2023.05.15
[Spring] @RequestPart 사용하여 MultipartFile과 Dto 함께 받기, Postman 테스트  (0) 2023.05.14
[Spring] AWS S3 서비스를 사용하여 이미지 업로드  (0) 2023.05.09
    'Spring' 카테고리의 다른 글
    • [Spring] Jacoco 적용하여 코드 커버리지 확인 및 Codecov사용한 PR에 커버리지 코멘트 추가
    • [Spring] SpringBoot Prometheus, Grafana를 사용한 모니터링(Feat. Docker)
    • [Spring] @RequestParam으로 Enum 타입 매개변수 받기
    • [Spring] @RequestPart 사용하여 MultipartFile과 Dto 함께 받기, Postman 테스트
    Chef.Yeon
    Chef.Yeon
    보기 좋고 깔끔한 코드를 요리하기 위해 노력하고 있습니다.

    티스토리툴바