상품수정

 

등록이나 조회는 쉬운편이지만, 수정은 복잡하다.

jpa에서 어떠한 방법으로 수정하는 것이 좋을 것인지(정석적인지)에 대해

고민해서 선택해야 한다. 방법중엔 변경감지, 병합이라는 방법이 있다고 한다.

 

jpa의 가이드는 변경감지를 best practice라고 권한다.

 

사진(item controller 수정구문)

 

updateItemForm.html 파일

-createItemForm 파일과의 차이점은 기존에 있는 데이터를 보유하고 있다의 정도이다.

 

*form-control : formcontrol로 명시할 경우 form이 제대로 설정이 되지 않는다.

 

사진

 

사진(수정버튼)

사진(수정화면)

 

*itemId 취약점

-브라우저창에서 상품수정시에 format이 넘어올 때에 인위적으로 아이디를 조작해서 넘길 수 있다.

 서비스계층이든 뒷단이든 유저가 item에 대해 권한이 있는지 체크하는 로직이 있어야 한다.

 

 

2. 변경감지와 병합

JPA에서 변경감지를 모르면 시간을 많이 낭비할 수 있다.

 

JPA기본 메커니즘

a. JPA값 변경시 예제

 

@RunWith(SpringRunner.class)

@SpringBootTest

public class ItemUpdateTest {

 

@Autowired

EntityManager em;

 

@Test

public void updateTest() throws Exception{

Book book = em.find(Book.class, primarykey:1L); //1번 데이터베이스에 있다고 가정한다.

 

//트랜잭션(TX)

book.setName("asdfasdf");

->트랜잭션안에서 이름을 변경하고 트랜잭션 커밋할때에 JPA가 자동으로 변경된구문을 찾아서

업데이트 쿼리를 자동 생성해서 데이터베이스에 생성한다.

이것을 변경감지 == dirty checking이라고 한다.

이 매커니즘을 기본으로 jpa 의 엔티티를 바꿀 수 있다.

 

이것과 비슷한 예제가 했었던 실습구문에 있다.

setStatus의 상태만 바꾸고 jpa가 트랜잭션 커밋시점에 바뀐 것을 찾아서 업데이트 쿼리를 이용해 디비에 전달하여 커밋을 완료한다. 엔티티매니저 merge와 같은 행위를 따로 해주지 않아도 된다.

 

정리

-엔티티가 영속성상태로 관리 되면 값만 바꾸면 변경된 JPA가 트랜잭션 커밋시점에 변경된값을 알고,

 디비에 반영시킨다.

 

문제는 준영속 엔티티를 이해해야 한다.

-jpa 영속성 컨텍스트가 더 이상 관리하지 않는 엔티티를 말한다.

 

새로운 객체는 새로운 객체이지만 아이디는 세팅이 된 상태이다.

JPA에 작업을 한번 거친 식별자(itemId)가 있는 것은 준영속 엔티티라고 한다.

데이터베이스에 들어갔다 나온 데이터라 JPA가 식별할 수 있는 아이디를 가지고 있다.

여기에서 준영속 엔티티는 book이다.

포트폴리에서는 내가 직접 만든 new Book이고, JPA가 관리하지 않는 준영속 엔티티이다.

jpa 영속성 컨텍스트가 관리하는 엔티티는 변경된 것을 알지만 준영속 엔티티는 그렇지 않다.

아무리 준영속엔티티에 정보를 변경해도 변경이 되지 않는다.

 

어떻게 하면 준영속 엔티티를 수정할 수 있을까에 대해 알아보기로 한다.

 

준영속 엔티티를 수정하는 2가지 방법

-변경감지기능사용(dirty checking)

-병합(merge)사용

 

변경감지 기능 사용

@Transactional

void update(Item itemParam) { //itemParam: 파라미터로 넘어온 준영속 상태엔티티이다.

 

Item findItem = em.find(Item.class, itemParam.getId()); 다시 동일한 엔티티를 조회한다.

 

findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.

}

 

== 비슷한 예제

@Transactional

public void updateItem(Long itemId, Book param) {

 

private final ItemRepository itemRepository;

 

@Transactional

public void saveItem(Item item) {

itemRepository.save(item);

}

 

@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());

//데이터를 수정한다.

}

 

 

설명

-@Transactional 안에서 파라미터로 넘어온 준영속 상태엔티티를 부른다.

-다시 동일한 엔티티를 조회한다.(findItem)

-넘어온 준영속상태의 엔티티의 값을 다시 재세팅(변경/수정)을 한다.

-트랜잭션이 커밋할 시에, flush를 보내어, 변경된 값을 찾아( 변경감지 : dirty checking)내어 감지한다.

-데이터베이스 update sql 실행하여 변경된 값이 전달 된다.(데이터를 조회 한다.)

 

->이것이 더 나은 수정방법이다.

 

 

2. 병합방법

-포트폴리오에 사용한 방법은 병합방법이다.

-준영속 상태 엔티티를 영속 상태로 변경해주고자 할 때 사용하는 기능 방법이다.

 

예제1)

@Transactional

vodi update(Item itemParam) { //itemParam : 파라미터로 넘어온 준영속 상태의 엔티티

Item mergeItem = em.merge(item);

}

 

병합 : 기존에 있는 엔티티

병합 동작 방식 정리

1. 준속 엔티티의 식별자 값으로 속 엔티티를 조회한다.

2. 속 엔티티의 값을 준속 엔티티의 값으로 모두 교체한다.(병합한다.)

3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행

 

주의

-변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만,

 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null로 업데이트 할 위험도 있다.

 (병합은 모든 필드를 교체한다.) -> 그래서 실무에서 병합기능은 위험하다.

 

예를들어

 

Book book = new Book();

book.setId(form.getId());

book.setName(form.getName());

book.setPrice(form.getPrice()); -> 이부분을 빠뜨리면 pricenull로 업데이트가 된다. book.setStockQuantity(form.getStockQuantity());

book.setAuthor(form.getAuthor());

book.setIsbn(form.getIsbn());

 

itemService.saveItem(book);

return "redirect:/items";

}

 

*자세한 원리는 매뉴얼에 보면 있다.

 

유지보수성 측면에서

@Transactional

public void updateItem(Long itemId, Book param) {

//파라미터로 넘어온 준영속 상태엔티티이다.

 

Item findItem = itemRepository.findOne(itemId);

//다시 동일한 엔티티를 조회한다.

//findItem.change(name, price, stockQuantity);

->이렇게 해주어야 change만 확인하면 어디가 변경된지 쉽게 추적할 수있다.

 

findItem.setPrice(param.getPrice());

findItem.setName(param.getName());

findItem.setStockQuantity(param.getStockQuantity());

//데이터를 수정한다.

}

 

가장 좋은 해결 방법

엔티티를 변경할 때는 항상 변경 감지를 사용할 것.

컨트롤러에서 어설프게 엔티티를 생성하지 마세요.

트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달할것.

+ Recent posts