중고거래장터 밍마켓
유저는 상품을 자유롭게 올릴 수 있다.
올린 상품을 수정할 수 있다
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()의 동작 방식
- merge()를 실행
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
- 만약 1차 캐시에 엔티티가 없으면 데이터베이스에 엔티티를 조회하고 1차 캐시에 저장.
- 무조건 1번은 db 조회를 하므로 성능에 좋지 않을 수 있다.
- 조회한 영속 엔티티에 product 엔티티의 값을 채워 넣음
- 이때 product 의 모든 값을 영속 엔티티에 채워 넣기 때문에 null 값이 들어갈 수 도 있는 문제가 생긴다.
- 이래서 업데이트 시 merge()보단 변경 감지를 사용하자.
- 영속 상태의 객체를 반환
수정된 코드
변경 감지 사용 (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를 한다.
댓글