본문 바로가기
내가 만난 error/해결

[MING-MARTKET]JPA - merge를 이용하여 값 수정시 null(변경 감지 방법_dirtyChecking 으로 수정)

by 피자보다 치킨 2022. 6. 23.
중고거래장터 밍마켓
유저는 상품을 자유롭게 올릴 수 있다.
올린 상품을 수정할 수 있다

 

Controller

    @PostMapping("/{productId}/edit")
    public String create(@PathVariable Long productId,@Valid @ModelAttribute("form") CreateProductForm form, BindingResult result,
                         @LoginCheck MemberDto.SessionMemberData loginMember,
                         RedirectAttributes redirectAttributes,
                         HttpServletRequest request
                         ) throws IOException {
        if (result.hasErrors()) { //만약에 result 안에 에러가 있으면
            return "product/createProductForm"; //다시 폼으로 이동
        }

        String realPath = request.getSession().getServletContext().getRealPath("/upload/");// 상대 경로
        String uploadFile = fileUpload.serverUploadFile(form.getThumbnail(), realPath);

        Product product = Product.updateProduct(form.getProductId, form.getTitle, form.getUploadFile, form.getIntro, form.getPrice);  
        // 저장
        productService.saveProduct(product);
        
        redirectAttributes.addAttribute("productId", productId);

        return "redirect:/product/detail/{productId}"; // 상품디테일 페이지로 넘어가게
    }

ProductRepository.java

@Repository
@RequiredArgsConstructor
public class ProductRepository {

    private final EntityManager em;

    // 상품 저장
    public void save(Product product) {
        if (product.getId() == null) { // 등록된 상품이 없을 경우 새로 등록
            em.persist(product);
        } else { // 상품이 존재할 경우 강제로 업데이트(즉, 수정)
            em.merge(product);
        }
    }
}

 

ProductDto.updateProductForm

	@Getter
    @Setter
    public static class updateProductForm {

        private Long productId; //pk

        @NotBlank(message = "제목을 입력해주세요")
        private String title;

        private String uploadFileName;

        private MultipartFile uploadFile;

        @NotBlank(message = "상품 설명을 작성해주세요")
        private String intro;

        @NotNull(message = "상품 가격을 입력해주세요")
        @Range(min = 1000, max = 99999999, message = "1,000 ~ 99,999,999원으로 다시 입력해주세요")
        private int price;

        public updateProductForm(Long productId, String title, String thumbnail, String intro, int price) {
            this.productId = productId;
            this.title = title;
            this.uploadFileName = thumbnail;
            this.intro = intro;
            this.price = price;
        }
    }

 

ProductEntity

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product extends BaseEntity { //상품

    @Id @GeneratedValue
    @Column(name = "product_id")
    private Long id; //pk

    private String title; //제목
    private String thumbnail; //섬네일
    private String intro; //설명(게시판)
    private int Price; //상품가격

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member; //fk

    private Product(String title, String thumbnail, String intro, int price, Member member) {
        this.title = title;
        this.thumbnail = thumbnail;
        this.intro = intro;
        this.Price = price;
        this.member = member;
    }

    //생성 메서드
    public static Product createProduct(String title, String thumbnail, String intro, int price, Member member) {
        Product product = new Product(title, thumbnail, intro, price, member);
        product.createDate(LocalDateTime.now());
        return product;
    }
 }

 

 

결과:

수정시 MEMBER_ID가 계속 null값이 채워진다.

 

게시글 수정시 게시글 작성자(member_id)는 변경될 일이 없다.

그래서 아래 코드 실행시 member_id = null값이다.

 else { // 상품이 존재할 경우 강제로 업데이트(즉, 수정)
    em.merge(product);
}

 

병합은 준영속 상태의 엔티티를 다시 영속 상태로 변경할 때 사용한다.
merge() 메서드는 준영속 상태의 엔티티를 받아 그 정보로 새로운 영속 상태의 엔티티를 반환한다.

 

merge()의 동작 방식

  1. merge()를 실행
  2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
    • 만약 1차 캐시에 엔티티가 없으면 데이터베이스에 엔티티를 조회하고 1차 캐시에 저장.
    • 무조건 1번은 db 조회를 하므로 성능에 좋지 않을 수 있다. 
  3. 조회한 영속 엔티티에 product 엔티티의 값을 채워 넣음
    • 이때 product 의 모든 값을 영속 엔티티에 채워 넣기 때문에 null 값이 들어갈 수 도 있는 문제가 생긴다.
    • 이래서 업데이트 시 merge()보단 변경 감지를 사용하자.
  4. 영속 상태의 객체를 반환

 

 

수정된 코드

변경 감지 사용 (dirtyChecking)

 

Service

 /**
     * 상품 수정
     * JPA 변경 감지를 활용하여 update.
     * 트렌젝션이 종료될 때 변경된 부분에 대한 update query를 날린다.
     */
    @Transactional
    public void updateProduct(Long productId, ProductDto.updateProductForm form, String thumbnail) {
        Product findProduct = productRepository.findSingleProduct(productId);
        findProduct.change(form.getTitle(), thumbnail, findProduct.getIntro(), form.getPrice());

    }

Repository

public Product findSingleProduct(Long productId) {
    return em.find(Product.class, productId);
}

 

 

entityManager로 entity를 직접 꺼내, 값을 수정한다.

 

@Transactional으로 인하여 로직이 끝날 때 JPA에서 트랜잭션 commit 시점에 변경 감지(Dirty Checking)한 후

Flush를 한다.

댓글