이 글은 김영한 강사님의 강의를 참고하여 작성하였습니다.
들어가기 앞서 gradle과 lombok 확인 잊지말자
요구사항이 정리되고 디자이너, 웹 퍼블리셔, 백엔드 개발자가 업무를 나누어 진행한다.
- 디자이너: 요구사항에 맞도록 디자인하고, 디자인 결과물을 웹 퍼블리셔에게 넘겨준다.
- 웹 퍼블리셔: 다자이너에서 받은 디자인을 기반으로 HTML, CSS를 만들어 개발자에게 제공한다.
- 백엔드 개발자: 디자이너, 웹 퍼블리셔를 통해서 HTML 화면이 나오기 전까지 시스템을 설계하고, 핵심
비즈니스 모델을 개발한다. 이후 HTML이 나오면 이 HTML을 뷰 템플릿으로 변환해서 동적으로 화면을
그리고, 또 웹 화면의 흐름을 제어한다.
참고
- React, Vue.js 같은 웹 클라이언트 기술을 사용하고, 웹 프론트엔드 개발자가 별도로 있으면, 웹 프론트엔드 개발자가 웹 퍼블리셔 역할까지 포함해서 하는 경우도 있다.
- 웹 클라이언트 기술을 사용하면, 웹 프론트엔드 개발자가 HTML을 동적으로 만드는 역할과 웹 화면의
흐름을 담당한다.
-> 이 경우 백엔드 개발자는HTML 뷰 템플릿을 직접 만지는 대신에, HTTP API를 통해 웹
클라이언트가 필요로 하는 데이터와 기능을 제공하면 된다.
이 웹 페이지 만들기에서의 목표: 상품을 관리할 수 있는 서비스
상품 도메인 모델
상품 ID
상품명
가격
수량
상품 관리 기능
상품 목록
상품 상세
상품 등록
상품 수정
웹사이트를 쉽게 만들 수 있게 도와주는 HTML, CSS, JS 프레임워크이다.
하나의 CSS로 휴대폰, 태블릿, 데스크탑까지 다양한 기기에서 작동한다. 다양한 기능을 제공하여 사용자가 쉽게 웹사이트를 제작, 유지, 보수할 수 있도록 도와준다
정적 파일이기 때문에 위치 경로는
resources/static/
아래에 css 폴더를 따로 만들어서 그 곳에 넣어주자.
- 주의 🧨
이렇게 정적 리소스가 공개되는resources/static/
폴더에 HTML을 넣어두면, 실제 서비스에서도 공개된다.
=> 곧 서비스를 운영한다면 지금처럼 공개할 필요없는 HTML을 두는 것은 주의
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {
private final ItemRepository itemRepository;
@GetMapping
public String items(Model model) {
List<Item> items = itemRepository.findAll();
model.addAttribute("items", items);
return "basic/items";
}
/**
* 테스트용 데이터 추가
*
*/
@PostConstruct
public void init() {
itemRepository.save(new Item("testA", 10000, 10));
itemRepository.save(new Item("testB", 20000, 20));
}
}
- 기능: final 이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.
- why? 테스트용 데이터가 없으면 회원 목록 기능이 정상 동작하는지 확인하기 어렵다.
@PostConstruct
: 해당 빈의 의존관계가 모두 주입되고 나면 초기화 용도로 호출된다.
-> 여기서는 간단히 테스트용 테이터를 넣기 위해서 사용했다.
먼저 뷰 템플릿이기 때문에 items.html
정적 HTML을 -> 뷰 템플릿(templates) 영역으로 복사하자
/resources/templates/basic/items.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2>상품 목록</h2>
</div>
<div class="row">
<div class="col">
<button class="btn btn-primary float-end"
onclick="location.href='addForm.html'"
th:onclick="|location.href='@{/basic/items/add}'|"
type="button">상품 등록</button>
</div>
</div>
<hr class="my-4">
<div>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>상품명</th>
<th>가격</th>
<th>수량</th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${items}">
<td><a href="items.html" th:href="@{/basic/items/{itemId}
(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="items.html" th:href="@{|/basic/items/${item.id}|}"
th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
<html xmlns:th="http://www.thymeleaf.org">
th:href="@{/css/bootstrap.min.css}"
href="value1" 을 th:href="value2" 의 값으로 변경한다.
th:href
의 값이 href
로 대체되면서 동적으로 변경할 수 있다.핵심만 말하자면 .
.
.
- 핵심은 th:xxx 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체한다.
th:xxx 이 없으면 기존 html의 xxx 속성이 그대로 사용 된다.**- HTML을 파일로 직접 열었을 때, th:xxx 가 있어도 웹 브라우저는 th: 속성을 알지 못하므로 무시한다.
=> 따라서 HTML을 파일 보기를 유지하면서 템플릿 기능도 할 수 있다
th:href="@{/css/bootstrap.min.css}"
@{...}
: 타임리프는 URL 링크를 사용하는 경우 @{...} 를 사용한다. 이것을 URL 링크 표현식이라 한다.속성 변경 - th:onclick
onclick="location.href='addForm.html'"
th:onclick="|location.href='@{/basic/items/add}'|"
|...| :이렇게 사용한다.
본래는 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 한다. 그냥 사용하면 문자와 표현식을 각각 따로 더해서 사용해야 하므로 다음과 같이 복잡해진다.
그래서 '리터럴'을 이용한다!
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
⇩
<span th:text="|Welcome to our application, ${user.name}!|">
ex)
th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"
⇩
th:onclick="|location.href='@{/basic/items/add}'|"
<tr th:each="item : ${items}">
<td th:text="${item.price}">10000</td>
( item.getPrice() )
<td th:text="${item.price}">10000</td>
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
http://localhost:8080/basic/items/1?query=test
th:href="@{|/basic/items/${item.id}|}"
상품 이름을 선택하는 링크를 확인해보자.
리터럴 대체 문법을 활용해서 간단히 사용할 수도 있
/**
* 특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
상품들을 등록하려면 정말,,, 정말 많은 과정을 거쳤다. 한 번 다 봐보자
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
@RequestParam int price,
@RequestParam Integer quantity,
Model model) {
Item item = new Item();
item.setItemName(itemName);
item.setPrice(price);
item.setQuantity(quantity);
itemRepository.save(item);
model.addAttribute("item", item);
return "basic/item";
} return "ok";
}
- Item 객체를 생성하고 itemRepository 를 통해서 저장한다.
- 저장된 item 을 모델에 담아서 뷰에 전달