HTML , CSS는 잘 몰라서 부트스트랩 프레임워크의 힘을 빌렸다..
스프링 부트에서 정적 리소스로 쓰기 위해 부트 스트랩으로 받은 파일들은 /resources/static 영역에 모아 두었다.
컨트롤러 생성
이제 컨트롤러와 뷰 템플릿을 구현 한다.
package hello.itemservice.web.basic;
import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor // 생성자 만들어줌 + 생성자가 하나니까 AutoWired도 발생
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("itemA", 10000, 10));
itemRepository.save(new Item("itemB", 20000, 20));
itemRepository.save(new Item("itemC", 30000, 30));
}
}
먼저 itemRepository에 있는 모든 상품을 조회하고 모델에 담는다. 그리고 뷰 템플릿을 호출한다.
클래스 레벨에 @RequiredArgsConstructor을 붙여줌으로써 itemRepository의 생성자와 @Autowired 생략 가능
또한, 테스트용 데이터를 넣기 위해 @PostConstruct 메서드를 사용했다.
Thymeleaf 로 뷰 템플릿 생성
타임리프는 정적인 HTML을 동적으로 사용할 수 있게 해주는 템플릿 엔진이다.
본격적으로 타임리프를 사용하기 전에 항상 타임리프 사용 선언을 다음과 같이 해줘야 한다
<html xmlns:th="http://www.thymeleaf.org">
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"> // 여기서 선언
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
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="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${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>
속성 변경
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
먼저 상대경로 였던 기존 HTML 코드를 뷰템플릿으로 렌더링 후에 절대 경로로 수정해주었다.
이게 어떤 문법이냐면, 기존의 href 태그의 값이 뷰 템플릿을 거치게 되면 th:href 의 값으로 대체가 된다. 다음 예시도 보자.
<button class="btn btn-primary float-end"
onclick="location.href='addForm.html'"
th:onclick="|location.href='@{/basic/items/add}'|"
type="button">상품 등록
</button>
기존의 onclick 주소를 th: 태그를 통해 설정한 주소로 이동하도록 속성을 변경할 수 있다.
이처럼 타임리프의 핵심은 th:xxx 가 붙은 부분이 뷰 템플릿을 거치게 되면 기존 HTML 부분을 대체 한다는 점이다. 이게 왜 핵심이냐면, 만약 th:xxx 부분을 빼먹거나 실수해도 웹 브라우저가 해당 태그 부분을 무시하고 정상적으로 화면을 그려줄 수 있기 때문에 오류가 발생하지 않는다는 점이다.
다른 타임 리프 문법이 있는데요..
@{} : 링크 표현식
|...| : 리터럴 대체 : || 안에 문자를 모두 문자열로 인식하여 문자열 익스케이프 처리를 따로 하지 않아도 된다.
th:each: for-each문과 생긴 것도 비슷하다.
<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
</tr>
${} : 변수 표현식 : 모델의 값이나 타임리프 변수로 선언된 값을 조회 가능
Natural Templates
JSP는 HTML과 자바코드를 섞어 작성한 파일이므로 순수 HTML로 열어보면 더이상 열 수 없는 파일이 된다.
하지만 타임리프는 확장자명에서도 보이듯이 HTML 포맷을 유지한다.
즉, HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네추럴 템플릿 이라고 한다.
'Project > Web' 카테고리의 다른 글
[Spring] MVC 패턴 웹 페이지 6 - 상품 등록 처리하기 @ModelAttribute (0) | 2025.06.25 |
---|---|
[Spring] MVC 패턴 웹 페이지 5 - 상품 등록 폼 (0) | 2025.06.25 |
[Spring] MVC 패턴 웹 페이지 4 - 상품 상세 페이지 (0) | 2025.06.25 |
[Spring] MVC 패턴 웹 페이지 2 - 상품 도메인, 저장소 구현 (0) | 2025.06.24 |
[Spring] MVC 패턴 웹 페이지 1 - 요구사항 분석 (0) | 2025.06.24 |