<aside> 📌

목차

DB 스키마 설계

개요

B:SCENE의 스키마는 49개 테이블(엔티티 48 + @ElementCollection 테이블 1)로 구성되며, 성격에 따라 9개 도메인으로 묶입니다. 설계의 중심에는 User(사람)와 Band(팀)라는 두 개의 허브가 있고, 나머지 도메인은 모두 이 둘 중 하나 또는 둘 다를 참조하는 형태로 뻗어 나갑니다. 밴드가 생산하는 콘텐츠(게시물·공연·라이브)와 사람을 모으는 활동(세션 모집), 그리고 그 반응을 전달하는 장치(알림·채팅·검색)가 각각 분리된 구조입니다.

전체 도메인 관계도

flowchart TB
    U["사용자 · 인증 · 프로필<br>10개 테이블"]
    B["밴드 · 팬 관계 · 추천<br>8개 테이블"]
    P["게시물 · 미디어<br>5개 테이블"]
    PF["공연<br>4개 테이블"]
    S["세션 모집 · 지원<br>10개 테이블"]
    L["라이브 스트리밍<br>6개 테이블"]
    C["채팅<br>2개 테이블"]
    N["알림 · 푸시<br>3개 테이블"]
    SR["검색 이력<br>1개 테이블"]

    U -->|"밴드 개설 · 멤버 참여"| B
    B -->|"게시물 발행"| P
    B -->|"공연 등록"| PF
    B -->|"구인 공고 등록"| S
    B -->|"방송 개설"| L
    U -->|"좋아요 · 댓글"| P
    U -->|"관심 · 참여"| PF
    U -->|"구직 프로필 · 지원"| S
    U -->|"공동 호스팅 · 시청"| L
    U -->|"최근 검색어"| SR
    S -->|"문의 대화방 개설"| C
    PF -->|"리마인드"| N
    L -->|"방송 시작 · 다시보기"| N
    S -->|"지원 결과 · 마감"| N
    C -->|"새 메시지"| N

스키마 전반의 공통 규칙

개별 도메인을 보기 전에, 전체 테이블에 공통으로 적용된 규칙과 예외를 먼저 정리합니다. 아래 내용은 업로드해주신 v2 SQL 초안이 아니라 B-Scene/bscene-server 레포의 실제 엔티티·마이그레이션 코드를 기준으로 확인한 것입니다.

규칙 내용 예외 · 유의사항
명명 네이밍 컨벤션이 도메인마다 갈립니다 테이블명이 PascalCase 단수형(Band, Post, Performance), snake_case 복수형(chat_rooms, oauth_accounts, user_blocks), snake_case 단수형(session_recruitment)이 섞여 있고, 컬럼도 camelCase(userId)와 snake_case(user_id)가 도메인별로 갈립니다. 신규 테이블 추가 시 기준을 정해두면 좋을 지점입니다
기본키 모든 엔티티가 @Id @GeneratedValue(IDENTITY)로 단일 컬럼 Auto Increment PK를 가집니다 session_application_activities만 JPA @ElementCollection 컬렉션 테이블이라 독립된 엔티티 PK가 없습니다(설계 의도)
외래키 연관관계는 JPA @ManyToOne / @OneToOne • @JoinColumn으로 매핑되어 실제 FOREIGN KEY 제약이 생성됩니다 userId·bandId처럼 외래키로 보이는 컬럼은 모두 실제 외래키입니다. 다만 AudioStream.bandId·AudioStream.broadcasterId, ReportHistory.reporterId, SessionApplication.userId, SessionRecruitmentSearchKeyword.userId는 연관관계 매핑 없이 ID 값만 보관하는 의도적 예외입니다(조회 성능·결합도 목적)
시간 컬럼 대부분의 엔티티가 BaseEntity를 상속해 createdAt / updatedAt을 Spring Data Auditing(@CreatedDate, @LastModifiedDate)으로 자동 관리합니다 SessionApplicationCareer·BandSimilarity·SessionRecruitmentSearchKeyword만 BaseEntity를 상속하지 않아 자동 시간 컬럼이 없습니다
삭제 정책 소프트 삭제를 쓰되 방식이 통일되어 있지 않습니다 deletedAt 컬럼 방식(User, LocalCredentials, 세션 모집 계열 4개)과 status enum에 DELETED를 두는 방식(User, Performance)이 혼재합니다
중복 방지 교차·반응 테이블에는 복합 UNIQUE 제약이 걸려 있습니다 Follow, PostLike, PerformanceInterest, PerformanceParticipation, BandMember, BandInteraction, UserTerms, UserGenres, UserRegions, UserAvailableModes, NotificationSetting, LiveAlarm, StreamMember, UserBlock, SessionRecruitmentInterest, SessionRecruitmentView, FanModeRecentSearch 등이 해당합니다. FanProfile·LocalCredentials·SessionBasicProfile·MusicLink는 @OneToOne • unique = true로 1:1을 강제합니다

사용자 · 인증 · 프로필

모든 사용자 계정과 로그인 수단, 그리고 모드(팬/밴드)별 부가 정보를 책임지는 도메인입니다. User가 신원·상태를 가진 마스터 테이블 역할을 하고, 인증 수단(소셜/로컬)·선호 속성(장르·지역·이용 가능 모드)·약관 동의·모드별 프로필이 각각 별도 테이블로 분리되어 1인당 여러 값을 가질 수 있는 속성들을 정규화하고 있습니다.

erDiagram
    User ||--o| LocalCredentials : "로컬 로그인"
    User ||--o| OauthAccount : "소셜 로그인 연동"
    User ||--o{ UserAvailableModes : "이용 가능 모드"
    User ||--o{ UserGenres : "선호 장르"
    User ||--o{ UserRegions : "활동 지역"
    User ||--o{ UserTerms : "약관 동의 이력"
    Terms ||--o{ UserTerms : "동의 대상 약관"
    User ||--o| FanProfile : "팬 프로필"
    User ||--o{ BandMemberProfile : "밴드 멤버 프로필"
    User ||--o{ Band : "밴드 개설"
    User ||--o{ BandMember : "밴드 소속"
    BandMemberProfile ||--o{ BandMember : "프로필 연결"
테이블 역할 핵심 컬럼 · 특징
User 전체 사용자 마스터 테이블 name(실명), gender, birthDate, phone, role(USER · ADMIN), currentMode(FAN · BAND), onboardingCompleted, status(ACTIVE · DELETED · SUSPENDED · INACTIVE), deletedAt
OauthAccount 소셜 로그인 계정 연동 정보 provider(KAKAO · GOOGLE), providerUid, email. @OneToOne이고 마이그레이션 V35가 user_id에 UNIQUE를 추가해 사용자당 소셜 계정 1개로 강제됩니다
LocalCredentials 자체 회원가입 인증 정보 loginId(UK, 이메일), passwordHash, passwordChangedAt, deletedAt
UserAvailableModes 사용자가 진입할 수 있는 모드 목록 mode(FAN · BAND). User.currentMode가 현재 모드라면 이 테이블은 전환 가능한 모드 집합입니다
UserGenres 사용자 선호 장르(다중 선택) genre 13종 enum. 밴드 추천·탐색 필터링의 입력입니다
UserRegions 사용자 활동·관심 지역(다중 선택) region 전국 17개 시도 enum
Terms 약관 마스터 name, content(text), type(MANDATORY · OPTIONAL)
UserTerms 사용자별 약관 동의 이력 termId, isAgreed, agreedAt — 약관 개정 시 재동의를 추적할 수 있는 이력형 구조
FanProfile 팬 모드 프로필 nickname(20자), profileImageUrl. 사용자당 1개
BandMemberProfile 밴드 모드 프로필(연주자 정체성) nickname(30자), part(VOCAL · GUITAR · BASS · KEYBOARD · DRUM · ETC), active. BandMember가 이걸 참조해 밴드별 정체성을 붙입니다

밴드 · 팬 관계 · 추천

밴드의 기본 정보와 멤버 구성·초대·음원 링크를 관리하고, 팬의 팔로우·클릭 상호작용을 기록해 밴드 추천에 활용하는 도메인입니다. 앞의 5개는 서비스 트랜잭션 테이블이고, 뒤의 3개(BandInteraction·BandSimilarity·BandRecommendationLog)는 추천 파이프라인이 읽고 쓰는 분석·배치 계열 테이블로 성격이 다릅니다.

erDiagram
    User ||--o{ Band : "소유"
    User ||--o{ BandMember : "가입"
    User ||--o{ Follow : "팔로우"
    User ||--o{ BandInteraction : "클릭 기록"
    User ||--o{ BandRecommendationLog : "추천 수신"
    Band ||--o{ BandMember : "멤버 구성"
    Band ||--o{ BandInviteLink : "초대 링크"
    Band ||--o{ MusicLink : "음원 링크"
    Band ||--o{ Follow : "팔로우 대상"
    Band ||--o{ BandInteraction : "상호작용 대상"
    Band ||--o{ BandRecommendationLog : "추천 대상"
    Band ||--o{ BandSimilarity : "기준 밴드"
    Band ||--o{ BandSimilarity : "유사 밴드"
    BandMemberProfile ||--o{ BandMember : "프로필 연결"
    Band ||--o{ Post : "게시물"
    Band ||--o{ Performance : "공연"
    Band ||--o{ AudioStream : "라이브 방송"
    Band ||--o{ SessionRecruitment : "세션 모집"
테이블 역할 핵심 컬럼 · 특징
Band 밴드 기본 정보 ownerId(리더), name, genre 13종, region 18종, profileImageUrl, description(1000자) — description이 ML 유사도 계산의 원천 텍스트입니다
BandMember 밴드–사용자 소속 매핑 bandMemberProfileId(NULL 허용), status(INVITED · ACCEPTED, 기본 INVITED), memberType(MEMBER · SESSION)으로 정규 멤버와 세션 참여자를 구분
BandInviteLink 밴드 가입 초대 링크 token(UNIQUE), expiresAt, memberType. (bandId, memberType) 복합 UNIQUE로 정규 멤버용·세션용 링크를 각각 1개만 유지하며, 재발급 시 renew()로 갱신합니다
MusicLink 밴드 음원 플랫폼 링크 spotify_url·youtube_url·soundcloud_url 전용 컬럼 + etc_platform(MELON · GENIE · BUGS · APPLE_MUSIC)과 etc_url·other_url. @OneToOne • unique = true로 밴드당 1개가 강제됩니다
Follow 팬의 밴드 팔로우 bandId • userId 단순 매핑. 검색 인기도 정렬의 기준이 되는 팔로워 수의 원천입니다
BandInteraction 사용자–밴드 클릭 누적 clickCount(기본 0), lastInteractedAt. 이벤트를 매번 쌓지 않고 (userId, bandId) 단위로 요약 갱신하는 구조입니다
BandSimilarity 밴드 간 유사도(ML 배치 적재) bandId · similarBandId(Band 자기참조), score(0.0~1.0), createdAt(배치 적재 시각). ml-server의 ko-sroberta 임베딩 코사인 유사도 결과가 들어옵니다
BandRecommendationLog 밴드 추천 노출 로그 position(추천 순번, 1부터), algorithmVersion(rule-v1 등). 알고리즘 버전별 성과 비교를 위한 기록입니다

게시물 · 미디어

밴드가 작성하는 게시물(Post)과 그에 딸린 이미지·동영상(PostMedia), 자유 태그(PostTag), 댓글(PostComment), 좋아요(PostLike)를 다루는 도메인입니다. 게시물의 작성 주체는 개인이 아니라 밴드이며(Post.bandId), 반응(댓글·좋아요)은 개인 사용자가 남기는 비대칭 구조입니다.

erDiagram
    Band ||--o{ Post : "작성"
    Post ||--o{ PostMedia : "미디어 포함"
    Post ||--o{ PostTag : "태그"
    Post ||--o{ PostComment : "댓글"
    Post ||--o{ PostLike : "좋아요"
    User ||--o{ PostComment : "댓글 작성"
    User ||--o{ PostLike : "좋아요 누름"
    BandMemberProfile ||--o{ PostComment : "밴드 정체성 표시"
테이블 역할 핵심 컬럼 · 특징
Post 밴드가 작성하는 게시물 본문 bandId, type(PHOTO · TEXT · VIDEO — 등록 후 수정 불가, 바꾸려면 삭제 후 재등록), title(필수, 200자), description(1000자, NULL 허용)
PostMedia 게시물에 첨부된 이미지·동영상 목록 mediaUrl(500자), sortOrder(기본 0)로 노출 순서 관리. Post에서 cascade = ALL • orphanRemoval로 관리되어 게시물 삭제 시 함께 삭제됩니다
PostTag 게시물에 붙는 자유 태그 tagName 문자열 직접 저장. 태그 마스터 테이블이 없습니다
PostComment 게시물 댓글 userId(항상 필수), bandMemberProfileId(NULL 허용), content(500자)
PostLike 게시물 좋아요 기록 postId • userId. (postId, userId) 복합 UNIQUE로 중복 좋아요가 DB 레벨에서 차단됩니다

공연

밴드가 개최하는 공연 정보(Performance)와, 팬이 그 공연에 남기는 두 가지 반응을 다룹니다. PerformanceInterest는 "관심 있음"을 표시하는 가벼운 북마크이고, PerformanceParticipation은 실제 참여 의사와 상태를 추적하는 별도 기록으로, 성격이 다른 두 액션이 의도적으로 분리되어 있습니다.

erDiagram
    Band ||--o{ Performance : "주최"
    Performance ||--o{ PerformanceInterest : "관심 등록"
    Performance ||--o{ PerformanceParticipation : "참여 등록"
    Performance ||--o{ PerformanceTag : "태그"
    User ||--o{ PerformanceInterest : "관심 표시"
    User ||--o{ PerformanceParticipation : "참여 표시"

| --- | --- | --- |


세션 모집 · 지원

이 도메인은 스키마에서 가장 복잡하며, 공고 → 프로필 → 지원의 3단 구조로 동작합니다. 밴드가 SessionRecruitment(구인 공고)를 올리고, 연주자는 이와 독립적으로 SessionApplication(자기소개 프로필 카드)을 한 번 만들어두면, 그 프로필로 여러 공고에 반복 지원할 수 있습니다. 실제 지원 행위는 ApplicationSubmission에 별도로 기록되며 상태(PENDING · BAND_ACCEPTED · ACCEPTED · REJECTED · CANCELED)와 확인 시각을 관리합니다. 경력·링크·활동이력은 지원 건이 아니라 프로필에 달려 있어 모든 지원에 공용으로 재사용됩니다.

erDiagram
    Band ||--o{ SessionRecruitment : "공고 등록"
    User ||--o{ SessionApplication : "프로필 작성"
    SessionApplication ||--o{ ApplicationSubmission : "지원 제출"
    SessionRecruitment ||--o{ ApplicationSubmission : "지원 접수"
    SessionApplication ||--o{ SessionApplicationCareer : "경력 등록"
    SessionApplication ||--o{ SessionApplicationLink : "포트폴리오 등록"
    SessionApplication ||--o{ SessionApplicationActivities : "활동이력 등록"
    SessionRecruitment ||--o{ SessionRecruitmentInterest : "관심 등록"
    User ||--o{ SessionRecruitmentInterest : "관심 표시"
    SessionRecruitment ||--o{ SessionRecruitmentView : "조회 기록"
    User ||--o{ SessionRecruitmentView : "공고 조회"
    User ||--o{ SessionRecruitmentSearchKeyword : "검색어 기록"
    User ||--o| SessionBasicProfile : "기본 프로필"
    SessionRecruitment ||--o{ ChatRoom : "공고 문의"
    SessionApplication ||--o{ ChatRoom : "프로필 문의"

| --- | --- | --- |


라이브 스트리밍

MediaMTX 기반 실시간 오디오 방송의 도메인입니다. AudioStream을 중심으로 공동 호스팅 참여(StreamMember), 다시보기(StreamReplay), 시작 알림 신청(LiveAlarm), 그리고 방송 단위 차단·신고(UserBlock, ReportHistory)가 연결됩니다.

erDiagram
    AudioStream ||--o{ StreamMember : "공동 호스팅 참여"
    AudioStream ||--o{ StreamReplay : "다시보기 생성"
    AudioStream ||--o{ LiveAlarm : "알림 신청"
    AudioStream ||--o{ UserBlock : "방송 단위 차단"
    AudioStream ||--o{ ReportHistory : "방송 단위 신고"
    User ||--o{ AudioStream : "방송 진행"
    User ||--o{ StreamMember : "공동 호스트 참여"
    User ||--o{ LiveAlarm : "알림 신청자"
    User ||--o{ UserBlock : "차단자"
    User ||--o{ UserBlock : "피차단자"
    User ||--o{ ReportHistory : "신고자"
    User ||--o{ ReportHistory : "피신고자"
    Band ||--o{ AudioStream : "소속 밴드 참조"

| --- | --- | --- |


채팅

ChatRoom은 독립적으로 생성되는 일반 채팅방이 아니라, 세션 모집이나 구직 프로필에서 파생되는 1:1 대화방입니다. 즉 채팅은 독립된 기능이 아니라 매칭의 후속 수단으로 설계되어 있습니다.

erDiagram
    ChatRoom ||--o{ ChatMessage : "메시지 전송"
    SessionRecruitment ||--o{ ChatRoom : "모집글 기반 개설"
    SessionApplication ||--o{ ChatRoom : "프로필 기반 개설"
    User ||--o{ ChatRoom : "발신자"
    User ||--o{ ChatRoom : "수신자"
    User ||--o{ ChatMessage : "메시지 발신자"

| --- | --- | --- |