📌 사용 기술 스택
- 스프링 부트 2.7.12
- 타임리프
- Spring Data JPA
- H2 DB
- Lombok
이번에 게시판에 필수요소인 페이징 기능을 추가해보았다.
페이징 기능은 Springframework가 제공하는 @PageableDefault 어노테이션, Pageable, Page 인터페이스로 구현할 수 있다.
이 포스팅은 페이징 기능을 구현하는 설명을 다루므로 페이징과 관계 없는 코드는 과감히 뺐으며, 나머지 부분은 아래 링크로 대체한다.
- 게시판 웹 서비스에 관한 코드 :
https://github.com/Hyeon0208/posting-webservice
- OAuth로 소셜 로그인 구현 :
2023.05.30 - [JAVA/Spring] - [Spring] Google OAuth 로그인 API 키 발급 및 구글 소셜 로그인 구현
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:
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이다.
타임리프 문법에 대한 정보는 아래 링크를 클릭
2023.04.02 - [JAVA/Thymeleaf] - Thymeleaf(타임리프)란 ? 타임리프의 기본 기능알아보기
<!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);
}
}
페이징 기능이 정상적으로 적용되어 작동하는 것을 확인할 수 있다!
페이징 기능은 어느 웹에서나 항상 필요한 기능이므로 알아둘 필요가 있을것 같다.
'◼ Spring' 카테고리의 다른 글
[Spirng/JPA] Dto와 Entity를 분리해서 사용하는 이유 (0) | 2023.06.16 |
---|---|
[Spring] @Controller와 @RestController의 차이 및 구분 (0) | 2023.06.16 |
[Spring] 스프링으로 파일 업로드, 다운로드 구현 (MultipartFile) (0) | 2023.05.31 |
[Spring] Google OAuth 로그인 API 키 발급 및 구글 소셜 로그인 구현 (8) | 2023.05.30 |
[Spring] 메트릭(matric) 직접 등록하기 (0) | 2023.05.24 |