아니 벌써 일주일이 지났다니 시간이 너무 빠른 것 같다. 남은 일주일동안 더 열심히 도전하고 몰입해서 멋진 결과를 만들어내고 성장하고 싶다💪🏼
오늘은 눈뜨자마자 JPA를 위해서 객체를 어떻게 설계할지에 대해서 UML 형태로 정리해보았다.
내가 만든 객체는 총 6개인데 그 중 LottoRank와 LottoResult는 계산과 규칙을 위한 객체여서 해당이 안되고 나머지 4개의 객체에 대해서 설계했다!
- JPA 설계 패턴
- 클래스: @Entity
- private Long id; : @Id, @GeneratedValue
- protected 기본 생성자(JPA용)를 추가
- '핵심 객체'(Lotto 객체)은 @OneToOne 어노테이션으로 관계를 맺는다.
- '단순 객체'(String, int, LocalDate)은 그냥 필드로 둔다.
- '검증 로직'(validate...)은 public 생성자 또는 private 메서드에 둔다. (JPA가 신경 쓰지X)


그리고 나서 JpaRepository 설계를 시작했다. repository 패키지 안에 객체 각각에 대해서 4개의 repository를 만들어야 한다. 이 4개의 인터페이스는 JpaRepository를 상속(extends)받으면 다음 메서드들은 구현하지 않아도 사용 가능하다.
- save(Entity): (저장/수정)
- findById(ID): (조회)
- findAll(): (조회)
- delete(Entity): (삭제)

이렇게 설계를 마치고 이제 구현에 들어가야 한다!!!
1. Lotto
package com.woowa.lotto.domain;
import java.util.List;
import jakarta.persistence.*;
@Entity
public class Lotto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 로또 한장을 저장하는 객체
@ElementCollection
@CollectionTable(name = "lotto_numbers", joinColumns = @JoinColumn(name = "lotto_id"))
private final List<Integer> numbers;
protected Lotto() {
this.numbers = null;
}
public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
}
// 이 객체는 로또 한 장에 해당하는 데이터를 이용해 검증, 보너스 숫자 존재, 당첨번호와 몇 개 일치하는 지 스스로 확인
private void validate(List<Integer> numbers) {
// 로또 번호 개수 확인
if (numbers.size() != 6) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다.");
}
// 로또 숫자 범위 1~45 확인
for(Integer num : numbers) {
if(num < 1 || num > 45) {
throw new IllegalArgumentException("[ERROR] 로또 번호의 범위는 1~45여야 합니다.");
}
}
// 로또 숫자가 서로 중복되지 않는 지 확인
if(numbers.size() != numbers.stream().distinct().count()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 서로 중복되면 안됩니다.");
}
}
// TODO: 추가 기능 구현
// 보너스 번호가 리스트 중에 존재하는지 확인해서 true/false 를 돌려주는 메서드
public boolean hasBonusNum(int bonusNum) {
return numbers.contains(bonusNum);
}
// 당첨 번호와 몇 개가 일치하는 확인해서 일치하는 개수를 돌려주는 메서드
public int matchCount(Lotto otherLotto) {
List<Integer> otherNumbers = otherLotto.getNumbers();
long count = this.numbers.stream()
.filter(otherNumbers::contains)
.count();
return (int) count;
}
public List<Integer> getNumbers() {return numbers;}
public Long getId() {return id;}
}
2. MyLotto
package com.woowa.lotto.domain;
import java.util.Objects;
import jakarta.persistence.*;
@Entity
public class MyLotto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(cascade = CascadeType.ALL) // MyLotto가 저장/삭제될 때, Lotto도 같이 저장/삭제
@JoinColumn(name = "lotto_id")
private final Lotto myLotto;
@Column
private final String lottoName;
protected MyLotto() {
this.myLotto = null;
this.lottoName = null;
}
public MyLotto(Lotto myLotto, String lottoName) {
this.myLotto = Objects.requireNonNull(myLotto, "[ERROR] 로또가 비워져 있습니다.");
Objects.requireNonNull(lottoName, "[ERROR] 로또 이름은 null일 수 없습니다.");
validateLottoName(lottoName);
this.lottoName = lottoName;
}
private void validateLottoName(String lottoName) {
if(lottoName.length() > 20 || lottoName.isEmpty()) {
throw new IllegalArgumentException("[ERROR] 로또 이름은 1~20글자 범위로 입력해주세요.");
}
}
public Lotto getMyLotto() {return myLotto;}
public String getLottoName() {return lottoName;}
public Long getId() {return id;}
}
3. PurchasedLotto
package com.woowa.lotto.domain;
import java.time.LocalDate;
import java.util.Objects;
import java.time.temporal.WeekFields;
import java.util.Locale;
import jakarta.persistence.*;
@Entity
public class PurchasedLotto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "lotto_id")
private final Lotto purchasedLotto;
@Column
private final LocalDate purchaseDate;
protected PurchasedLotto() {
this.purchasedLotto = null;
this.purchaseDate = null;
}
public PurchasedLotto(Lotto purchasedLotto, LocalDate purchaseDate) {
this.purchasedLotto = Objects.requireNonNull(purchasedLotto, "[ERROR] 로또가 비워져 있습니다.");
this.purchaseDate = Objects.requireNonNull(purchaseDate, "[ERROR] 날짜가 비워져 있습니다.");
}
public boolean isPurchasedInWeek(LocalDate dateToCompare) {
WeekFields weekRule = WeekFields.of(Locale.KOREA);
// 2. 나의 '연도'와 '주차 번호'를 가져옵니다.
int myYear = this.purchaseDate.getYear();
int myWeekNumber = this.purchaseDate.get(weekRule.weekOfWeekBasedYear());
// 3. 비교할 날짜의 '연도'와 '주차 번호'를 가져옵니다.
int compareYear = dateToCompare.getYear();
int compareWeekNumber = dateToCompare.get(weekRule.weekOfWeekBasedYear());
// 4. 두 개의 '연도'와 '주차 번호'가 "둘 다" 같은지 확인합니다.
return (myYear == compareYear) && (myWeekNumber == compareWeekNumber);
}
public Lotto getPurchasedLotto() {return purchasedLotto;}
public LocalDate getPurchaseDate() {return purchaseDate;}
public Long getId() {return id;}
}
4. WinningLotto
package com.woowa.lotto.domain;
import java.time.LocalDate;
import jakarta.persistence.*;
import java.util.Objects;
@Entity
public class WinningLotto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 당첨 번호를 가지는 객체 (6개 번호 + 1개의 보너스 번호)
@OneToOne(cascade = CascadeType.ALL)
private final Lotto winningLotto;
@Column
private final Integer bonusNum;
@Column
private final LocalDate drawDate;
protected WinningLotto() {
this.winningLotto = null;
this.bonusNum = null;
this.drawDate = null;
}
public WinningLotto(Lotto winningLotto, Integer bonusNum, LocalDate drawDate) {
validate(winningLotto, bonusNum);
this.winningLotto = winningLotto;
this.bonusNum = bonusNum;
this.drawDate = drawDate;
}
// 보너스 번호를 가지는 객체이므로 보너스 번호에 대한 검증은 winningLotto가 해야함
private void validate(Lotto winningLotto, Integer bonusNum) {
Objects.requireNonNull(bonusNum, "[ERROR] 보너스 번호는 null일 수 없습니다.");
if(bonusNum < 1 || bonusNum > 45) {
throw new IllegalArgumentException("[ERROR] 보너스 번호의 범위는 1~45여야 합니다.");
}
if(winningLotto.hasBonusNum(bonusNum)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨 번호와 중복되면 안됩니다.");
}
}
// 서비스에서 보너스 번호, 당첨번호, 만든 날짜를 사용하기 위한 getter
public Lotto getWinningLotto() {return winningLotto;}
public Integer getBonusNum() {return bonusNum;}
public LocalDate getDrawDate() {return drawDate;}
public Long getId() {return id;}
}
Lotto만 구현하고 나니 나머지 객체들은 어렵지 않게 구현할 수 있었다. 다만 WinningLotto에서 bonusNum이 int로 선언되어 있어 null로 생성자를 구현할 수 없어 보너스 숫자에 관한 것은 Integer로 객체로 수정했다. 그리고 Lotto 자체를 활용하는 경우에는 cascade를 사용해서 연관성을 이어줬다. 항상 sql 같은 표 형식 데이터베이스정도만 설계해보다가 이렇게 코드로 데이터베이스를 구현한다는게 어색하고 신기했다. 이 다음으로는 각 객체에 연결되는 repository 인터페이스를 구현했다. JpaRepository를 상속만 받으면 기본 기능을 다 사용할 수 있다니 와우~
1. LottoRepository
package com.woowa.lotto.repository;
import com.woowa.lotto.domain.Lotto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface LottoRepository extends JpaRepository<Lotto, Long> {
}
2. MyLottoRepository
package com.woowa.lotto.repository;
import com.woowa.lotto.domain.MyLotto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MyLottoRepository extends JpaRepository<MyLotto, Long> {
}
3. PurchasedRepository
package com.woowa.lotto.repository;
import com.woowa.lotto.domain.PurchasedLotto;
import java.time.LocalDate;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PurchasedLottoRepository extends JpaRepository<PurchasedLotto, Long> {
List<PurchasedLotto> findAllByPurchaseDateBetween(LocalDate start, LocalDate end);
}
4. WinningLottoRepository
package com.woowa.lotto.repository;
import com.woowa.lotto.domain.WinningLotto;
import java.time.LocalDate;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface WinningLottoRepository extends JpaRepository<WinningLotto, Long> {
Optional<WinningLotto> findByDrawDate(LocalDate drawDate);
}
우선 뭔가 해보기는 했는데 정말 이걸로 레포지토리가 작동하는지는? 모르겄따... 인터페이스 상속만 받고 설계할 때 커스텀 메서드가 필요한 경우에는 추가를 해줬다. 근데 이 메서드도 안에는 다 비워져있고 Jpa가 알아서 해준다고 한다..? 흠
지금까지 한 것은
1. Jpa가 도메인을 인식할 수 있도록 객체에 @Entity, @Column, @ID 등의 어노테이션 추가하기
2. Jpa Repository 기능을 사용할 수 있도록 인터페이스 상속받아 구현하기
이제 할 것은 1,2번으로 정말 둘이 연결이 되었는지? 단위테스트로 테스트해보기!
domain과 repository가 H2 데이터베이스와 연동되는지 확인하기 위해, MyLottoRepository에 대한 단위 테스트(@DataJpaTest)를 실행했다.
package com.woowa.lotto.repository;
import com.woowa.lotto.domain.Lotto;
import com.woowa.lotto.domain.MyLotto;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
// H2 같은 인메모리 DB를 자동으로 실행하고, 끝나면 자동 롤백
@DataJpaTest
class MyLottoRepositoryTest {
// 테스트에 필요한 repository 2개 추가: MyLotto를 저장하려면 '부품'인 Lotto도 저장해야 하므로 2개 다 필요
@Autowired
private MyLottoRepository myLottoRepository;
@Autowired
private LottoRepository lottoRepository;
@DisplayName("MyLotto 객체를 저장하고, ID로 다시 찾을 수 있다.")
@Test
void saveAndFindById() {
Lotto lottoEngine = new Lotto(List.of(1, 2, 3, 4, 5, 6));
Lotto savedLottoEngine = lottoRepository.save(lottoEngine);
MyLotto myLotto = new MyLotto(savedLottoEngine, "나의 행운 번호");
MyLotto savedMyLotto = myLottoRepository.save(myLotto);
Optional<MyLotto> foundMyLottoOptional = myLottoRepository.findById(savedMyLotto.getId());
assertThat(foundMyLottoOptional).isPresent();
MyLotto foundMyLotto = foundMyLottoOptional.get();
assertThat(foundMyLotto.getLottoName()).isEqualTo("나의 행운 번호");
assertThat(foundMyLotto.getMyLotto().getId()).isEqualTo(savedLottoEngine.getId());
}
}
결과
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
12:40:47.557 [Test worker] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.woowa.lotto.repository.MyLottoRepositoryTest]: MyLottoRepositoryTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
12:40:47.604 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.woowa.lotto.LottoApplication for test class com.woowa.lotto.repository.MyLottoRepositoryTest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.7)
2025-11-10T12:40:47.739+09:00 INFO 88624 --- [lotto] [ Test worker] c.w.l.repository.MyLottoRepositoryTest : Starting MyLottoRepositoryTest using Java 21.0.8 with PID 88624 (started by imminji in /Users/imminji/Desktop/git/lotto)
2025-11-10T12:40:47.740+09:00 INFO 88624 --- [lotto] [ Test worker] c.w.l.repository.MyLottoRepositoryTest : No active profile set, falling back to 1 default profile: "default"
2025-11-10T12:40:47.861+09:00 INFO 88624 --- [lotto] [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-11-10T12:40:47.881+09:00 INFO 88624 --- [lotto] [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 17 ms. Found 4 JPA repository interfaces.
2025-11-10T12:40:47.903+09:00 INFO 88624 --- [lotto] [ Test worker] beddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version
2025-11-10T12:40:47.933+09:00 INFO 88624 --- [lotto] [ Test worker] o.s.j.d.e.EmbeddedDatabaseFactory : Starting embedded database: url='jdbc:h2:mem:9711f2b8-cb70-4d92-ae94-abee6608eb86;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
2025-11-10T12:40:48.030+09:00 INFO 88624 --- [lotto] [ Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-11-10T12:40:48.045+09:00 INFO 88624 --- [lotto] [ Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.33.Final
2025-11-10T12:40:48.055+09:00 INFO 88624 --- [lotto] [ Test worker] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2025-11-10T12:40:48.149+09:00 INFO 88624 --- [lotto] [ Test worker] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-11-10T12:40:48.175+09:00 INFO 88624 --- [lotto] [ Test worker] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy@d28c214']
Database driver: undefined/unknown
Database version: 2.3.232
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2025-11-10T12:40:48.487+09:00 INFO 88624 --- [lotto] [ Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Hibernate: drop table if exists lotto cascade
Hibernate: drop table if exists lotto_numbers cascade
Hibernate: drop table if exists my_lotto cascade
Hibernate: drop table if exists purchased_lotto cascade
Hibernate: drop table if exists winning_lotto cascade
Hibernate: create table lotto (id bigint generated by default as identity, primary key (id))
Hibernate: create table lotto_numbers (numbers integer, lotto_id bigint not null)
Hibernate: create table my_lotto (id bigint generated by default as identity, lotto_id bigint unique, lotto_name varchar(255), primary key (id))
Hibernate: create table purchased_lotto (purchase_date date, id bigint generated by default as identity, lotto_id bigint unique, primary key (id))
Hibernate: create table winning_lotto (bonus_num integer, draw_date date, id bigint generated by default as identity, winning_lotto_id bigint unique, primary key (id))
Hibernate: alter table if exists lotto_numbers add constraint FKjygr57v329fiddscag72upl74 foreign key (lotto_id) references lotto
Hibernate: alter table if exists my_lotto add constraint FKbvrahjnautgsykjnrr3d22k6o foreign key (lotto_id) references lotto
Hibernate: alter table if exists purchased_lotto add constraint FKp656mcein2dt79ytubhngsy9b foreign key (lotto_id) references lotto
Hibernate: alter table if exists winning_lotto add constraint FKtrsgkv2afixd3i90g2qwimu2a foreign key (winning_lotto_id) references lotto
2025-11-10T12:40:48.509+09:00 INFO 88624 --- [lotto] [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-11-10T12:40:48.650+09:00 INFO 88624 --- [lotto] [ Test worker] c.w.l.repository.MyLottoRepositoryTest : Started MyLottoRepositoryTest in 1.02 seconds (process running for 1.44)
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (/Users/imminji/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy-agent/1.17.8/f09415827a71be7ed621c7bd02550678f28bc81c/byte-buddy-agent-1.17.8.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
Hibernate: insert into lotto (id) values (default)
Hibernate: insert into my_lotto (lotto_name,lotto_id,id) values (?,?,default)
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2025-11-10T12:40:48.977+09:00 INFO 88624 --- [lotto] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
Hibernate: drop table if exists lotto cascade
Hibernate: drop table if exists lotto_numbers cascade
Hibernate: drop table if exists my_lotto cascade
Hibernate: drop table if exists purchased_lotto cascade
Hibernate: drop table if exists winning_lotto cascade
> Task :test
BUILD SUCCESSFUL in 6s
4 actionable tasks: 2 executed, 2 up-to-date
12:40:49 PM: Execution finished ':test --tests "com.woowa.lotto.repository.MyLottoRepositoryTest"'.
BUILD SUCCESSFUL로 테스트가 통과했으며, 실행 로그(Log)를 통해 다음을 확인할 수 있었다.
1. H2 인메모리 DB 자동 실행
- 로그: Starting embedded database: url='jdbc:h2:mem:...'
- @DataJpaTest 어노테이션이 작동하여, 별도 설정 없이도 H2 의존성을 감지하고 테스트용 '인메모리 DB'를 자동으로 실행했다.
2. @Entity 스캔 및 테이블 자동 생성
- 로그: Hibernate: create table lotto ..., create table my_lotto ..., create table purchased_lotto ...
- JPA(Hibernate)가 domain 패키지에 있는 @Entity 어노테이션들을 인'했다.
- 인식의 증거로, Lotto, MyLotto 등 4개 @Entity에 매핑되는 DB 테이블 5개(Lotto의 @ElementCollection 포함)를 H2 DB에 자동으로 생성했다. (domain 1단계 설계가 성공했음을 증명)
3. JpaRepository 자동 구현 및 작동
- 로그: Found 4 JPA repository interfaces.
- repository 패키지에 선언한 4개의 JpaRepository 인터페이스를 스프링이 발견하고 자동 구현체를 주입(@Autowired)했다.
4. save / find (CRUD) 연동 성공
- 로그: Hibernate: insert into lotto ..., insert into my_lotto ...
- 테스트 코드의 myLottoRepository.save() 메서드가 JPA를 통해, H2 DB가 알아듣는 INSERT SQL 쿼리로 정상 번역및 실행되었다.
- findById()로 다시 '조회'하고 assertThat()으로 '검증'하는 테스트가 최종 BUILD SUCCESSFUL로 통과했다.
이렇게 해서 도메인과 레포지토리 구현은 완료했다! (아닐지도 모르지만...) 이제 오늘은 서비스 설계를 한 후 마무리할 것이다. 졸업을 위한 토익시험을 일요일로 접수해버려서 그것도 얼른 공부해야한다 그래서 아침에 일어나자마자 오늘 구현 계획을 1차 마무리한 후 이제 다른 것들을 하다가 저녁에 다시 서비스 설계를 해야할 것 같다 아자자!!
마지막으로 오늘이 딱 7일 남은 시점이므로 오늘부터 제출일까지의 계획을 설정해보았다
D-7 (월, 11/10): domain 수정 및 repository TDD (1단계: JPA 연동)
- [핵심 목표] domain이 H2 DB에 저장되는 지 확인하기
- [To-Do]
- domain 수정: @Entity 객체 4개(Lotto, MyLotto 등)에 @Id, @GeneratedValue, @OneToOne, protected 생성자 등 어노테이션을 붙여 '엔티티'로 변환.
- repository 구현: lotto.repository 패키지에 4개의 JpaRepository 인터페이스 생성.
- TDD (핵심): src/test에 repository 패키지를 만들고, @DataJpaTest 어노테이션을 사용한 MyLottoRepositoryTest.java 작성
- 검증: myLottoRepository.save(myLotto)가 성공하는지, myLottoRepository.findById(id)가 저장된 객체를 잘 반환하는지 테스트
D-6 (화, 11/11): service 설계 및 TDD (2단계: 비즈니스 로직 Part 1)
- [핵심 목표] Service가 Repository를 호출하는 로직 구현
- [To-Do]
- service 설계: LottoService 클래스와 구현할 메서드(예: generateRandomLotto, saveToMyLotto, purchaseFromMyLotto)의 껍데기를 설계
- service 구현 (단순 로직): '랜덤 번호 생성', '나만의 로또 저장' 등 '계산'이 적은 기능부터 구현.
- TDD: @SpringBootTest (또는 Mockito)를 사용한 LottoServiceTest.java 작성. Repository를 '가짜(Mock)'로 만들거나, '실제 DB(@DataJpaTest처럼)'를 사용해 Service의 '단순 로직'이 잘 작동하는지 검증.
D-5 (수, 11/12): service 구현 (2단계: 비즈니스 로직 Part 2)
- [핵심 목표] 통계 계산 로직 구현
- [To-Do]
- service 구현 (복잡 로직): getWeeklyStatistics(LocalDate drawDate) 메서드 구현 (이 메서드는 WinningLottoRepository와 PurchasedLottoRepository를 Join하고, LottoRank와 LottoResult를 활용해야함)
- TDD: Service 테스트 코드에 '통계 검증' 로직 추가
- 검증: '가짜' 구매 내역과 '가짜' 당첨 번호를 DB에 save한 뒤, getWeeklyStatistics를 호출했을 때, LottoResult가 정확한 '수익률'(예: 62.5)을 반환하는지 assertThat으로 증명하기
D-4 (목, 11/13): controller 설계 및 구현 (3단계: API)
- [핵심 목표] 웹에서 Service로 요청이 잘 연결되는지 확인하기
- [To-Do]
- dto 설계: controller가 service와 주고받을 데이터 봉투(LottoPurchaseRequest 등) 설계
- controller 구현: @RestController, @PostMapping, @GetMapping, @RequestBody를 사용해 API 구현.
- 테스트 (핵심): Postman (또는 IntelliJ HTTP Client)을 사용해, localhost:8080/api/lotto/purchase 같은 API 직접 호출해보기
- 검증: API 호출 시, H2 DB에 데이터가 저장되는지 확인. (H2 콘솔 접속)
D-3 (금, 11/14): 프론트 연동 및 통합 테스트 (4단계: End-to-End)
- [핵심 목표] 기능만 볼 수 있도록 Controller API를 호출하는 최소한의 UI 구현
- [To-Do]
- resources/static 폴더에 index.html, app.js 파일 생성.
- app.js에서 fetch() API를 사용해 Controller의 API(예: GET /api/lotto/random)를 호출.
- API가 반환한 JSON 데이터를 console.log로 찍거나, div에 간단히 표시.
- (버퍼) 이 과정에서 100% 발생하는 'CORS' 에러, JSON 파싱 에러 등 '통합 버그' 수정.
오늘하는 것부터는 정말 처음 해보는 것들이여서 걱정이 많았는데 계속 도전하고 성공하고 하는 느낌이여서 기분이 좋았던 것 같다. 발전해나가는 느낌은 항상 좋다. 물론 제일 큰 산인 서비스와 컨트롤러가 남아있지만 나는 해낼 것이다!!! 계획대로 열심히 해보자구☀️ 아자자
그리고 토,일요일 계획은 목요일쯤에 다시 한 번 계획을 세워볼 것이다. 17일 3시부터 제출이라고 하였는데 마감은 또 25일이여서 어떻게 되는 건지 잘 모르겄다. 우선은 월요일 제출을 목표로 달려가보자!!