<aside> 📌
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 : "메시지 발신자"
| --- | --- | --- |