[JAVA] Pageable로 페이징 처리하기
November 2, 2023
스프링에서 처음 페이징을 구현할 때 일반적으로 페이징에 필요한 데이터(총 개수, 현재 페이지, 이전/다음 페이지 여부 등)를 만들었는데, 스프링에 만들어놓은 Pageable 인터페이스를 사용해 보았다.
최대한 간단하게 구현하기 위해, 이미 만들어진 코드에서 가공한거라 누락된 부분이 있을 수 있다.
어떻게 사용되는지 힌트만 얻길 바란다.
환경
JAVA 17
Spring Boot 3.14
JPA
QueryDSL 5.0.0
Thymeleaf
Domain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Member {
@Id
@GeneratedValue ( strategy = GenerationType . IDENTITY )
@Column ( name = "member_id" )
private Long id ;
@Column ( updatable = false )
private String userId ;
private String userNm ;
private String userPw ;
private String email ;
}
Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping ( "/member" )
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService ;
@GetMapping
public String list ( @Search MemberSearchRequest request , @PageableDefault ( size = 10 ) Pageable pageable , Model model ) {
Page < Member > members = memberService . list ( request , pageable );
model . addAttribute ( "lists" , members );
model . addAttribute ( "search" , request );
return "member/list" ;
}
}
Dto
1
2
3
4
5
6
@Data
public class MemberSearchRequest {
private String userId ;
private String userNm ;
private String email ;
}
Service
1
2
3
4
5
6
7
8
9
10
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository ;
@Transactional ( readOnly = true )
public Page < Member > list ( MemberSearchRequest request , Pageable pageable ) {
return memberRepository . findByConditions ( request , pageable );
}
}
Repository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RequiredArgsConstructor
public class MemberRepository {
private final JPAQueryFactory queryFactory ;
private final QMember member = QMember . member ;
public Page < Member > findByConditions ( MemberSearchRequest request , Pageable pageable ) {
List < Member > members = queryFactory
. selectFrom ( member )
. where ( isConditions ( request ))
. offset ( pageable . getOffset ())
. limit ( pageable . getPageSize ())
. orderBy ( member . id . asc ())
. fetch ();
JPAQuery < Long > countQuery = queryFactory
. select ( member . count ())
. from ( member )
. where ( isConditions ( request ));
return PageableExecutionUtils . getPage ( members , pageable , countQuery: : fetchOne );
}
private BooleanBuilder isConditions ( MemberSearchRequest request ) {
BooleanBuilder builder = new BooleanBuilder ();
return builder
. and ( containsUserId ( request . getUserId ()))
. and ( containsEmail ( request . getEmail ()))
. and ( containsUserNm ( request . getUserNm ()));
}
private BooleanExpression containsUserId ( String userId ) {
return StringUtils . hasText ( userId ) ? member . userId . contains ( userId ) : null ;
}
private BooleanExpression containsEmail ( String email ) {
return StringUtils . hasText ( email ) ? member . email . contains ( email ) : null ;
}
private BooleanExpression containsUserNm ( String UserNm ) {
return StringUtils . hasText ( UserNm ) ? member . userNm . contains ( UserNm ) : null ;
}
}
Thymeleaf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<div class= "container-fluid pt-3 pb-3" >
<form class= "w-25 mb-4" th:action= "@{/member}" th:object= "${search}" >
<div class= "row mb-3" >
<label th:for= "userId" class= "col-sm-2 col-form-label" > 아이디</label>
<div class= "col-sm-8" >
<input type= "text" class= "form-control" th:field= "*{userId}" >
</div>
</div>
<div class= "row mb-3" >
<label th:for= "userNm" class= "col-sm-2 col-form-label" > 이름</label>
<div class= "col-sm-8" >
<input type= "text" class= "form-control" th:field= "*{userNm}" >
</div>
</div>
<div class= "row mb-3" >
<label th:for= "email" class= "col-sm-2 col-form-label" > 이메일</label>
<div class= "col-sm-8" >
<input type= "text" class= "form-control" th:field= "*{email}" >
</div>
</div>
<div class= "row" >
<div class= "col-sm-10" >
<button class= "form-control btn btn-dark" type= "submit" > 조회</button>
</div>
</div>
</form>
<table class= "table table-striped table-hover" >
<tr>
<th class= "text-center" > 아이디</th>
<th class= "text-center" > 이름</th>
<th class= "text-center" > 이메일</th>
</tr>
<th:block th:if= "${!#lists.isEmpty(memberList)}" th:each= "member : ${memberList}" >
<tr th:data-member-id= "${member.id}" >
<td class= "align-middle" >
<div th:text= "${member.userId}" ></div>
</td>
<td>
<input class= "form-control" type= "text" name= "userNm" th:value= "${member.userNm}" >
</td>
<td>
<input class= "form-control" type= "text" name= "email" th:value= "${member.email}" >
</td>
</tr>
</th:block>
</table>
<nav aria-label= "Page navigation" >
<ul class= "pagination justify-content-center" >
<li class= "page-item" th:classappend= "${!memberList.hasPrevious} ? 'disabled' : ''" >
<a class= "page-link" th:href= "@{/member(page=${memberList.number - 1}, userId=${search.userId}, userNm=${search.userNm}, email=${search.email})}" aria-label= "Previous" >
<span aria-hidden= "true" > « </span>
</a>
</li>
<li th:each= "page : ${#numbers.sequence(0, memberList.totalPages - 1)}" class= "page-item" th:classappend= "${page == memberList.number} ? 'active' : ''" >
<a class= "page-link" th:href= "@{/member(page=${page}, userId=${search.userId}, userNm=${search.userNm}, email=${search.email})}" th:text= "${page + 1}" ></a>
</li>
<li class= "page-item" th:classappend= "${!memberList.hasNext} ? 'disabled' : ''" >
<a class= "page-link" th:href= "@{/member(page=${memberList.number + 1}, userId=${search.userId}, userNm=${search.userNm}, email=${search.email})}" aria-label= "Next" >
<span aria-hidden= "true" > » </span>
</a>
</li>
</ul>
</nav>
</div>
Leave a comment