SLF4J (Simple Logging Facade for Java)
란?최종 사용자가 배포시 원하는 로깅 프레임워크를 결정하고 사용해도 SLF4J가 인터페이스화 되어있기 때문에, SLF4J를 의존하는 클라이언트 코드에서는 실제 구현을 몰라도 됨.
(의존관계 역전 법칙)
폼 객체
를 사용해서 화면 계층과 서비스 계층을 명확하게 분리하기 !! 📍 참고: 폼 객체 (MemberForm) vs 엔티티 (Member) 직접 사용
요구사항이 정말 단순할 때를 제외하고 엔티티 (Member)를 직접 등록하면 유지보수가 어려워진다.
실무에서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다.
화면이나 API 요구사항을 폼 객체나 DTO로 처리하고, 엔티티는 최대한 순수 하게 유지하자.
package jpabook.jpashop.web;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수 입니다") private String name;
private String city;
private String street;
private String zipcode;
}
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping(value = "/members/new")
public String createForm(Model model) {
model.addAttribute("memberForm", new MemberForm());
return "members/createMemberForm";
}
@PostMapping(value = "/members/new")
public String create(@Valid MemberForm form, BindingResult result) {
if (result.hasErrors()) {
return "members/createMemberForm";
}
Address address = new Address(form.getCity(), form.getStreet(),
form.getZipcode());
Member member = new Member();
member.setName(form.getName());
member.setAddress(address);
memberService.join(member);
return "redirect:/";
}
}
package jpabook.jpashop.web;
@Controller
@RequiredArgsConstructor
public class MemberController {
//추가
@GetMapping(value = "/members") public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members); // 조회한 상품을 뷰에 전달하기 위해 모델 객체에 보관
return "members/memberList"; // 실행할 뷰 이름 반환
}
}
상품 등록 폼은 회원 등록의 경우와 매우 유사하므로 생략.
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping(value = "/items/new")
public String createForm(Model model) {
model.addAttribute("form", new BookForm());
return "items/createItemForm";
}
@PostMapping(value = "/items/new") // POST 요청
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:/items"; // 상품 목록 화면으로 리다이렉트
}
}
@Controller
@RequiredArgsConstructor
public class ItemController {
/**
* 상품 수정 폼
*/
@GetMapping(value = "/items/{itemId}/edit") // GET 요청
public String updateItemForm(@PathVariable("itemId") Long itemId, Model
model) {
Book item = (Book) itemService.findOne(itemId); // 준영속 상태 ( book 객체에 해당 ), 수정할 상품 조회
BookForm form = new BookForm();
form.setId(item.getId());
form.setName(item.getName());
form.setPrice(item.getPrice());
form.setStockQuantity(item.getStockQuantity());
form.setAuthor(item.getAuthor());
form.setIsbn(item.getIsbn());
model.addAttribute("form", form);
return "items/updateItemForm";
}
/**
* 상품 수정
*/
@PostMapping(value = "/items/{itemId}/edit") // POST 요청
public String updateItem(@ModelAttribute("form") BookForm form) {
Book book = new Book();
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) ⭐️ ⭐️ ⭐️ 변경 감지 기능 사용 ⭐️ ⭐️ ⭐️
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한 다.
findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}
영속성 컨텍스트에서 엔티티를 다시 조회** -> 변경할 값 선택 트랜잭션 커밋 시점에 변경 감지(Dirty Checking) -> 데이터베이스에 UPDATE SQL 실행
-> 데이터 수정
권장 코드
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
/**
*상품 수정,권장 코드
*/
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
itemService.updateItem(form.getId(), form.getName(), form.getPrice());
return "redirect:/items";
}
}
@Service
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
/**
* 영속성 컨텍스트가 자동 변경
*/
@Transactional
public void updateItem(Long id, String name, int price) {
Item item = itemRepository.findOne(id);
item.setName(name);
item.setPrice(price);
}
}
2) 병합 사용
- 준영속 엔티티의 식별자 값으로 영속 엔티티 조회
- 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체 ( = 병합 )
- 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 DB에 UPDATE SQL이 실행됨.
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 Item mergeItem = em.merge(item);
}
위의 과정과 매우 유사하므로 생략.