// Paging 적용 직원리스트
@GetMapping
@ResponseStatus(HttpStatus.OK)
public Iterable<Employee> findpaginatedEmployees(@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "5") int size) {
//page설정 객체를 사용하려면 PagingAndSortingRepository를 참조한다.
Pageable pageable = PageRequest.of(page, size);
return empRepo.findAll(pageable);
}
{
"content": [
{
"employeeId": 1,
"firstName": "아지",
"lastName": "강강",
"email": "kang@naver.com"
},
{
"employeeId": 2,
"firstName": "길금",
"lastName": "홍",
"email": "hong@naver.com"
},
{
"employeeId": 3,
"firstName": "순신",
"lastName": "이",
"email": "lee@naver.com"
},
{
"employeeId": 4,
"firstName": "탱이",
"lastName": "곰",
"email": "kom@naver.com"
},
{
"employeeId": 9,
"firstName": "양이",
"lastName": "고",
"email": "go@naver.com"
}
],
"pageable": {
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"offset": 0,
"pageNumber": 0,
"pageSize": 5,
"paged": true,
"unpaged": false
},
"last": false,
"totalPages": 2,
"totalElements": 7,
"size": 5,
"number": 0,
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"first": true,
"numberOfElements": 5,
"empty": false
}
Project 생성시 library 추가 (Spring Dev Tools, Spring Web, MySql Driver, Thymeleaf, Spring Data JPA, Lombok)
Lombok Library는 pom.xml에 추가하고 따로 설치가 필요함
.m2\repository\org\projectlombok\lombok\1.18.22
DB생성
DB사용을 위해 application.properties에 추가
# MySQL DB 설정
spring.datasource.url=jdbc:mysql://localhost:3306/shoppingmall?useSSL=false&serverTimezone=Asia/Seoul
spring.datasource.username=root
spring.datasource.password=1234
<head th:fragment="head-front">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>쇼핑몰</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.6/examples/starter-template/" />
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" />
<!-- Custom styles for this template -->
<link rel="stylesheet" th:href="@{/css/style.css}" />
</head>
<head th:fragment="head-admin">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>관리자페이지</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.6/examples/starter-template/" />
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" />
<!-- Custom styles for this template -->
<link rel="stylesheet" th:href="@{/css/style.css}" />
</head>
<nav th:fragment="nav-front" class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" href="#">🛒SHOP</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarShop" aria-controls="navbarShop" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarShop">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
</ul>
</div>
</nav>
<nav th:fragment="nav-admin" class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" th:href="@{/}">📱관리자</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarShop" aria-controls="navbarShop" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarShop">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" th:href="@{/admin/pages}">Pages</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
</ul>
</div>
</nav>
<footer th:fragment="footer">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
</footer>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="/fragments/head :: head-front"></head>
<body>
<nav th:replace="/fragments/nav :: nav-front"></nav>
<main role="main" class="container"></main>
<footer th:replace="/fragments/footer :: footer"></footer>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="/fragments/head :: head-admin"></head>
<body>
<nav th:replace="/fragments/nav :: nav-admin"></nav>
<main role="main" class="container">
<div class="display-2">Pages</div>
<a th:href="@{/admin/pages/add}" class="btn btn-primary my-3">추가하기</a>
<div th:if="${!pages.empty}">
<table class="table" id="pages">
<tr class="home">
<th>제 목</th>
<th>슬러그</th>
<th>수 정</th>
<th>삭 제</th>
</tr>
<tr th:each="page : ${pages}">
<td th:text="${page.title}"></td>
<td th:text="${page.slug}"></td>
<td><a th:href="@{'/admin/pages/edit/' + ${page.id}}">수정</a></td>
<td><a th:href="@{'/admin/pages/delete/' + ${page.id}}">삭제</a></td>
</tr>
</table>
</div>
<div th:if="${pages.empty}">
<div class="display-4">현재 페이지가 없습니다.</div>
</div>
</main>
<footer th:replace="/fragments/footer :: footer"></footer>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="/fragments/head :: head-admin"></head>
<body>
<nav th:replace="/fragments/nav :: nav-admin"></nav>
<main role="main" class="container">
<div class="display-2">페이지 추가</div>
<a th:href="@{/admin/pages}" class="btn btn-primary my-3">돌아가기</a>
<form method="post" th:object="${page}" th:action="@{/admin/pages/add}">
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">에러 발생</div>
<div class="form-group">
<label for="">제 목</label>
<input type="text" class="form-control" th:field="*{title}" placeholder="제목" />
<span class="error" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></span>
</div>
<div class="form-group">
<label for="">슬러그</label>
<input type="text" class="form-control" th:field="*{slug}" placeholder="슬러그" />
</div>
<div class="form-group">
<label for="">내용</label>
<textarea class="form-control" th:field="*{content}" cols="30" rows="10" placeholder="내용"></textarea>
<span class="error" th:if="${#fields.hasErrors('content')}" th:errors="*{content}"></span>
</div>
<button type="submit" class="btn btn-danger">추 가</button>
</form>
</main>
<footer th:replace="/fragments/footer :: footer"></footer>
</body>
</html>
package com.myapp.shoppingmall.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
// 실제 Table과 매칭
@Entity
@Table(name = "pages")
@Data // Get, Set, Construct, toSring 생성됨
public class Page {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotBlank(message = "제목을 입력해주세요.")
@Size(min = 2, message = "최소 2글자 이상 입력해주세요.")
private String title;
private String slug; // title을 소문자 띄워쓰기 특수문자등을 - 으로 변환
@NotBlank(message = "내용을 입력해주세요.")
@Size(min = 5, message = "최소 5글자 이상 입력해주세요.")
private String content;
private int sorting; // 정렬 순서
}
package com.myapp.shoppingmall.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.myapp.shoppingmall.entities.Page;
public interface PageRepository extends JpaRepository<Page, Integer> {
// List<Page>로 리턴되는 findAll() 등 여러 메소드가 이미 생성되어 있음
}
package com.myapp.shoppingmall.controller;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.myapp.shoppingmall.dao.PageRepository;
import com.myapp.shoppingmall.entities.Page;
@Controller
@RequestMapping("/admin/pages")
public class AdminPageController {
@Autowired
private PageRepository pageRepo;
@GetMapping
public String index(Model model) {
List<Page> pages = pageRepo.findAll();
model.addAttribute("pages", pages);
return "admin/pages/index";
}
// page값을 자동으로 model에 전송
@GetMapping("/add")
public String add(@ModelAttribute Page page) {
// model.addAttribute("page", new Page());
return "admin/pages/add"; // templates의 admin안의 pages폴더 안에 add.html로 이동
}
@PostMapping("/add")
public String add(@Valid Page page, BindingResult bindingResult) {
//Validation 결과 Error가 있으면 이전페이지로 이동
if (bindingResult.hasErrors()) {
return "admin/pages/add";
}
return "redirect:admin/pages/add";
}
}
package com.myapp.shoppingmall;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// Controller 대신에 View를 Mapping 가능
registry.addViewController("/").setViewName("home");
}
}