어제했던 보드에 수정/삭제 기능은 1일차에 했던것과 똑같기때문에 생략 1일차 글을참고.
단, 삭제는 게시글을 DB에서 삭제하는게아닌 업데이트해 is_del을 'N'에서 'Y'로 바꾸도록 구현.
<update id="delete">
update board set is_del = 'Y'
where bno = #{bno}
</update>
페이지네이션 pagenation
한 페이지에 목록을 다 나타내기 힘들 때 사용
- 한페이지에 10개의 게시물을 표현
- 전체 게시글의 수에 계산되어 페이지네이션 값들이 결정이 됨.
DB상에서 => limit 시작번지, 개수
1페이지 => limit 0,10 => 0번지부터 10개 => 0번지부터 9번지까지
2페이지 => limt 10,10 => 10번지부터 10개 => 10번지부터 19번지까지
3페이지 => limit 20,10 => 20번지부터 10개 => 20번지부터 29번지까지
...
pageNo => 페이지네이션 번호
getPageStart => 시작번지
qty => 개수
● 일단 부트스트랩에서 페이지네이션 화면 구성
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" href="#"
aria-label="Previous"> <span aria-hidden="true">«</span>
</a></li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#"
aria-label="Next"> <span aria-hidden="true">»</span>
</a></li>
</ul>
</nav>
● PagingVO를 생성해주고 pageNo, qty 선언 기본생성자에서 기본값 설정 시작번지는 getter로 받을예정
private int pageNo; // 페이지네이션 번호
private int qty; // 한페이지에 표시되는 페이지 수
// 첫 리스트 시 기본적으로 설정될 값
public PagingVO() {
this.pageNo = 1;
this.qty = 10;
}
● getPageStart() 구성 시작번지를 구하려면 페이지넘버에 -1 하고 수량을 곱하면 됨.
// DB에서 사용될 시작번지 구하기
// select * from board limit 번지,개수
// 1페이지는 limit 0,10 => 2페이지 limit 10,20 => 3페이지 limit 20,30
public int getPageStart() {
return (this.pageNo - 1) * this.qty;
}
● 컨트롤러에 list 부분에 PagingVO 객체를 하나 생성해서 리스트안에 객체를줘서 페이지마다 리스트를 가져올 수 있게함.
@GetMapping("/list")
public String list(Model m) {
PagingVO pgvo = new PagingVO();
List<BoardVO> list = bsv.getList(pgvo);
m.addAttribute("list",list);
return "/board/list";
}
● mapper에 limit 부분을 pageStart ( get은 생략) qty로 바꿔줌
<select id="getList" resultType="com.ezen.spring.domain.BoardVO">
select * from board
where is_del = 'N'
order by bno desc
limit #{pageStart},#{qty}
</select>
● 페이지를 전체 데이터 양에 따라 변하게만들고 버튼을 활성화 시키기위해 PagingHandler 생성
한페이지에 나오는 페이지네이션 개수 : 10개 => 그냥 qty로 처리
한페이지에 나오는 페이지네이션 시작번호 : 1 11 21 ...
한페이지에 나오는 페이지네이션 끝번호 : 10 20 30 ...
이전, 다음 버튼의 노출여부 boolean
전체 게시글 수 : DB에서 조회해 와야 하는 값. ( 매개변수로 받아오기 )
현재 페이지 번호 : PagingVO pageNo 사용 (매개변수로 받아오기)
전체 마지막 페이지 끝번호
private int qty; // 한페이지에 나오는 페이지네이션 개수
private int startPage; // 한페이지에 나오는 페이지네이션 시작번호 1 11 21 ...
private int endPage; // 한페이지에 나오는 페이지네이션 끝번호 : 10 20 30 ...
private boolean prev; // 이전버튼의 노출여부
private boolean next; // 다음버튼의 노출여부
private int totalCount; // 전체 게시글 수 : DB에서 조회해 와야 하는 값. ( 매개변수로 받아오기 )
private PagingVO pgvo; // 현재 페이지 번호 ( 매개변수로 받아오기 )
private int realEndPage; // 전체 마지막 페이지 끝번호
그대로 선언해주고
● 생성자에서 모든 값들이 계산되어서 설정되어야 함.
this.qty = 10;
this.pgvo = pgvo;
this.totalCount = totalCount;
얘네는 그대로 가져와주고
● endPage나 startPage중 하나를 계산해서 나타내줘야함
1 / 10개 => 0.1 결과를 올림 => 1 * 10 => 10
11 / 10 => 1.1 결과를 올림(ceil) => 2 * 10 => 20
정수 / 정수 => 정수 형변환을 해서 소수점을 유지 이런식으로 표현
this.endPage = (int) Math.ceil(pgvo.getPageNo() / (double)qty) * qty;
this.startPage = endPage - (qty-1);
계산된 endPage에 qty-1 을 빼주면 startPage.
● 이전은 startPage가 1보다 크면 활성화돼야하고 다음페이지는 전체 마지막페이지이면 활성화되지않아야하므로
endPage보다 realEndPage가 크면 활성화되고 endPage가 더크면 비활성화돼야함 true / false로
this.prev = this.startPage > 1;
this.next = this.endPage < this.realEndPage;
if(endPage > realEndPage) {
this.endPage = realEndPage;
}
● list에 totalCount를 getTotal 해서 mapper까지 쭉 연결한다음 가져올 수 있도록 해주고 페이징핸들러를 totalCount와 pgvo 객체를 넣어서 생성해주고 모델로 addAttribute해서 설정해줌.
<select id="getTotal" resultType="int">
select count(bno) from board
</select>
int totalCount = bsv.getTotal();
log.info(">>> totalCount > {}" , totalCount);
PagingHandler ph = new PagingHandler(totalCount, pgvo);
m.addAttribute("ph",ph);
● list에 페이지, 이전,다음 버튼을 눌렀을때 이동해서 게시글리스트가 나오도록 a태그의 href를 설정해주어야함
- 이전, 다음버튼이 false일때 disabled 될 수 있게 설정.
- 누른 페이지넘버가 active 될 수있게 설정.
<li class="page-item ${ph.prev eq false ? 'disabled' : '' }"><a class="page-link" href="/board/list?pageNo=${ph.startPage-1 }&qty=${ph.pgvo.qty}"
aria-label="Previous"> <span aria-hidden="true">«</span>
</a></li>
<c:forEach begin="${ph.startPage }" end="${ph.endPage }" var = "i">
<li class="page-item ${ph.pgvo.pageNo eq i ? 'active' : '' }"><a class="page-link" href="/board/list?pageNo=${i } &qty=${ph.pgvo.qty}">${i }</a></li>
</c:forEach>
<li class="page-item ${ph.next eq false ? 'disabled' : '' }"><a class="page-link" href="/board/list?pageNo=${ph.endPage+1 }&qty=${ph.pgvo.qty}"
aria-label="Next"> <span aria-hidden="true">»</span>
</a></li>
● 컨트롤러에 list에 PagingVO를 매개변수로 받아오게 수정해서 list로 보내 a링크가 적용될 수 있도록함.
@GetMapping("/list")
public String list(Model m, PagingVO pgvo ) {
// PagingVO pgvo = new PagingVO();
List<BoardVO> list = bsv.getList(pgvo);
// totalCount 구해서 PagingHandler에 매개변수로 전달
int totalCount = bsv.getTotal();
log.info(">>> totalCount > {}" , totalCount);
PagingHandler ph = new PagingHandler(totalCount, pgvo);
m.addAttribute("list",list);
m.addAttribute("ph",ph);
return "/board/list";
}
● 부트스트랩에서 가져와서 list 윗부분에 서치 화면 구현
<div class ="container-fluid">
<form action="/board/list" class="d-flex" role="search" >
<select class="form-select" name="type" id="inputGroupSelect01" style="width: 70%; margin-right: 20px ">
<option selected>Choose...</option>
<option value="t">title</option>
<option value="w">writer</option>
<option value="c">content</option>
<option value="tw">title + writer</option>
<option value="wc">writer + content</option>
<option value="tc">title + content</option>
<option value="twc">all</option>
</select>
<input class="form-control me-2" name="keyword" type="search" placeholder="Search" aria-label="Search" >
<input type="hidden" name="pageNo" value="${ph.pgvo.pageNo }">
<input type="hidden" name="qty" value="${ph.pgvo.qty }">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
● 서치기능 구현 객체를 두개 보내면 복잡해지기에 PagingVO에 서치변수까지 묶어서 추가
// search 변수 포함
private String type;
private String keyword;
타입과 키워드가 생성됐을때 null인 생성자를 추가로 만들어주고
public PagingVO(int pageNo, int qty) {
this.pageNo = pageNo;
this.qty = qty;
}
type의 복합 타입을 각각 검색하기 위해 배열로 생성
split으로 하나씩 배열안에 넣음 => type == twc [t, w, c]
public String[] getTypeToArray() {
return this.type == null ? new String[] {} : this.type.split("");
}
● 타입 t , w , c 에 따른 조건문을 구현하기위해 동적 쿼리를 이용해야함
<sql id="search">
<if test="type != null">
<trim prefix="and (" suffix=")" suffixOverrides="or" >
<foreach collection="typeToArray" item="type">
<trim suffix="or">
<choose>
<when test="type == 't'.toString()">
title like concat('%', #{keyword}, '%')
</when>
<when test="type == 'w'.toString()">
writer like concat('%', #{keyword}, '%')
</when>
<when test="type == 'c'.toString()">
content like concat('%', #{keyword}, '%')
</when>
</choose>
</trim>
</foreach>
</trim>
</if>
type이 null이 아닐경우 작동하도록 해주고
t , w , c를 그냥 변수로 인지하기때문에 't' 로 문자열안에 넣어주고 toString해서 비교해주어야함.
'%#{keyword}%'; => '% "#{keyword}" %' 이런식으로 문자열로 인지하기때문에 concat을 이용해서 붙혀주어야함.
prefix, suffix로 앞뒤로 꼭 붙혀야하는 구문을 붙혀주고 suffixOverrides를 이용해서 foreach가 끝나고 남은 or을 지워주어야한다.
● 만든 쿼리문을 리스트에 include해서 삽입해주고 검색한 게시글들의 총 갯수도 인지할 수 있도록 total에도 넣어주도록한다.
<select id="getList" resultType="com.ezen.spring.domain.BoardVO">
select * from board
where is_del = 'N'
<include refid="search"></include>
order by bno desc
limit #{pageStart},#{qty}
</select>
<select id="getTotal" resultType="int">
select count(bno) from board where is_del = 'N'
<include refid="search"></include>
</select>
쿼리문안에 타입과 키워드를 인지할 수 있도록 pgvo 객체를 넣어주어 수정해주어야한다.
int totalCount = bsv.getTotal(pgvo);
넣어주고 다오까지 연결 수정.
● 서치 이후에 type과 keyword를 초기화시키지 않고 고정시켜 확인할 수 있도록 구현하기위해 selected를 수정해준다.
<select class="form-select" name="type" id="inputGroupSelect01" style="width: 70%; margin-right: 20px ">
<option ${ph.pgvo.type == null ? 'selected' : '' }>Choose...</option>
<option value="t" ${ph.pgvo.type eq 't' ? 'selected' : '' }>title</option>
<option value="w" ${ph.pgvo.type eq 'w' ? 'selected' : '' }>writer</option>
<option value="c" ${ph.pgvo.type eq 'c' ? 'selected' : '' }>content</option>
<option value="tw"${ph.pgvo.type eq 'tw' ? 'selected' : '' } >title + writer</option>
<option value="wc"${ph.pgvo.type eq 'wc' ? 'selected' : '' }>writer + content</option>
<option value="tc"${ph.pgvo.type eq 'tc' ? 'selected' : '' }>title + content</option>
<option value="twc"${ph.pgvo.type eq 'twc' ? 'selected' : '' }>all</option>
</select>
그리고 input value에다가 keyword를 넣어주고 서치 버튼을 눌렀을때 현재페이지가 1로 돌아가도록 설정해주어야 현재페이지가 초과해도 게시글을 띄울 수 있기에 value를 수정해주어야함.
<input type="hidden" name="pageNo" value="1">
<input class="form-control me-2" name="keyword" type="search" placeholder="Search" aria-label="Search" value="${ph.pgvo.keyword }">
이러면 select에 원하는 검색 카테고리를 골라서 서치할 수 있는 기능을 완성할 수 있다.
● 마지막으로 조회수를 기능을 구현하기위해 detail로 들어갈때마다 1씩 증가해야하므로 컨트롤러에 detail을 수정해준다
@GetMapping({"/detail","/modify"})
public void detail(Model m, int bno, HttpServletRequest request) {
BoardVO bvo = bsv.getDetail(bno);
String path = request.getServletPath();
log.info(">>>>>path > {}" , path);
if(path.equals("/board/detail")) {
int readCountOk = bsv.readCount(bno);
bvo = bsv.getDetail(bno);
}
m.addAttribute("bvo",bvo);
}
modify가아닌 detail로 갈 때만 조회수가 증가해야하기때문에 따로 path로 저장해서 분리해야한다.
그래서 HttpServletRequest 객체를 매개변수로받아서 getServletPath()를 이용해 path 경로를 따로 받을 수 있게해준다.
그리고 if 문처리로 readCount가 detail로갈때 +1씩 다오까지연결하고 mapper에서 구현해주면 끝.
'Spring 수업 정리' 카테고리의 다른 글
Spring 8일 (0) | 2024.11.06 |
---|---|
Spring 7일 (0) | 2024.11.05 |
Spring 6일 (3) | 2024.11.04 |
Spring 5일 (1) | 2024.11.01 |
Spring 1일차. (0) | 2024.10.28 |