🧠 AI 브레인스토밍 플랫폼 개발 일지 - Day 3~4
날짜: 2024년 11월 21일 (저녁)
진행자: 개발자 + AI 코칭
목표: Repository 및 Service 계층 구현
📝 오늘의 진행 사항
🎯 총 작업량
- Repository 3개 작성 (30분)
- Service 3개 작성 (1시간)
- 코드 리뷰 및 개념 학습 (1시간 30분)
총 소요 시간: 약 3시간
1️⃣ Repository 계층 구현
📁 생성한 파일
domain/
├── user/repository/UserRepository.java
├── idea/repository/IdeaRepository.java
└── inquiry/repository/InquiryRepository.javaUserRepository
public interface UserRepository extends JpaRepository<User, Long> {
// 이메일로 사용자 찾기 (OAuth 로그인용)
Optional<User> findByEmail(String email);
// 이메일 존재 여부 확인 (중복 체크용)
boolean existsByEmail(String email);
}
핵심 메서드:
findByEmail(): OAuth 로그인 시 기존 사용자 확인existsByEmail(): 회원가입 시 중복 체크
IdeaRepository
public interface IdeaRepository extends JpaRepository<Idea, Long> {
// 특정 사용자의 모든 아이디어 조회
List<Idea> findByUserId(Long userId);
// 특정 사용자의 아이디어 개수
long countByUserId(Long userId);
}
핵심 메서드:
findByUserId(): 마이페이지에서 내 아이디어 목록 표시countByUserId(): 아이디어 개수 표시
InquiryRepository
public interface InquiryRepository extends JpaRepository<Inquiry, Long> {
// 특정 사용자의 문의 목록
List<Inquiry> findByUserId(Long userId);
// 상태별 문의 목록 (관리자용)
List<Inquiry> findByStatus(InquiryStatus status);
// 특정 사용자의 상태별 문의
List<Inquiry> findByUserIdAndStatus(Long userId, InquiryStatus status);
}
핵심 메서드:
findByUserId(): 내 문의 목록findByStatus(): 관리자가 미답변 문의 확인findByUserIdAndStatus(): 사용자별 필터링
2️⃣ Service 계층 구현
📁 생성한 파일
domain/
├── user/service/UserService.java
├── idea/service/IdeaService.java
└── inquiry/service/InquiryService.javaIdeaService (5개 메서드)
@Service
@RequiredArgsConstructor
public class IdeaService {
private final IdeaRepository ideaRepository;
/**
* 아이디어 저장
*/
public Idea save(Idea idea) {
return ideaRepository.save(idea);
}
/**
* ID로 아이디어 조회
*/
public Idea findById(Long ideaId) {
return ideaRepository.findById(ideaId)
.orElseThrow(() -> new RuntimeException("아이디어를 찾을 수 없습니다."));
}
/**
* 특정 사용자의 모든 아이디어 조회
*/
public List<Idea> findByUserId(Long userId) {
return ideaRepository.findByUserId(userId);
}
/**
* 아이디어 삭제
*/
public void delete(Long ideaId) {
ideaRepository.deleteById(ideaId);
}
/**
* 사용자의 아이디어 개수
*/
public long countByUserId(Long userId) {
return ideaRepository.countByUserId(userId);
}
}
InquiryService (4개 메서드)
@Service
@RequiredArgsConstructor
public class InquiryService {
private final InquiryRepository inquiryRepository;
/**
* 문의사항 저장
*/
public Inquiry save(Inquiry inquiry) {
return inquiryRepository.save(inquiry);
}
/**
* ID로 문의사항 조회
*/
public Inquiry findById(Long inquiryId) {
return inquiryRepository.findById(inquiryId)
.orElseThrow(() -> new RuntimeException("문의사항을 찾을 수 없습니다."));
}
/**
* 특정 사용자의 모든 문의 조회
*/
public List<Inquiry> findByUserId(Long userId) {
return inquiryRepository.findByUserId(userId);
}
/**
* 문의사항 삭제
*/
public void delete(Long inquiryId) {
inquiryRepository.deleteById(inquiryId);
}
}
UserService (4개 메서드)
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
/**
* 사용자 저장 (OAuth 자동 가입용)
*/
public User save(User user) {
return userRepository.save(user);
}
/**
* ID로 사용자 조회
*/
public User findById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
}
/**
* 이메일로 사용자 조회
*/
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
/**
* 사용자 삭제 (회원 탈퇴)
*/
public void delete(Long userId) {
userRepository.deleteById(userId);
}
}
💡 주요 학습 내용
1. Repository 인터페이스 자동 구현
Spring Data JPA의 마법:
// 이것만 작성하면
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
// 이런 메서드들이 자동 생성!
save(user) // INSERT / UPDATE
findById(1L) // SELECT * WHERE id = 1
findAll() // SELECT *
delete(user) // DELETE
count() // COUNT(*)
existsById(1L) // 존재 여부
findByEmail(email) // SELECT * WHERE email = ?
Query Method 규칙:
findBy+ 필드명: 조회countBy+ 필드명: 개수existsBy+ 필드명: 존재 여부deleteBy+ 필드명: 삭제
2. Service 계층의 역할
Repository vs Service:
| Repository | Service |
|---|---|
| DB 접근 | 비즈니스 로직 |
| CRUD 자동 생성 | 검증, 변환, 조합 |
| 인터페이스만 | 구현 필요 |
Service 패턴:
Service 메서드 = Repository 메서드 + 비즈니스 로직
// 간단한 경우
public Idea save(Idea idea) {
return repository.save(idea);
}
// 복잡한 경우 (나중에)
public Idea save(Idea idea) {
// 1. 검증
validate(idea);
// 2. 저장
Idea saved = repository.save(idea);
// 3. 알림 등 추가 작업
notifyUser(saved);
return saved;
}
3. Optional 사용 전략
언제 예외? 언제 Optional?
예외 처리 (orElseThrow):
// 없으면 시스템 오류
public User findById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("사용자 없음"));
}
사용 시나리오:
- 마이페이지 조회 (사용자 반드시 존재)
- 아이디어 상세 (아이디어 반드시 존재)
- 권한 확인 (사용자 반드시 존재)
Optional 반환:
// 없어도 정상 흐름
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
사용 시나리오:
- OAuth 로그인 (첫 로그인 시 없음)
- 이메일 중복 체크 (없는 게 정상)
- 검색 기능 (검색 결과 없을 수 있음)
패턴 비교:
// 패턴 1: 없으면 예외
public User findById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new RuntimeException("Not Found"));
}
// 패턴 2: Optional 반환
public Optional<User> findByEmail(String email) {
return repository.findByEmail(email);
}
// 패턴 3: 기본값 제공
public User findByIdOrDefault(Long id) {
return repository.findById(id)
.orElse(createGuestUser());
}
4. Primary Key vs Foreign Key 차이
PK (Primary Key) - ideaId
// PK로 조회 → 1개만!
Idea idea = ideaService.findById(1L);
// DB:
idea_id | title
--------|-------
1 | "AI 앱" ← 딱 1개만 존재!
특징:
- 유일 (UNIQUE)
- 중복 불가
- 조회하면 0개 또는 1개
FK (Foreign Key) - userId
// FK로 조회 → 여러 개!
List<Idea> ideas = ideaService.findByUserId(100L);
// DB:
idea_id | user_id | title
--------|---------|-------
1 | 100 | "AI 앱"
2 | 100 | "챗봇" ← 같은 userId
3 | 100 | "게임" ← 여러 개 가능!
특징:
- 중복 가능
- 여러 개 허용
- 조회하면 0개 이상
5. @RequiredArgsConstructor 이해
Lombok이 자동 생성:
@RequiredArgsConstructor
public class IdeaService {
private final IdeaRepository ideaRepository;
}
// 실제 생성되는 코드:
public class IdeaService {
private final IdeaRepository ideaRepository;
public IdeaService(IdeaRepository ideaRepository) {
this.ideaRepository = ideaRepository;
}
}
장점:
- 생성자 자동 생성
- 의존성 주입 자동 처리
- 코드 간결화
🤔 의사결정 과정
Q1. Repository 메서드는 최소한으로?
결정: 필요한 것만 추가 ✅
이유:
- YAGNI (You Aren't Gonna Need It)
- 나중에 필요하면 추가
- 미리 만들면 유지보수 부담
예시:
// 처음
List<Idea> findByUserId(Long userId);
// 나중에 필요하면
List<Idea> findByUserIdAndTitle(Long userId, String title);
Page<Idea> findByUserId(Long userId, Pageable pageable);
Q2. UserService에 save() 필요?
결정: 필요! ✅
이유:
- OAuth 자동 회원가입
- 테스트 데이터 생성
- 관리자 직접 생성
OAuth 로그인 시나리오:
public User handleOAuthLogin(OAuth2User oauth2User) {
String email = oauth2User.getAttribute("email");
// 기존 사용자 확인
Optional<User> existing = userService.findByEmail(email);
if (existing.isPresent()) {
return existing.get();
} else {
// 첫 로그인 → 자동 회원가입
User newUser = new User(...);
return userService.save(newUser); // save 필요!
}
}
Q3. findById vs findByEmail 패턴 차이?
결정: 비즈니스 의미에 따라 다르게 ✅
findById - 예외 처리:
public User findById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(...);
}
- 의미: "반드시 있어야 함"
- 없으면: 시스템 오류
findByEmail - Optional 반환:
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
- 의미: "있을 수도, 없을 수도"
- 없음: 정상 흐름
🐛 트러블슈팅
Issue 1: findById 파라미터명 혼동
문제:
public Idea findById(Long userId) { // ❌ userId?
원인: 복붙하면서 파라미터명 수정 안 함
해결:
public Idea findById(Long ideaId) { // ✅ ideaId
교훈: 파라미터명은 명확하게!
ideaIdfor IdeainquiryIdfor InquiryuserIdfor User
Issue 2: 주석 복붙 실수
문제:
// InquiryService
/**
* 아이디어 삭제 // ❌ 복붙 실수!
*/
public void delete(Long inquiryId) {
해결:
/**
* 문의사항 삭제 // ✅
*/
public void delete(Long inquiryId) {
교훈: 복붙 후 항상 주석 확인!
Issue 3: countByUserId 타입 혼용
문제:
public Long countByUserId(long userId) { // Long vs long 혼용
해결:
public long countByUserId(Long userId) { // ✅ 일관성
교훈: 타입 일관성 중요!
- 파라미터:
Long(객체형) - 반환:
long(기본형, 개수는 null 없음)
📊 현재 상태
프로젝트 진행률: ████████░░ 40%
완료:
✅ 프로젝트 생성 (Day 1)
✅ H2 데이터베이스 설정 (Day 1)
✅ ERD 설계 (Day 2)
✅ Entity 3개 작성 (Day 2)
✅ JPA Auditing 설정 (Day 2)
✅ Repository 3개 구현 (Day 3) ← 오늘!
✅ Service 3개 구현 (Day 4) ← 오늘!
진행 중:
⏳ 없음
예정:
📋 JUnit 테스트 작성
📋 Controller 작성
📋 Postman API 테스트
📋 OAuth2 로그인 연동
📋 Python FastAPI 연동🎯 다음 할 일 (Day 5)
필수
JUnit 테스트 작성
@SpringBootTest class IdeaServiceTest { @Test void 아이디어_저장_테스트() { // given Idea idea = new Idea(...); // when Idea saved = ideaService.save(idea); // then assertThat(saved.getIdeaId()).isNotNull(); } }간단한 Controller
@RestController @RequestMapping("/api/ideas") public class IdeaController { @PostMapping public Idea create(@RequestBody Idea idea) { return ideaService.save(idea); } }
선택
- Postman으로 API 테스트
- CommandLineRunner로 샘플 데이터 생성
🔗 참고 자료
💬 회고
😊 잘한 점
- 하루에 Repository + Service 모두 완성 (효율적!)
- 각 메서드의 역할 명확히 이해
- Optional 사용 전략 학습
- PK vs FK 개념 정리
- 실수를 통한 학습 (파라미터명, 주석)
😅 아쉬운 점
- 개념 이해에 시간 소요 (Optional, 예외 처리)
- 완벽 이해보다 실습 우선 필요
- 너무 디테일에 집중
🎓 배운 점
- Repository: Spring Data JPA가 자동 구현
- Service: 비즈니스 로직 처리 계층
- Optional vs 예외: 비즈니스 의미에 따라 선택
- PK vs FK: 유일성 vs 중복 가능
- @RequiredArgsConstructor: 생성자 자동 생성
- Query Method: 메서드명으로 쿼리 자동 생성
- Service 패턴: Repository + 비즈니스 로직
💡 깨달은 점
- 완벽 이해보다 빠른 완성과 반복이 중요
- 80% 이해하고 다음 프로젝트로!
- 실무에서도 완벽한 코드는 없음
- 작동하는 코드 → 리팩토링 순서
- 디테일보다 큰 흐름 파악 우선
📈 성장 포인트
- Repository 인터페이스만으로 CRUD 구현 이해
- Service 계층의 역할과 패턴 습득
- Optional 활용법 (언제 반환? 언제 예외?)
- Spring Data JPA Query Method 작성법
- Lombok을 통한 코드 간결화
🚀 Day 5에서 만나요!
테스트 코드와 Controller로 실제 동작하는 API를 만들어봅시다! 💪
'그래서 일단 프로젝트 > 개인프로젝트-IdeaMaker(클로드 작성본 md 모음)' 카테고리의 다른 글
| 기록 6 . 파이썬 모듈 JAVA 백엔드에 연결하기 .. (1) | 2025.12.01 |
|---|---|
| 기록 5 . 컨트롤러 계층 완성 및 테스트완료 (0) | 2025.11.27 |
| 기록 4 . DTO 작성 및 Test 코드 작성 단계 (0) | 2025.11.26 |
| 기록 2 . 엔티티 만들기 ERD까지. (0) | 2025.11.21 |
| 기록 1 . 기획 클로드 코드와 함께 Spring프로젝트 생성과 의존성생성 (0) | 2025.11.19 |