관리 메뉴

Mini

변경감지와 병합(merge) // 엔티티변경시 변경감지를 사용하라 , dto언제써야하나, 추적가능한 설계 본문

Java/Spring-app

변경감지와 병합(merge) // 엔티티변경시 변경감지를 사용하라 , dto언제써야하나, 추적가능한 설계

Mini_96 2023. 7. 26. 01:50

* 변경감지 == dirty checking

set이후 DB에 따로 작업을 안해도, 스프링JPA가 자동으로 DB에 반영해준다

@RunWith(SpringRunner.class)
@SpringBootTest
public class ItemUpdateTest {

    @Autowired
    EntityManager em;

    @Test
    public void updateTest() throws Exception{

        Book book = em.find(Book.class, 1L);

        //트랜젝션안에서
        book.setName("asdfghf");
        //set이후 트랜잭션 커밋하면 스프링이 자동으로 DB에 반영해줌 == 변경감지

    }
}

예시2) Order-Cancel

    public void cancel(){
        if(delivery.getStatus()==DeliveryStatus.COMP){
            throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
        }

        //else
        this.setStatus(OrderStatus.CANCEL);
        for(OrderItem orderItem : orderItems){
            orderItem.cancel();
        }
    }

 

-단점 : (JPA가) 준영속 엔티티는 관리안함 -> set만으로 자동DB반영(업뎃) 안해줌

-준영속 엔티티 : 한번이상 DB에 갔다온 얘

아래코드에서 book객체는 새로만들었지만, id기반으로 생각을해보면, 이미 DB에 갔다온 애를 이용해서 세팅하므로  Book은 준영속엔티티다.

    public String updateItem(@ModelAttribute("form") BookForm form) {
        Book book = new Book();

        //스마트하게 복붙 : edit-colum selection mode - shift, ctrl로 조정
        //shift+ctrl+u => 대문자로

        //보안취약점 : id조작가능
        //해결 : 유저가 item권한이 있는지 체크해주는 로직추가
        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";   //끝나면 목록으로 ㄱㄱ
    }

 

* 해결1 : 변경감지 사용

ex1) 교재예시

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
    Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
    findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}

 

1. @Transactional

2. 파라미터로 준영속넘겨

3. 다시조회 후 수정

4. 트랜잭션 커밋 됨-> 변경감지 -> update SQL 실행!

ex2) ItemService.class

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

 

*해결 2: 병합(merge) 사용

파라미터의 값으로 모든 필드값을 바꿔치기한다.

item이 영속성으로 바뀌는게아님.

em.merge에서 반환된 것이 영속성컨텍스트임 -> 사용할때 이거 받아서 쓰면됨.

    public void save(Item item){
        if(item.getId()==null){
            em.persist(item);   //아이디가없으면(새로생성된객체), 아이템 저장
        }
        else{
            em.merge(item); //이미DB에 등록된객체 -> 업데이트
        }
    }

내부동작 : 아래코드와 유사하다. return추가.

    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;

    }

- 단점 : NULL가능 -> 머지 쓰지마

가격이 고정이라 set안하는경우 -> 가격이 NULL로 set되서 망한다!

@PostMapping("/items/{itemId}/edit")    //itemId는 html에서 넘어오니까 @PathValiable필요X
    //@ModelAttribue로 html에서 {form}에 해당하는 객체를 받아온다.
    public String updateItem(@ModelAttribute("form") BookForm form) {
        Book book = new Book();

        //스마트하게 복붙 : edit-colum selection mode - shift, ctrl로 조정
        //shift+ctrl+u => 대문자로

        //보안취약점 : id조작가능
        //해결 : 유저가 item권한이 있는지 체크해주는 로직추가
        book.setId(form.getId());
        book.setName(form.getName());
       // book.setPrice(form.getPrice());
        book.setStockQuantity(form.getStockQuantity());
        book.setAuthor(form.getAuthor());
        book.setIsbn(form.getIsbn());

 

* 리팩토링 (set을쓰지말자, 메소드로관리하라, 추적가능한 설계)

    @Transactional
    public void updateItem(Long itemId, Book param){
        Item findItem = itemRepository.findOne(itemId);
        findItem.change(price,name,stockQuantity);
        findItem.setPrice(param.getPrice());
        findItem.setName(param.getName());
        findItem.setStockQuantity(param.getStockQuantity());

    }

set 남발 =>버그시 어디서 가격을바꾸는지 찾는데 어려움

chagne메소드로 관리 => 관리쉬움(추적가능한 설계)

 

*리팩토링2(dto 언제사용하나)

    @Transactional
    public void updateItem(Long itemId, String name, int price, int stockQuantity){
        Item findItem = itemRepository.findOne(itemId);
        //findItem.change(price,name,stockQuantity);    //리팩토링 set남발금지
        findItem.setPrice(price);
        findItem.setName(name);
        findItem.setStockQuantity(stockQuantity);

    }
    public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") BookForm form) {
        Book book = new Book();

        //스마트하게 복붙 : edit-colum selection mode - shift, ctrl로 조정
        //shift+ctrl+u => 대문자로

        //보안취약점 : id조작가능
        //해결 : 유저가 item권한이 있는지 체크해주는 로직추가
//        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);

        //리팩토링

        itemService.updateItem(itemId, form.getName(),form.getPrice(), form.getStockQuantity());

 

- 더 업데이트할게 많은 경우 해결 : dto class를 만들고, dto를 updateItem 파라미터로 넘기고, dto.set으로 세팅