본문 바로가기

Spring 수업 정리

Spring 3일차.

어제했던 보드에 수정/삭제 기능은 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">&laquo;</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">&raquo;</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">&laquo;</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">&raquo;</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