**Spring Boot + JPA 게시판 실습에서 자주 겪는 실수와 올바른 패턴**

Yeeun·2025년 4월 22일
0

SpringBoot

목록 보기
9/46

[Spring Boot/JPA] 게시판 실습에서 자주 겪는 실수와 올바른 CRUD 패턴 총정리

Spring Boot와 JPA로 게시판을 만들면서 겪었던 다양한 실수와,
그 원인, 그리고 올바른 해결 방법을 한 번에 정리합니다.
실제 코딩 예시와 함께, 초보자가 헷갈리기 쉬운 부분을 콕 집어 설명합니다!


1. JPA에서 CRUD, 영속성 컨텍스트란?

JPA의 모든 데이터 조작(CRUD)은 영속성 컨텍스트라는 컨테이너에서 이루어집니다.

  • Create: save() 또는 persist()로 새로운 엔티티를 저장
  • Read: findById(), findAll() 등으로 조회 (1차 캐시 활용)
  • Update: 반드시 기존 엔티티를 조회해서 setter로 값 변경 후 save()
  • Delete: deleteById() 등으로 삭제

영속성 컨텍스트란?

  • 엔티티 객체를 관리하는 일종의 "저장소"
  • 같은 트랜잭션 내에서 동일 엔티티는 항상 같은 객체로 관리됨
  • 변경 감지(Dirty Checking)로 setter로 값만 바꿔도 자동 UPDATE

2. Random 값 생성과 테스트 데이터 삽입

자바에서 1~100 사이의 랜덤 정수 생성:

Random random = new Random();
int randNum = random.nextInt(100) + 1; // 1~100

테스트 데이터 100개 삽입 예시:

@BeforeAll
public void insertData() {
    Random random = new Random();
    for (int i = 0; i  findByTitle("1"); // 제목이 "1"과 정확히 일치하는 것만 조회

제대로 하려면?

List findByTitleContaining(String keyword); // 제목에 keyword가 포함된 게시글 조회
  • "1"이 포함된 모든 게시글을 찾으려면 Containing을 반드시 붙여야 한다.

여러 조건, 정렬까지 하고 싶다면?

List findByTitleContainingOrContentContainingOrderByTitleDescContentDesc(String t, String c);
  • 제목 또는 내용에 키워드가 포함된 게시글을, 제목/내용 내림차순으로 정렬

4. 엔티티 수정(UPDATE)에서 자주 하는 실수

잘못된 코드:

Board updated = boardRepo.findById(board.getSeq()).get().builder()
    .cnt(2L).content("수정").title("수정포스트").build();
boardRepo.save(updated);
  • .builder()는 새 객체를 만드는 것!
    → 기존 데이터가 모두 사라질 수 있음(필드 누락 시 null로 덮어쓰기)

올바른 코드:

Board existing = boardRepo.findById(board.getSeq()).get();
existing.setTitle("수정포스트");
existing.setContent("수정");
existing.setCnt(2L);
boardRepo.save(existing);
  • 반드시 조회 → setter로 수정 → save() 순서로!
  • 부분 업데이트, 유연한 처리가 필요하면 DTO를 활용

5. REST API 컨트롤러 작성과 호출 방법

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private BoardRepository boardRepo;

    @GetMapping("/board")
    public List getBoards() {
        return boardRepo.findAll();
    }

    @PostMapping("/board")
    public Board postBoard(@RequestBody Board board) {
        return boardRepo.save(board);
    }

    @PutMapping("/board")
    public Board putBoard(@RequestBody Board board) {
        Board existing = boardRepo.findById(board.getSeq()).orElseThrow();
        existing.setTitle(board.getTitle());
        existing.setContent(board.getContent());
        existing.setCnt(board.getCnt());
        return boardRepo.save(existing);
    }

    @DeleteMapping("/board/{seq}")
    public void deleteBoard(@PathVariable Long seq) {
        boardRepo.deleteById(seq);
    }
}
  • GET /test/board : 전체 게시글 조회
  • POST /test/board : 게시글 등록 (JSON body 필요)
  • PUT /test/board : 게시글 수정 (JSON body 필요, seq 필수)
  • DELETE /test/board/{seq} : 게시글 삭제

6. findById()는 왜 Optional인가?

  • findById(seq)Optional를 반환
  • 값이 없을 때 null 대신 안전하게 처리 가능
  • 실제 객체를 꺼내려면 .get(), 또는 .orElseThrow() 등 사용

7. 실전 팁 요약

  • Builder는 INSERT용, UPDATE는 조회 후 setter로!
  • 쿼리 메서드는 네이밍 규칙에 맞게 작성 (Containing, OrderBy 등)
  • 테스트 데이터는 @BeforeAll, @TestInstance 등으로 관리
  • REST API는 HTTP 메서드와 URL을 명확히 구분
  • Optional을 적극 활용해 NPE 방지

이 글이 Spring Boot + JPA 실습 중 헷갈리는 부분을 한 번에 정리하는 데 도움이 되길 바랍니다!
더 궁금한 점은 댓글로 남겨주세요 😊


Spring Data JPA에서 JpaRepository를 상속받은 인터페이스에는 @Repository 어노테이션이 필요하지 않습니다. 이는 Spring Data JPA의 내부 메커니즘 때문입니다. 아래에서 자세히 설명드리겠습니다.


1. 왜 @Repository가 필요 없을까?

(1) 자동 빈 등록

  • JpaRepository를 상속받는 인터페이스는 Spring이 자동으로 빈(Bean)으로 등록합니다.
  • Spring Data JPA는 @EnableJpaRepositories가 활성화되면,
    JpaRepository를 상속받은 모든 인터페이스를 스캔하여 프록시 객체를 생성하고 빈으로 등록합니다.
    (예: BoardRepositoryBoardRepositoryImpl 프록시 생성)

(2) 예외 변환 자동화

  • @Repository의 주요 기능 중 하나는 JPA 예외를 Spring의 DataAccessException 계층으로 변환하는 것입니다.
  • JpaRepository를 상속하면 예외 변환이 자동으로 처리되므로 별도 어노테이션이 필요 없습니다.

2. 직접 작성한 Repository에는 @Repository가 필요할까?

  • 네, 필요합니다!
    직접 구현한 클래스(예: CustomBoardRepositoryImpl)가 있다면,
    @Repository를 붙여야 Spring이 빈으로 등록합니다.
  • 단, JpaRepository를 상속한 인터페이스가 아닌 경우에 한합니다.

3. 예시 코드 비교

(1) JpaRepository 상속 시 (어노테이션 불필요)

// ✅ @Repository 없어도 정상 동작
public interface BoardRepository extends JpaRepository {
    List findByTitleContaining(String keyword);
}

(2) 직접 구현한 Repository (어노테이션 필요)

@Repository  // ✅ 필수
public class CustomBoardRepositoryImpl implements CustomBoardRepository {
    // 사용자 정의 메서드 구현
}

4. 정리

상황@Repository 필요 여부
JpaRepository 상속❌ 불필요
직접 구현한 Repository 클래스✅ 필요

5. 참고: Spring Data JPA의 내부 동작

  • @EnableJpaRepositories
    이 어노테이션은 프로젝트 시작 시 JpaRepository를 상속한 인터페이스를 스캔합니다.
    JpaRepositoriesRegistrar 클래스가 이 과정을 처리합니다[8].
  • 예외 변환
    JpaRepository의 프록시 객체가 자동으로 PersistenceExceptionTranslationPostProcessor와 연동되어 예외를 변환합니다.

결론:
JpaRepository를 상속했다면 @Repository를 생략해도 됩니다!
이는 Spring Data JPA가 이미 모든 것을 자동화했기 때문입니다 😊

Citations:
[1] https://jaehoney.tistory.com/250
[2] https://velog.io/@astrataraxia/JPA-Repository-%EC%83%81%EC%86%8D%EC%9D%84-%EB%B0%9B%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0
[3] https://learngoeson.tistory.com/63
[4] https://velog.io/@dltkdgns3435/SpringBoot-spring-data-jpa-%EC%82%AC%EC%9A%A9%EC%8B%9C-Repository-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%80-%EA%BC%AD-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80
[5] https://peonyf.tistory.com/entry/Spring-%EA%B2%8C%EC%8B%9C%ED%8C%90-Repository-%EC%9D%84-%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-QnA
[6] https://ohzzi.io/jpa-repository-no-repository-yes/
[7] https://creampuffy.tistory.com/179
[8] https://parkadd.tistory.com/106
[9] https://ttl-blog.tistory.com/1124
[10] https://sudo-minz.tistory.com/147

0개의 댓글