Skip to content

동시성 이슈를 확인하기위해 쿠폰 엔티티 생성#1

Open
chojisung wants to merge 3 commits intomasterfrom
develop
Open

동시성 이슈를 확인하기위해 쿠폰 엔티티 생성#1
chojisung wants to merge 3 commits intomasterfrom
develop

Conversation

@chojisung
Copy link
Owner

@chojisung chojisung commented Jan 31, 2026

쿠폰 발급 동시성 이슈를 확인하기 위해서 엔티티를 설계했습니다.

Summary by CodeRabbit

  • New Features

    • POST /api/coupon/issue로 쿠폰을 발급할 수 있습니다. 성공 시 "쿠폰 발급 성공"을 반환합니다.
    • 쿠폰 발급 시 재고가 감소하도록 동작합니다.
    • 앱 시작 시 초기 쿠폰 재고 100개가 자동 생성됩니다.
  • Chores

    • 개발용 인메모리 DB(H2)와 웹 콘솔이 활성화되어 있습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

쿠폰 엔티티를 JPA 엔티티로 매핑하고 Lombok을 적용했으며, 애플리케이션 초기 데이터 주입, REST 컨트롤러·서비스·레포지토리 계층과 H2 인메모리 데이터소스 설정이 추가되었습니다. 쿠폰 발급은 컨트롤러 → 서비스 → 레포지토리 → DB 순으로 처리됩니다.

Changes

Cohort / File(s) 요약
엔티티: Coupon
src/main/java/com/jisung0509/couponEvent/domain/coupon/Coupon.java
@Entity, @Table(name="coupon"), Lombok (@Getter, @NoArgsConstructor, @Builder, @FieldDefaults) 적용. id: Long(PK, @GeneratedValue.IDENTITY), stock: int(@Column(nullable=false)), version: Long(@Version) 추가. decreaseStock() 및 빌더 생성자 추가.
리포지토리
src/main/java/com/jisung0509/couponEvent/reposotiory/CouponRepository.java
CouponRepository 인터페이스 추가: extends JpaRepository<Coupon, Long>.
서비스
src/main/java/com/jisung0509/couponEvent/service/CouponService.java
@Service 클래스 추가. 트랜잭션 내 findById(1L) 조회, decreaseStock() 호출, couponRepository.save(coupon)로 영속성 반영 로직 추가.
컨트롤러
src/main/java/com/jisung0509/couponEvent/controller/CouponController.java
@RestController 추가: POST /api/coupon/issue 엔드포인트에서 couponService.issue() 호출하고 고정 문자열 응답 반환.
애플리케이션 초기화
src/main/java/com/jisung0509/couponEvent/common/init/DataInit.java
@Component 초기화 클래스 추가: @PostConstruct에서 Coupon(stock=100) 생성 및 저장.
설정: 데이터소스 및 JPA
src/main/resources/application.yaml
H2 인메모리 데이터베이스(jdbc:h2:mem:testdb) 설정, H2 콘솔 활성화, spring.jpa.hibernate.ddl-auto: create, show-sqlformat_sql 활성화.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as "CouponController\n(POST /api/coupon/issue)"
    participant Service as "CouponService"
    participant Repo as "CouponRepository (JPA)"
    participant DB as "H2 Database"

    Client->>Controller: POST /api/coupon/issue
    Controller->>Service: issue()
    Service->>Repo: findById(1L)
    Repo->>DB: SELECT ... FROM coupon WHERE id=1
    DB-->>Repo: Coupon row
    Repo-->>Service: Coupon entity
    Service->>Service: decreaseStock()
    Service->>Repo: save(coupon)
    Repo->>DB: UPDATE coupon SET stock=...
    DB-->>Repo: OK
    Repo-->>Service: saved entity
    Service-->>Controller: return
    Controller-->>Client: "쿠폰 발급 성공"
Loading

예상 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~25 분

🐰 새 쿠폰이 깜짝 피어났네,
DB 속 잔고 한 줄 반짝이고,
컨트롤러 부르면 서비스가 폴짝,
레포지토리 손끝에 저장되어,
작은 재고가 우리를 웃게 하네 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 쿠폰 엔티티 생성이라는 주요 변경사항을 명확히 설명하며, 동시성 이슈 확인 목적을 함께 언급하여 변경의 의도를 잘 전달하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/main/java/com/jisung0509/couponEvent/domain/coupon/Coupon.java`:
- Around line 12-15: The Coupon JPA entity class lacks the required no-args
constructor; add Lombok's `@NoArgsConstructor` with an appropriate visibility
(public or protected) to the Coupon class declaration so JPA can instantiate it
(i.e., annotate the Coupon class with `@NoArgsConstructor` and ensure Lombok is
available), keeping the existing `@Entity` and `@Getter` annotations intact.
- Around line 17-22: The Coupon entity is missing optimistic locking and a
controlled way to mutate stock: add a `@Version` field (e.g., private Long
version) to the Coupon class to enable JPA optimistic locking, and implement
explicit business methods to modify stock (for example a decreaseStock(int
amount) or adjustStock(int delta) on Coupon) that validate availability and
update the stock field instead of exposing it publicly; reference the existing
id and stock fields when locating where to add the version field and the new
business method.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/main/java/com/jisung0509/couponEvent/service/CouponService.java`:
- Around line 16-20: The current read-then-decrease in CouponService uses
couponRepository.findById(...) which allows lost updates under concurrency; add
a pessimistic lock repository method (e.g., declare Optional<Coupon>
findByIdForUpdate(Long id) annotated with `@Lock`(LockModeType.PESSIMISTIC_WRITE)
and a matching `@Query`) and replace the service call to
couponRepository.findById(...) with couponRepository.findByIdForUpdate(id), then
call coupon.decreaseStock() and persist/flush as before; ensure you import
jakarta.persistence.LockModeType and
org.springframework.data.jpa.repository.Lock and handle the Optional the same
way as before.

In `@src/main/resources/application.yaml`:
- Around line 2-25: Move development-only H2 and JPA settings out of the default
configuration into a dedicated dev profile (e.g., application-local.yaml or
application-dev.yaml): create the new profile file containing keys
datasource.url, h2.console.enabled/path, username/password,
jpa.hibernate.ddl-auto (set to create), and jpa.show-sql; then update the main
application.yaml to use safe production defaults (remove H2 console enablement,
require a password or omit in prod, set jpa.hibernate.ddl-auto to validate or
none, and set show-sql: false) and document activating the dev profile
(spring.profiles.active: local or via environment) so dev settings are only
loaded when that profile is active.
🧹 Nitpick comments (1)
src/main/java/com/jisung0509/couponEvent/service/CouponService.java (1)

5-15: Spring @Transactional 사용을 권장합니다

Spring Boot에서는 org.springframework.transaction.annotation.Transactional이 전파/격리/읽기 전용 옵션 측면에서 더 적합합니다.

🔧 수정 제안
-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;

Comment on lines +16 to +20
Coupon coupon = couponRepository.findById(1L)
.orElseThrow(() -> new IllegalArgumentException("쿠폰이 존재하지 않습니다."));

coupon.decreaseStock();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "Coupon|coupon" | head -20

Repository: chojisung/couponEvent

Length of output: 558


🏁 Script executed:

cat -n src/main/java/com/jisung0509/couponEvent/service/CouponService.java | sed -n '1,30p'

Repository: chojisung/couponEvent

Length of output: 1045


🏁 Script executed:

find . -name "Coupon.java" -o -name "CouponRepository.java" | head -10

Repository: chojisung/couponEvent

Length of output: 210


🏁 Script executed:

cat -n src/main/java/com/jisung0509/couponEvent/domain/coupon/Coupon.java

Repository: chojisung/couponEvent

Length of output: 1108


🏁 Script executed:

cat -n src/main/java/com/jisung0509/couponEvent/reposotiory/CouponRepository.java

Repository: chojisung/couponEvent

Length of output: 361


동시 발급 시 재고 손실 방지를 위해 비관적 잠금 추가 필요

현재 코드는 조회 후 감소 작업 사이에 보호 메커니즘이 없어서 동시 요청 시 재고 업데이트가 손실될 수 있습니다. 예를 들어, 재고가 10개일 때 두 개의 동시 요청이 모두 10을 읽고 각각 9로 저장하면 재고는 8이 아닌 9가 됩니다. 비관적 잠금을 적용하여 읽기 시점에 행을 잠그세요.

수정 제안
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;

`@Lock`(LockModeType.PESSIMISTIC_WRITE)
`@Query`("select c from Coupon c where c.id = :id")
Optional<Coupon> findByIdForUpdate(`@Param`("id") Long id);

그리고 CouponService에서:

-    Coupon coupon = couponRepository.findById(1L)
+    Coupon coupon = couponRepository.findByIdForUpdate(1L)
         .orElseThrow(() -> new IllegalArgumentException("쿠폰이 존재하지 않습니다."));
🤖 Prompt for AI Agents
In `@src/main/java/com/jisung0509/couponEvent/service/CouponService.java` around
lines 16 - 20, The current read-then-decrease in CouponService uses
couponRepository.findById(...) which allows lost updates under concurrency; add
a pessimistic lock repository method (e.g., declare Optional<Coupon>
findByIdForUpdate(Long id) annotated with `@Lock`(LockModeType.PESSIMISTIC_WRITE)
and a matching `@Query`) and replace the service call to
couponRepository.findById(...) with couponRepository.findByIdForUpdate(id), then
call coupon.decreaseStock() and persist/flush as before; ensure you import
jakarta.persistence.LockModeType and
org.springframework.data.jpa.repository.Lock and handle the Optional the same
way as before.

Comment on lines +2 to +25
datasource:
# H2 데이터베이스는 기본적으로 메모리(jdbc:h2:mem:)에 띄워집니다.
# testdb는 데이터베이스 이름입니다.
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: # 비밀번호는 없습니다 (비워둠)

# H2 콘솔(웹 화면)을 활성화하는 설정입니다.
h2:
console:
enabled: true
# 접속 경로: localhost:8080/h2-console 이 접속 가능하도록 설정
path: /h2-console

# JPA 관련 설정
jpa:
hibernate:
# (중요) 'create': 앱 실행할 때마다 테이블 지우고 새로 만듭니다.
# 데이터가 날아가는 게 싫으면 'update'로 바꾸세요.
# 개발 중에는 'create'가 편합니다.
ddl-auto: create
# (중요) 쿼리를 실행할 때 콘솔에 SQL 로그를 보여줍니다. ("왜 에러가 났지?" 할 때 유용함)
show-sql: true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find all application.yaml and profile-specific files
find . -type f -name "application*.yaml" -o -name "application*.yml" | head -20

Repository: chojisung/couponEvent

Length of output: 102


🏁 Script executed:

# Check the main application.yaml file
cat -n src/main/resources/application.yaml 2>/dev/null || echo "File not found at expected path"

Repository: chojisung/couponEvent

Length of output: 986


🏁 Script executed:

# Look for Spring Boot profile configurations across the project
fd -e yaml -e yml | xargs grep -l "spring:" 2>/dev/null | head -10

Repository: chojisung/couponEvent

Length of output: 100


🏁 Script executed:

# Check project structure to understand the setup
ls -la src/main/resources/ 2>/dev/null || echo "Directory not found"

Repository: chojisung/couponEvent

Length of output: 241


개발용 DB/콘솔 설정을 프로파일로 격리하세요

현재 H2 콘솔 활성화, 무비밀번호, ddl-auto: create, show-sql 등이 기본 프로파일에 적용되어 있습니다. 프로파일 분리가 없는 상태에서 이 설정들이 모든 환경(개발, 스테이징, 운영)에 로드되므로, 운영 환경에서 데이터 유실 및 콘솔 노출 위험이 매우 큽니다. application-local.yaml 또는 application-dev.yaml을 별도로 생성하여 이동하고, 기본 설정에서는 운영 환경에 적합한 값으로 설정하는 것이 필수입니다.

🔧 수정 제안 (local 프로파일로 한정)
 spring:
+  config:
+    activate:
+      on-profile: local
   datasource:
     # H2 데이터베이스는 기본적으로 메모리(jdbc:h2:mem:)에 띄워집니다.
     # testdb는 데이터베이스 이름입니다.
     url: jdbc:h2:mem:testdb
🤖 Prompt for AI Agents
In `@src/main/resources/application.yaml` around lines 2 - 25, Move
development-only H2 and JPA settings out of the default configuration into a
dedicated dev profile (e.g., application-local.yaml or application-dev.yaml):
create the new profile file containing keys datasource.url,
h2.console.enabled/path, username/password, jpa.hibernate.ddl-auto (set to
create), and jpa.show-sql; then update the main application.yaml to use safe
production defaults (remove H2 console enablement, require a password or omit in
prod, set jpa.hibernate.ddl-auto to validate or none, and set show-sql: false)
and document activating the dev profile (spring.profiles.active: local or via
environment) so dev settings are only loaded when that profile is active.

Repository owner deleted a comment from coderabbitai bot Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants