
🧠 AI 브레인스토밍 플랫폼 개발 일지 - Day 2
날짜: 2024년 11월 21일
진행자: AI 코칭 + 개발자
목표: ERD 설계 및 JPA 엔티티 구현
📝 오늘의 진행 사항
1️⃣ ERD 설계 (손그림 스케치)
- ✅ 4개 테이블 구조 확정
- User (사용자)
- Idea (아이디어)
- Inquiry (문의)
- BaseEntity (공통 필드)
ERD 구조:
User (사용자)
├─ PK userId
├─ UK email
├─ username
├─ provider (OAuth2)
├─ providerId
├─ role
└─ createdAt, updatedAt (상속)
Idea (아이디어)
├─ PK ideaId
├─ FK userId → User
├─ title
├─ content (JSON)
├─ purpose
└─ createdAt, updatedAt (상속)
Inquiry (문의)
├─ PK inquiryId
├─ FK userId → User
├─ title
├─ content
├─ status
├─ reply
└─ createdAt, updatedAt (상속)
BaseEntity (추상 클래스)
├─ createdAt
└─ updatedAt
2️⃣ DDD 패키지 구조 생성
src/main/java/com/brainstorming/brainstorming_platform/
├── domain/
│ ├── user/
│ │ └── entity/
│ │ ├── User.java
│ │ ├── LoginProvider.java (Enum)
│ │ └── MyRole.java (Enum)
│ ├── idea/
│ │ └── entity/
│ │ └── Idea.java
│ └── inquiry/
│ └── entity/
│ ├── Inquiry.java
│ └── InquiryStatus.java (Enum)
├── global/
│ └── BaseEntity.java
└── BrainstormingPlatformApplication.java
DDD 구조 선택 이유:
- 도메인별 응집도 높음
- 기능 추가/삭제 용이
- 각 도메인 독립적 관리
3️⃣ BaseEntity 구현 (공통 필드 상속)
최종 코드:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
핵심 Annotation:
@MappedSuperclass: 테이블 생성 안 함, 필드만 상속@EntityListeners: JPA Auditing 리스너 등록@CreatedDate: 생성 시간 자동 입력@LastModifiedDate: 수정 시간 자동 갱신
4️⃣ Enum 클래스 작성
LoginProvider (OAuth2 제공자):
public enum LoginProvider {
GOOGLE, KAKAO, NAVER
}
MyRole (사용자 권한):
public enum MyRole {
ADMIN, USER
}
InquiryStatus (문의 상태):
public enum InquiryStatus {
PENDING, // 답변 대기
ANSWERED // 답변 완료
}
5️⃣ User 엔티티 구현
최종 코드:
@Entity
@Table(name = "users")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String email;
private String username;
@Enumerated(EnumType.STRING)
private LoginProvider provider;
private String providerId;
@Enumerated(EnumType.STRING)
private MyRole role;
}
주요 Annotation 설명:
@Entity: JPA 엔티티 선언@Table(name = "users"): MySQL 예약어 충돌 방지@Id: PK 지정@GeneratedValue(IDENTITY): Auto Increment@Enumerated(EnumType.STRING): Enum을 문자열로 저장
6️⃣ Idea 엔티티 구현
최종 코드:
@Entity
@Table(name = "ideas")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Idea extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ideaId;
private Long userId; // FK (나중에 연관관계 추가)
private String title;
private String content; // JSON 형식으로 저장
private String purpose; // Q1 답변 (선택사항)
}
설계 결정:
- status 필드 제외 (CRUD만 지원, 상태 관리 불필요)
- content는 TEXT 타입으로 JSON 저장
- purpose는 브레인스토밍 Q1 답변 저장용
7️⃣ Inquiry 엔티티 구현
최종 코드:
@Entity
@Table(name = "inquiries")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Inquiry extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long inquiryId;
private Long userId; // FK
private String title;
private String content;
@Enumerated(EnumType.STRING)
private InquiryStatus status;
private String reply; // 관리자 답변
}
8️⃣ JPA Auditing 활성화
Application.java 수정:
@EnableJpaAuditing // JPA Auditing 활성화
@SpringBootApplication
public class BrainstormingPlatformApplication {
public static void main(String[] args) {
SpringApplication.run(BrainstormingPlatformApplication.class, args);
}
}
Auditing 작동 원리:
1. @EnableJpaAuditing → JPA Auditing 켜기
2. @EntityListeners → 감시자 등록
3. @CreatedDate, @LastModifiedDate → 자동 시간 입력
9️⃣ H2 콘솔에서 테이블 확인
실행 결과:
-- 생성된 테이블들
USERS (user_id, email, username, provider, provider_id, role, created_at, updated_at)
IDEAS (idea_id, user_id, title, content, purpose, created_at, updated_at)
INQUIRIES (inquiry_id, user_id, title, content, status, reply, created_at, updated_at)
✅ 모든 테이블 정상 생성!
✅ BaseEntity의 createdAt, updatedAt 필드 상속 확인!
💡 주요 학습 내용
1. Entity vs DTO 개념 정리
Entity (지금 만든 것)
@Entity
@Getter // Entity에도 Getter 필요!
public class User extends BaseEntity {
private Long userId;
private String email;
}
역할:
- DB 테이블과 1:1 매핑
- 비즈니스 로직 내부에서 사용
- JPA가 데이터 읽을 때 Getter 필요
DTO (나중에 추가)
@Getter
@Setter
public class UserResponseDto {
private Long userId;
private String email;
// 필요한 필드만!
}
역할:
- API 요청/응답
- Controller ↔ Service 데이터 전달
- 외부와 통신할 때 사용
실제 흐름
1. Client → Controller: DTO (요청)
2. Controller → Service: DTO → Entity 변환
3. Service → Repository: Entity (저장)
4. Repository → Service: Entity (조회)
5. Service → Controller: Entity → DTO 변환
6. Controller → Client: DTO (응답)
왜 Entity에 @Getter가 필요한가?
- JPA가 데이터 읽을 때
- Service에서 Entity → DTO 변환할 때
- 로그/디버깅할 때
왜 Entity에 @Setter는 안 붙이나?
// ❌ Setter 사용 - 무분별한 변경 가능
user.setRole(MyRole.ADMIN); // 위험!
// ✅ 비즈니스 메서드 - 검증 로직 포함
user.updateEmail(newEmail); // 안전!
2. long vs Long
| 타입 | 설명 | 사용 |
|---|---|---|
long |
기본형, null 불가 | ❌ Entity에서 비권장 |
Long |
객체형, null 가능 | ✅ Entity에서 권장 |
이유: JPA는 null을 다뤄야 하는 경우가 많음
3. @Enumerated(EnumType.STRING)
@Enumerated(EnumType.ORDINAL) // ❌ 숫자로 저장 (0, 1, 2)
@Enumerated(EnumType.STRING) // ✅ 문자열로 저장 ("GOOGLE")
STRING을 써야 하는 이유:
// ORDINAL 사용 시 문제
public enum LoginProvider {
GOOGLE, // 0
KAKAO, // 1
NAVER // 2
}
// 나중에 순서 변경하면?
public enum LoginProvider {
NAVER, // 0 ← 기존 GOOGLE 데이터와 충돌!
GOOGLE, // 1 ← 기존 KAKAO 데이터와 충돌!
KAKAO // 2
}
// STRING 사용하면 안전
DB: "GOOGLE", "KAKAO", "NAVER" ← 순서 바뀌어도 OK
4. Auditing 3종 세트 (자동 시간 관리)
세트 구성
// 1. BaseEntity
@EntityListeners(AuditingEntityListener.class)
@CreatedDate
@LastModifiedDate
// 2. Application
@EnableJpaAuditing
// 3. 작동!
User user = new User(...);
userRepository.save(user);
// createdAt, updatedAt 자동 입력!
작동 원리
@EnableJpaAuditing (켜기)
↓
@EntityListeners (감시자 등록)
↓
save() 호출 감지
↓
@CreatedDate, @LastModifiedDate (자동 입력)
5. Lombok Annotation 정리
| Annotation | 생성되는 것 | 목적 |
|---|---|---|
@NoArgsConstructor |
기본 생성자 | JPA 필수 |
@AllArgsConstructor |
전체 필드 생성자 | 테스트/편의 |
@Getter |
getter 메서드 | 필드 접근 |
@Setter |
setter 메서드 | ⚠️ Entity에 비권장 |
@Builder |
빌더 패턴 | 가독성 좋은 객체 생성 |
Entity에서 권장하는 조합:
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class User {
// ...
}
🤔 의사결정 과정
Q1. BaseEntity를 왜 상속으로?
선택: extends BaseEntity ✅
이유:
- 모든 엔티티에 createdAt, updatedAt 필요
- 코드 중복 제거 (DRY 원칙)
- 자동 시간 관리 (Auditing)
비교:
// ❌ 상속 안 쓰면
public class User {
private LocalDateTime createdAt; // 중복
private LocalDateTime updatedAt; // 중복
}
public class Idea {
private LocalDateTime createdAt; // 중복
private LocalDateTime updatedAt; // 중복
}
// ✅ 상속 사용
public abstract class BaseEntity {
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
public class User extends BaseEntity {}
public class Idea extends BaseEntity {}
Q2. Idea에 status 필드 필요?
선택: 불필요 ❌
이유:
- 브레인스토밍 → 완성된 아이디어만 저장
- 임시 저장 기능 없음
- 공개/비공개 구분 없음
- CRUD만 지원
status가 필요한 경우:
- 임시 저장 (DRAFT)
- 승인 프로세스 (PENDING, APPROVED)
- 공개/비공개 (PUBLISHED, PRIVATE)
Q3. InquiryStatus 어떻게?
선택: 2가지 상태 ✅
public enum InquiryStatus {
PENDING, // 답변 대기
ANSWERED // 답변 완료
}
이유:
- 간단하고 명확
- 확장 가능 (CLOSED 등 나중에 추가)
- NOTREPLY → PENDING (더 명확한 네이밍)
Q4. DTO는 지금 만들어야 하나?
선택: 나중에 (Controller 만들 때) ✅
이유:
- Entity는 DB 매핑용
- DTO는 API 통신용
- 단계별 학습 (지금은 Entity 집중)
개발 순서:
1. Entity 완성 (지금) ✅
2. Repository 생성 (다음)
3. Service 생성
4. Controller 생성 (이때 DTO 추가)
Q5. @Getter는 Entity에 필요?
선택: 필수! ✅
이유:
// 1. JPA가 사용
user.getEmail()
// 2. DTO 변환 시
new UserResponseDto(user.getUserId(), user.getEmail())
// 3. 비즈니스 로직
if (user.getRole() == MyRole.ADMIN) { ... }
🐛 트러블슈팅
Issue 1: BaseEntity를 필드로 포함
문제:
public class User {
BaseEntity baseentity; // ❌ 필드로 포함
}
원인: 상속 개념 착각
해결:
public class User extends BaseEntity { // ✅ 상속
// createdAt, updatedAt 자동 포함
}
교훈: Java 상속은 extends 키워드!
Issue 2: Idea의 @Id 위치 오류
문제:
@Entity
public class Idea {
@Id // ❌ userId가 PK?
private Long userId;
private Long ideaId; // PK인데 @Id 없음!
}
원인: PK와 FK 혼동
해결:
@Entity
public class Idea {
@Id // ✅ ideaId가 PK
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ideaId;
private Long userId; // FK
}
교훈: 각 테이블은 자신의 고유 ID(PK) 필요!
Issue 3: @Entity만 붙였는데 에러
문제: "No identifier specified for entity"
원인: @Id 없음
해결:
@Entity
public class User {
@Id // 필수!
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
}
교훈: @Entity에는 반드시 @Id 필요!
Issue 4: Auditing 작동 안 함
문제: createdAt, updatedAt이 null
원인: @EnableJpaAuditing 누락
해결:
@EnableJpaAuditing // ← 추가!
@SpringBootApplication
public class BrainstormingPlatformApplication {
}
교훈: Auditing은 3종 세트가 모두 필요!
📊 현재 상태
프로젝트 진행률: ████████░░ 30%
완료:
✅ 프로젝트 생성 (Day 1)
✅ 개발 환경 설정 (Day 1)
✅ ERD 설계 (Day 2)
✅ Entity 클래스 작성 (Day 2)
✅ JPA Annotation 설정 (Day 2)
✅ Auditing 설정 (Day 2)
✅ H2 테이블 확인 (Day 2)
진행 중:
⏳ 없음
예정:
📋 연관관계 매핑 (@ManyToOne, @OneToMany)
📋 Repository 생성
📋 간단한 CRUD 테스트
🎯 다음 할 일 (Day 3)
필수
- 연관관계 매핑
- User ↔ Idea (1:N)
- User ↔ Inquiry (1:N)
@ManyToOne @JoinColumn(name = "user_id") private User user;- Repository 인터페이스 생성
public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); }- 간단한 테스트
- CommandLineRunner로 데이터 삽입
- 조회 테스트
선택 (시간 되면)
- Service 계층 구조 설계
- 기본 CRUD 로직 작성
🔗 참고 자료
💬 회고
😊 잘한 점
- ERD를 손으로 먼저 그려서 구조 명확화
- DDD 패키지 구조로 확장성 확보
- Entity 설계 시 발생한 실수들을 즉시 수정하며 학습
- DTO vs Entity 개념 명확히 정리
- Auditing 자동 시간 관리 적용
😅 아쉬운 점
- 처음에 상속 개념 혼동 (필드 vs extends)
- @Id 위치 실수 (userId vs ideaId)
- DTO를 지금 만들어야 하나 고민
🎓 배운 점
- 상속 활용: BaseEntity로 코드 중복 제거
- Auditing: @CreatedDate, @LastModifiedDate로 자동 시간 관리
- Enum 저장: EnumType.STRING이 안전한 이유
- Entity vs DTO: 역할 구분과 사용 시점
- long vs Long: JPA에서는 Long 권장
- @Getter 필요성: Entity에도 Getter 필수인 이유
💡 깨달은 점
- 단계별로 학습하는 게 이해도가 높음
- 실수를 통해 개념이 더 명확해짐
- 문서화(ERD)의 중요성
- DDD 구조의 장점 (도메인별 독립성)
🚀 Day 3에서 만나요!
다음에는 연관관계 매핑과 Repository를 만들어보겠습니다! 💪
'그래서 일단 프로젝트 > 개인프로젝트-IdeaMaker(클로드 작성본 md 모음)' 카테고리의 다른 글
| 기록 6 . 파이썬 모듈 JAVA 백엔드에 연결하기 .. (1) | 2025.12.01 |
|---|---|
| 기록 5 . 컨트롤러 계층 완성 및 테스트완료 (0) | 2025.11.27 |
| 기록 4 . DTO 작성 및 Test 코드 작성 단계 (0) | 2025.11.26 |
| 기록 3 . Java Service 계층과 Repository 계층 작성 (0) | 2025.11.25 |
| 기록 1 . 기획 클로드 코드와 함께 Spring프로젝트 생성과 의존성생성 (0) | 2025.11.19 |