Skip to content

[Feat/#13] Flyway 마이그레이션·JPA 엔티티 26개 추가 - #16

Merged
tnals0924 merged 5 commits into
refactor/#14-domain-module-structurefrom
feat/#13-jpa-entities
Sep 8, 2026
Merged

[Feat/#13] Flyway 마이그레이션·JPA 엔티티 26개 추가#16
tnals0924 merged 5 commits into
refactor/#14-domain-module-structurefrom
feat/#13-jpa-entities

Conversation

@tnals0924

@tnals0924 tnals0924 commented Sep 4, 2026

Copy link
Copy Markdown
Member

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

ERD를 이 프로젝트 컨벤션에 맞는 Flyway 마이그레이션 + JPA Entity + 도메인 클래스로 옮긴다. 회원 약관 동의(member_term_agreements)·알림 설정(member_notification_settings) 테이블을 새로 추가한다.

❓ 왜 해결해야 하나요?

모든 도메인 기능 작업의 출발점이 되는 스키마와 매핑이 아직 없다. 원본 SQL은 MySQL에 없는 배열 타입, AUTO_INCREMENT 누락, 오타(OPEM) 등이 있어 그대로 쓸 수 없다.

⭐ 어떻게 해결했나요?

  • 마이그레이션 4개(V1~V4, 도메인별)로 26개 테이블. FK 제약은 걸지 않는다.
  • 도메인 클래스 26개 + enum 11개를 {module}.domain.{도메인}.domain에, Entity 26개를 infrastructure:db에 추가. 도메인 범위 — member{member, notification} / event{event, locker, archive} / welfare{rental, fee, notice, feedback, chat} / internal{schedule, config, display, file}.
  • 원본 SQL 변환 규칙
    • PK·student_id 등 컬럼명은 원본 유지. Entity는 @Column(name = ...)으로 매핑. locker_applications.applied_atcreated_at으로 흡수
    • MySQL ENUMVARCHAR + @Enumerated(STRING), 배열 컬럼(BIGINT [] 등) → JSON + @JdbcTypeCode(SqlTypes.JSON)
    • is_deleted TINYINT(1) NOT NULL DEFAULT 0, 모든 테이블에 created_at/updated_at(+ ON UPDATE CURRENT_TIMESTAMP)
    • items.typeItemType { CONSUMABLE, RENTAL }, work_schedulesmember_id만(staff_id 제거), events.recruit_typeOPEMOPEN
    • image_urlimage_key (items, display_posters)
  • 신규 테이블
    • member_term_agreements: 회원 × 약관 종류(PRIVACY_POLICY, TERMS_OF_SERVICE)당 1행, term_version·agreed·agreed_at, (member_id, term_type) 유니크
    • member_notification_settings: 회원당 1행, rental/event/locker/notice_enabled 4개 boolean, member_id 유니크
  • 인덱스는 참조 컬럼 조회·유니크 제약만 두고, 정렬 커버 인덱스는 Repository 작업에서 추가한다. members는 generated column active_student_id로 활성 학번만 유니크.

🧩 이 PR의 한계 & 트레이드오프

  • Repository/Service/Controller는 포함하지 않는다. 스키마와 매핑까지다.
  • 실제 MySQL로 ddl-auto: validate 검증을 하지 못했다. 작업 환경에 MySQL·Docker가 없었다. 대신 마이그레이션 SQL과 Entity 컬럼명을 스크립트로 대조해 26개 테이블 모두 일치함을 확인했다(의도한 미매핑은 generated column active_student_id뿐). 리뷰어 중 로컬 MySQL이 있는 분이 bootRun으로 한 번 확인해 주면 좋겠다.
  • 원본 모델의 이중화(members.is_fee_paidstudent_fees, rental_histories.item_code)는 그대로 옮겼다. 개선은 기능 작업에서.
  • Testcontainers MySQL이 없어 CI에서 Flyway 스크립트를 실행하지 못한다.

⛓️ 기존 기능에 미치는 영향

  • Member가 원본 컬럼 전체(department, email, fcmToken, feePaid, role, councilDepartment)로 확장된다. MemberJpaEntity의 PK 컬럼이 member_id로 매핑된다.
  • core:domain:{event,welfare,internal}에 첫 소스가 생겨 Modulith가 세 모듈을 새로 인식한다. verify()는 통과한다.

🔀 Edge Case & 실패 시나리오

  • event_applications(event_id, member_id) 유니크를 걸지 않았다. 취소 후 재신청을 새 행으로 남기기 위해서다. 중복 신청 방지는 애플리케이션에서 한다.
  • members.role은 원본대로 단일 컬럼이다. 학생회 임원의 STUDENT+ADMIN 동시 보유는 로그인 작업에서 재검토한다.

📋 검토한 대안과 선택 이유

  • PK를 id로 통일(컨벤션): 레거시 데이터 이관 편의를 위해 원본 이름 유지로 결정.
  • 배열 컬럼을 별도 매핑 테이블로: 파일 정합성 추적에는 유리하지만 테이블 5~6개가 늘어 JSON 컬럼을 택했다.
  • 알림 설정을 종류별 1행으로: 종류가 4개로 고정된 단순 토글이라 회원당 1행 4컬럼이 더 단순하다.

💬 리뷰 포인트

  • [r] V1~V4 마이그레이션이 원본 SQL과 맞는지(특히 컬럼 nullable·기본값)
  • [r] 로컬 MySQL이 있다면 bootRun으로 validate 통과 확인
  • [c] 신규 테이블 2개의 설계
  • [a] 도메인 범위(패키지) 나눔

@tnals0924 tnals0924 changed the title [Feat/#13] stream-init.sql 기반 Flyway 마이그레이션·JPA 엔티티 26개 추가 [Feat/#13] Flyway 마이그레이션·JPA 엔티티 26개 추가 Sep 4, 2026
@tnals0924
tnals0924 force-pushed the feat/#13-jpa-entities branch from 40ce499 to 1cb0e53 Compare September 4, 2026 11:20
@xeoxxn
xeoxxn self-requested a review September 7, 2026 05:48

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(member_id, term_type)이 유니크여서 약관 버전 변경 후 재동의할 경우, 이전 버전에 대한 동의 이력이 남지 않고 기존 term_version과 agreed_at이 업데이트되는 구조로 이해했습니다. 이전 약관 동의 이력의 경우는 따로 관리가 필요 없을지 궁금합니다.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

사실 이용 약관 동의 여부에 대한 기록을 가지고 있어야 하는 건 법적인 문제와 관련이 있는 거라서 이전 기록까지 가지고 있어야 하는지는 관련 자료를 한 번 찾아봐야할 거 같네요!

리뷰 감사드립니다~

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

JpaEntity와 Domain을 분리한 결정은 외부 기술인 JPA를 내부 구조인 Domain과 분리해서 Domain이 외부 기술에 의존하지 않도록 한 것이라고 생각하면 될까요?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

네 맞습니다. core 모듈의 경우에는 최대한 외부 기술에 의존하지 않기 위해 순수 자바 클래스를 지향하도록 설계했습니다.
트랜잭션, DI 등은 개발 편의성과 스프링을 사용하는 이유기도 하기 때문에 두 의존성과 모듈리스 의존성만 core 모듈에 남겨두었습니다.

@leegain1 leegain1 Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

저는 이렇게 도메인과 JPA를 똑같은 쌍으로(두배로) 둘만큼 이를 분리한 구체적인 이유가 좀 더 궁금합니다..!
트랜젝션 측면에서는 (트랜잭션을 제대로 다뤄본 경험이 없어 이해를 잘 못하는 것일 수도 있지만..) JPA엔티티를 도메인이 쓰게된다면 트랜젝션 안에서 값만 바뀌어도 db에 저장되어버릴 수 있어서 그런걸까요?
도메인 객체는 순수 자바 객체라 아무리 바꿔도 DB에 영향이 없으니, 저장은 Repository를 거칠 때만 일어나게 하려는 의도로 이해하면 되는건지 궁금합니다

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

간단히 얘기하자면 DB는 실제 비즈니스 로직과 직접적인 연관이 있는 게 아닌 데이터를 저장하기 위한 외부 기술에 해당합니다.
인프라와 비즈니스 로직 간의 분리를 했다고 생각하시면 될 거 같아요.
DB같은 외부 기술은 바뀌기 쉬워서 코드 베이스에 많이 침투해있다면 실제로 그 기술을 덜어낼 때 변경해야 하는 범위가 커져서 유지보수가 힘들어집니다.

이 관점에서 한 번 생각해보시면 좋을 거 같아요!

https://velog.io/@jonghyun3668/%EB%AA%A8%EB%93%88-%EA%B3%84%EC%B8%B5%EA%B0%84-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84%EC%8B%9C%ED%82%A4%EA%B8%B0

@Table(
name = "archive_related_links",
indexes = {
@Index(name = "idx_archive_related_links_archive_id", columnList = "archive_id")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이 부분이 이해가 잘 안되는데 archive_id 컬럼을 이용해서 하나의 테이블이나 자료구조를 새롭게 생성하는 게 맞을까요? 그렇다면 수정/삭제 등의 작업에서는 이 구조가 어떻게 동작하게 되는지도 궁금합니다.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

archive_related_links 테이블을 이야기하시는 거라면 아카이빙에서 연관된 링크 모음에 대응하는 테이블입니다. 링크를 추가하거나 삭제하는 경우에는 이 테이블에 내용을 넣거나, 삭제하면 됩니다.

정확히 어떤 부분이 궁금하신 지 코멘트 남겨주시면 추가로 설명드릴게요!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

정확히는 @Index 어노테이션의 동작에 대한 부분에 대한 질문이었습니다. 혼자 알아봤을때는 특정 컬럼에 인덱스를 부여해 값을 빠르게 찾아낼 수 있다 정도로 이해했는데, 이 인덱스라는 것이 테이블에 생성되는 것인지 별도로 관리되는 구조인 것인지가 궁금했습니다!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Index는 데이터베이스에서 자료를 더 빨리 찾기 위한 보조 자료구조 개념이라고 이해하시면 될 거 같아요!
별도 자료구조라고 생각하시면 될 거 같은데 아래 아티클 읽어보시면 도움이 될 거 같습니다~

https://velog.io/@bagt/DB-Index%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC


@Entity
@Table(name = "archives")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

JPAEntity의 AccessLevel이 protected인 이유가 궁금합니다. 생성자를 막는다는 관점으로 생각하면 Domain처럼 private으로 해야할 것 같은데, 여기에서는 JPA가 객체를 생성할 수 있도록 열어둔 게 맞을까요?

@tnals0924 tnals0924 Sep 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Jpa는 DB에서 데이터를 가져와 내부적으로 엔티티 인스턴스를 새로 생성할 때 빈 생성자로 인스턴스를 생성한 후에 데이터를 집어넣습니다. 따라서 무조건 접근할 수 있는 빈 생성자가 있어야 하기에 JpaEntity는 protected로 빈 생성자를 생성하도록 구현했습니다.

@Column(name = "answer_text", columnDefinition = "TEXT")
private String answerText;

@JdbcTypeCode(SqlTypes.JSON)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

바로 아래줄 컬럼에서 json으로 타입을 define해준 것으로 보이는데 JdbcTypeCode가 추가로 필요한 이유가 궁금합니다.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

둘의 역할이 살짝 다릅니다.

@Column(name = "selected_options", columnDefinition = "json")에서의 json은 JPA가 DDL로 컬럼을 생성할 때 컬럼의 타입이나 상세 정의를 넣을 때 사용합니다.
반면, @JdbcTypeCode는 Hibernate가 자바 클래스로 매핑을 해올 때 원래 데이터베이스의 어떤 데이터 타입이었는지를 확인하기 위한 어노테이션입니다.


public static MemberJpaEntity from(Member member) {
return new MemberJpaEntity(member);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

from()은 Domain 객체를 DB에 저장하기 전에 JPA Entity로 변환하고, toDomain()은 DB에서 조회한 Entity를 비즈니스 코드에서 사용할 Domain 객체로 바꾸는 메서드로 이해했는데 맞을까요?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

네 맞습니다!

@tnals0924
tnals0924 merged commit dd392ec into refactor/#14-domain-module-structure Sep 8, 2026
@tnals0924
tnals0924 deleted the feat/#13-jpa-entities branch September 8, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants