Spring/JPA

[스프링] JPA_S7 : 쿼리 파라미터와 변경 감지 & 병합

ajeong7038 2024. 1. 9. 00:13

 

`실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발` 강의를 듣고 정리한 자료입니다

 

✨ @PathVariable

@GetMapping("/items/{itemId}/edit")
public String updateItemForm(@PathVariable("itemId") Long itemId, Model model) {
    Book item = (Book) itemService.findOne(itemId); // 예제 단순화를 위해 캐스팅 사용

    BookForm form = new BookForm();
    form.setId(item.getId());
    ...
}

 

- {itemId} 는 변경될 수 있기 때문에 @PathVariable로 itemId을 가져다 써야 한다

@PostMapping("items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
        
}

 

- 근데 필수는 아님 (????)

- 위 예제에서는 form에 itemId가 담겨 오기 때문에 @PathVariable가 따로 필요하지 않다


✨ 핸들러 매개변수 매핑

@RequestParam

- `사용자가 요청 시 전달하는 값`을 핸들러(컨트롤러)의 매개변수로 1:1 매핑 시 사용

@Controller
public class TestController {
    @GetMapping("/")
    public String getTestPage(@RequestParam("name") String name) {
        System.out.println("이름 : " + name);
        return "test";
    }
}
// 출처: https://galid1.tistory.com/769 [배움이 즐거운 개발자:티스토리]

 

- 사용자가 `/?name=test`를 전달했을 시 핸들러의 매개변수 name과 "test"를 매핑

@ModelAttribute

- `사용자가 요청 시 전달하는 값`을 오브젝트 형태로 매핑

- 하나가 아닌 여러 개의 매가변수 매핑 시 효과적이다

@Getter
@Setter
public class TestModel {
    private String name;
    private int age;
}

@RestController
public class TestController {
    @GetMapping("/")
    public String getTestPage(@ModelAttribute TestModel testModel) {
        System.out.println("이름 : " + testModel.getName());
        System.out.println("나이 : " + testModel.getAge());
        return "test";
    }
}
// 출처: https://galid1.tistory.com/769 [배움이 즐거운 개발자:티스토리]

 

- TestModel 객체는 Setter이 존재 해야 한다

- Model을 사용하는 경우 model에 자동으로 담기기 때문에 생략이 가능하다

@GetMapping("/orders")
public String orderList(@ModelAttribute("orderSearch")OrderSearch orderSearch, Model model) {
    // 회원이름, 주문상태
    List<Order> orders = orderService.findOrders(orderSearch);
    model.addAttribute("orders", orders);
    model.addAttribute("orderSearch", orderSearch);

    return "order/orderList";
}

 

- model.addAttribute("orderSearch", orderSearch); 구문 생략 가능

결론

- @RequestParam & @ModelAttribute 모두 사용자가 요청 시 전달하는 값을 매핑해준다

- 그러나 @RequestParam은 1:1로 매핑, @ModelAttribute는 오브젝트 형태로 매핑해준다

- @RequestParam은 전달 값이 한 개 일 때 효과적이고, @ModelAttribute는 전달 값이 여러 개일 때 효과적이다

- @ModelAttribute를 사용하면 전달 값의 인자가 추가될 경우 controller를 건들지 않아도 되어 안전하다


준영속 엔티티

- 영속성 컨텍스트가 더는 관리하지 않는 엔티티

- 기존에는 `dirty checking`을 통해 변경을 감지해 자동으로 트랜잭션이 끝나는 시점에 db에 반영해주는데, 문제는 준영속 엔티티일 때 발생한다

- 변경 감지 (dirty checking) 는 영속성 컨텍스트에서 관리되는 엔티티만 추적하기 때문이다

- 더티체킹 관련 정리본 : https://ajeong7038.tistory.com/26

- 객체가 DB에 저장될 때 식별자가 생성되는데, 기존 식별자를 가지고 있는 경우 준영속 엔티티가 되어 영속성 컨텍스트(JPA)가 더이상 관리하지 않는다

- id (식별자)가 있는 경우 준영속 엔티티가 된다고 한다


✨ 준영속 엔티티 수정 방법

1. 변경 감지 기능 사용

- 영속성 컨텍스트에서 엔티티를 다시 조회한 후 데이터를 수정하는 방법

- 트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 -> 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작해 db에 업데이트 쿼리를 날림

동작 방식

1) 식별자로 db에서 찾아와 영속성 상태의 엔티티 찾아오기

2) 값 수정 시 변경 감지 기능으로 알아서 플러시 날려준다.

@Transactional
public Item updateItem(Long itemId, Book param) {
    Item findItem = itemRepository.findOne(itemId); // id를 기반으로 실제 db에 있는 영속성 상태의 엔티티 찾아오기
    findItem.setPrice(param.getPrice());
    findItem.setName(param.getName());
    findItem.setStockQuantity(param.getStockQuantity());
    itemRepository.save(findItem);
    return findItem;
}

 

2. 병합(merge) 사용

- 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능

- id값(식별자)가 존재하는 경우 em.merge() 사용

동작 방식

1. merge() 실행

2. 준영속 엔티티의 식별자 값 (ex, id)으로 영속 엔티티 조회

3. 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체 (병합)

4. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해 db에 업데이트 쿼리 실행

@PostMapping("items/{itemId}/edit")
public String updateItem(@PathVariable String itemId, @ModelAttribute("form") BookForm form) {
	Book book = new Book();
    book.setIsbn(form.getIsbn());
    book.setId(form.getId());
    book.setName(form.getName());
    book.setPrice(form.getPrice());
    book.setStockQuantity(form.getStockQuantity());
    book.setAuthor(form.getAuthor());
    book.setIsbn(form.getIsbn());
    itemService.saveItem(book);
    return "redirect:/items";
}

 

주의사항

- 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다 (값이 없을 시 null값으로 대체될 수 있기 때문에 위험하다)

- 최대한 merge 사용하지 말 것

- 외부(ex, controller)에서는 식별자만 넘기고 실제 핵심 비즈니스 서비스에서는 엔티티를 찾는 것부터 설계 (영속 상태로 진행이 가능하다)


✨ 질문

1. Service와 Repository의 관계

- Repository에서 구현되어 있는 메소드들을 이름만 바꿔서 구현...?

2. 사진 업로드 기능이 필요할 때 백엔드...?

3. API 개발

- 강의에서 하는 방식이 API 방식의 개발이 아닌가요 ?.?

4. @PathVariable

- 필수로 필요한 경우?

- 메소드 내부에서 @PathVariable이 붙은 변수를 사용하는 경우 필수 지정인가요?

5. 변경 감지와 병합

- 변경 감지 기능 사용은 변수에 식별자를 통해 (find 등 사용) 영속성 엔티티를 찾는 것이고, 병합은 new로 새 객체를 생성...? 차이점이 뭘까요...


✨ 참고자료

https://velog.io/@sin_0/Spring-model.addAttribute-%EC%82%AC%EC%9A%A9%EB%B2%95