GDGOC 6th BE Week 4 - JPA 변경 감지와 트랜잭션

2026. 10. 23. 18:08·배움 일기

지난주에는 Spring이 객체를 관리하고 연결하는 방식과, JPA로 DB에서 데이터를 가져오는 흐름을 공부했습니다. 이번 주에는 게시판 코드로 넘어와 게시글 조회·수정·삭제를 살펴보았습니다. 객체의 값만 바꿨는데 어떻게 DB에도 저장되는지, 그 사이에서 JPA와 Spring이 하는 일을 중심으로 정리합니다.

 

이번 내용은 Week 4 실습 자료와 관련 학습 글을 읽으며 이해한 내용을 바탕으로 합니다. 실습 자료에 나온 응답과 SQL의 차이도 함께 정리하면서, Java 객체의 변경과 DB 저장을 구분해 보려고 합니다.

1. 게시판 코드에서 다시 만난 계층 구조와 DTO

지난주 자기소개 API에서는 Controller가 요청을 받고, Service가 필요한 처리를 수행한 뒤, Repository에서 이름을 가져왔습니다. 게시판도 이 구조를 그대로 사용합니다. 이름 하나를 조회하던 코드에서 게시글의 제목과 내용을 다루는 코드로 대상이 달라진 셈입니다.

 

이번 실습 자료의 환경은 JDK 21과 MySQL 8.4 LTS입니다. H2 대신 MySQL의 gdg_board DB를 사용하고, DB 계정은 DB_USERNAME과 DB_PASSWORD 환경변수로 전달합니다. 설정 파일도 application.yml로 바뀌지만, 연결 정보는 spring.datasource, JPA 설정은 spring.jpa 아래에 둔다는 점은 같습니다. 저장소가 바뀌어도 Controller–Service–Repository의 역할 구분은 이어집니다.

 

코드를 읽으면서 패키지 이름부터 조금 헷갈렸습니다. post는 게시판 전체가 아니라 게시글 하나를 뜻하고, 게시판은 board에 해당합니다. 현재 구조의 post는 게시글 관련 코드를 묶는 패키지입니다.

 

dto와 domain도 Spring이 특별하게 인식하는 이름이 아니라, 클래스의 역할을 구분하려고 붙인 패키지 이름입니다. 이 실습에서는 post/domain 아래에 Post를 둡니다. 다른 프로젝트에서 볼 수 있는 domain/post, domain/member 같은 구조와는 domain이라는 이름을 사용하는 범위가 다릅니다. 폴더 이름만 보고 기능이 정해진다고 생각했던 부분을 다시 정리하게 됩니다.

 

DTO는 처음에 요청을 담는 그릇으로 이해했습니다. 그러다 JSON이나 XML과 비슷한 것인지 궁금했는데, JSON·XML은 데이터의 표현 형식이고 DTO는 프로그램 안에서 데이터를 전달하는 객체라는 차이가 있습니다. 요청 JSON을 Java 객체로 변환한 것이 요청 DTO이고, 응답 DTO도 다시 JSON 같은 형식으로 변환되어 전달됩니다.

PostUpdateRequest 바꾸고 싶은 제목과 내용을 전달하는 요청 DTO
Post 게시글의 현재 상태와 변경 규칙을 가진 Entity
PostResponse 응답에 필요한 값을 담는 DTO

Post가 Entity인 이유도 domain 폴더에 있기 때문이 아니라 @Entity로 선언되어 있기 때문입니다. 요청 DTO가 들어온다고 Post가 저절로 바뀌는 것도 아닙니다. Service가 기존 게시글을 조회하고 Post.update()에 입력값을 전달하는 과정이 필요합니다. 각 클래스에 데이터가 담겨 있어 비슷하게 보였지만, 전달할 값과 실제로 바뀌는 객체를 나누어 생각하니 역할이 조금 더 분명해집니다.

기존 코드의 @RequiredArgsConstructor 역시 새로운 주입 방식은 아닙니다. Lombok이 final 필드 등을 받는 생성자를 만들어 주므로, 지난주 직접 작성했던 생성자 주입을 더 짧게 표현한 것으로 이해합니다.

2. 요청 데이터의 위치와 Controller의 역할

게시글 API에서는 같은 경로라도 HTTP 메서드에 따라 다른 작업으로 연결됩니다. /posts/1이라는 주소만으로는 조회인지 수정인지 알 수 없고, GET·PATCH·DELETE를 함께 봐야 합니다.

기능 요청 성공 응답

게시글 작성 POST /posts 201 Created
게시글 목록 조회 GET /posts 200 OK
게시글 단건 조회 GET /posts/{postId} 200 OK
게시글 부분 수정 PATCH /posts/{postId} 200 OK
게시글 삭제 DELETE /posts/{postId} 204 No Content

요청 데이터를 받는 어노테이션도 입력값의 위치와 연결해 정리합니다.

어노테이션 이번 코드에서 받는 값

@PathVariable /posts/1의 게시글 번호
@RequestParam /posts/search?keyword=스프링의 검색어
@RequestBody 요청 본문의 {"title": "새 제목"} 같은 수정 데이터

수정 API는 이 중 두 가지를 함께 사용합니다. 어떤 게시글인지는 경로에서 받고, 무엇을 바꿀지는 본문에서 받습니다.

// PostController의 수정 메서드
@PatchMapping("/{postId}")
public ResponseEntity<PostResponse> update(@PathVariable Long postId,
                                         @RequestBody PostUpdateRequest request) {
    return ResponseEntity.ok(postService.update(postId, request));
}

클래스에 선언된 @RequestMapping("/posts")와 메서드의 경로가 합쳐져 /posts/{postId}가 됩니다. @RequestBody는 JSON 본문을 메시지 변환기를 통해 PostUpdateRequest 객체로 받게 합니다. 앞에서 헷갈렸던 JSON과 DTO의 관계가 실제 코드에서는 이 부분에 해당합니다. (Spring 요청 본문 처리 문서)

 

반환 타입인 ResponseEntity는 응답 본문과 상태 코드, 헤더를 함께 표현합니다. 지난주에는 문자열을 그대로 반환했는데, 이번에는 성공한 요청도 작성은 201, 조회·수정은 200, 삭제는 204로 구분합니다. 응답 데이터뿐 아니라 처리 결과를 어떤 상태로 전달할지도 Controller의 역할에 들어간다고 이해합니다. (Spring ResponseEntity 문서)

3. 단건 조회·수정·삭제 코드에서 이해한 것

이번 주에는 기존 작성·목록 조회 코드에 단건 조회·수정·삭제를 더하는 흐름을 공부합니다. 세 기능 모두 ID로 기존 게시글을 찾는 부분에서 시작합니다. 단건 조회의 Service 코드는 다음과 같습니다.

public PostResponse findById(Long postId) {
    Post post = postRepository.findById(postId)
            .orElseThrow(() -> new IllegalArgumentException("게시글이 없습니다. id=" + postId));
    return PostResponse.from(post);
}

findById()는 Optional<Post>를 반환합니다. 지난주에는 Optional을 값이 없을 수도 있는 결과로 접했는데, 여기서는 orElseThrow()를 통해 게시글이 없는 경우를 예외로 처리합니다. 조회한 Entity는 PostResponse.from(post)를 거쳐 응답 DTO로 바뀝니다.

 

여기서 놓치기 쉬웠던 부분은 예외를 던지는 것과 HTTP 상태 코드를 정하는 것이 별개의 처리라는 점입니다. 현재 실습 자료에서는 없는 게시글에 IllegalArgumentException을 던지지만, 이를 404로 바꾸는 예외 처리는 아직 없습니다. 따라서 이 단계의 응답은 500이고, 리소스가 없다는 의미에 맞게 404로 응답하는 처리는 이후 학습 내용으로 남습니다.

수정은 요청 DTO와 Entity의 변경 메서드가 연결되는 과정입니다. DTO는 record로 선언되어 있고, 값은 getTitle()이 아니라 title()로 꺼냅니다.

public record PostUpdateRequest(String title, String content) {
}
// Post의 변경 메서드
public void update(String title, String content) {
    if (title != null) {
        this.title = title;
    }
    if (content != null) {
        this.content = content;
    }
}

이 코드에서 제목만 전달하면 내용은 기존 값을 유지합니다. PATCH를 사용해서 자동으로 이렇게 되는 것이 아니라, Post.update()가 null이 아닌 값만 반영하기 때문입니다. 필드를 생략한 경우와 명시적으로 null을 전달한 경우는 모두 변경하지 않는 것으로 처리하고, 빈 문자열은 null이 아니므로 반영합니다. HTTP 메서드의 의미와 실제 코드의 변경 규칙을 따로 생각하게 되는 부분입니다.

Service에서는 기존 게시글을 가져와 이 변경 메서드를 호출합니다.

@Transactional
public PostResponse update(Long postId, PostUpdateRequest request) {
    Post post = postRepository.findById(postId)
            .orElseThrow(() -> new IllegalArgumentException("게시글이 없습니다. id=" + postId));
    post.update(request.title(), request.content());
    return PostResponse.from(post);
}

이 짧은 코드에서 가장 궁금했던 점이 save() 없이 끝난다는 것이었습니다. 응답 DTO에 새 제목을 담는 것은 이해되지만, DB에도 변경 내용이 남는 이유는 객체의 필드를 바꾸는 코드만으로는 설명되지 않습니다. 이 질문이 영속성 컨텍스트와 변경 감지로 이어집니다.

 

삭제는 조회한 Entity를 postRepository.delete(post)에 전달합니다. Service는 쓰기 트랜잭션으로 묶이고, Controller는 ResponseEntity.noContent().build()로 본문 없는 204 응답을 반환합니다. 조회·수정과 마찬가지로 먼저 게시글을 찾기 때문에, 없는 ID를 처리하는 흐름도 공통으로 드러납니다.

 

실습 자료의 확인 과정에는 수정 후 다시 단건 조회를 하고, 삭제 후 목록을 조회하는 과정이 포함되어 있습니다. 처음 요청의 응답뿐 아니라 다음 요청에서도 변경된 상태가 유지되는지를 보는 과정이라는 점이 기억에 남습니다.

4. save() 없는 수정과 영속성 컨텍스트

수정 코드에 대한 설명의 출발점은 조회한 Post가 평범한 Java 객체이면서 동시에 JPA의 관리 대상이라는 점입니다. JPA는 Entity를 영속성 컨텍스트(Persistence Context)에서 관리합니다. 같은 컨텍스트 안에서는 같은 Entity 타입과 식별자에 해당하는 객체의 동일성이 유지됩니다.

 

일반적인 쓰기 트랜잭션에서 Hibernate는 관리 중인 Entity의 상태를 추적합니다. 스냅샷을 사용하는 방식에서는 기존 상태와 현재 상태를 비교해 변경 여부를 판단합니다. 이 과정이 더티 체킹(Dirty Checking)이고, 변경을 SQL로 DB에 동기화하는 과정이 플러시(flush)입니다. (Hibernate 공식 가이드)

 

게시글 코드에 대응시키면 findById()로 관리 대상인 Post를 가져오고, post.update()로 객체의 값을 바꾼 뒤, 플러시 과정에서 UPDATE SQL이 실행됩니다. 트랜잭션이 커밋되면 그 변경이 확정됩니다. save()를 다시 호출하지 않아도 되는 이유를 이 흐름으로 이해합니다.

 

향로님의 더티 체킹 실험 글에서도 저장 메서드를 다시 호출하지 않고 Entity를 수정한 뒤, 테스트와 SQL 로그로 UPDATE 실행을 확인합니다. 설명만 읽는 것보다 객체의 변경, 실행된 SQL, 다시 읽은 값을 연결해 보여 주는 방식이 이해에 도움이 됩니다.

 

실습 자료에서는 현재와 다른 제목을 보내고 SELECT 다음에 UPDATE가 나타나는지 비교합니다. 제목만 바꿔도 SQL에 내용 컬럼이 함께 포함될 수 있다는 점도 정리할 부분입니다. Hibernate의 기본 UPDATE가 변경된 필드만으로 구성되는 것은 아니므로, SQL에 내용 컬럼이 있다는 것과 내용 값이 달라졌다는 것은 같은 의미가 아닙니다.

 

영속성 컨텍스트의 또 다른 특징은 1차 캐시입니다. 같은 트랜잭션 안에서 같은 ID를 두 번 조회하는 예제로 이해합니다.

@Transactional(readOnly = true)
public PostResponse checkFirstLevelCache(Long postId) {
    Post first = postRepository.findById(postId).orElseThrow();
    Post second = postRepository.findById(postId).orElseThrow();
    System.out.println("같은 객체인가? " + (first == second));
    return PostResponse.from(first);
}

이 메서드를 Service 프록시를 통해 호출하고, 해당 Entity가 컨텍스트에 아직 없는 기본 실습 조건이라면 SELECT 한 번과 true를 예상할 수 있습니다. 두 번째 조회는 컨텍스트에 있는 객체를 사용하기 때문입니다. 기준은 같은 영속성 컨텍스트이므로, 서로 다른 HTTP 요청을 두 번 보내는 상황과는 구분합니다.

 

결국 save()를 생략할 수 있다는 설명에는 조건이 붙습니다. 이번 수정 코드는 쓰기 트랜잭션 안에서 조회한, 관리 상태의 Entity를 변경하는 경우입니다. 생성만 한 객체나 컨텍스트에서 분리된 객체까지 값을 바꾸면 자동 저장된다고 이해하지 않도록 이 조건을 함께 기록합니다.

5. 트랜잭션의 경계와 프록시의 역할

변경 감지를 공부하다 보니 수정 메서드 위의 @Transactional도 함께 보게 됩니다. 트랜잭션은 여러 DB 작업을 하나의 단위로 묶고, 변경을 확정하거나 취소할 수 있게 합니다. 계좌 이체의 출금과 입금을 함께 성공시키거나 취소하는 예시가 원자성을 이해하는 데 도움이 됩니다.

 

게시글 수정에서는 조회부터 변경까지가 하나의 Service 작업입니다. **커밋(Commit)**은 이 트랜잭션의 변경을 확정하고, **롤백(Rollback)**은 변경을 취소합니다.

 

Spring의 기본 프록시 방식에서는 Controller가 실제 Service를 바로 호출하는 대신, 주입받은 프록시를 거칩니다. 프록시는 실제 메서드 실행 전후에 트랜잭션 처리를 수행합니다. 지난주에 공부한 “Spring이 객체를 만들고 연결한다”는 내용이 이번에는 호출 앞뒤에 처리를 더하는 방식으로 이어집니다.

 

실제 Service 메서드가 DTO를 만드는 시점과 커밋이 끝나는 시점은 다릅니다. 이 흐름에서는 메서드가 반환된 후 프록시의 커밋 처리를 거쳐 Controller에 결과가 전달됩니다. 메서드 본문에 커밋 코드가 없어도 트랜잭션이 처리되는 이유를 프록시의 역할로 이해합니다. (Spring 트랜잭션 어노테이션 문서)

 

실습 코드에는 클래스의 읽기 전용 설정과 메서드의 쓰기 설정이 함께 있습니다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {
    private final PostRepository postRepository;

    @Transactional
    public PostResponse update(Long postId, PostUpdateRequest request) {
        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new IllegalArgumentException("게시글이 없습니다. id=" + postId));
        post.update(request.title(), request.content());
        return PostResponse.from(post);
    }
}

메서드 수준의 설정이 클래스 수준보다 우선하므로, 위 수정 메서드는 readOnly = false인 쓰기 트랜잭션으로 실행됩니다. 조회에는 클래스의 읽기 전용 설정을 사용하고, 작성·수정·삭제에는 쓰기 설정을 따로 두는 구조입니다. 기본 전파 방식으로 이미 시작된 트랜잭션에 참여한다면 바깥 트랜잭션의 설정도 영향을 준다는 점은 함께 기록합니다. (Spring Data JPA 트랜잭션 문서)

 

또 하나 구분한 것은 플러시와 커밋입니다. 플러시는 SQL을 보내 DB와 동기화하는 과정이고, 커밋은 변경을 확정하는 과정입니다. 플러시 후에도 커밋 전이라면 롤백으로 변경을 취소할 수 있습니다. 플러시는 커밋 직전뿐 아니라 설정과 쿼리에 따라 조회 전에 발생하거나, 명시적인 flush() 호출로 발생할 수도 있습니다. 따라서 UPDATE 로그가 있다는 사실만으로 저장이 최종 확정되었다고 판단할 수는 없습니다. (Hibernate 플러시 설명)

6. 응답과 DB의 값이 다를 수 있다는 점

이번 자료에서 특히 기억에 남는 부분은 응답에 새 제목이 나와도 DB에는 기존 제목이 남을 수 있다는 실험입니다. 수정 API가 200을 반환하면 저장도 성공했다고 생각하기 쉬운데, DTO가 어떤 값으로 만들어졌는지와 트랜잭션이 무엇을 반영했는지는 따로 볼 문제였습니다.

 

실험의 조건은 PostService.update() 위의 @Transactional만 제거하고, 클래스의 @Transactional(readOnly = true)는 남기는 것입니다. 이 경우 트랜잭션이 없어지는 것이 아니라 수정 메서드에도 클래스의 읽기 전용 설정이 적용됩니다.

실습 자료에서 설명하는 동작을 비교하면 다음과 같습니다.

쓰기용 @Transactional 유지 200, 새 제목 실행됨 새 제목
메서드 어노테이션만 제거 200, 새 제목 실행되지 않음 기존 제목
쓰기 설정에서 플러시 전 RuntimeException 발생 500 이 예제에서는 실행되지 않음 기존 제목

읽기 전용 상태에서도 post.update()는 Java 객체의 값을 바꿉니다. 그 객체로 PostResponse를 만들기 때문에 응답에는 새 제목이 담깁니다. 하지만 실습의 Spring·Hibernate 설정에서는 읽기 전용 트랜잭션의 최적화로 자동 플러시와 변경 감지가 억제되어 DB에 반영되지 않습니다. 응답은 객체에서 만든 값이고, 재조회는 DB에 남은 결과를 확인하는 별도 과정이라는 차이가 여기서 드러납니다.

 

readOnly를 모든 쓰기 SQL을 막는 장치로 이해하는 것도 정확하지는 않습니다. Spring Data JPA 공식 문서는 이를 드라이버와 JPA 구현체에 전달하는 최적화 힌트로 설명합니다. 위 결과는 실습의 설정과 코드 흐름에 대응하는 것으로 정리합니다. (Spring Data JPA 읽기 전용 트랜잭션 설명)

 

함께 읽은 읽기 전용 트랜잭션 분석 글은 어노테이션 설명에서 출발해 Spring 내부 코드와 커넥션 로그를 따라갑니다. 설정의 이름을 외우는 데서 끝내지 않고, 실제로 어떤 처리가 달라지는지 추적하는 방식이 인상적입니다. 아직 내부 코드를 모두 이해하기보다는, 이번 예제의 응답·SQL·재조회가 서로 무엇을 보여 주는지부터 정리하려고 합니다.

롤백 예제에서는 쓰기 트랜잭션 안에서 Entity를 수정한 직후 예외를 던집니다.

@Transactional
public PostResponse update(Long postId, PostUpdateRequest request) {
    Post post = postRepository.findById(postId)
            .orElseThrow(() -> new IllegalArgumentException("게시글이 없습니다. id=" + postId));
    post.update(request.title(), request.content());
    throw new RuntimeException("롤백 테스트");
}

이 코드는 응답 DTO를 반환하는 대신 항상 예외로 끝납니다. 변경 뒤 별도 쿼리나 명시적인 플러시 없이 예외가 발생하므로, 기본 실습 조건에서는 UPDATE를 보내기 전에 롤백되고 DB에는 기존 값이 남습니다. 반대로 플러시 후 예외가 발생했다면 UPDATE 로그는 있어도 DB의 변경은 롤백될 수 있습니다.

 

롤백이 Java 객체의 필드까지 이전 값으로 되돌려 주는 것은 아니라는 점도 구분합니다. 취소되는 것은 DB 트랜잭션의 변경이며, 저장 결과를 확인하려면 새 요청으로 다시 읽은 값이 기준이 됩니다.

 

기본 롤백 규칙에서는 RuntimeException과 Error가 롤백 대상이고 체크 예외는 자동 롤백 대상이 아닙니다. 체크 예외까지 포함하려면 rollbackFor = Exception.class 같은 설정이 필요합니다. 별도 전역 설정이나 롤백 규칙에 따라 달라질 수 있지만, 적어도 HTTP 500이라는 응답 코드 자체가 롤백을 결정하는 것은 아니라는 점을 이해합니다. (Spring 롤백 규칙 문서)

 

프록시를 배운 뒤에는 같은 클래스 내부 호출도 다르게 보입니다. 기본 프록시 방식에서 this.update(...)는 프록시를 통과하지 않으므로 호출된 메서드의 트랜잭션 설정이 새로 적용되지 않습니다. 바깥 메서드에 트랜잭션이 있다면 그 안에서 실행될 수는 있습니다. 어노테이션이 붙어 있는지만 볼 것이 아니라 호출이 어디에서 시작되는지도 함께 생각해야 하는 이유입니다. (Spring 프록시와 내부 호출 설명)

7. Swagger와 API 문서화

게시글 API가 늘어나면서 경로, 요청 형식, 응답을 모아 보는 도구도 학습 내용에 들어옵니다. Swagger라는 이름으로 한꺼번에 생각하기 쉬웠지만, 자료에서는 OpenAPI·springdoc·Swagger UI의 역할을 구분합니다.

OpenAPI API의 경로와 요청·응답을 기술하는 명세 형식
springdoc-openapi Spring 애플리케이션에서 OpenAPI 문서를 생성하는 라이브러리
Swagger UI 명세를 화면으로 보여 주고 API 호출을 돕는 도구

Week 4 자료에 제시된 조합은 Spring Boot 4.1.1과 springdoc 3.1.1입니다. 의존성은 다음 한 줄로 추가하는 형태입니다.

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.1.1'

Week 2에서 기록한 Boot 4.0.x와는 버전 차이가 있습니다. springdoc 공식 문서는 Boot 4에 springdoc 3.x를 사용하도록 안내하고, Boot 4.0.x에 대한 호환 표에는 3.0.x를 제시합니다. 의존성 코드가 짧아도 다른 글의 버전을 그대로 가져오기보다는 프로젝트의 Boot 버전과 함께 읽어야 한다는 점을 다시 생각하게 됩니다. (springdoc 공식 문서와 호환 표)

 

기본 설정에서 문서 화면은 /swagger-ui/index.html, OpenAPI JSON은 /v3/api-docs로 제공됩니다. 실습의 다섯 CRUD API가 화면에 모이고, Try it out과 Execute를 통해 요청할 수 있는 구조입니다. Postman과 도구는 다르지만, 요청을 보내 상태 코드와 본문을 확인한다는 점은 같습니다.

 

제가 이해한 문서화의 역할은 API의 사용 방법을 코드와 연결해 공유하는 것입니다. 화면에 API가 나타났다는 사실과 실제 저장·롤백이 올바르게 동작한다는 사실은 별개이므로, 문서화와 동작 검증도 나누어 생각합니다.

8. 제목 검색으로 연결해 본 요청 처리 흐름

마지막 학습 내용은 제목에 특정 문자열이 들어간 게시글을 찾는 검색 API입니다. GET /posts/search?keyword=스프링이라는 요청 하나 안에 앞에서 공부한 요청 바인딩, Repository, DTO 변환이 함께 들어갑니다.

 

Repository에는 쿼리를 직접 작성하는 대신 다음 메서드를 선언합니다.

public interface PostRepository extends JpaRepository<Post, Long> {
    List<Post> findByTitleContaining(String keyword);
}

Spring Data JPA의 쿼리 메서드는 이름의 규칙으로 조회 조건을 표현합니다. Title은 Entity의 title 속성, Containing은 해당 문자열을 포함하는 조건입니다. 일반 검색어는 앞뒤에 %를 붙인 LIKE 조건에 대응합니다. %와 _ 같은 특수 입력은 이 계열의 파생 쿼리에서 이스케이프 처리된다는 점도 공식 문서에서 함께 정리합니다. (Spring Data JPA 쿼리 메서드 문서)

Service에서는 조회한 Entity 목록을 응답 DTO 목록으로 바꿉니다. 이 메서드는 클래스의 읽기 전용 트랜잭션 설정을 사용합니다.

public List<PostResponse> search(String keyword) {
    return postRepository.findByTitleContaining(keyword).stream()
            .map(PostResponse::from)
            .toList();
}

map(PostResponse::from)을 각 게시글을 응답용 객체로 변환하는 부분으로 읽으니, 앞의 단건 조회와도 연결됩니다. Controller는 @RequestParam으로 검색어를 받아 Service에 전달하고, 결과 목록을 200으로 반환합니다. 검색 결과가 없다면 응답 본문은 빈 목록 []입니다.

 

실습 자료의 검색용 데이터는 제목이 스프링 공부인 게시글입니다. 이 데이터와 keyword=스프링, SQL의 LIKE 조건을 연결하면 메서드 이름이 어떤 조회로 이어지는지 이해할 수 있습니다. 검색 기능까지 더하면 문서에 나타나는 API는 여섯 개가 됩니다.

 

이번 주 내용을 정리하면서 가장 크게 달라진 부분은 “수정됐다”는 말을 나누어 생각하게 된 것입니다. Java 객체의 값이 바뀐 상태, SQL이 DB에 전달된 상태, 커밋으로 확정된 상태는 서로 다릅니다. save()가 없는 수정 코드에서 시작한 질문이 영속성 컨텍스트와 트랜잭션으로 이어졌고, API 응답만 보고 저장을 판단하기 어려운 이유도 함께 이해하게 됩니다. 다음 복습에서는 이 흐름을 수정 메서드의 각 줄과 연결해 설명할 수 있는지 확인하려고 합니다.

'배움 일기' 카테고리의 다른 글

GDGOC 6th BE Week 3 - Spring 핵심 원리와 JPA 입문  (0) 2026.10.04
GDGOC 6th BE Week 2 - Backend와 Spring Boot로 첫 API 만들기  (0) 2026.10.04
Windows 데스크탑을 WSL + Tailscale + SSH 기반 원격 개발 서버로 구축하기  (0) 2026.09.27
Less is more, 바우하우스부터 조너선 아이브까지  (0) 2026.03.01
Tistory 스킨 'hELLO' HTML 커스텀하기  (0) 2026.02.28
'배움 일기' 카테고리의 다른 글
  • GDGOC 6th BE Week 3 - Spring 핵심 원리와 JPA 입문
  • GDGOC 6th BE Week 2 - Backend와 Spring Boot로 첫 API 만들기
  • Windows 데스크탑을 WSL + Tailscale + SSH 기반 원격 개발 서버로 구축하기
  • Less is more, 바우하우스부터 조너선 아이브까지
youn9._.hon9
youn9._.hon9
  • youn9._.hon9
    youn9._.hon9
    youn9._.hon9
    • 분류 전체보기 (36) N
      • 배움 일기 (7) N
      • 대외 활동 (17) N
      • 논문 리뷰 (0)
      • 프로젝트 회고 (3)
      • 낙서장 (9)
  • 태그

    Galaxy campus
    Upstage
    GDGOC
    kakaotech bootcamp
    BITAmin
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
youn9._.hon9
GDGOC 6th BE Week 4 - JPA 변경 감지와 트랜잭션
상단으로

티스토리툴바