@Controller
@Slf4j //lombok 써서 log임
public class HomeController {
@RequestMapping("/")
public String home(){
log.info("home controller");
return "home"; //home.html파일로
}
}
home.html 폼 작성 등을 하고 잠시 결과를 보면 이렇게 HomeController가 구동되었다고 뜬다
- 상단의 home.html의
th: replace ="fragments/헤더위치 :: 이름 "
에서 해당 위치를 찾아 'header'이름의fragments
형태를 찾아 리플래쉬 된다.
=> 그러나 (header , footer) 같은 템플릿 파일을 반복해서 포함하기 때문에 하단의 링크처럼 Hierarchical-style layouts을 참고하여 이런 부분도 중복을 제거할 수 있고 여러 방법이 있다.
build.gradle 파일에서 이렇게
devtools
를 찾을 수 있는데 이것 덕분에 html파일에서 내용을 수정하고 '리컴파일(ctrl + shift + F9)'를 해주면 변경된다.
[bootStrap 설치]((https://getbootstrap.com/)를 해서 압축을 풀어준다.
resources/static/css/jumbotron-narrow.css
추가
=> 이곳에서는 회원기능 중 회원 가입을 눌렀을때 발생될 웹계층을 제작하는 것이 목적.
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/members/new")
public String createForm(Model model){
//여기서 model은 controller에서 View로 넘어갈때 데이터를 실어 넘겨줌
//곧 memberForm 컨트롤러에서 View로 넘어갈때 new MemberForm()을 실어서 넘겨줌
model.addAttribute("memberForm" ,new MemberForm());
return "members/createMemberForm";
}
//createMemberForm 이름에서 post타입으로 넘어옴
//url이 같아도-> get은 화면을 열어보는 것이고 , post는 실제로 등록하는 것이 서로 다름
//@Valid 해당하는 클래스에 가서 애노테이션들을 다 Validation해준다.
//앞에 @Valid와 BlindingResult가 같이 뒤에 쓰이게 되면 result에 오류가 잇을경우 이곳에 담김
@PostMapping(value = "/members/new")
public String create(@Valid MemberForm form, BindingResult result) {
if (result.hasErrors()) {//result에 에러 있냐
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:/"; //재로딩(redirect)해서 홈(/)으로
}
}
😶MemberForm.java
@Getter @Setter
//폼 계층으로 화면과 서비스 계층을 분리할 것
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수 입니다")
private String name; //name을 필수로 받겠다. -> 값이 비어있으면 오류가 남
private String city;
private String street;
private String zipcode;
}
->
@GetMapping("/members/new")
의 url을 찾아 창을 띄운다
- model은 현 "Member"컨트롤러에서 View(리턴값)로 넘어갈때 'new MemberForm()'을 담아서 보내준다.
곧, model에는createMemberForm.html
로 Member 컨트롤러에서 'MemberForm'을 챙겨서 보낸다.
@NotEmpty
가 설정되어 있다.[이 블로그에서 참조하여 작성하였습니다.]
값이 null 혹은 "" 빈 문자열이 들어와선 안된다.
=> 곧 'name' 필드는 값이 들어가야 한다.
이곳에서 회원가입에서 값들을 입력을 받아 저장해준다.
1. @Post 타입으로 받았다
2. @Valid와 BlindingResult 가 함께 있을때
3. 값을 받지 못한 경우에 에러를 어떻게 처리하는가
둘다 같은 url "/members/new"
을 갖고 있찌만 get방식은 화면을 열어본다면 post방식은 실제로 그것을 등록한다는 것에서 다르다.
이 블로그를 참조하였습니다.
@Valid
에 해당되는 객체인 MemberForm에서 쓰인 애노테이션들을 Validation 해준다.BlindingResult
가 함께 쓰인다면, 이곳에서는 방금 전 객체를 검사하며 바인딩 중 생긴 에러가 담긴다.result.hasErrors()
로 에러가 담겨있다면 createMemberForm.html로 날린다.
- object에 담긴 객체의 프로퍼티를 filed *{필드이름} 형식으로 사용
class="${ #fields.hasErrors('name')}?
로 memberForm의 'result'와 같이 'name' 에러가 있다면, 상단의 'fieldError'에서 설정되있는 빨간색 테두리로 바뀌게 된다.if="${#fields.hasErrors('name')}"
에서 에러가 발생되면 object에서 참고하는 객체에서 해당 'name'의 메세지를 가지고 출력
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
.
.
.
(중략)
//회원목록 눌렀을때
//주의! 여기서는 어쩔 수 없이 엔티티를 넘겨주었으나
//API를 이용할때는 절대 엔티티를 외부로 넘겨주거나 노출하면 안된다. ->ex 패스워드 노출 등
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
//members객체를 가지고 넘겨줌
model.addAttribute("members", members);
return "members/memberList";
}
이곳에선 어쩔 수 없이 엔티티를 그대로 넘겨주었으나,
=> Mock객체
를 받았지만 List<Member> members=
로 엔티티를 다시 이용함.
실제 API를 이용할때는 절대 '엔티티'를 외부로 넘겨주거나 노출해선 안된다 ex)패스워드 노출 등
'회원목록'의 html을 templates/members/
에 생성해준다.
th:each="member : ${members}"
Get 매핑에서 Mock에 넘겨준members
를 참조하여 바인딩하겠다.${member.address?.city}
에서 '?'는 null이면 더 진행하지 않겠다 는 의미.
@Getter @Setter
public class BookForm {
private Long id;
private String name;
private int price;
private int stockQuantity;
private String author;
private String isbn;
}
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/items/new")
public String createForm(Model model) {
model.addAttribute("form", new BookForm());
return "items/createItemform";
}
//Validation 안써줌 (여기서 단순하게 해주려고)
@PostMapping(value = "/items/new")
public String create(BookForm form) {
//웬만해서 이처럼 setter 안쓰길 바람..
// ->담엔 createBook()처럼 파라미터로 넘기도록 해라
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:/";
}
}
- model.addAttribute("form", new BookForm()); 에서 'new BookForm()'을 가지고 'createItemform으로 넘겨주었다.
- 예제를 단순하게 하기 위해서 Validation은 해주지 않았다.
- 위와 같이 입력받은 정보를 가지고
'form'
에서 입력받은 값으로, book 객체의 name, price, stockQuantity, author, isbn 에 값을 넣어준다.- 웬만해서 이처럼
setter
로 값을 입력받는 것보다createBook()
처럼 따로 메서드를 만들어 파라미터로 입력받는 것을 더 추천.
상단의 Get매핑에서 model객체로 함께 넘어온 'new Book()'의 'form'객체로 값이 저장된다.
createItemform.html
에서 값을 넘겨 받은 'form'은, 'ItemController'의 PostMapping()에서 'form'을 파라미터로 들어간다.'ItemController'
의 PostMapping()에서 book 객체의 필드들에 값을 넣고 저장 한다.return "redirect:/items"
로 상품 목록에 보내준다.이렇게 Book을 상품등록해줘서 DB에서는 'DTYPE=B'가 뜨며, 나머지 'ACTOR'나 'DIRECTOR', 'ARTIST', 'ETC' 와 같은 경우
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
싱글 테이블로 설정해줬기 때문에 같이 나온다!
"/items"
url과 매핑 되어있음- model객체에 'items'를 가지고
return "items/itemList"
에 위치한 파일로 보내준다.
📣 itemList
에서는 model 객체에서 넘어온 'items'
로 값을 text에 넣어준다.
상품 목록에서는 itemList
에서는 아래와 같이 '수정' 버튼이 생기는데
이와 같이 itemList의 클래스에서 url이 id에 따라 달라진다.
ↆↆↆ
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
.
.
(중략)
//상품 수정 폼
@GetMapping("items/{itemId}/edit")
//@PathVariable 'itemId'가 변경 되기 때문
public String updateItemForm(@PathVariable("itemId") Long itemId, Model model){
//예제 단순화를 위해 Item 타입이지만 Book타입으로 캐스팅함
Book item=(Book) itemService.findOne(itemId);
//item엔티티 보내지 않고 우리는 BookForm을 보낼 것
BookForm form = new BookForm();
//item엔티티로부터 저장받은 필드들의 값을 다시 form에 세팅
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에 form객체를 담아서 보내줌
model.addAttribute("form", form);
return "items/updateItemform";
}
/**
* 상품 수정
*/
@PostMapping(value = "/items/{itemId}/edit")
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);//수정된 book객체 필드들 저장
return "redirect:/items"; //상품목록으로 반환
}
}
url을 ("items/{itemId}/edit")
로 정해준다.
이때 itemId는 계속 변경이 되기 때문에 @PathVariable("itemId") Long itemId
멤버필드를 넣어줘야 한다.
(1) item 엔티티로부터 가져온 필드 값들을 'BookForm'의
form 객체
에 전부 세팅해준다.
(2) form객체에 필드들은 수정 전 값들이 들어가 있다.
(3)form객체
를 담은 'model'을 return하는 곳에 넘겨 준다.
- 그렇게 form으로 값이 수정되어
updateItemform.html
으로부터 넘어오고 'book'객체로 그 값들을 다시 옮겨준다.- 넘겨받은 값들은 다시 db에 저장후 return 값에 의해 '상품목록'창으로 넘긴다.
포멜로 님 과 Bing9님의 jeongdalma참조하였습니다.
영속 상태였었다가 연속성 컨텍스트에서 더이상 관리를 하지 않는 상태를 뜻한다.
- 영속성 컨텍스트가 더는 관리하지 않는 엔티티
- 식별자 값을 소지한 상태(이미 한 번 영속상태였으므로 소지함.)
- 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법
- 트랜잭션 안에서 엔티티를 다시 조회 , 변경할 값을 선택 -> 트랜잭션 커밋 시점에 "변경 감지"가 발생하여 수정 됨.
- 이 동작에서 데이터베이스 UPDATE SQL 실행
member 엔티티의 값
으로 바꿔줌.
- 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
- 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다(merge)
- 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행
(주의):
'변경 감지' 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, '병합'을 사용하면 모든 속성이 변경된다.
->병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)
- 앞으로 '변경감지'를 이용해 엔티티를 변경하는 것이 좋다.
- 컨트롤러에서 엔티티를 생성하는 것이 아니라, 트랜잭션이 있는 '서비스 계층'에서 식별자 id와 변경할 데이터들을 파라미터로 넘겨주어 영속상태의 엔티티를 조회하고 데이터를 변경해주는 것이 best!
메인화면에서 '상품주문' 클릭->/order
url 로 'GET 방식으로 호출'
=> model객체에 모든 고객정보와 상품정보를 담아 View로 넘긴다.
-> 해당 /order
url을 'post'방식으로 호출한다.
(1) 주문 등록 후 or 홈화면에서 주문목록을 클릭시 'GET방식'으로 url이 연결된다.
이렇게 model 객체에 자동으로 담기며 form에서 값을 받아온다 =>model.addAttribute("orderSearch", orderSearch);
(이게 생략 된 것과 같음)
(2) model객체를 그렇게 orderList
View로 넘겨주면 form 태그에 th:object="${orderSearch}"
에서 이름과 주문상태 검색창은 object를 통해 값을 가져와 옵션값을 비춘다.
(3) 회원이름과 주문상태를 맞춘후 '검색'버튼을 클릭시, 다시 'GET방식'에서 'orderSearch'로 받아온 검색 값을
List<Order> orders = orderService.findOrders(orderSearch);
통해 model객체에서 다시 View로 보내준다.
주문검색을 하고, 만약 주문상태가 'ORDER'이라면 자바스크립트를 통해 'POST방식'을 호출하여 주문취소를 해줄 수 있다.
(1)
"'javascript:cancel('+${item.id}+')'"
에서 주문id를 이용해 cancel()메서드를 호출한 후, form객체에 'POST방식'으로 url을 넘겨준다.
(2) 자바스크립트로부터 url로 호출되어 주문id로 cancelOrder()메서드를 실행해주고 '주문내역'으로 돌아온다.
[이 블로그를 참조하였습니다.]
@PostMapping이나 @GetMapping 등의 {변수명}을
@PathVariable("변수명")뒤의 자료형과 변수에 전달받을 값으로 넣는다.