๐ ์ฌ์ฉ ๊ธฐ์ ์คํ
- ์คํ๋ง ๋ถํธ 2.7.12
- ํ์๋ฆฌํ
- Spring Data JPA
- H2 DB
- Lombok
์ด๋ฒ์ ๊ฒ์ํ์ ํ์์์์ธ ํ์ด์ง ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณด์๋ค.
ํ์ด์ง ๊ธฐ๋ฅ์ Springframework๊ฐ ์ ๊ณตํ๋ @PageableDefault ์ด๋ ธํ ์ด์ , Pageable, Page ์ธํฐํ์ด์ค๋ก ๊ตฌํํ ์ ์๋ค.
์ด ํฌ์คํ ์ ํ์ด์ง ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ์ค๋ช ์ ๋ค๋ฃจ๋ฏ๋ก ํ์ด์ง๊ณผ ๊ด๊ณ ์๋ ์ฝ๋๋ ๊ณผ๊ฐํ ๋บ์ผ๋ฉฐ, ๋๋จธ์ง ๋ถ๋ถ์ ์๋ ๋งํฌ๋ก ๋์ฒดํ๋ค.
- ๊ฒ์ํ ์น ์๋น์ค์ ๊ดํ ์ฝ๋ :
https://github.com/Hyeon0208/posting-webservice
- OAuth๋ก ์์ ๋ก๊ทธ์ธ ๊ตฌํ :
aplication.yml ์ค์
# database ์ฐ๋ ์ค์
spring:
profiles:
include: oauth
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/board
username: sa
thymeleaf:
cache: false
# spring data jpa ์ค์
jpa:
open-in-view: false
show-sql: true
hibernate:
dialect: org.hibernate.dialect.MySQLInnoDBDialect
ddl-auto: create
๊ฒ์ํ ํ์ด์ง ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ
1. ๋จผ์ ๊ฐ ํ์ด์ง๋ง๋ค ๋ฑ๋ก๋ ๋ฐ์ดํฐ(์ฌ๊ธฐ์๋ ๊ฒ์๊ธ)์ด ์์ผ๋ฏ๋ก Entity ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด DB์ ๋ฑ๋กํ๋ค.
Posts
@Getter
@NoArgsConstructor
@Entity
public class Posts {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column
private String title;
@NotNull
@Column(length = 1000)
private String content;
@Column
private String author;
@Builder
public Posts(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
}
2. Posts ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
PostsRepository
public interface PostsRepository extends JpaRepository<Posts, Long> {
}
3. ํ ์คํธ์ฉ ๋ฐ์ดํฐ๋ฅผ DB์ ์ถ๊ฐ
ํ์ด์ง๊ธฐ๋ฅ ๊ตฌํ์ ๋ํ ์ค๋ช ๋ง์ ํฌํจํ๊ณ ๋ฏธ๋ฆฌ ์ฌ๋ฌ ๊ฒ์๊ธ์ ๋ฑ๋กํด๋ ผ ์ํ๋ก ํ์ด์ง ๊ธฐ๋ฅ์ด ์ ์๋ํ๋์ง ํ์ธํ๊ธฐ ์ํด ํ ์คํธ์ฉ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๊ฒ ๋ค.
TestDataInit
@RequiredArgsConstructor
public class TestDataInit {
private final PostsRepository postsRepository;
@EventListener(ApplicationReadyEvent.class)
public void initData() {
postsRepository.save(Posts.builder()
.title("๊ธ1")
.content("๊ฐ๋๋ค๋ผ๋ง๋ฐ์ฌ")
.author("๊น๋ถ๊ฐ")
.build());
postsRepository.save(Posts.builder()
.title("๊ธ2")
.content("zxcbzxcv")
.author("๋ง๊น์น")
.build());
postsRepository.save(Posts.builder()
.title("๊ธ3")
.content("qwetyhgbz")
.author("์ซ๋ฉด")
.build());
postsRepository.save(Posts.builder()
.title("๊ธ4")
.content("asdfasdfa")
.author("์ถค์ถ๋๋ค์ค")
.build());
postsRepository.save(Posts.builder()
.title("๊ธ5")
.content("fasdfas")
.author("๋ฐฐ๊ฐ์ ํ๋ฌปํ ํ๋ก๋")
.build());
}
}
4. DB์์ ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ ๋ฐ์ DTO ์์ฑ
DB์ View ์ฌ์ด์ ์ญํ ์ ๋ถ๋ฆฌํ๊ณ ,Entity ๊ฐ์ฒด๋ฅผ ๋ณดํธํ๊ธฐ ์ํดEntity์ DTO๋ฅผ ๋ถ๋ฆฌํด์ ์ฌ์ฉํ์๋ค.
PostsResponseDto
- Posts ์ํฐํฐ์ ์์ฑํ ID ๊ฐ๊ณผ Colum์ ๋ค ์ฌ์ฉํ ๊ฒ์ด๊ธฐ์ ๋ค์๊ณผ ๊ฐ์ด DTO๊ฐ์ฒด๋ฅผ ์์ฑ.
- ์์ฑ์๋ PostsRepository์์ Posts Entity ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๊ทธ ๊ฐ์ ๊ฐ๋๋ก ํ์๋ค.
@Getter
public class PostsResponseDto {
private Long id;
private String title;
private String content;
private String author;
public PostsResponseDto(Posts entity) {
this.id = entity.getId();
this.title = entity.getTitle();
this.content = entity.getContent();
this.author = entity.getAuthor();
}
}
5. Respository์ ์ ๊ทผํ PostsService๋ฅผ ๋ง๋ค์.
์คํ๋ง์ด ์ ๊ณตํ๋ Pageable์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ paging ๋ก์ง ๊ตฌํํ์๋ค.
PostsService
- paging๋ก์ง์ PostsRepository์์ ์๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์๋์ ์ฃผ์๊ณผ ๊ฐ์ ์กฐ๊ฑด์ผ๋ก ๊ฐ์ ธ์ Posts๊ฐ์ฒดํ์ ์ ๊ฐ์ Page๋ฅผ ๋ฐํํ๋ค.
- ์ด Page<Posts>๋ ๋ฆฌ์คํธ์ ๊ฐ์ ๊ฐ๋ ์ด๋ค.
- ๊ฐ์ ธ์จ ๋ชจ๋ Posts ์ํฐํฐ ๊ฐ์ฒด๋ค์ PostsResponseDto๋ก ๋ณํํด ์ด ๊ฐ์ฒด ๊ฐ๋ค์ ๊ฐ๋ Page<PostsResponseDto>๋ฅผ ๋ฐํํ๋ค.
@Service
@RequiredArgsConstructor
public class PostsService {
private final PostsRepository postsRepository;
public Page<PostsResponseDto> paging(Pageable pageable) {
int page = pageable.getPageNumber() - 1; // page ์์น์ ์๋ ๊ฐ์ 0๋ถํฐ ์์ํ๋ค.
int pageLimit = 3; // ํํ์ด์ง์ ๋ณด์ฌ์ค ๊ธ ๊ฐ์
// ํ ํ์ด์ง๋น 3๊ฐ์ ๊ธ์ ๋ณด์ฌ์ฃผ๊ณ ์ ๋ ฌ ๊ธฐ์ค์ ID๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์
Page<Posts> postsPages = postsRepository.findAll(PageRequest.of(page, pageLimit, Sort.by(Direction.DESC, "id")));
// ๋ชฉ๋ก : id, title, content, author
Page<PostsResponseDto> postsResponseDtos = postsPages.map(
postPage -> new PostsResponseDto(postPage));
return postsResponseDtos;
}
}
6. ์ปจํธ๋กค๋ฌ๋ฅผ ์์ฑํด url๋ฅผ ๋งคํํ๊ณ view์ ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ์.
์ฝ๊ฐ์ ๋ก์ง์ด ํฌํจ๋์ด ์๋๋ฐ ์ฃผ์์ ํตํด ์ดํดํ ์ ์์ ๊ฒ์ด๋ค.
๋ค๋ง startPage์ endPage์ ๊ณ์ฐ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค๊ณ ๋ง ์ฐธ๊ณ ํ์
PostsController
@Controller
@RequiredArgsConstructor
public class PostsController {
private final PostsService postsService;
// @PageableDefault(page = 1) : page๋ ๊ธฐ๋ณธ์ผ๋ก 1ํ์ด์ง๋ฅผ ๋ณด์ฌ์ค๋ค.
@GetMapping("/posts/paging")
public String paging(@PageableDefault(page = 1) Pageable pageable, @Login SessionUser user, Model model) {
Page<PostsResponseDto> postsPages = postsService.paging(pageable);
/**
* blockLimit : page ๊ฐ์ ์ค์
* ํ์ฌ ์ฌ์ฉ์๊ฐ ์ ํํ ํ์ด์ง ์ ๋ค๋ก 3ํ์ด์ง์ฉ๋ง ๋ณด์ฌ์ค๋ค.
* ex : ํ์ฌ ์ฌ์ฉ์๊ฐ 4ํ์ด์ง๋ผ๋ฉด 2, 3, (4), 5, 6
*/
int blockLimit = 3;
int startPage = (((int) Math.ceil(((double) pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1;
int endPage = Math.min((startPage + blockLimit - 1), postsPages.getTotalPages());
model.addAttribute("postsPages", postsPages);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "paging";
}
}
7. paging.html
๊ฐ๋จํ๊ฒ ๊ฒ์ํ ํ ์ด๋ธ๊ณผ ํ์ด์ง๋ง ๋์ค๋๋ก ํ๋ HTML์ด๋ค.
ํ์๋ฆฌํ ๋ฌธ๋ฒ์ ๋ํ ์ ๋ณด๋ ์๋ ๋งํฌ๋ฅผ ํด๋ฆญ
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>๊ฒ์ํ ์น ์๋น์ค</h1>
<div class="col-md-12">
<!-- ๋ชฉ๋ก ์ถ๋ ฅ ์๋ ฅ -->
<table class="table table-horizontal table-bordered">
<tr>
<th>๊ฒ์๊ธ ๋ฒํธ</th>
<th>์ ๋ชฉ</th>
<th>์์ฑ์</th>
<th>์ต์ข
์์ ์ผ</th>
</tr>
<tr th:each="post: ${postsPages}">
<td th:text="${post.id}"></td>
<td><a th:href="@{|/posts/update/${post.id}|(page=${postsPages.number + 1})}" th:text="${post.title}"></a></td>
<td th:text="${post.author}"></td>
</tr>
</table>
<!-- ์ฒซ๋ฒ์งธ ํ์ด์ง๋ก ์ด๋ -->
<a th:href="@{/posts/paging(page=1)}">์ฒซ ํ์ด์ง </a>
<!-- ์ด์ ๋งํฌ ํ์ฑํ ๋นํ์ฑํ -->
<a th:href="${postsPages.first} ? '#' : @{/posts/paging(page=${postsPages.number})}"> ์ด์ </a>
<!-- ํ์ด์ง ๋ฒํธ ๋งํฌ(ํ์ฌ ํ์ด์ง๋ ์ซ์๋ง)
for(int page=startPage; page<=endPage; page++)-->
<span th:each="page: ${#numbers.sequence(startPage, endPage)}">
<!-- ํ์ฌํ์ด์ง๋ ๋งํฌ ์์ด ์ซ์๋ง -->
<span th:if="${page == postsPages.number + 1}" th:text="${page}"></span>
<!-- ํ์ฌํ์ด์ง ๋งํฌ X, ๋ค๋ฅธ ํ์ด์ง๋ฒํธ์๋ ๋งํฌ๋ฅผ ๋ณด์ฌ์ค๋ค -->
<span th:unless="${page == postsPages.number + 1}">
<a th:href="@{/posts/paging(page=${page})}" th:text="${page}"></a>
</span>
</span>
<!-- ๋ค์ ๋งํฌ ํ์ฑํ ๋นํ์ฑํ -->
<a th:href="${postsPages.last} ? '#' : @{/post/paging(page=${postsPages.number + 2})}"> ๋ค์ </a>
<!-- ๋ง์ง๋ง ํ์ด์ง๋ก ์ด๋ -->
<a th:href="@{/posts/paging(page=${postsPages.totalPages})}"> ๋ง์ง๋ง ํ์ด์ง</a>
</div>
</div>
</body>
</html>
ํ์ด์ง ๊ธฐ๋ฅ ์ ์ ์๋ ํ์ธ
Main ํด๋์ค์ธ BoardApplication์ ํ ์คํธ์ฉ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ TestDataInit์ Bean ๋ฑ๋กํด์ฃผ๊ณ ์คํํด๋ณด์.
BoardApplication
@SpringBootApplication
public class BoardApplication {
public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}
@Bean
public TestDataInit testDataInit(PostsRepository postsRepository) {
return new TestDataInit(postsRepository);
}
}
ํ์ด์ง ๊ธฐ๋ฅ์ด ์ ์์ ์ผ๋ก ์ ์ฉ๋์ด ์๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค!
ํ์ด์ง ๊ธฐ๋ฅ์ ์ด๋ ์น์์๋ ํญ์ ํ์ํ ๊ธฐ๋ฅ์ด๋ฏ๋ก ์์๋ ํ์๊ฐ ์์๊ฒ ๊ฐ๋ค.