h2 연동 에러들

 

에러문구 

Caused by: java.lang.RuntimeException: Driver com.mysql.cj.jdbc.Driver claims to not accept jdbcUrl,

 

 

해결

application.yml 또는 .properties 에 분명 오타 또는 띄어쓰기 잘못된거 있으니까 잘 보면 된다..

 

 

정말 삽질을 오래했는데 알고봤더니 jdbc 에서 j가 빠져있었다.. 

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/vintage-camera-shop

 

 

 

 

에러문구 : h2 DB가 제대로 연결이 안됐다?

java.lang.ClassNotFoundException: org.h2.Driver

 

 

해결

 build.gradle 에 runtimeOnly 'com.h2database:h2'  추가하면 된다!

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    compileOnly 'org.projectlombok:lombok'

    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    runtimeOnly('org.springframework.boot:spring-boot-devtools')
    
     runtimeOnly 'com.h2database:h2'
}
반응형
LIST

 

 

상품등록 후 새로고침하면 중복등록이 되는 문제가 발생

 

 

[실제 상품을 등록한 뷰 화면]

 

위처럼 상품 등록한 상태에서 새로고침을 누른다면 상품ID는 계속 올라가고 상품이 계속 등록되는 이슈가 생긴다.

 

 

 

[상품등록 POST - BasicItemController ]

@PostMapping("/add")
public String addItemV4(Item item){
    itemRepository.save(item);
    return "basic/item";
}

 

  • 전체 흐름
    • 실제 상품등록
    • 상품 상세 뷰로 뷰템플릿(basic/item)을 호출 - 끝
    • 상품 등록 (POST / add) URL 이 유지되어있는 상태
    • 새로고침하면 계속 POST / add 행위가 지속된다.
    • 새로고침 시 마지막 행위가 다시 요청되기 때문에 상품이 중복저장이 되는 이슈가 발생한다. 
  •  

 

 

 

 

 

문제 해결 방법은 리다이렉트! 

 

 

POST ▶ Redirect ▶ GET

실제 상품을 등록하고 뷰 템플릿으로 호출하는게 아니라 상품 상세화면으로 리다이렉트를 하면된다.

그렇게 하면 상품 등록 후 상품 상세 화면으로 다시 이동해서 (리다이렉트) 마지막에 호출한 GET /items/{id} 이 된다.

 

  • 전체 흐름 
    • 실제 상품등록 POST/ add
    • 상품 상세 화면으로 리다이렉트 Redirect items/{itemId} 호출 http://localhost:8080/basic/items/3
    • 웹 브라우저가 상품 상세를 새로 요청 GET /items/{id} 
    • 새로고침을 해도 GET /items/{id} 상품 상세 화면으로 이동
    • 문제 해결 

 

 

 

[상품등록 POST redirect - BasicItemController ]

@PostMapping("/add")
public String addItemV5(Item item) {
    itemRepository.save(item);
    return "redirect:/basic/items/" + item.getId();
}

 

※ 주의

+item.getId() 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험

 

 

 

 

RedirectAttributes

RedirectAttributes 을 사용하여 URL 인코딩 문제 해결과 상품이 실제로 등록이 됐을때 "저장되었습니다." 메세지가 나오도록 해보자 

@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes){
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/basic/items/{itemId}";
}

 

RedirectAttributes 는 리다이렉트할 때 모델에 데이터를 전달할 수 있고 파라미터에 itemIdstatus 를 붙여주는 역할이다. 

  • basic/items/1?status=true

 

 

 

이제 저장완료 메세지를 만들어보자

 

리다이렉트  redirect:/basic/items/{itemId}  ▶ 최종 basic/item 뷰 템플릿을 호출한다. 

@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {

    private final ItemRepository itemRepository;

    //상품 상세
    @GetMapping("/{itemId}")
    public String item(@PathVariable Long itemId, Model model){
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "basic/item";
    }
    
    ....

 

 

 

 

뷰템플릿

resources/templates/basic/item.html 

상품 상세

<div class="container">
    <div class="py-5 text-center">
        <h2>상품 상세</h2>
    </div>

    <!-- -->
    <h2 th:if="${param.status}" th:text="'저장 완료'"></h2>
    
    ...
  • th:if 해당 조건이 참일 때
  • ${param.status} : 파라미터 값을 조회할 수 있는 기
  • 참이면 '저장완료' 가 나온다.

 

 

 

상품 실제 등록시 '저장 완료' 메세지를 확인할 수 있다. 

반응형
LIST

 

상품수정은 상품등록처럼 같은 url (/{itemId}/edit)이다 

GET - 상품 수정 폼 

POST - 상품 수정 처리

 

 

 

컨트롤러 - 상품 수정 [BasicItemController] 에 추가 

 

  • 상품 수정 페이지를 열때는 GET (실제 수정 X)
//상품 수정
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model){

	Item item = itemRepository.findById(itemId);
	model.addAttribute("item", item);
	
	return "basic/editForm";
}

 

 

 

 

뷰 템플릿 [edit.html]

/resources/templates/basic/editForm.html

 

  • 타임리프 설정
<html xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/bootstrap.min.css}"
<!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">
    <style>
        .container {
        max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 수정 폼</h2>
    </div>
    <form action="item.html" th:action method="post">
        <div>
            <label for="id">상품 ID</label>
            <input type="text" id="id" name="id" class="form-control" value="1"
                   readonly>
        </div>
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" value="상품A" >
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control"
                   value="10000" th:value="${item.price}">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" value="10">
        </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">저장</button>
            </div>
            <!--수정폼에서 취소 눌렀을때 상품상세 페이지로-->
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='item.html'"
                        th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
                        type="button">취소</button>
            </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

 

 

<form action="item.html" th:action method="post">

상품수정은 상품등록처럼 유사하게 GET,POST URL이 같기 때문에 th:action 에 생략해도 괜찮다

 

 

 

상품 수정화면

그러나 '상품 수정' 버튼을 눌러서 상품 수정 폼에 들어가면 상품명이 안 바뀌어있다  ( itemA ▶ 상품A )

 

 

폼 input에 각 상품명, 가격, 수량에 th:value 값을 넣으면 된다.

  • th:value="${item.id}"
  • th:value="${item.itemName}"
  • th:value="${item.quantity}"

 

변경 되는 상품수정 화면 ( itemA itemA )

 

 

 

 

취소 버튼 눌렀을때 다시 수정 폼으로 돌아가기 

th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})
<button class="w-100 btn btn-secondary btn-lg"
        onclick="location.href='item.html'"
        th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
        type="button">취소
</button>

 

 

 

 

 

컨트롤러 - 상품 수정 [BasicItemController] 에 추가 

  • 상품을 실제로 수정할 때는 POST
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId,@ModelAttribute Item item){

    itemRepository.update(itemId, item);
    //수정저장 후 redirect
    return "redirect:/basic/items/{itemId}";
}

 

페이지 이동 : 상품을 수정하고 저장하면 상품 상세 페이지로 이동하도록 한다.

 

 

 

 

리다이렉트 redirect 

상품 수정 마지막에 뷰 템플릿을 호출하는게 아니라 상품 상세 화면으로 이동하는 리다이렉트를 호출한다. 

  • redirect:/basic/items/{itemId}basic/items/1

 

 

 

상품 수정했을때 개발자 모드를 보면 

  • Location : http://localhost:8080/basic/items/1 로 리다이렉트를 한 뒤 
  • itemId가 1인 항목을 불러온다. 즉, 상품 상세 컨트롤러를 다시 호출한다.

 

 

 

 

반응형
LIST

 

 

컨트롤러 - 상품 등록 [BasicItemController] 에 추가 

 

  • 상품 등록 페이지를 열때는 GET (실제 등록 X)
//상품 등록 
@GetMapping("/add")
public String addForm(){
    return "basic/addForm";
}

 

  • 상품을 실제로 등록할 때는 POST
@PostMapping("/add")
public String save(){
    return "basic/addForm";
}

 

 

같은 URL인데 HTTP 메서드로 기능을 구별해준다. 

 

 

 

 

뷰 템플릿 [addForm.html]

/resources/templates/basic/addForm.html

 

  • 타임리프 설정
<html xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/bootstrap.min.css}"
<!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">
    <style>
        .container {
        max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 등록 폼</h2>
    </div>
    <h4 class="mb-3">상품 입력</h4>
    <form action="item.html" th:action method="post">
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control"
                   placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">
        </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">상품 등
                    록</button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='items.html'"
                        th:onclick="|location.href='@{/basic/items}'|"
                        type="button">취소</button>
            </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

 

 

 

 

타임리프 속성

th:action

 

상품 등록 버튼을 눌렀을때 동적으로 URL이 변경되도록 하는 액션속성

 

  • 사실상 GET, POST 같은 URL (basic/items/add) 이기 때문에 안 써도 된다.
    • th:action="/basic/items/add"  ▶ <form action="item.html" th:action method="post">
  • 등록 페이지에 갔을때 '페이지 소스보기' 를 하면 action="" 비어있는 것을 확인할 수 있다. 

 

 

 

 

 

취소 버튼 눌렀을때 url 수정하기 

취소했을때는 목록으로 돌아가기 

th:onclick="|location.href='@{/basic/items}'|"
<button class="w-100 btn btn-secondary btn-lg"
        onclick="location.href='items.html'"
        th:onclick="|location.href='@{/basic/items}'|"
        type="button">취소
</button>

 

 

 


 

이제 실제로 상품을 등록해보자 

 

 

상품 등록 POST - @ModelAttribute

 

우선 @ModelAttribute 와 @RequestParam 차이점을 알아보자

 

 

@RequestParam

@RequestParam - 상품 등록 처리

 

클라이언트의 HTTP POST 요청파라미터를 처리하기 위해 @RequestParam 을 사용한다 

 

[addItemV1 - BasicItemController에 추가]

@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 "";
}

 

 

@RequestParam String itemName 은 addForm.html 의 name="" 값이다. 

  • <input type="text" id="itemName" name="itemName" ~ >

 

 

 

실제 상품을 등록한 뷰 화면

 

 

 

@ModelAttribute 

@ModelAttribute - 상품 등록 처리

 

[addItemV2 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item ) {
	
	itemRepository.save(item);
	//model.addAttribute("item",item);
    
	return "basic/item";
}
  • @ModelAttribute - 요청 파라미터 처리를 해준다. 
    • Item 객체를 생성하고 요청 파라미터 값을 setXxxx 으로 Item 객체의 필드에 값을 설정해준다.
    •  Spring이 자동으로 처리해줌 개발자가 직접 setXxxx 메서드를 호출할 필요가 없어진다. 
    • Item item = new Item();
      item.setItemName(itemName);
      item.setPrice(price);
      item.setQuantity(quantity);
  • @ModelAttribute 은 자동으로 model에 뷰를 넣어준다.
    • @ModelAttribute("item") 이름이 ▶ model.addAttribute("item", item) 이다. 
    • model.addAttribute("item", item) 가 주석처리를 해도 잘 동작한다. 

 

 

 

ModelAttribute 이름 생략

 

[addItemV3 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
	
	itemRepository.save(item);
	//model.addAttribute("item",item);
    
	return "basic/item";
}
  • @ModelAttribute("item") Item item 이름을 생략하면 클래스의 첫글자만 소문자로 변경해서 등록한다. 
  • 클래스명 Item item

 

 

 

ModelAttribute 전체 생략

 

[addItemV4 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV4(Item item) {
	
	itemRepository.save(item);
    
	return "basic/item";
}
  •  addItemV1 처럼 단순타입인 String이라면 @RequestParam 이 자동적용되고 
  • 객체 타입인 경우 @ ModelAttribute가 자동적용이 된다. 
  • 이것 또한 클래스명 Item  item 소문자로 변경해서 모델에 담긴다. 

 

 

 

반응형
LIST

 

 

컨트롤러 - 상품 상세 [BasicItemController] 에 추가 

//상품 상세
@GepMapping(/{itemId})
public String item(@PathVariable Long itemId, Model model) {
	Item item = itemRepository.findById(itemId);
	model.addAttribute("item", item);
	return "basic/item";
}
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {

    private final ItemRepository itemRepository;

	//상품 목록
    @GetMapping
    public String items(Model model){
        List<Item> itmes = itemRepository.findAll(); //아이템 전체목록
        model.addAttribute("items", itmes);
        return "basic/items";
    }

	//상품 상세
    @GepMapping(/{itemId})
    public String item(@PathVariable Long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "basic/item";
    }
}

 

 

 

 

뷰 템플릿 [ item.html ]

  /resources/templates/basic/item.html

 

  • 타임리프 설정
<html xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/bootstrap.min.css}"
<!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">
    <style>
        .container {
        max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 상세</h2>
    </div>

    <div>
        <label for="itemId">상품 ID</label>
        <input type="text" id="itemId" name="itemId" class="form-control"
               value="1" th:value="${item.id}" readonly>
    </div>
    <div>
        <label for="itemName">상품명</label>
        <input type="text" id="itemName" name="itemName" class="form-control"
               value="상품A" th:value="${item.itemName}" readonly>
    </div>
    <div>
        <label for="price">가격</label>
        <input type="text" id="price" name="price" class="form-control"
               value="10000" th:value="${item.price}" readonly>
    </div>
    <div>
        <label for="quantity">수량</label>
        <input type="text" id="quantity" name="quantity" class="form-control"
               value="10" th:value="${item.quantity}" readonly>
    </div>
    <hr class="my-4">
    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-primary btn-lg"
                    onclick="location.href='editForm.html'"
                    th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
                    type="button">상품 수정</button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg"
                    onclick="location.href='items.html'"
                    th:onclick="|location.href='@{/basic/items}'|"
                    type="button">목록으로</button>
        </div>
    </div>
</div> <!-- /container -->
</body>
</html>

 

 

  • 렌더링 하기 - 상품ID 
    <div>
        <label for="itemId">상품 ID</label>
        <input type="text" id="itemId" name="itemId" class="form-control"
               value="1" th:value="${item.id}" readonly>
    </div>

 

  • 상품명
    • th:value="${item.itemName}"
  • 가격
    • th:value="${item.price}"
  • 수량
    • th:value="${item.quantity}"

 

 

[뷰화면]

 

 

 

 

버튼 링크 수정하기 

 

  • 상품수정 버튼 
  •  th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
  • basic/items/1/edit 같은 URL
<button class="w-100 btn btn-primary btn-lg"
        onclick="location.href='editForm.html'"
        th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
        type="button">상품 수정
</button>

 

 

  • 목록으로 버튼
  • th:onclick="|location.href='@{/basic/items}'|"
<button class="w-100 btn btn-secondary btn-lg"
        onclick="location.href='items.html'"
        th:onclick="|location.href='@{/basic/items}'|"
        type="button">목록으로
</button>
반응형
LIST

 

 

부트스트랩 사용하기

웹 사이트를 쉽게 만들어주는 틀 같은 것

 

 

 

1. 아래 URL에 접속하여 css 파일을 다운받고 압축풀어서 bootstrap.min.css 를 복사한다. 

https://getbootstrap.com/docs/5.0/getting-started/download/

 

2. 그리고 resources/static/css/bootstrap.min.css 폴더에 추가해보자 

 

 

3. 실행 후 http://localhost:8080/css/bootstrap.min.css 접속했을때 나오면 정상작동!

 

 

 

/resources/static 에 넣으면 스프링 부트가 정적 리소스를 제공한다 

 

주의!

정적리소스인 /resources/static 에 html을 넣어두면 모두 공개된다 실제 서비스도 공개되기때문에 

공개할 필요가 없는 html 을 저장하는것을 주의해야한다 

 

 

 

 

정적 리소스 - 상품 목록 HTML

resources/static/html/items.html

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <link 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'" 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>
                <td><a href="item.html">1</a></td>
                <td><a href="item.html">테스트 상품1</a></td>
                <td>10000</td>
                <td>10</td>
            </tr>
            <tr>
                <td><a href="item.html">2</a></td>
                <td><a href="item.html">테스트 상품2</a></td>
                <td>20000</td>
                <td>20</td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->
</body>
</html>

 

 

 

 

 

정직 리소스를 사용하면 노출 위험이 있으니 보안과 동적 콘텐츠 생성할 수 있는 컨트롤러와 뷰 템플릿으로 개발해보자 

 

뷰 템플릿? 

HTML 과 비슷하지만 서버 측에서 동적으로 생성되며 클라이언트에게 전달된다. 
그러므로 클라이언트에게 전달하기 전에 템플릿을 처리하여 필요한 데이터를 삽입해준다
위치는 주로 /resources/templates 에 위치한다. 

 

 

 

items.html 정적 HTML을 뷰 템플릿(templates) 영역으로 복사하기

/resources/static/items.html → 복사 → /resources/templates/basic/items.html

 

 

 

 


 

 

컨트롤러 의존성 주입(Dependency Injection) 간편화하기

 

 

[ BasicItemController ]

@Controller
@RequestMapping("/basic/items")
public class BasicItemController {

    private final ItemRepository itemRepository;
    
    //생성자
    @Autowired
    public BasicItemController(ItemRepository itemRepository){
    	this.itemRepository = itemRepository;
    }
    
}

 

 

@Autowired
public BasicItemController(ItemRepository itemRepository){
    this.itemRepository = itemRepository;
}
  • 스프링에서 해당 생성자가 @Autowired 로 의존관계를 주입해주는데 생성자가 하나밖에 없으면 @Autowired를 생략가능하다 
  • @RequiredArgsConstructor
    • final 이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어주는 어노테이션
  • 따라서 private final ItemRepository itemRepository; 에 final을 지우면 안된다 그럼 itemRepository 의존관계가 주입이 안되기 때문

 

 

간결해진 [ BasicItemController ] 

@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {

    private final ItemRepository itemRepository;
}

 

 

 


 

이제 컨트롤러와 뷰 템플릿을 개발하기

 

 

1. 상품목록 - 타임리프

컨트롤러  [ BasicItemController ] 

@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("itemA", 1000,10));
    itemRepository.save(new Item("itemB", 2000,20));
}

 

 

 

 

 

 

뷰템플릿 [ items.html ]

 

타임리프 사용하기 

th:href 가 있으면 기존거를 안쓴다 

<html xmlns:th="http://www.thymeleaf.org">

<link th:href="@{/css/bootstrap.min.css}"
        href="../css/bootstrap.min.css" rel="stylesheet">
<!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/${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 에 사용한 타임리프 속성 알아보기 

 

상품 등록 버튼 누르면 http://localhost:8080/basic/items/add 경로로 이동

th:onclick="|location.href='@{/basic/items/add}'|"

<button class="btn btn-primary float-end"
        onclick="location.href='addForm.html'"
        th:onclick="|location.href='@{/basic/items/add}'|"
        type="button">상품 등록
</button>

 

 

리터럴 '|  |' 문자

 

타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 해야하는데 

| | 을 쓰게 되면 더하기 연산자 없이 한꺼번에 쓸 수 있다 

 

그냥 사용하게 되면 

  • th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"

리터럴 문법

  • th:onclick="|location.href='@{/basic/items/add}'|"

 

 

th:each - 상품 목록 반복출력
th:text 는 단순하게 데이터를 넣어주는 것 (테스트용으로 데이터를 넣은게 바로 들어간다)
<tr th:each="item : ${items}">
    <td><a href="item.html" th:text="${item.id}">회원id</a></td>
    <td><a href="item.html" th:text="${item.itemName}">상품명</a></td>
    <td th:text="${item.price}">10000</td>
    <td th:text="${item.quantity}">10</td>
</tr>

 

  • model.addAttribute("items", items);에서 "items"이 HTML 템플릿에서 ${items}에 접근하여 해당 데이터들을 item 에 넣어준다

 

th:href 문법

 

회원 ID, 상품명을 클릭시 이동하는 URL 은 같다 (상품id만 나오면 된다.  /basic/items/1과 같은 URL이 생성)

  • th:href="@{/basic/items/{itemId}(itemId=${item.id})}" : 
    • (itemId=${item.id}) 값이 {itemId} 경로 변수에 들어간다.
  • th:href="@{|/basic/items/${item.id}|}"  : 리터럴을 사용해서 위보다 URL을 간단히 만든 것
<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/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
    <td th:text="${item.price}">10000</td>
    <td th:text="${item.quantity}">10</td>
</tr>

 

 

 

 

반응형
LIST

+ Recent posts