웹 계층과의 데이터 송수신을 위한 MemberForm 클래스를 생성하고, Controller를 작성한다.
이는 일종의 DTO 이다.
왜 Member 객체가 아닌 새로운 Form을 만들었을까?
@NotEmpty 같은 어노테이션은 @Valid 어노테이션의 처리를 위한 어노테이션인데, 이를 entity 에서 하게되면 지저분해지고,
도메인에서 원하는 제약조건과 Web Controll에서 넘어오는 제약조건이 다를수도 있다.
getter setter만 있는 전송을 위한 객체 DTO
폼을 쓰지않고 엔티티에서 처리하면,
엔티티에 화면을 처리하기 위한 코드가 늘어나게 되있다. (이번은 예제라 간단할 뿐)
지저분하고 화면 종속적으로 설계하게 된다.
엔티티는 최대한 다른 dependency 없이 핵심 비즈니스 로직만 의존하도록 설계 해야 한다.
그래야 엔티티를 유연하게 사용할 수 있고 유지보수성이 향상된다.
화면에 맞는 api는 Form 객체나 DTO를 사용하자.
api를 만들때는(웹에 뿌릴때) 엔티티를 넘기면 안된다.
엔티티를 변경 시 api스펙을 변경해야하고, 중요 정보가 유출될 수 있다.
일단 회원가입을 처리하는 간단한 메서드를 보자.
여기서 넘어오는 Model 객체는 어떤 놈일까?
Model에 들어가보면 인터페이스 임을 알 수 있고, 내부에 Map<String, Object>
를 멤버로 가지고 있다.
또한 addAttribute()와 같은 기능을 통해 모델에 원하는 속성과 그것에 대한 값을 주어 전달할 뷰에 데이터를 전달할 수 있다.
Spring에서 Controller의 메서드를 작성할 때는 특별하게 Model이라는 타입을 파라미터로 지정할 수 있다. Model 객체는 JSP에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할을 하는 존재다. 이를 이용해서 JSP와 같은 뷰(View)로 전달해야 하는 데이터를 담아서 보낼 수 있다. 메서드의 파라미터에 Model 타입이 지정된 경우에는 스프링은 특별하게 Model 타입의 객체를 만들어서 메서드에 주입하게 된다.
가장 빈번하게 사용하게될 addAttribute(String, Object)
메서드를 살펴보면 attributeName 과 attributeValue를 담아 Model에 저장해서, Controller를 통해 넘겨준다는 것을 알 수 있다.
추후에 보게 될 @ModelAttribute
어노테이션을 살펴보자.
@ModelAttribute
는 강제로 전달받은 파라미터를 Model에 담아서 전달하도록 할 때 필요한 어노테이션이다.
@ModelAttribute
가 걸린 파라미터는 타입에 관계없이 무조건 Model에 담아서 전달되므로, 파라미터로 전달된 데이터를 다시 화면에서 사용해야 할 경우 유용하게 사용된다.
솔직히 html, css, js 에 대한 지식은 거의 전무하다.
타임리프 문법 정도를 좀 공부해야될 것 같다.
<form role="form" action="/members/new"
th:object="${memberForm}" method="post">
위 코드를 보면, 대충 /members/new
에서 액션이 일어나면 form 이라는 이름으로 memberForm 객체를 post 요청을 보내는 것같다.
그래서 아래의 메서드가 실행 되는 것같다.
@Valid 어노테이션이 걸린 곳의 @NotEmpty 같은 조건들을 검사해준다.
@Valid 에서 문제가 생기면 BindingResult에 데이터가 들어온다. 그에 대한 결과를 처리하는 것
상품쪽 요구사항을 보면, 상품을 등록하고 등록된 상품에 대해
세부 속성을 변경 기능을 요구하고 있다.
이전의 강의에서 영속성 컨텍스트가 변경 내용을 1차 캐시에서 더티캐싱 하여 자동으로 update 쿼리를 날려준다는 내용을 배우며, 준영속 상태에 대해 공부했다.
준영속 엔티티란, 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.
이 준영속 엔티티를 수정하려면, 두가지 방법을 사용 할 수 있다.
1. 변경 감지
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한 다.
findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다`
}
영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법이다.
트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택,
트랜잭션 커밋 시점에 변경 감지(Dirty Checking) 이 동작해서 데이터베이스에 UPDATE SQL 실행한다.
2. merge
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item mergeItem = em.merge(item);
}
결국 뜯어보면, 변경 감지와 merge의 내부 작동 구조는 동일하다.
하지만 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있다.
병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)
참고: 실무에서는 보통 업데이트 기능이 매우 제한적이다. 그런데 병합은 모든 필드를 변경해버리고, 데이터 가 없으면 null 로 업데이트 해버린다. 병합을 사용하면서 이 문제를 해결하려면, 변경 폼 화면에서 모든 데 이터를 항상 유지해야 한다. 실무에서는 보통 변경가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거롭다.
==> 변경 감지를 사용하자.
컨트롤러에서 어설프게 엔티티를 생성하지 마라.
트랜잭션이 있는 서비스 계층에 식별자와 변경할 데이터를 명확하게 전달하세요.(파라미터 or dto)
트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하라.
트랜잭션 커밋 시점에 변경 감지가 실행된다.
html, 타임 리프를 공부할 것..
Web의 동작 방식에 대해서도 아직 이해가 빈약하다.