웹 계층 개발
- @NotEmpty() : jakarta를 통해서 값을 확인해주는 역할로 값이 비어있으면 알려주는 역할
- @Valid : @NotEmpty, @NotNull과 같은 어노테이션이 붙은 항목을 validation 해준다.
- Spring Model : key와 value로 이뤄져있는 HashMap이다.
- addAttribute()를 호출함으로 view에 전달할 데이터를 담아서 전달할 수 있다.
- Servlet의 request.setAttribute()와 유사한 역할을 한다.
- BindingResult : 스프링에서 제공하는 검증 오류 처리 방법이다.
- BindingResult가 없으면 오류 발생 시 Controller가 정상 호출되지 못하고 오류가 발생해 Error Page로 이동한다.
- @Valid 이후에 사용 시 오류가 발생하면 BindingResult 객체 담기고 Controller가 정상 호출된다.
- 엔티티는 최대한 순수하게 유지하고 다른 곳에서 필요로 하는 경우 DTO를 별도로 만들어서 사용해야한다.
- API를 만들 때는 엔티티를 절대 외부로 반환하면 안된다.
=> 엔티티에 로직 추가 시 API 스펙이 변한다, 패스워드 노출 가능성
- API를 만들 때는 엔티티를 절대 외부로 반환하면 안된다.
JPA 데이터 수정
- JPA에서 데이터 수정 시 변경 감지, 병합(merge) 방식을 사용함으로 데이터를 반영할 수 있다.
- ID를 URI에 넘기는 경우 조심해야 한다.
- 다른 사람이 인위적으로 다른 ID 값을 넘기면 다른 데이터가 수정될 수 있다.
ex) ID = 1 인 상품 정보를 수정해야하지만 ID = 2로 넘겨주면 ID = 2 인 상품의 정보가 수정된다. - 해결 방법 -> ex) 서비스 계층에서 유저가 상품에 대한 권한이 있는지 체크하는 로직이 필요
- 다른 사람이 인위적으로 다른 ID 값을 넘기면 다른 데이터가 수정될 수 있다.
- 준영속 엔티티 : 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 의미한다.
- 준영속 상태는 식별자를 기준으로 영속상태가 되어서 DB에 저장된 적이 있는지의 여부이다.
- DB에 한번 저장되서 식별자(ID)가 존재하는 경우 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.
- 준영속 엔티티의 경우 JPA가 관리하지 않기 때문에 변경 감지가 일어나지 않는다.
//준영속 엔티티 예제
@PostMapping("/items/new")
public String create(BookForm form) {
Book book = new Book();
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:/";
}
- 예제에서 book 객체를 만들었지만 form 객체를 통해 기존 식별자를 가지고 있기에 새로 만든 book 객체는 준영속 엔티티이다.
//변경 감지 예제
@Transactional
public void updateItem(Long itemId, Book param) {
Item findItem = itemRepository.findOne(itemId);
findItem.setPrice(param.getPrice());
findItem.setName(param.getName());
findItem.setStockQuantity(param.getStockQuantity());
...
}
//병합(merge) 예제
public void save(Item item) {
...
if (item.getId() != null) {
Item mergeItem = em.merge(item); //update
}
}
//병합(merge) 동작 방식과 동일
@Transactional
public Item updateItem(Long itemId, Book param) {
Item findItem = itemRepository.findOne(itemId);
findItem.setPrice(param.getPrice());
findItem.setName(param.getName());
findItem.setStockQuantity(param.getStockQuantity());
...
return findItem;
}
- 준영속 엔티티를 수정하는 방법
- 변경 감지 기능 사용
- 변경 감지 (Dirty Checking) : 트랜잭션 커밋시 영속화된 엔티티에서 가지고 있었던 정보와 바뀐 엔티티 정보를 비교해서 바뀐 부분을 데이터베이스에 자동으로 저장해주는 기능이다.
- 영속성 컨텍스트에서 관리하는 엔티티 => 즉, 영속화된 엔티티만 변경감지가 일어난다.
- 영속성 컨텍스트에서 엔티티를 다시 조회(find)한 후 데이터를 수정하는 방법
=> em.persist(), em.find()를 하는 경우 엔티티는 영속 상태가 되기 때문에
- 변경 감지 (Dirty Checking) : 트랜잭션 커밋시 영속화된 엔티티에서 가지고 있었던 정보와 바뀐 엔티티 정보를 비교해서 바뀐 부분을 데이터베이스에 자동으로 저장해주는 기능이다.
- 병합 (merge) 사용
- 병합 : 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능이다.
- em.merge()로 변경 감지 예제 코드와 유사한 기능을 JPA가 제공한다.
- 예제에서 파라미터 값인 item이 영속 상태로 변하는 것이 아닌 mergeItem이 영속 상태이기에 별도로 사용할 일이 있다면 item이 아닌 mergeItem을 사용해야 한다.
- 변경 감지 기능 사용
- 변경 감지 과정
- 1차 캐시의 엔티티와 스냅샷을 비교한다.
- 비교해서 다른 경우 UPDATE SQL을 생성해서 쓰기 지연 SQL 저장소에 담아둔다.
- commit() 호출 시점에 UPDATE SQL을 DB로 날린다.
- 실제 DB 트랜잭션이 commit 된다.
- 병합(merge) 동작 방식
- merge()를 호출
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
- 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고 1차 캐시에 저장한다.
- 조회한 영속 엔티티(mergeMember)에 수정할 엔티티(member)로 값을 채워 넣는다.
- 영속 상태인 mergeMember를 반환한다.
- 변경 감지 기능과 병합(merge) 차이점
- 변경 감지는 원하는 속성만 선택해서 변경할 수 있다.
- 병합은 모든 속성이 변경된다.
- 병합 시 값이 없으면 null로 업데이트한다. (병합은 모든 필드를 교체한다)
- 실무에서는 변경가능한 데이터만 노출하기 때문에 병합(merge)을 사용하는 것은 번거롭고 사용하기 어렵기에 변경 감지 기능을 사용해야 한다.
- 엔티티 변경 시 항상 변경 감지를 사용할 것!!!!!!
//잘못된 예시
item.setName(...);
item.setPrice(...);
//옳은 예시
item.change(name,price);
- 업데이트 시 Setter가 아닌 엔티티에서 의미있는 메소드를 통해 업데이트를 해야 한다.
- 그래야 변경 지점이 흩어져있지 않고 엔티티에 모일 수 있게 된다.
- 컨트롤러에서 어설프게 엔티티를 생성하지 말 것!!
- 필요한 필드만 각각 보내주는 것이 좋다.
- 혹은 별도의 필요한 필드로 구성된 DTO를 만들어서 보내주는 것이 좋다.
- 트랜잭션이 있는 서비스 계층에 식별자(ID)와 변경할 데이터를 명확하게 전달할 것!!
- 트랜잭션 안에서 조회를 해야 엔티티가 영속 상태로 조회가 되고 그 때 값을 변경해야 트랜잭션 커밋 시점에 변경 감지가 가능해진다.
- 서비스 계층에 별다른 비지니스 로직이 없고 레포지토리만 호출한다면 컨트롤러에서 바로 해도 괜찮다.
- @RequestParam : 사용자가 요청 시 전달하는 값을 매개 변수로 1:1 매핑할 때 사용하는 어노테이션
- @ModelAttribute : 메소드 레벨, 메소드 파라미터에서 적용이 가능하며 사용자가 전달하는 값을 오브젝트 형태로 매핑해주는 어노테이션
출처: [인프런 김영한 실전 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발]
'Spring > [인프런 김영한 실전 스프링 부트와 JPA 활용 1]' 카테고리의 다른 글
[인프런 김영한 실전 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 도메인 개발 (0) | 2024.08.31 |
---|---|
[인프런 김영한 실전 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 도메인 분석 설계 (0) | 2024.08.29 |
[인프런 김영한 실전 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 프로젝트 환경설정 (0) | 2024.08.29 |