From 2de83069c68b703cf619aec70b32011aca05e5ca Mon Sep 17 00:00:00 2001
From: YEON <65826145+yyy96@users.noreply.github.com>
Date: Sun, 19 Mar 2023 18:38:55 +0900
Subject: [PATCH 01/12] Update README.md
---
README.md | 61 +++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 46 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 48216e3..8c5ad31 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,52 @@
-# 1주차
-----
+## Weekly Project Study
-## 줌근마켓 : 동네 직거래 시스템 구현 ( 2.24 ~ 3.8 ) [ Feat. 라영지 ]
+🚩 **매주 학습할 주제를 선정하며, 해당 주제를 학습하고 간단한 상황 구현을 통해 고민해봅니다.**
-Special Thanks to : Pir
+🙋🏻♀️ 주차별 프로젝트의 코드 리뷰까지 모두 마친 후, README가 업데이트 됩니다.
-### 요구사항
+
-1. 동네 정보, 상품 정보, 직거래 시간, 상품 거래 목록을 데이터 베이스에 저장합니다.
-2. 사용자는 동네와 직거래 시간을 선택 후 상품을 등록할 수 있습니다.
-3. 사용자는 원하는 상품을 선택하여 구매할 수 있습니다. 상품거래가 완료되는 동안 다른 사용자는 해당 상품을 구매할 수 없습니다.
-4. 사용자는 동일한 직거래 시간에 여러 상품을 구매할 수 없습니다.
-5. 사용자는 자신이 거래 또는 판매한 상품 목록을 조회할 수 있습니다.
+### 🗓 1주차 (2.24 ~ 3.8)
-### 구현해야 할 기능 리스트
+**✏️ 학습 목표**
+1. JPA 연관관계에 대해 다시 한번 학습
+2. 동적 쿼리가 필요한 경우를 생각해보고, QueryDSL을 통해 작성
-- 구매 또는 판매한 상품 조회
-- 전체 판매되고있는 상품 조회
-- 상품 구매 - 이미 동일한 거래 시간에 상품 구매 예정이 되어있다면 다른 상품은 구매가 불가
-- 상품 판매 - 상품을 등록, 수정, 삭제 할 수 있음
+**주제**
+- 줌근마켓 : 동네 직거래 시스템 구현
+
+**요구사항 (상황구현)**
+- 동네 정보, 상품 정보, 직거래 시간, 상품 거래 목록을 데이터 베이스에 저장합니다.
+- 사용자는 동네와 직거래 시간을 선택 후 상품을 등록할 수 있습니다.
+- 사용자는 원하는 상품을 선택하여 구매할 수 있습니다. 상품거래가 완료되는 동안 다른 사용자는 해당 상품을 구매할 수 없습니다.
+- 사용자는 동일한 직거래 시간에 여러 상품을 구매할 수 없습니다.
+- 사용자는 자신이 거래 또는 판매한 상품 목록을 조회할 수 있습니다.
+
+**참고**
+- [구현된 프로젝트 브랜치 → **querydsl**]()
+- [해당 프로젝트 팀원 코드리뷰](https://github.com/zum-spring-study/Daily-Project/pull/2)
+
+
+
+### 🗓 2주차 (3.10 ~ 3.22)
+
+**✏️ 학습 목표**
+1. 실제로 동시성이 발생하는 경우를 생각해보고, 해당 동시성을 예방하기 위한 적절한 방법들을 모색
+3. 여러 방법들을 비교하고 적절한 대안을 생각
+
+**주제**
+- 줌터파켓: 한국에 드디어 상륙한 캣츠! 티켓 1000장을 잡아라!
+
+**요구사항 (상황구현)**
+- 하나의 예매 서비스에 다량의 유저들이 접속한다는걸 가정합니다.
+- 유저들은 접속하는 환경이 다를겁니다 ( ex. 모바일 , 컴퓨터 ) 하나의 서버가 아닌 여러 서버를 가정합니다.
+- 뮤지컬의 경우 (날짜는 무관) 3일동안 1000장의 티켓이 발행됩니다.
+- 1000장의 수량이 떨어질 경우 1001번째 사람은 티켓을 구매할 수 없습니다.
+- 티켓을 이미 구매한 사람은 사제기의 요소를 방지하기 위해 더이상의 티켓을 구매할 순 없습니다.
+- 티켓을 구매했을 경우 당일 뿐만이 아닌 다른날도 구매가 불가합니다.
+
+**참고**
+- [구현된 프로젝트 브랜치 → **concurrency**]()
+- [해당 프로젝트 팀원 코드리뷰]()
+
+
From 3302658658828f60673f37be5bef89e5845f5942 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Mon, 20 Mar 2023 00:17:43 +0900
Subject: [PATCH 02/12] setting: update setting
---
...plication.java => WeeklyStudyProject.java} | 4 ++--
src/main/resources/application.yml | 19 +++++++++++++++++++
...ests.java => WeeklyStudyProjectTests.java} | 2 +-
3 files changed, 22 insertions(+), 3 deletions(-)
rename src/main/java/com/week/zumgnmarket/{ZumgnmarketApplication.java => WeeklyStudyProject.java} (68%)
rename src/test/java/com/week/zumgnmarket/{ZumgnmarketApplicationTests.java => WeeklyStudyProjectTests.java} (84%)
diff --git a/src/main/java/com/week/zumgnmarket/ZumgnmarketApplication.java b/src/main/java/com/week/zumgnmarket/WeeklyStudyProject.java
similarity index 68%
rename from src/main/java/com/week/zumgnmarket/ZumgnmarketApplication.java
rename to src/main/java/com/week/zumgnmarket/WeeklyStudyProject.java
index 87f7f1c..1ac5271 100644
--- a/src/main/java/com/week/zumgnmarket/ZumgnmarketApplication.java
+++ b/src/main/java/com/week/zumgnmarket/WeeklyStudyProject.java
@@ -4,10 +4,10 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
-public class ZumgnmarketApplication {
+public class WeeklyStudyProject {
public static void main(String[] args) {
- SpringApplication.run(ZumgnmarketApplication.class, args);
+ SpringApplication.run(WeeklyStudyProject.class, args);
}
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index e69de29..c61453e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -0,0 +1,19 @@
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/study?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimeZone=Asia/Seoul
+ username: study
+ password: study123
+ driver-class-name: com.mysql.cj.jdbc.Driver
+
+ jpa:
+ hibernate:
+ ddl-auto: create
+ properties:
+ hibernate:
+ show_sql: true
+ format_sql: true
+ use_sql_comments: true
+
+logging.level:
+ org.hibernate.SQL: debug
+ org.hibernate.type: trace
\ No newline at end of file
diff --git a/src/test/java/com/week/zumgnmarket/ZumgnmarketApplicationTests.java b/src/test/java/com/week/zumgnmarket/WeeklyStudyProjectTests.java
similarity index 84%
rename from src/test/java/com/week/zumgnmarket/ZumgnmarketApplicationTests.java
rename to src/test/java/com/week/zumgnmarket/WeeklyStudyProjectTests.java
index 2495be3..3d03c13 100644
--- a/src/test/java/com/week/zumgnmarket/ZumgnmarketApplicationTests.java
+++ b/src/test/java/com/week/zumgnmarket/WeeklyStudyProjectTests.java
@@ -4,7 +4,7 @@
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
-class ZumgnmarketApplicationTests {
+class WeeklyStudyProjectTests {
@Test
void contextLoads() {
From 792215bfc10ba51e6cad0eb941f92467344af905 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Mon, 20 Mar 2023 23:50:59 +0900
Subject: [PATCH 03/12] setting: update setting
---
.../zumgnmarket/common/config/JpaConfig.java | 9 +++++++++
.../common/config/QuerydslConfig.java | 20 +++++++++++++++++++
src/main/resources/application.yml | 2 +-
3 files changed, 30 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/com/week/zumgnmarket/common/config/JpaConfig.java
create mode 100644 src/main/java/com/week/zumgnmarket/common/config/QuerydslConfig.java
diff --git a/src/main/java/com/week/zumgnmarket/common/config/JpaConfig.java b/src/main/java/com/week/zumgnmarket/common/config/JpaConfig.java
new file mode 100644
index 0000000..dc1ca51
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/common/config/JpaConfig.java
@@ -0,0 +1,9 @@
+package com.week.zumgnmarket.common.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+@EnableJpaAuditing
+@Configuration
+public class JpaConfig {
+}
diff --git a/src/main/java/com/week/zumgnmarket/common/config/QuerydslConfig.java b/src/main/java/com/week/zumgnmarket/common/config/QuerydslConfig.java
new file mode 100644
index 0000000..17ebfab
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/common/config/QuerydslConfig.java
@@ -0,0 +1,20 @@
+package com.week.zumgnmarket.common.config;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+
+@Configuration
+public class QuerydslConfig {
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Bean
+ public JPAQueryFactory jpaQueryFactory() {
+ return new JPAQueryFactory(entityManager);
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index c61453e..153d1a3 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -2,7 +2,7 @@ spring:
datasource:
url: jdbc:mysql://localhost:3306/study?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimeZone=Asia/Seoul
username: study
- password: study123
+ password: study123!@#
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
From 010ae3ba5aace3a27e0d3a49e87f76deb99b5661 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Mon, 20 Mar 2023 23:51:25 +0900
Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?=
=?UTF-8?q?=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EB=A7=A4=ED=95=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../zumgnmarket/common/domain/BaseEntity.java | 23 +++++++++
.../week/zumgnmarket/order/entity/Order.java | 50 +++++++++++++++++++
.../order/entity/OrderRepository.java | 6 +++
.../week/zumgnmarket/order/entity/Orders.java | 18 +++++++
.../zumgnmarket/ticket/entity/Ticket.java | 44 ++++++++++++++++
.../ticket/entity/TicketRepository.java | 6 +++
.../week/zumgnmarket/user/entity/User.java | 33 ++++++++++++
.../zumgnmarket/user/entity/UserLogin.java | 27 ++++++++++
.../user/entity/UserRepository.java | 6 +++
.../order/entity/OrderRepositoryTest.java | 47 +++++++++++++++++
10 files changed, 260 insertions(+)
create mode 100644 src/main/java/com/week/zumgnmarket/common/domain/BaseEntity.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/entity/Order.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/entity/Orders.java
create mode 100644 src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
create mode 100644 src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java
create mode 100644 src/main/java/com/week/zumgnmarket/user/entity/User.java
create mode 100644 src/main/java/com/week/zumgnmarket/user/entity/UserLogin.java
create mode 100644 src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java
create mode 100644 src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java
diff --git a/src/main/java/com/week/zumgnmarket/common/domain/BaseEntity.java b/src/main/java/com/week/zumgnmarket/common/domain/BaseEntity.java
new file mode 100644
index 0000000..1a10e52
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/common/domain/BaseEntity.java
@@ -0,0 +1,23 @@
+package com.week.zumgnmarket.common.domain;
+import java.time.LocalDateTime;
+
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import lombok.Getter;
+
+@Getter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public class BaseEntity {
+ @CreatedDate
+ private LocalDateTime createdDate;
+
+ @LastModifiedDate
+ private LocalDateTime updatedDate;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/week/zumgnmarket/order/entity/Order.java b/src/main/java/com/week/zumgnmarket/order/entity/Order.java
new file mode 100644
index 0000000..903ce62
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/entity/Order.java
@@ -0,0 +1,50 @@
+package com.week.zumgnmarket.order.entity;
+
+import static javax.persistence.FetchType.*;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import com.week.zumgnmarket.common.domain.BaseEntity;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Entity
+@Getter
+@ToString
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Table(name = "orders")
+public class Order extends BaseEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = LAZY)
+ @JoinColumn(name = "user_id")
+ private User user;
+
+ @ManyToOne(fetch = LAZY)
+ @JoinColumn(name = "ticket_id")
+ private Ticket ticket;
+
+ private int ticketCount;
+
+ @Builder
+ public Order(User user, Ticket ticket, int ticketCount) {
+ this.user = user;
+ this.ticket = ticket;
+ this.ticketCount = ticketCount;
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java b/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java
new file mode 100644
index 0000000..8c7b95a
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java
@@ -0,0 +1,6 @@
+package com.week.zumgnmarket.order.entity;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface OrderRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/entity/Orders.java b/src/main/java/com/week/zumgnmarket/order/entity/Orders.java
new file mode 100644
index 0000000..3d84316
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/entity/Orders.java
@@ -0,0 +1,18 @@
+package com.week.zumgnmarket.order.entity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Embeddable;
+import javax.persistence.OneToMany;
+
+import lombok.Getter;
+
+@Getter
+@Embeddable
+public class Orders {
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
+ List orders = new ArrayList<>();
+
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
new file mode 100644
index 0000000..be2840c
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
@@ -0,0 +1,44 @@
+package com.week.zumgnmarket.ticket.entity;
+
+import static javax.persistence.FetchType.*;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import com.week.zumgnmarket.common.domain.BaseEntity;
+import com.week.zumgnmarket.order.entity.Order;
+
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Table(name = "tickets")
+public class Ticket extends BaseEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+
+ private int price;
+
+ private int remainingCount;
+
+ @Builder
+ public Ticket(String name, int price, int remainingCount) {
+ this.name = name;
+ this.price = price;
+ this.remainingCount = remainingCount;
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java
new file mode 100644
index 0000000..4ea86d6
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java
@@ -0,0 +1,6 @@
+package com.week.zumgnmarket.ticket.entity;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TicketRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/week/zumgnmarket/user/entity/User.java b/src/main/java/com/week/zumgnmarket/user/entity/User.java
new file mode 100644
index 0000000..40b02f7
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/user/entity/User.java
@@ -0,0 +1,33 @@
+package com.week.zumgnmarket.user.entity;
+
+import javax.persistence.*;
+
+import com.week.zumgnmarket.common.domain.BaseEntity;
+import com.week.zumgnmarket.order.entity.Orders;
+
+import lombok.*;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Table(name = "users")
+public class User extends BaseEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+
+ @Embedded
+ private UserLogin userLogin;
+
+ @Embedded
+ private Orders orders;
+
+ @Builder
+ public User(String name, String email, String password) {
+ this.name = name;
+ this.userLogin = new UserLogin(email, password);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/week/zumgnmarket/user/entity/UserLogin.java b/src/main/java/com/week/zumgnmarket/user/entity/UserLogin.java
new file mode 100644
index 0000000..53811a2
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/user/entity/UserLogin.java
@@ -0,0 +1,27 @@
+package com.week.zumgnmarket.user.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@Embeddable
+@EqualsAndHashCode
+@NoArgsConstructor
+public class UserLogin {
+
+ @Column(length = 50, nullable = false)
+ private String email;
+
+ @Column(length = 20, nullable = false)
+ private String password;
+
+ public UserLogin(String email, String password) {
+ this.email = email;
+ this.password = password;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java b/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java
new file mode 100644
index 0000000..8267446
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java
@@ -0,0 +1,6 @@
+package com.week.zumgnmarket.user.entity;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserRepository extends JpaRepository {
+}
diff --git a/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java b/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java
new file mode 100644
index 0000000..9a146b4
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java
@@ -0,0 +1,47 @@
+package com.week.zumgnmarket.order.entity;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+@DataJpaTest
+public class OrderRepositoryTest {
+
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TicketRepository ticketRepository;
+ @Autowired
+ private OrderRepository orderRepository;
+
+ User 회원;
+ Ticket 티켓;
+
+ @BeforeEach
+ void setup() {
+ 회원 = new User("kim", "email@email.com", "pwd");
+ 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userRepository.save(회원);
+ ticketRepository.save(티켓);
+ }
+
+ @Test
+ void 연관관계_매핑() {
+ //given
+ Order 주문 = new Order(회원, 티켓, 1);
+
+ //when
+ Order result = orderRepository.save(주문);
+
+ //then
+ assertEquals(result.getTicket(), 티켓);
+ }
+}
From 428f6a41e5b4422b717410cf756b01e9d4969cec Mon Sep 17 00:00:00 2001
From: yyy96
Date: Wed, 22 Mar 2023 20:13:24 +0900
Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=ED=8B=B0=EC=BC=93=20=EC=A3=BC?=
=?UTF-8?q?=EB=AC=B8=20=EA=B8=B0=EB=B3=B8=20API=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../order/controller/OrderController.java | 26 ++++++
.../order/controller/OrderFacade.java | 31 +++++++
.../zumgnmarket/order/dto/OrderRequest.java | 11 +++
.../zumgnmarket/order/dto/OrderResponse.java | 33 ++++++++
.../week/zumgnmarket/order/entity/Order.java | 10 ++-
.../order/entity/OrderRepository.java | 2 +-
.../week/zumgnmarket/order/entity/Orders.java | 2 +-
.../order/service/OrderService.java | 9 +++
.../order/service/OrderServiceImpl.java | 25 ++++++
.../zumgnmarket/ticket/entity/Ticket.java | 24 +++---
.../ticket/entity/TicketRepository.java | 2 +-
.../ticket/service/TicketService.java | 22 +++++
.../user/entity/UserRepository.java | 2 +-
.../zumgnmarket/user/service/UserService.java | 22 +++++
.../order/entity/OrderRepositoryTest.java | 2 +-
.../order/service/OrderServiceTest.java | 80 +++++++++++++++++++
16 files changed, 287 insertions(+), 16 deletions(-)
create mode 100644 src/main/java/com/week/zumgnmarket/order/controller/OrderController.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/service/OrderService.java
create mode 100644 src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java
create mode 100644 src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
create mode 100644 src/main/java/com/week/zumgnmarket/user/service/UserService.java
create mode 100644 src/test/java/com/week/zumgnmarket/order/service/OrderServiceTest.java
diff --git a/src/main/java/com/week/zumgnmarket/order/controller/OrderController.java b/src/main/java/com/week/zumgnmarket/order/controller/OrderController.java
new file mode 100644
index 0000000..b11638a
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/controller/OrderController.java
@@ -0,0 +1,26 @@
+package com.week.zumgnmarket.order.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.week.zumgnmarket.order.dto.OrderRequest;
+import com.week.zumgnmarket.order.dto.OrderResponse;
+
+import lombok.RequiredArgsConstructor;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/orders")
+public class OrderController {
+
+ private final OrderFacade orderFacade;
+
+ @PostMapping("/tickets")
+ public ResponseEntity orderTickets(@RequestBody OrderRequest request) {
+ return ResponseEntity.ok(orderFacade.order(request));
+ }
+
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
new file mode 100644
index 0000000..73d695a
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
@@ -0,0 +1,31 @@
+package com.week.zumgnmarket.order.controller;
+
+import org.springframework.stereotype.Service;
+
+import com.week.zumgnmarket.order.dto.OrderRequest;
+import com.week.zumgnmarket.order.dto.OrderResponse;
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.order.service.OrderService;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.service.TicketService;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.service.UserService;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class OrderFacade {
+
+ private final UserService userService;
+ private final TicketService ticketService;
+ private final OrderService orderService;
+
+ public OrderResponse order(OrderRequest request) {
+ User user = userService.getUser(request.getUserId());
+ Ticket ticket = ticketService.getTicket(request.getTicketId());
+ Order order = orderService.orderTicket(user, ticket, request.getQuantity());
+
+ return OrderResponse.of(order, user, ticket);
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java b/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java
new file mode 100644
index 0000000..38ebd0b
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java
@@ -0,0 +1,11 @@
+package com.week.zumgnmarket.order.dto;
+
+import lombok.Getter;
+
+@Getter
+public class OrderRequest {
+ public Long userId;
+ public Long ticketId;
+ public int quantity;
+
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java b/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java
new file mode 100644
index 0000000..003a599
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java
@@ -0,0 +1,33 @@
+package com.week.zumgnmarket.order.dto;
+
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+public class OrderResponse {
+ public Long orderId;
+ public Long userId;
+ public Long ticketId;
+ public int quantity;
+
+ @Builder
+ public OrderResponse(Long orderId, Long userId, Long ticketId, int quantity) {
+ this.orderId = orderId;
+ this.userId = userId;
+ this.ticketId = ticketId;
+ this.quantity = quantity;
+ }
+
+ public static OrderResponse of(Order order, User user, Ticket ticket) {
+ return OrderResponse.builder()
+ .orderId(order.getId())
+ .userId(user.getId())
+ .ticketId(ticket.getId())
+ .quantity(order.getTicketCount())
+ .build();
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/entity/Order.java b/src/main/java/com/week/zumgnmarket/order/entity/Order.java
index 903ce62..88924eb 100644
--- a/src/main/java/com/week/zumgnmarket/order/entity/Order.java
+++ b/src/main/java/com/week/zumgnmarket/order/entity/Order.java
@@ -42,9 +42,17 @@ public class Order extends BaseEntity {
private int ticketCount;
@Builder
- public Order(User user, Ticket ticket, int ticketCount) {
+ private Order(User user, Ticket ticket, int ticketCount) {
this.user = user;
this.ticket = ticket;
this.ticketCount = ticketCount;
}
+
+ public static Order of(User user, Ticket ticket, int ticketCount) {
+ return Order.builder()
+ .user(user)
+ .ticket(ticket)
+ .ticketCount(ticketCount)
+ .build();
+ }
}
diff --git a/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java b/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java
index 8c7b95a..c049604 100644
--- a/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java
+++ b/src/main/java/com/week/zumgnmarket/order/entity/OrderRepository.java
@@ -2,5 +2,5 @@
import org.springframework.data.jpa.repository.JpaRepository;
-public interface OrderRepository extends JpaRepository {
+public interface OrderRepository extends JpaRepository {
}
diff --git a/src/main/java/com/week/zumgnmarket/order/entity/Orders.java b/src/main/java/com/week/zumgnmarket/order/entity/Orders.java
index 3d84316..d501a89 100644
--- a/src/main/java/com/week/zumgnmarket/order/entity/Orders.java
+++ b/src/main/java/com/week/zumgnmarket/order/entity/Orders.java
@@ -12,7 +12,7 @@
@Getter
@Embeddable
public class Orders {
- @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
List orders = new ArrayList<>();
}
diff --git a/src/main/java/com/week/zumgnmarket/order/service/OrderService.java b/src/main/java/com/week/zumgnmarket/order/service/OrderService.java
new file mode 100644
index 0000000..e2b16d2
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/service/OrderService.java
@@ -0,0 +1,9 @@
+package com.week.zumgnmarket.order.service;
+
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+public interface OrderService {
+ Order orderTicket(User user, Ticket ticket, int quantity);
+}
diff --git a/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java b/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java
new file mode 100644
index 0000000..a5c2159
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java
@@ -0,0 +1,25 @@
+package com.week.zumgnmarket.order.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.order.entity.OrderRepository;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class OrderServiceImpl implements OrderService {
+
+ private final OrderRepository orderRepository;
+
+ @Override
+ public Order orderTicket(User user, Ticket ticket, int quantity) {
+ ticket.decreaseQuantity(quantity);
+ return orderRepository.save(Order.of(user, ticket, quantity));
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
index be2840c..0871884 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
@@ -1,19 +1,12 @@
package com.week.zumgnmarket.ticket.entity;
-import static javax.persistence.FetchType.*;
-
-import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.OneToMany;
import javax.persistence.Table;
import com.week.zumgnmarket.common.domain.BaseEntity;
-import com.week.zumgnmarket.order.entity.Order;
import lombok.AccessLevel;
import lombok.Builder;
@@ -33,12 +26,23 @@ public class Ticket extends BaseEntity {
private int price;
- private int remainingCount;
+ private int quantity;
@Builder
- public Ticket(String name, int price, int remainingCount) {
+ public Ticket(String name, int price, int quantity) {
this.name = name;
this.price = price;
- this.remainingCount = remainingCount;
+ this.quantity = quantity;
+ }
+
+ public void decreaseQuantity(int orderQuantity) {
+ checkQuantity(orderQuantity);
+ this.quantity -= orderQuantity;
+ }
+
+ private void checkQuantity(int orderQuantity) {
+ if (quantity == 0 || quantity < orderQuantity) {
+ throw new RuntimeException();
+ }
}
}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java
index 4ea86d6..94a1a8a 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketRepository.java
@@ -2,5 +2,5 @@
import org.springframework.data.jpa.repository.JpaRepository;
-public interface TicketRepository extends JpaRepository {
+public interface TicketRepository extends JpaRepository {
}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
new file mode 100644
index 0000000..7ca7c92
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
@@ -0,0 +1,22 @@
+package com.week.zumgnmarket.ticket.service;
+
+import javax.persistence.EntityNotFoundException;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+
+@Service
+@Transactional
+public class TicketService {
+
+ private TicketRepository ticketRepository;
+
+ @Transactional(readOnly = true)
+ public Ticket getTicket(Long id) {
+ return ticketRepository.findById(id)
+ .orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java b/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java
index 8267446..26429cf 100644
--- a/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java
+++ b/src/main/java/com/week/zumgnmarket/user/entity/UserRepository.java
@@ -2,5 +2,5 @@
import org.springframework.data.jpa.repository.JpaRepository;
-public interface UserRepository extends JpaRepository {
+public interface UserRepository extends JpaRepository {
}
diff --git a/src/main/java/com/week/zumgnmarket/user/service/UserService.java b/src/main/java/com/week/zumgnmarket/user/service/UserService.java
new file mode 100644
index 0000000..431a4ed
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/user/service/UserService.java
@@ -0,0 +1,22 @@
+package com.week.zumgnmarket.user.service;
+
+import javax.persistence.EntityNotFoundException;
+
+import org.springframework.stereotype.Service;
+
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class UserService {
+
+ private final UserRepository userRepository;
+
+ public User getUser(Long id) {
+ return userRepository.findById(id)
+ .orElseThrow(() -> new EntityNotFoundException("User not found with id: " + id));
+ }
+}
diff --git a/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java b/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java
index 9a146b4..92073f4 100644
--- a/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/entity/OrderRepositoryTest.java
@@ -36,7 +36,7 @@ void setup() {
@Test
void 연관관계_매핑() {
//given
- Order 주문 = new Order(회원, 티켓, 1);
+ Order 주문 = Order.of(회원, 티켓, 1);
//when
Order result = orderRepository.save(주문);
diff --git a/src/test/java/com/week/zumgnmarket/order/service/OrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/OrderServiceTest.java
new file mode 100644
index 0000000..f8dbda2
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/service/OrderServiceTest.java
@@ -0,0 +1,80 @@
+package com.week.zumgnmarket.order.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.jupiter.api.AfterEach;
+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.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+@SpringBootTest
+public class OrderServiceTest {
+
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TicketRepository ticketRepository;
+ @Autowired
+ private OrderService orderService;
+
+ private User 회원;
+ private Ticket 티켓;
+ private final int thread = 1000;
+
+ @BeforeEach
+ void setup() {
+ 회원 = new User("kim", "email@email.com", "pwd");
+ 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userRepository.save(회원);
+ ticketRepository.save(티켓);
+ }
+
+ @AfterEach
+ void after() {
+ userRepository.deleteAll();
+ ticketRepository.deleteAll();
+ }
+
+ @Test
+ void 티켓_주문() {
+ //given
+ int orderQuantity = 2;
+ int remainingQuantity = 티켓.getQuantity();
+
+ // when
+ Order 주문 = orderService.orderTicket(회원, 티켓, orderQuantity);
+
+ //then
+ assertEquals(티켓.getQuantity(), remainingQuantity - orderQuantity);
+ }
+
+ @Test
+ void 동시에_티켓_주문_실패() throws InterruptedException {
+ //given
+ ExecutorService executorService = Executors.newFixedThreadPool(123);
+ CountDownLatch countDownLatch = new CountDownLatch(thread);
+
+ //when
+ for (int i = 0; i < thread; i++) {
+ executorService.execute(() -> {
+ orderService.orderTicket(회원, 티켓, 1);
+ countDownLatch.countDown();
+ });
+ }
+ countDownLatch.await();
+
+ //then
+ assertNotEquals(0, 티켓.getQuantity());
+ }
+}
From f7b264a89acfa86dfe634019ec2457afb26c19bd Mon Sep 17 00:00:00 2001
From: yyy96
Date: Wed, 22 Mar 2023 20:39:00 +0900
Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=ED=8B=B0=EC=BC=93=20=EC=A3=BC?=
=?UTF-8?q?=EB=AC=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20synchronized=20?=
=?UTF-8?q?=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../order/service/OrderServiceImpl.java | 2 +
.../service/SynchronizedOrderService.java | 23 ++++++
.../service/SynchronizedOrderServiceTest.java | 72 +++++++++++++++++++
3 files changed, 97 insertions(+)
create mode 100644 src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java
create mode 100644 src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java
diff --git a/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java b/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java
index a5c2159..5106dba 100644
--- a/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java
+++ b/src/main/java/com/week/zumgnmarket/order/service/OrderServiceImpl.java
@@ -1,5 +1,6 @@
package com.week.zumgnmarket.order.service;
+import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -10,6 +11,7 @@
import lombok.RequiredArgsConstructor;
+@Primary
@Service
@Transactional
@RequiredArgsConstructor
diff --git a/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java b/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java
new file mode 100644
index 0000000..ebc906b
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java
@@ -0,0 +1,23 @@
+package com.week.zumgnmarket.order.service;
+
+import org.springframework.stereotype.Service;
+
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.order.entity.OrderRepository;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class SynchronizedOrderService implements OrderService {
+
+ private final OrderRepository orderRepository;
+
+ @Override
+ public synchronized Order orderTicket(User user, Ticket ticket, int quantity) {
+ ticket.decreaseQuantity(quantity);
+ return orderRepository.save(Order.of(user, ticket, quantity));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java
new file mode 100644
index 0000000..33edd5f
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java
@@ -0,0 +1,72 @@
+package com.week.zumgnmarket.order.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.jupiter.api.AfterEach;
+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.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+@SpringBootTest
+public class SynchronizedOrderServiceTest {
+
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TicketRepository ticketRepository;
+ @Autowired
+ private SynchronizedOrderService orderService;
+
+ private User 회원;
+ private Ticket 티켓;
+ private final int thread = 1000;
+
+ @BeforeEach
+ void setup() {
+ 회원 = new User("kim", "email@email.com", "pwd");
+ 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userRepository.save(회원);
+ ticketRepository.save(티켓);
+ }
+
+ @AfterEach
+ void after() {
+ userRepository.deleteAll();
+ ticketRepository.deleteAll();
+ }
+
+ /**
+ * 문제)
+ * 서버가 한 대일 경우 문제가 없지만
+ * synchronized 는 각 프로세스 안에서만 보장이 되기 때문에
+ * 여러 서버 스레드에서 접근을 하게 된다면 race condition 이 발생할 수 있습니다.
+ * */
+ @Test
+ void 동시에_티켓_주문() throws InterruptedException {
+ //given
+ ExecutorService executorService = Executors.newFixedThreadPool(123);
+ CountDownLatch countDownLatch = new CountDownLatch(thread);
+
+ //when
+ for (int i = 0; i < thread; i++) {
+ executorService.execute(() -> {
+ orderService.orderTicket(회원, 티켓, 1);
+ countDownLatch.countDown();
+ });
+ }
+ countDownLatch.await();
+
+ //then
+ assertEquals(0, 티켓.getQuantity());
+ }
+}
From 29ff99d4ba34a7ae7993d2cc62bbc3729e94a543 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Wed, 22 Mar 2023 23:08:42 +0900
Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D,exception?=
=?UTF-8?q?=20=EB=93=B1=20=EA=B8=B0=ED=83=80=20=EB=A6=AC=ED=8C=A9=ED=84=B0?=
=?UTF-8?q?=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../order/controller/OrderFacade.java | 4 ++--
.../service/SynchronizedOrderService.java | 7 +++++++
.../zumgnmarket/ticket/entity/Ticket.java | 3 ++-
.../exception/NotEnoughTicketException.java | 21 +++++++++++++++++++
4 files changed, 32 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/com/week/zumgnmarket/ticket/exception/NotEnoughTicketException.java
diff --git a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
index 73d695a..4f5e5d7 100644
--- a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
+++ b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
@@ -1,6 +1,6 @@
package com.week.zumgnmarket.order.controller;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
import com.week.zumgnmarket.order.dto.OrderRequest;
import com.week.zumgnmarket.order.dto.OrderResponse;
@@ -13,7 +13,7 @@
import lombok.RequiredArgsConstructor;
-@Service
+@Component
@RequiredArgsConstructor
public class OrderFacade {
diff --git a/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java b/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java
index ebc906b..e00c2a1 100644
--- a/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java
+++ b/src/main/java/com/week/zumgnmarket/order/service/SynchronizedOrderService.java
@@ -9,6 +9,13 @@
import lombok.RequiredArgsConstructor;
+/**
+ * synchronized
+ * 문제) 서버가 한 대일 경우 문제가 없지만
+ * synchronized 는 각 프로세스 안에서만 보장이 되기 때문에
+ * 여러 서버 스레드에서 접근을 하게 된다면 race condition 이 발생할 수 있다.
+ * */
+
@Service
@RequiredArgsConstructor
public class SynchronizedOrderService implements OrderService {
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
index 0871884..d573dfd 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
@@ -7,6 +7,7 @@
import javax.persistence.Table;
import com.week.zumgnmarket.common.domain.BaseEntity;
+import com.week.zumgnmarket.ticket.exception.NotEnoughTicketException;
import lombok.AccessLevel;
import lombok.Builder;
@@ -42,7 +43,7 @@ public void decreaseQuantity(int orderQuantity) {
private void checkQuantity(int orderQuantity) {
if (quantity == 0 || quantity < orderQuantity) {
- throw new RuntimeException();
+ throw new NotEnoughTicketException();
}
}
}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/exception/NotEnoughTicketException.java b/src/main/java/com/week/zumgnmarket/ticket/exception/NotEnoughTicketException.java
new file mode 100644
index 0000000..09445c7
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/ticket/exception/NotEnoughTicketException.java
@@ -0,0 +1,21 @@
+package com.week.zumgnmarket.ticket.exception;
+
+public class NotEnoughTicketException extends RuntimeException {
+
+ public NotEnoughTicketException() {
+ super();
+ }
+
+ public NotEnoughTicketException(String message) {
+ super(message);
+ }
+
+ public NotEnoughTicketException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NotEnoughTicketException(Throwable cause) {
+ super(cause);
+ }
+
+}
From e94afc91313aa8d224513cf9c92b6c4bf53eba2e Mon Sep 17 00:00:00 2001
From: yyy96
Date: Wed, 22 Mar 2023 23:13:30 +0900
Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=ED=8B=B0=EC=BC=93=20=EC=A3=BC?=
=?UTF-8?q?=EB=AC=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20PessimisticLock=20?=
=?UTF-8?q?=EC=A0=81=EC=9A=A9=20(error)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../service/PessimisticLockOrderService.java | 32 +++++++++
.../entity/TicketQueryDslRepository.java | 29 ++++++++
.../ticket/service/TicketService.java | 14 +++-
.../PessimisticLockOrderServiceTest.java | 70 +++++++++++++++++++
.../service/SynchronizedOrderServiceTest.java | 6 --
5 files changed, 144 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/com/week/zumgnmarket/order/service/PessimisticLockOrderService.java
create mode 100644 src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java
create mode 100644 src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
diff --git a/src/main/java/com/week/zumgnmarket/order/service/PessimisticLockOrderService.java b/src/main/java/com/week/zumgnmarket/order/service/PessimisticLockOrderService.java
new file mode 100644
index 0000000..b2b5787
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/service/PessimisticLockOrderService.java
@@ -0,0 +1,32 @@
+package com.week.zumgnmarket.order.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.order.entity.OrderRepository;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Pessimistic Lock
+ * 실제로 데이터에 Lock 을 걸어서 정합성을 맞추는 방법.
+ * Lock 이 해제되기 전에는 다른 트랜잭션에서 데이터를 가져갈 수 없다.
+ * */
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class PessimisticLockOrderService implements OrderService {
+
+ private final OrderRepository orderRepository;
+
+ @Override
+ public Order orderTicket(User user, Ticket ticket, int quantity) {
+ ticket.decreaseQuantity(quantity);
+ return orderRepository.save(Order.of(user, ticket, quantity));
+ }
+
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java
new file mode 100644
index 0000000..0a23afd
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java
@@ -0,0 +1,29 @@
+package com.week.zumgnmarket.ticket.entity;
+
+import static com.week.zumgnmarket.ticket.entity.QTicket.*;
+
+import java.util.Optional;
+
+import javax.persistence.LockModeType;
+
+import org.springframework.data.jpa.repository.Lock;
+import org.springframework.stereotype.Repository;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+
+import lombok.RequiredArgsConstructor;
+
+@Repository
+@RequiredArgsConstructor
+public class TicketQueryDslRepository {
+
+ private final JPAQueryFactory queryFactory;
+
+ @Lock(value = LockModeType.PESSIMISTIC_WRITE)
+ public Optional findByIdWithLock(Long id) {
+ return Optional.ofNullable(queryFactory.selectFrom(ticket)
+ .where(ticket.id.eq(id))
+ .fetchOne());
+ }
+
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
index 7ca7c92..b9fb7d9 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
@@ -6,17 +6,29 @@
import org.springframework.transaction.annotation.Transactional;
import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketQueryDslRepository;
import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import lombok.RequiredArgsConstructor;
+
@Service
@Transactional
+@RequiredArgsConstructor
public class TicketService {
- private TicketRepository ticketRepository;
+ private final TicketRepository ticketRepository;
+
+ private final TicketQueryDslRepository ticketQueryDslRepository;
@Transactional(readOnly = true)
public Ticket getTicket(Long id) {
return ticketRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
}
+
+ @Transactional(readOnly = true)
+ public Ticket getTicketWithLock(Long id) {
+ return ticketQueryDslRepository.findByIdWithLock(id)
+ .orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
+ }
}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
new file mode 100644
index 0000000..79d3797
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
@@ -0,0 +1,70 @@
+package com.week.zumgnmarket.order.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.jupiter.api.AfterEach;
+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.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.ticket.service.TicketService;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+@SpringBootTest
+public class PessimisticLockOrderServiceTest {
+
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TicketRepository ticketRepository;
+ @Autowired
+ private TicketService ticketService;
+ @Autowired
+ private PessimisticLockOrderService orderService;
+
+ private User 회원;
+ private Ticket 티켓;
+ private final int thread = 1000;
+
+ @BeforeEach
+ void setup() {
+ 회원 = new User("kim", "email@email.com", "pwd");
+ 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userRepository.save(회원);
+ ticketRepository.save(티켓);
+ }
+
+ @AfterEach
+ void after() {
+ userRepository.deleteAll();
+ ticketRepository.deleteAll();
+ }
+
+ @Test
+ void 동시에_티켓_주문() throws InterruptedException {
+ //given
+ ExecutorService executorService = Executors.newFixedThreadPool(123);
+ CountDownLatch countDownLatch = new CountDownLatch(thread);
+
+ //when
+ for (int i = 0; i < thread; i++) {
+ executorService.execute(() -> {
+ //TODO: 미해결 - 변경감지가 일어나지 않는 이슈
+ orderService.orderTicket(회원, ticketService.getTicketWithLock(티켓.getId()), 1);
+ countDownLatch.countDown();
+ });
+ }
+ countDownLatch.await();
+
+ //then
+ assertEquals(0, 티켓.getQuantity());
+ }
+}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java
index 33edd5f..29206ab 100644
--- a/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/service/SynchronizedOrderServiceTest.java
@@ -45,12 +45,6 @@ void after() {
ticketRepository.deleteAll();
}
- /**
- * 문제)
- * 서버가 한 대일 경우 문제가 없지만
- * synchronized 는 각 프로세스 안에서만 보장이 되기 때문에
- * 여러 서버 스레드에서 접근을 하게 된다면 race condition 이 발생할 수 있습니다.
- * */
@Test
void 동시에_티켓_주문() throws InterruptedException {
//given
From 4e70400c8eeeba99b8e9ddd43a009e17eb958879 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Wed, 22 Mar 2023 23:57:29 +0900
Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=ED=8B=B0=EC=BC=93=20=EC=A3=BC?=
=?UTF-8?q?=EB=AC=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20OptimisticLock=20?=
=?UTF-8?q?=EC=A0=81=EC=9A=A9=20(error)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../order/controller/OrderFacade.java | 23 ++++++
.../zumgnmarket/order/dto/OrderRequest.java | 5 ++
.../service/OptimisticLockOrderService.java | 27 +++++++
.../zumgnmarket/ticket/entity/Ticket.java | 7 ++
.../entity/TicketQueryDslRepository.java | 9 ++-
.../ticket/service/TicketService.java | 10 ++-
.../OptimisticLockOrderServiceTest.java | 73 +++++++++++++++++++
.../PessimisticLockOrderServiceTest.java | 2 +-
8 files changed, 152 insertions(+), 4 deletions(-)
create mode 100644 src/main/java/com/week/zumgnmarket/order/service/OptimisticLockOrderService.java
create mode 100644 src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
diff --git a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
index 4f5e5d7..3096233 100644
--- a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
+++ b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
@@ -28,4 +28,27 @@ public OrderResponse order(OrderRequest request) {
return OrderResponse.of(order, user, ticket);
}
+
+ public OrderResponse orderWithPLock(OrderRequest request) {
+ User user = userService.getUser(request.getUserId());
+ Ticket ticket = ticketService.getTicketWithPessimisticLock(request.getTicketId());
+ Order order = orderService.orderTicket(user, ticket, request.getQuantity());
+
+ return OrderResponse.of(order, user, ticket);
+ }
+
+ public OrderResponse orderWithOLock(OrderRequest request) throws InterruptedException {
+ User user = userService.getUser(request.getUserId());
+
+ //OptimisticLock 같은 경우 실패했을 때 재시도를 위해 while 을 사용하였습니다.
+ while (true) {
+ try {
+ Ticket ticket = ticketService.getTicketWithOptimisticLock(request.getTicketId());
+ Order order = orderService.orderTicket(user, ticket, request.getQuantity());
+ return OrderResponse.of(order, user, ticket);
+ } catch (Exception e) {
+ Thread.sleep(10);
+ }
+ }
+ }
}
diff --git a/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java b/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java
index 38ebd0b..0089a68 100644
--- a/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java
+++ b/src/main/java/com/week/zumgnmarket/order/dto/OrderRequest.java
@@ -8,4 +8,9 @@ public class OrderRequest {
public Long ticketId;
public int quantity;
+ public OrderRequest(Long userId, Long ticketId, int quantity) {
+ this.userId = userId;
+ this.ticketId = ticketId;
+ this.quantity = quantity;
+ }
}
diff --git a/src/main/java/com/week/zumgnmarket/order/service/OptimisticLockOrderService.java b/src/main/java/com/week/zumgnmarket/order/service/OptimisticLockOrderService.java
new file mode 100644
index 0000000..7d8f856
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/order/service/OptimisticLockOrderService.java
@@ -0,0 +1,27 @@
+package com.week.zumgnmarket.order.service;
+
+import org.springframework.stereotype.Service;
+
+import com.week.zumgnmarket.order.entity.Order;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.user.entity.User;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Optimistic Lock
+ * Lock 이 아니라 Version 을 통해 정합성을 맞추는 방법.
+ * 처음에 현재 데이터의 version 을 읽고,
+ * 이후 update 하는 시점에서 처음 읽은 버전과 동일한지 확인 후 update 수행.
+ * */
+
+@Service
+@RequiredArgsConstructor
+public class OptimisticLockOrderService implements OrderService{
+
+ @Override
+ public Order orderTicket(User user, Ticket ticket, int quantity) {
+ ticket.decreaseQuantity(quantity);
+ return Order.of(user, ticket, quantity);
+ }
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
index d573dfd..5a03eda 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/Ticket.java
@@ -5,18 +5,21 @@
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
+import javax.persistence.Version;
import com.week.zumgnmarket.common.domain.BaseEntity;
import com.week.zumgnmarket.ticket.exception.NotEnoughTicketException;
import lombok.AccessLevel;
import lombok.Builder;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
@Table(name = "tickets")
public class Ticket extends BaseEntity {
@Id
@@ -29,6 +32,10 @@ public class Ticket extends BaseEntity {
private int quantity;
+ //Optimistic Lock Version
+ @Version
+ private Long version;
+
@Builder
public Ticket(String name, int price, int quantity) {
this.name = name;
diff --git a/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java
index 0a23afd..d9b8ac2 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/entity/TicketQueryDslRepository.java
@@ -20,7 +20,14 @@ public class TicketQueryDslRepository {
private final JPAQueryFactory queryFactory;
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
- public Optional findByIdWithLock(Long id) {
+ public Optional findByIdWithPessimisticLock(Long id) {
+ return Optional.ofNullable(queryFactory.selectFrom(ticket)
+ .where(ticket.id.eq(id))
+ .fetchOne());
+ }
+
+ @Lock(value = LockModeType.OPTIMISTIC)
+ public Optional findByIdWithOptimisticLock(Long id) {
return Optional.ofNullable(queryFactory.selectFrom(ticket)
.where(ticket.id.eq(id))
.fetchOne());
diff --git a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
index b9fb7d9..4deff27 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
@@ -27,8 +27,14 @@ public Ticket getTicket(Long id) {
}
@Transactional(readOnly = true)
- public Ticket getTicketWithLock(Long id) {
- return ticketQueryDslRepository.findByIdWithLock(id)
+ public Ticket getTicketWithPessimisticLock(Long id) {
+ return ticketQueryDslRepository.findByIdWithPessimisticLock(id)
+ .orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
+ }
+
+ @Transactional(readOnly = true)
+ public Ticket getTicketWithOptimisticLock(Long id) {
+ return ticketQueryDslRepository.findByIdWithOptimisticLock(id)
.orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
}
}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
new file mode 100644
index 0000000..464e669
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
@@ -0,0 +1,73 @@
+package com.week.zumgnmarket.order.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.jupiter.api.AfterEach;
+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.week.zumgnmarket.order.controller.OrderFacade;
+import com.week.zumgnmarket.order.dto.OrderRequest;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+@SpringBootTest
+public class OptimisticLockOrderServiceTest {
+
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TicketRepository ticketRepository;
+ @Autowired
+ private OrderFacade orderFacade;
+
+ private User 회원;
+ private Ticket 티켓;
+ private final int thread = 1000;
+
+ @BeforeEach
+ void setup() {
+ 회원 = new User("kim", "email@email.com", "pwd");
+ 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userRepository.save(회원);
+ ticketRepository.save(티켓);
+ }
+
+ @AfterEach
+ void after() {
+ userRepository.deleteAll();
+ ticketRepository.deleteAll();
+ }
+
+ @Test
+ void 동시에_티켓_주문() throws InterruptedException {
+ //given
+ ExecutorService executorService = Executors.newFixedThreadPool(123);
+ CountDownLatch countDownLatch = new CountDownLatch(thread);
+
+ //when
+ for (int i = 0; i < thread; i++) {
+ executorService.execute(() -> {
+ //TODO: 미해결 - 변경감지가 일어나지 않는 이슈
+ try {
+ orderFacade.orderWithOLock(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ countDownLatch.countDown();
+ });
+ }
+ countDownLatch.await();
+
+ //then
+ assertEquals(0, 티켓.getQuantity());
+ }
+}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
index 79d3797..43e33e7 100644
--- a/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
@@ -58,7 +58,7 @@ void after() {
for (int i = 0; i < thread; i++) {
executorService.execute(() -> {
//TODO: 미해결 - 변경감지가 일어나지 않는 이슈
- orderService.orderTicket(회원, ticketService.getTicketWithLock(티켓.getId()), 1);
+ orderService.orderTicket(회원, ticketService.getTicketWithPessimisticLock(티켓.getId()), 1);
countDownLatch.countDown();
});
}
From 2b88904759aa862c7b8e27c1c8f22caadc2a5159 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Thu, 23 Mar 2023 00:44:05 +0900
Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=ED=8B=B0=EC=BC=93=20=EC=A3=BC?=
=?UTF-8?q?=EB=AC=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20RedissonLock=20?=
=?UTF-8?q?=EC=A0=81=EC=9A=A9=20(error)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 3 +
.../order/controller/OrderFacade.java | 27 +++++++-
.../zumgnmarket/order/dto/OrderResponse.java | 2 +
.../OptimisticLockOrderServiceTest.java | 2 +-
.../service/RedissonLockOrderServiceTest.java | 69 +++++++++++++++++++
5 files changed, 100 insertions(+), 3 deletions(-)
create mode 100644 src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java
diff --git a/build.gradle b/build.gradle
index 2eb8618..e9acc9c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -38,6 +38,9 @@ dependencies {
// 쿼리 파라미터 로그
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8'
+ //Redisson
+ implementation 'org.redisson:redisson-spring-boot-starter:3.17.4'
+
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
diff --git a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
index 3096233..37bc0b4 100644
--- a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
+++ b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
@@ -1,5 +1,9 @@
package com.week.zumgnmarket.order.controller;
+import java.util.concurrent.TimeUnit;
+
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import com.week.zumgnmarket.order.dto.OrderRequest;
@@ -20,6 +24,7 @@ public class OrderFacade {
private final UserService userService;
private final TicketService ticketService;
private final OrderService orderService;
+ private final RedissonClient redissonClient;
public OrderResponse order(OrderRequest request) {
User user = userService.getUser(request.getUserId());
@@ -29,7 +34,7 @@ public OrderResponse order(OrderRequest request) {
return OrderResponse.of(order, user, ticket);
}
- public OrderResponse orderWithPLock(OrderRequest request) {
+ public OrderResponse orderWithPessimisticLock(OrderRequest request) {
User user = userService.getUser(request.getUserId());
Ticket ticket = ticketService.getTicketWithPessimisticLock(request.getTicketId());
Order order = orderService.orderTicket(user, ticket, request.getQuantity());
@@ -37,7 +42,7 @@ public OrderResponse orderWithPLock(OrderRequest request) {
return OrderResponse.of(order, user, ticket);
}
- public OrderResponse orderWithOLock(OrderRequest request) throws InterruptedException {
+ public OrderResponse orderWithOptimisticLock(OrderRequest request) throws InterruptedException {
User user = userService.getUser(request.getUserId());
//OptimisticLock 같은 경우 실패했을 때 재시도를 위해 while 을 사용하였습니다.
@@ -51,4 +56,22 @@ public OrderResponse orderWithOLock(OrderRequest request) throws InterruptedExce
}
}
}
+
+ public OrderResponse orderWithRedisson(OrderRequest request) {
+ User user = userService.getUser(request.getUserId());
+ RLock lock = redissonClient.getLock(request.getTicketId().toString());
+
+ try {
+ if (!lock.tryLock(2, 1, TimeUnit.SECONDS)) {
+ return new OrderResponse();
+ }
+ Ticket ticket = ticketService.getTicket(request.getTicketId());
+ Order order = orderService.orderTicket(user, ticket, request.getQuantity());
+ return OrderResponse.of(order, user, ticket);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ lock.unlock();
+ }
+ }
}
diff --git a/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java b/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java
index 003a599..4b1e9e6 100644
--- a/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java
+++ b/src/main/java/com/week/zumgnmarket/order/dto/OrderResponse.java
@@ -6,8 +6,10 @@
import lombok.Builder;
import lombok.Getter;
+import lombok.NoArgsConstructor;
@Getter
+@NoArgsConstructor
public class OrderResponse {
public Long orderId;
public Long userId;
diff --git a/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
index 464e669..ed15184 100644
--- a/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
@@ -58,7 +58,7 @@ void after() {
executorService.execute(() -> {
//TODO: 미해결 - 변경감지가 일어나지 않는 이슈
try {
- orderFacade.orderWithOLock(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+ orderFacade.orderWithOptimisticLock(new OrderRequest(회원.getId(), 티켓.getId(), 1));
} catch (InterruptedException e) {
e.printStackTrace();
}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java
new file mode 100644
index 0000000..99109e0
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java
@@ -0,0 +1,69 @@
+package com.week.zumgnmarket.order.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.jupiter.api.AfterEach;
+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.week.zumgnmarket.order.controller.OrderFacade;
+import com.week.zumgnmarket.order.dto.OrderRequest;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.entity.UserRepository;
+
+@SpringBootTest
+public class RedissonLockOrderServiceTest {
+
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TicketRepository ticketRepository;
+ @Autowired
+ private OrderFacade orderFacade;
+
+ private User 회원;
+ private Ticket 티켓;
+ private final int thread = 1000;
+
+ @BeforeEach
+ void setup() {
+ 회원 = new User("kim", "email@email.com", "pwd");
+ 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userRepository.save(회원);
+ ticketRepository.save(티켓);
+ }
+
+ @AfterEach
+ void after() {
+ userRepository.deleteAll();
+ ticketRepository.deleteAll();
+ }
+
+ @Test
+ void 동시에_티켓_주문() throws InterruptedException {
+ //given
+ ExecutorService executorService = Executors.newFixedThreadPool(123);
+ CountDownLatch countDownLatch = new CountDownLatch(thread);
+
+ //when
+ for (int i = 0; i < thread; i++) {
+ executorService.execute(() -> {
+ //TODO: 미해결 - 변경감지가 일어나지 않는 이슈
+ orderFacade.orderWithRedisson(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+ countDownLatch.countDown();
+ });
+ }
+ countDownLatch.await();
+
+ //then
+ assertEquals(0, 티켓.getQuantity());
+ }
+}
From 703a852015d9062f6e3c2d997f522c844bed312e Mon Sep 17 00:00:00 2001
From: yyy96
Date: Thu, 23 Mar 2023 00:59:41 +0900
Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20error=20?=
=?UTF-8?q?message=20=EB=B6=84=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../week/zumgnmarket/ticket/exception/ErrorMessage.java | 5 +++++
.../com/week/zumgnmarket/ticket/service/TicketService.java | 7 ++++---
2 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/com/week/zumgnmarket/ticket/exception/ErrorMessage.java
diff --git a/src/main/java/com/week/zumgnmarket/ticket/exception/ErrorMessage.java b/src/main/java/com/week/zumgnmarket/ticket/exception/ErrorMessage.java
new file mode 100644
index 0000000..48ed6eb
--- /dev/null
+++ b/src/main/java/com/week/zumgnmarket/ticket/exception/ErrorMessage.java
@@ -0,0 +1,5 @@
+package com.week.zumgnmarket.ticket.exception;
+
+public class ErrorMessage {
+ public static final String TICKET_NOT_FOUND_BY_ID = "Ticket not found with id ";
+}
diff --git a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
index 4deff27..17a8d60 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
@@ -8,6 +8,7 @@
import com.week.zumgnmarket.ticket.entity.Ticket;
import com.week.zumgnmarket.ticket.entity.TicketQueryDslRepository;
import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.ticket.exception.ErrorMessage;
import lombok.RequiredArgsConstructor;
@@ -23,18 +24,18 @@ public class TicketService {
@Transactional(readOnly = true)
public Ticket getTicket(Long id) {
return ticketRepository.findById(id)
- .orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
+ .orElseThrow(() -> new EntityNotFoundException(ErrorMessage.TICKET_NOT_FOUND_BY_ID + id));
}
@Transactional(readOnly = true)
public Ticket getTicketWithPessimisticLock(Long id) {
return ticketQueryDslRepository.findByIdWithPessimisticLock(id)
- .orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
+ .orElseThrow(() -> new EntityNotFoundException(ErrorMessage.TICKET_NOT_FOUND_BY_ID + id));
}
@Transactional(readOnly = true)
public Ticket getTicketWithOptimisticLock(Long id) {
return ticketQueryDslRepository.findByIdWithOptimisticLock(id)
- .orElseThrow(() -> new EntityNotFoundException("Ticket not found with id: " + id));
+ .orElseThrow(() -> new EntityNotFoundException(ErrorMessage.TICKET_NOT_FOUND_BY_ID + id));
}
}
From ab5189d6fcabf2920e824ecb6cb9bbf43177b470 Mon Sep 17 00:00:00 2001
From: yyy96
Date: Sat, 25 Mar 2023 00:08:45 +0900
Subject: [PATCH 12/12] =?UTF-8?q?fix:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?=
=?UTF-8?q?=EB=B3=80=EA=B2=BD=EA=B0=90=EC=A7=80=20=EC=9D=B4=EC=8A=88=20?=
=?UTF-8?q?=ED=95=B4=EA=B2=B0=20(=EB=8D=B0=EB=93=9C=EB=9D=BD=20=EB=AF=B8?=
=?UTF-8?q?=ED=95=B4=EA=B2=B0)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 4 ++
.../order/controller/OrderFacade.java | 2 +
.../ticket/service/TicketService.java | 4 ++
.../zumgnmarket/user/service/UserService.java | 7 +++
.../order/controller/OrderFacadeTest.java | 46 +++++++++++++++++++
.../OptimisticLockOrderServiceTest.java | 11 ++++-
.../PessimisticLockOrderServiceTest.java | 14 ++++--
.../service/RedissonLockOrderServiceTest.java | 11 ++++-
8 files changed, 91 insertions(+), 8 deletions(-)
create mode 100644 src/test/java/com/week/zumgnmarket/order/controller/OrderFacadeTest.java
diff --git a/build.gradle b/build.gradle
index e9acc9c..7c43456 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,7 +45,11 @@ dependencies {
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
+
+ //test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
+ testCompileOnly 'org.projectlombok:lombok'
+ testAnnotationProcessor 'org.projectlombok:lombok'
}
tasks.named('test') {
diff --git a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
index 37bc0b4..3522460 100644
--- a/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
+++ b/src/main/java/com/week/zumgnmarket/order/controller/OrderFacade.java
@@ -5,6 +5,7 @@
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
import com.week.zumgnmarket.order.dto.OrderRequest;
import com.week.zumgnmarket.order.dto.OrderResponse;
@@ -18,6 +19,7 @@
import lombok.RequiredArgsConstructor;
@Component
+@Transactional
@RequiredArgsConstructor
public class OrderFacade {
diff --git a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
index 17a8d60..e6e7bc4 100644
--- a/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
+++ b/src/main/java/com/week/zumgnmarket/ticket/service/TicketService.java
@@ -21,6 +21,10 @@ public class TicketService {
private final TicketQueryDslRepository ticketQueryDslRepository;
+ public Ticket save(Ticket ticket){
+ return ticketRepository.save(ticket);
+ }
+
@Transactional(readOnly = true)
public Ticket getTicket(Long id) {
return ticketRepository.findById(id)
diff --git a/src/main/java/com/week/zumgnmarket/user/service/UserService.java b/src/main/java/com/week/zumgnmarket/user/service/UserService.java
index 431a4ed..97e8643 100644
--- a/src/main/java/com/week/zumgnmarket/user/service/UserService.java
+++ b/src/main/java/com/week/zumgnmarket/user/service/UserService.java
@@ -3,6 +3,7 @@
import javax.persistence.EntityNotFoundException;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import com.week.zumgnmarket.user.entity.User;
import com.week.zumgnmarket.user.entity.UserRepository;
@@ -10,11 +11,17 @@
import lombok.RequiredArgsConstructor;
@Service
+@Transactional
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
+ public User save(User user){
+ return userRepository.save(user);
+ }
+
+ @Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found with id: " + id));
diff --git a/src/test/java/com/week/zumgnmarket/order/controller/OrderFacadeTest.java b/src/test/java/com/week/zumgnmarket/order/controller/OrderFacadeTest.java
new file mode 100644
index 0000000..afdcac8
--- /dev/null
+++ b/src/test/java/com/week/zumgnmarket/order/controller/OrderFacadeTest.java
@@ -0,0 +1,46 @@
+package com.week.zumgnmarket.order.controller;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import com.week.zumgnmarket.order.dto.OrderRequest;
+import com.week.zumgnmarket.ticket.entity.Ticket;
+import com.week.zumgnmarket.ticket.service.TicketService;
+import com.week.zumgnmarket.user.entity.User;
+import com.week.zumgnmarket.user.service.UserService;
+
+@SpringBootTest
+public class OrderFacadeTest {
+ @Autowired
+ private UserService userService;
+ @Autowired
+ private TicketService ticketService;
+ @Autowired
+ private OrderFacade orderFacade;
+
+ /**
+ * 학습 테스트)
+ * 해당 엔티티가 트랜잭션에 따라 변경 감지가 일어나는 조건을 테스트.
+ * 만약 orderFacade 에 @Transactional 이 없다면 해당 테스트는 실패합니다.
+ * 또한 static '티켓'은 order 의 Ticket 과 다른 트랜잭션, 영속성 컨텍스트에 존재하기 때문에 변경감지가 발생하지 않습니다.
+ * */
+ @Test
+ void 영속성_컨텍스트_변경감지_테스트() {
+ //given
+ User 회원 = new User("kim", "email@email.com", "pwd");
+ Ticket 티켓 = new Ticket("CAT'S", 10000, 1000);
+ userService.save(회원);
+ ticketService.save(티켓);
+
+ //when
+ orderFacade.order(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+
+ //then
+ Ticket result = ticketService.getTicket(티켓.getId());
+ assertEquals(result.getQuantity(), 999);
+ assertNotEquals(티켓.getQuantity(), 999);
+ }
+}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
index ed15184..6d4d319 100644
--- a/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/service/OptimisticLockOrderServiceTest.java
@@ -16,9 +16,13 @@
import com.week.zumgnmarket.order.dto.OrderRequest;
import com.week.zumgnmarket.ticket.entity.Ticket;
import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.ticket.service.TicketService;
import com.week.zumgnmarket.user.entity.User;
import com.week.zumgnmarket.user.entity.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
@SpringBootTest
public class OptimisticLockOrderServiceTest {
@@ -27,6 +31,8 @@ public class OptimisticLockOrderServiceTest {
@Autowired
private TicketRepository ticketRepository;
@Autowired
+ private TicketService ticketService;
+ @Autowired
private OrderFacade orderFacade;
private User 회원;
@@ -56,9 +62,9 @@ void after() {
//when
for (int i = 0; i < thread; i++) {
executorService.execute(() -> {
- //TODO: 미해결 - 변경감지가 일어나지 않는 이슈
try {
orderFacade.orderWithOptimisticLock(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+ log.info("남은 티켓 수량 : " + ticketService.getTicket(티켓.getId()).getQuantity());
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -68,6 +74,7 @@ void after() {
countDownLatch.await();
//then
- assertEquals(0, 티켓.getQuantity());
+ Ticket result = ticketService.getTicket(티켓.getId());
+ assertEquals(result.getQuantity(), 0);
}
}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
index 43e33e7..7e0f60e 100644
--- a/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/service/PessimisticLockOrderServiceTest.java
@@ -12,12 +12,17 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import com.week.zumgnmarket.order.controller.OrderFacade;
+import com.week.zumgnmarket.order.dto.OrderRequest;
import com.week.zumgnmarket.ticket.entity.Ticket;
import com.week.zumgnmarket.ticket.entity.TicketRepository;
import com.week.zumgnmarket.ticket.service.TicketService;
import com.week.zumgnmarket.user.entity.User;
import com.week.zumgnmarket.user.entity.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
@SpringBootTest
public class PessimisticLockOrderServiceTest {
@@ -28,7 +33,7 @@ public class PessimisticLockOrderServiceTest {
@Autowired
private TicketService ticketService;
@Autowired
- private PessimisticLockOrderService orderService;
+ private OrderFacade orderFacade;
private User 회원;
private Ticket 티켓;
@@ -57,14 +62,15 @@ void after() {
//when
for (int i = 0; i < thread; i++) {
executorService.execute(() -> {
- //TODO: 미해결 - 변경감지가 일어나지 않는 이슈
- orderService.orderTicket(회원, ticketService.getTicketWithPessimisticLock(티켓.getId()), 1);
+ orderFacade.orderWithPessimisticLock(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+ log.info("남은 티켓 수량 : " + ticketService.getTicket(티켓.getId()).getQuantity());
countDownLatch.countDown();
});
}
countDownLatch.await();
//then
- assertEquals(0, 티켓.getQuantity());
+ Ticket result = ticketService.getTicket(티켓.getId());
+ assertEquals(result.getQuantity(), 0);
}
}
diff --git a/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java b/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java
index 99109e0..8ed3f52 100644
--- a/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java
+++ b/src/test/java/com/week/zumgnmarket/order/service/RedissonLockOrderServiceTest.java
@@ -16,9 +16,13 @@
import com.week.zumgnmarket.order.dto.OrderRequest;
import com.week.zumgnmarket.ticket.entity.Ticket;
import com.week.zumgnmarket.ticket.entity.TicketRepository;
+import com.week.zumgnmarket.ticket.service.TicketService;
import com.week.zumgnmarket.user.entity.User;
import com.week.zumgnmarket.user.entity.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
@SpringBootTest
public class RedissonLockOrderServiceTest {
@@ -27,6 +31,8 @@ public class RedissonLockOrderServiceTest {
@Autowired
private TicketRepository ticketRepository;
@Autowired
+ private TicketService ticketService;
+ @Autowired
private OrderFacade orderFacade;
private User 회원;
@@ -56,14 +62,15 @@ void after() {
//when
for (int i = 0; i < thread; i++) {
executorService.execute(() -> {
- //TODO: 미해결 - 변경감지가 일어나지 않는 이슈
orderFacade.orderWithRedisson(new OrderRequest(회원.getId(), 티켓.getId(), 1));
+ log.info("남은 티켓 수량 : " + ticketService.getTicket(티켓.getId()).getQuantity());
countDownLatch.countDown();
});
}
countDownLatch.await();
//then
- assertEquals(0, 티켓.getQuantity());
+ Ticket result = ticketService.getTicket(티켓.getId());
+ assertEquals(result.getQuantity(), 0);
}
}