TODAY TOTAL
분류 전체보기 (78)
[Spring] 페이징 처리 및 번호 출력

 

저번에도 올렸던 페이징 처리

 

아무래도 빠르게 보다보니 많이 까먹어서 다시 영상을 보면서 글을 올려본다.

 

별거 아닌 거 같지만 페이징 만큼은 난 많이 헷갈린다. 

 

처음에는 StartPage와 EndPage 그냥 보면 식만 외우면 될꺼 같은데 뭔가 어렵다.. ㅠ

 

먼저 매퍼쪽부터 보자

 

<select id ="GetListWithPaging" resultType = "org.zerock.domain.BoardVO">
  	<![CDATA[
	  SELECT * FROM
	(
	SELECT /*+ INDEX_DESC(tbl_board pk_board)*/ rownum rn, bno,title,writer,regDate,updateDate
	FROM tbl_board 
	WHERE bno > 0 and rownum > 0 and rownum <= (#{pageNum} * #{amount})
	)
	WHERE rn > (#{pageNum}-1) * #{amount}
	]]>
  </select>

BoardMapper.xml에 추가한 페이징 쿼리

 

참... 복잡해보인다 인라인뷰부터 시작해서 아무래도 이해를 확실히 해야할꺼같다.

 

::  resultType은 select절에는 항상 들어간다. 결과 데이터를 어떻게 처리해줘야하는지 명시해 줘야하는데 그때 쓰는게 resultType이다.

 

FROM부터 조회가 되기때문에 인라인뷰를 타는데 여기서 인라인뷰 조건절 끝에 #{pageNum} * #{amount}은

 

Criteria.java에 있는 필드인데 pageNum은 현재페이지, amount는 한페이지당 몇개씩 보자 정해주는 것이다.

 

그리고 <![CDATA[ 쿼리 ]]> 꺽새가 있는 쿼리에 쓰는데, 이게 >,<,&가 있을 경우 컴퓨터는 태그로 인식 하기 때문에

 

CDATA를 쓰면 "야! 이거 쿼리에 쓰는 거야 오해하지마" 라고 해주는 것!

 


public class Criteria {
	//현재 페이지 번호
	private int pageNum;
	private int amount;
	//한페이지당 10개씩 즉, 1~10개의 페이지 번호
	public Criteria() {
		this(1,10);
	}

	public Criteria(int pageNum, int amount) {
		super();
		this.pageNum = pageNum;
		this.amount = amount;
	}
	
}

 

Criteria.java

 

클래스이름 진짜 적응안되서 바꾸고 싶지만, 혹시나 헷갈리까바 냅뒀다.

 

Criteria는 페이징처리와 검색을 동시에 할 수 있는 클래스

 

 

PageDTO.java

 

public class PageDTO {
	
	private int startPage ,endPage;
	private boolean prev, next;
	private int total;
	private Criteria cri;
	
	public PageDTO(Criteria cri, int total) {
		this.cri = cri;
		this.total = total;
	
		this.endPage = (int)(Math.ceil(cri.getPageNum()/ 10.0)) * 10;
		
		this.startPage = endPage - 9;
		
		this.prev = this.startPage > 1;
		// 만약 총 페이지가 71건이면 71.0 / 10 = realEnd는 7.1이 됨 ceil 사용으로 올린다.
		
		int realEnd = (int) ( Math.ceil( (total * 1.0) / cri.getAmount()) ) ;
		
		this.endPage = realEnd <= endPage? realEnd : endPage;
		
		this.next = this.endPage < realEnd;
	}
}

실질적으로 페이징을 담당하는 요녀석 Criteria에 현재페이지, 한페이지에 보여줄 페이지를 가져와

밑에 게시판 1~10 페이징버튼을 만드는데 필요하다.

 

endPage가 은근 헷갈려서 고생했는데, 이해하고 나니 쉬운거였다.

 

현재페이지가 예로 4페이지라고 한다면 4/10.0 * 10 하면 그냥 똑같은 숫자가 나온다. 이러면 의미가 없고

 

0.4를 강제로 올림 해주는 Math.ceil를 쓴다. ceil은 반환형이 double형이기 때문에 캐스팅 int를 꼭 해줘야한다.

 

realEnd(마지막페이지 계산)

 

 

'Back-End > Spring' 카테고리의 다른 글

[Spring] 검색 화면 처리  (0) 2021.08.11
[Spring] 검색 처리  (0) 2021.08.11
[Spring] 인덱스,힌트와 ROWNM  (0) 2021.08.05
[Spring] 조회 처리 및 페이지 이동  (0) 2021.08.04
[Spring] 한글 필터 설정 및 모달창  (0) 2021.08.03
  Comments,     Trackbacks
[Spring] 인덱스,힌트와 ROWNM

 

 

인덱스하면 난 책에 있는 목차가 떠오른다. 찾고자 하는 단어, 문장, 페이지를 빨리 찾고 하는 목차(인덱스)

 

DB에서의 인덱스도 같은 기능을 한다. 

 

먼저 DB에 있는 ORDER BY부터 보기

 

 

SELECT * FROM tbl_board ORDER BY bno asc;

SELECT * FROM tbl_board ORDER BY bno DESC;

ORDER BY (컬럼) asc/desc; :: 여기서 ORDER BY 기본값이 오름차순(asc)이기 때문에 asc 쓸땐 생략이 가능하다.

 

 

옵션에 있는 FULL은 전체를 검색한 것을 의미, 카디널리티(행의 수는 34개, 비용은 3 :: 비용은 적을 수록 좋다.)

(잘 이해가 되질 않을때는 계획 설명을 눌러 확인하면 과정을 알 수 있어 좋다)

 

 

ORDER BY는 오름차순 내림차순으로 정렬을 해준다. 하지만 지금은 데이터량이 엄청 작기 때문에 시간이 0.003초 나온다. 즉, 1만건 이하 정도는 속도가 어느정도 나오겠지만, 그 이상이 되면 시간이 많이 걸린다. 

 

사이트를 눌렀을때 하얀페이지에 페이지 불러오는게 2초이상 걸리기만 해도 분노의 F5누르는데 누가 기다릴까? ㅋㅋㅋ

 

그러니 DB에서 데이터를 불러오는 속도는 중요하다.

 

여기서 쓰는게 인덱스다.

 

 

SELECT /*+ INDEX_DESC(tbl_board pk_board)*/ rownum, bno,title 
FROM tbl_board 
WHERE bno > 0 
ORDER BY bno DESC;


SELECT /*+ FULL(tbl_board pk_board)*/ rownum, bno,title 
FROM tbl_board 
WHERE bno > 0 
ORDER BY bno DESC;

SELECT 앞에 이상하게 써져있는게 힌트라는 것인데

 

힌트는 컴퓨터에게 "난 너가 이 인덱스를 타고 했으면 좋겠어!!"라고 생각하면 된다

 

ORDER BY를 쓰지 않아도 인덱스_DESC을 썼기 떄문에 오름차순 정렬이 된다.

 

인덱스를 여러개 쓸 수 있지만, 대부분 한개만? 쓴다고 한다.

 

주의 할 점은 /* 하고 꼭 +를 붙여줘야한다. 안그러면 주석으로 처리된다. 

또한 /*+ 말고도 --+(싱글주석)도 가능하다.

 

INDEX_() DESC나 ASC를 쓸 수 있다. (오름,내림정렬)

 

FULL과 INDEX만 알아도 충분 (두개밖에 쓰질 않나보다)

 

FULL : 전체

 

INDEX 그 특정 부분 정렬


ROWNUM은 의사 컬럼만 참조, DB에 저장되지는 않는다.

 

거의 페이징을 위해 태어난 존재랄까?

 

 

여기서 보면 BNO는 오름차순이고, ROWNUM은 1이다. 즉, ROWNUM은 순위를 매기는 것 즉, 인덱스로 가장 먼저 온 애가 1등이 되버린다.

 

하지만 페이징을 할 경우에 이런 조건을 쓸 수 있는데,

 

WHERE bno > 0 and rownum <= 10; 

0 ~ 10 행을 보여준다. 근데 여기서 2페이지를 보여줄때 이렇게 써버리면 데이터가 나오지 않는다.

 

10~20 까지 (2페이지)

 

이렇게 생각 할 수 있는데, 가장 먼저 온 1등이 되기 때문에 오면 바로 1등이 되고 WHERE절에 적용이 안되 필터에 걸러진다.

 

 

그래서 페이징 할 때는 이렇게 쿼리를 작성한다.

(외우거나 계속 써보자!!! 페이징 할땐 필수니깐)

 

SELECT * FROM
(
SELECT /*+ INDEX_DESC(tbl_board pk_board)*/ rownum rn, bno,title,writer 
FROM tbl_board 
WHERE bno > 0 and rownum > 0 and rownum <=20
)
WHERE rn > 10
;

 

여기서 FROM절에 ( )안에 있는게 인라인뷰. 저 ( )에 있는 쿼리가 조건에 맞는 것을 실행 하고 그다음 rn > 10이 실행이 된다. ( rownum은 키워드라서 가짜로 컬럼을 만든다. rn)

 

쿼리 실행 순서

FROM - WHERE - GROUP BY - HAVING -SELECT - ORDER BY

-> -> ->

 

 

2Page

 

  Comments,     Trackbacks
[Spring] 조회 처리 및 페이지 이동

 

조회 = Get.jsp

파일이름처럼 Get.jsp(조회)는 무조건 Get방식으로!

 

 

조회는 게시판 리시트에 제목을 클릭 시 제목 내용 글쓴이를 확인 할 수 있는 화면

 

간단한 화면 list.jsp 내용을 가져오기만 하면 된다.

 

<div class="form-group">
       <label>BNO</label>
       <input class="form-control" name="bno" readonly="readonly" value='<c:out value="${board.bno}"/>'>
       </div>                            
                                 
       <div class="form-group">
         <label>Title</label>
         <input class="form-control" name="title" readonly="readonly" value='<c:out value="${board.title}"/>'>
       </div>
     
       <div class="form-group">
         <label>Content</label>
         <textarea class="form-control" rows="5" cols="50" name="content"><c:out value="${board.content}"/></textarea>
       </div>
     
     
       <div class="form-group">
         <label>Writer</label>
         <input class="form-control" name="writer" value='<c:out value="${board.writer}"/>'>
       </div>

	 <button type="submit" class="btn btn-default"><a href='/board/list'>List</a></button>
     <button type="reset" class="btn btn-default"><a href='/board/modify?bno=<c:out value="${board.bno}"/>'>Modify</a></button>

input 속성 중 readonly는 읽기만 가능 하고 값 변경은 불가능 하다. 

 

 

마지막 버튼 부분

 <button type="reset" class="btn btn-default">

<a href='/board/modify?bno=<c:out value="${board.bno}"/>'>Modify</a></button>

 

modify에서 클릭한 제목의 bno를 수정 하는 a 태그 처음에는 이해가 안됬는데 써보니 이해가 됬다.

그리고 작은 따옴표 주의!

 

 

 

BoardController에서는 

 

@GetMapping({"/get", "/modify"})
	public void get(@RequestParam("bno") Long bno, Model model) {
		
		model.addAttribute("board", service.get(bno));
	}

 

get은 게시물 번호만 조회를 하기 때문에 bno를 받아야 하기때문에 저렇게 적는다.

 

board라는 키에 bno 값 가져오기!

 

근데 여기서 @RequestParam를 쓰는 이유는 bno는 int 타입인데 int는 파라미터 수집이 안된다. 모델 어트리뷰트를 써야 수집이 된다.

 

@RequestParam :: 하나 이상의 타입을 적용 할 수 있다.

 

 

  Comments,     Trackbacks
[Spring] 한글 필터 설정 및 모달창

 

한글은 항상 말썽이다. 전에도 이클립스 한글 깨진 적이 있어서 속성 들어가서 바꾸고 그랬는데 이번에도.. 어김없이

 

하지만 이상하게 필터를 적용해도 한글이 ?????? 가 나와서 구글의 힘을 빌려 찾아보았다.

 

필터와 UTF-8 적용이 겹쳐서 그렇다.. 톰캣 서버 Server.xml에 커넥션에 UTF-8 추가하면 된다. 라는 정보가 많았지만

 

결국 물음표는 없어지지 않았다.

 

그러나 30분동안 찾다가 찾았다. 

 

원인은 register,list랑 연결된 footer.jsp 와 header.jsp도 같이 charset=UTF-8" pageEncoding="UTF-8"

 

바꿔줘야 했던 것이였다.

 

 

해결은 했지만 DB 데이터는 엉망진창...

 

ㅠㅠ

그래도 해결해서 다행 휴..

 

모달창은 소스 위치 상관 없이 위 아래에 놔도 적용이 된다.

 

난 맨 밑에 있는 스크립트 바로 위에 썼다. (부트스트랩 이용)

 

https://getbootstrap.com/docs/4.0/components/modal/

 

Modal

Use Bootstrap’s JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.

getbootstrap.com

 

모달창 소스 복사 붙여넣기 후  첫번째 div에 id="myModal" 를 준 뒤

 

$(document).ready(function() {

$("#regBtn").click(function(){
		self.location = "/board/register";
  });

자바스크립트로 클릭시 register로 가게 했다. 공부하는 영상에선

자바스크립트는 따로 파일을 만들어 개별로 관리 한다고 한다.

 

a 태그를 이용해서 할 수도 있다.

 

적용 후 모습

 

 

  Comments,     Trackbacks
[Spring] 화면 구현 및 등록 처리

 

영상을 보면서 얼마나 화면구현하는게 오래걸리는지 새삼 실감했다.

부트스트랩이 없었다면 진짜 진짜 오래걸렸을꺼같다.

 

영상도 화면구현이 제일 길다 ㅋㅋ

 

 

include로 헤더와 풋터를 넣었다. 그 이유는 공통적으로 쓰기 때문 view단에 똑같은 코드를 넣을 필요가 없다

 

 

또한 resources 폴더 js,css을 넣는 폴더로 부트스트랩 관련 css,js,html을 넣었주었다.

디자인은 최대한 html 소스를 보며 복사 붙여넣기를 하고 최대한 스프링 전체를 이해하는거에 중점을 두었다.

 

 

<div class="panel-body">
                            <table width="100%" class="table table-striped table-bordered table-hover" >
                                <thead>
                                    <tr>
                                        <th>BNO</th>
                                        <th>Title</th>
                                        <th>writer</th>
                                        <th>RegDate</th>
                                        <th>UpdateDate</th>
                                    </tr>
                                </thead>
                                <tbody>
                                <c:forEach items="${list}" var="board">
                                    <tr class="odd gradeX">
                                        <td>${board.bno }</td>
                                        <td>${board.title }</td>
                                        <td>${board.writer }</td>
                                        <td><fmt:formatDate pattern="yyyy-MM-dd"
						                   value="${board.regdate }" /></td>
						                <td><fmt:formatDate pattern="yyyy-MM-dd"
						                   value="${board.updateDate }" /></td>
                                    </tr>
                                </c:forEach>
                                </tbody>
                            </table>
                            
                        </div>

 

여기서 EL태그나 JSTL(C태그..)를 쓸려면

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

이걸 맨 위쪽에 붙여야한다. 그래야 태그를 쓸 수 있다.

 

c:forEach는 List,배열 요소를 순서대로 반복하는 태그다

 

items엔 BoardController에 있는 키값을 받아와야하는데  키값은 list 그래야 전체 테이블 List를 가져와 반복한다.

var는 변수다 아무렇게 써도 무방 하지만 연관짓는게 좋다.

 

BoardController.java

@GetMapping("/list")
	public void list(Model model) {
		
		log.info("list............");
		
		model.addAttribute("list", service.getList());
	}

 

등록은 보내는방식이 POST이지만 등록 할 화면을 보여주는건 GET방식이여야 한다.

전에 POSTMapping을 한건 실제 게시물의 등록 처리이고 지금 적을 GETMapping은 등록 화면이다.

 

@GetMapping("/register")
	 public void registerGET() {
	 }

 

<div class="panel-body">
                        
                            <form action ="/board/register" method="post"> 
                            
                            <div class="form-group">
                                <label>Title</label>
                                <input class="form-control" name="title">
                            </div>
                            
                            <div class="form-group">
                                <label>Content</label>
                                <textarea class="form-control" rows="5" cols="50" name="content">
                                </textarea>
                            </div>
                            
                            
                            <div class="form-group">
                                <label>Writer</label>
                                <input class="form-control" name="writer">
                            </div>

						<button type="submit" class="btn btn-default">Submit Button</button>
                        <button type="reset" class="btn btn-default">Reset Button</button>

                            </form>
                        </div>

 

 

 

맨 위에 추가된 것을 볼 수 있다.

 

 

  Comments,     Trackbacks
[Spring] 컨트롤러 등록,수정 및 삭제

 

POST 방식인 (등록, 수정, 삭제) POST는 '리소스를 생성,변경해야 할때 쓴다' 그리고 별도의 페이지. 삭제 되었으면

삭제 되었다고 다른 페이지에 가거나 등록되었습니다.. 알림메시지 같은거

 

그리고 POST방식 후에는 redirect/를 써 (등록,수정,삭제) 후 list.jsp에 이동 하게끔 하자

 

하지만 여기서 왜 redirect를 쓰는 것이냐는거다. redirect를 쓰지 않으면 사용자가 도배를 할 수 있기 때문이다.

 

무슨 말이라면, 쓰지 않으면 URL가 항상 같기 때문에 똑같은 글을 쓸 수 있다. 하지만 redirect를 쓰면 URL가 계속 바뀌기 때문에 똑같은 글을 쓸 수 없게 된다.

 

 

등록이나 수정, 삭제 작성하는 건 비슷비슷하다.

 

등록

 

//등록
	@PostMapping("/register")
	public String register(BoardVO board,RedirectAttributes rttr) {
	
		
		log.info("board:" + board);
		
		Long bno = service.register(board);
		
		log.info("BNO:" + bno);
		rttr.addFlashAttribute("result",bno);
		//아주 잠깐만 결과를 보내는 거, 한번은 전송되는데 그다음 브라우저 주소창엔 남지 않는다.
		//rttr.addAttribute("result",bno)남는다.
		
		return "redirect:/board/list";
	}

매개변수에 RedirectAttributes가 있다. 처음본다..

 

찾아보니 RedirectAttributes의 클래스를 통해 string형태와 map,list,vo 등의 object형태로 넘겨줄 수 있다.

그리고 String으로 하는 이유는 rttr 문자열 접두어로 보내기 때문에 String으로 한다.

 

addFlashAttribute는 이름 그대로 플래쉬.. 잠깐만 결과를 보여주는 것이다. 그 뒤론 URL창에 남지 않는다. (휘발성)

 

 

 

수정과 삭제 ( 똑같다)

 

@PostMapping("/modify")
	public String modify(BoardVO board,RedirectAttributes rttr) {
		
		int count = service.modify(board);

		
		if(count == 1) {
			rttr.addFlashAttribute("result", "success");
		}
		return "redirect:/board/list";
	}
	
@PostMapping("/remove")
	public String remove(@RequestParam("bno")Long bno,RedirectAttributes rttr) {
		
		int count = service.remove(bno);

		
		if(count == 1) {
			rttr.addFlashAttribute("result", "success");
		}
		return "redirect:/board/list";
	}

 

int count를 쓴 이유는 Service에 modify와 remove를 int형으로 썼기 때문이고 if를 활용해 잠깐 결과 확인

 

 

BoardService.java

public interface BoardService {
	
	Long register(BoardVO vo);
	
	BoardVO get(Long bno);
	
	int modify(BoardVO board);
	
	int remove(Long bno);
	
	List<BoardVO> getList();

}

 

  Comments,     Trackbacks
[Spring] 컨트롤러 등록 테스트

 

 

테스트.. 공부하면서 뭐이리 귀찮은걸 테스트패키지에 파일을 만들어서 할까? 생각했다.

 

하지만 영상을 보면서 테스트는 중요하다고 다시 한번 생각하게 되었다.

 

먼저 확신을 할 수 있다는 것. 만약 BoardController에 등록부분을 테스트를 해서 등록테스트를 확인했다면

오류가 날 시 등록을 제외하고 다른 것을 빠르게 확인할 수 있다. 하지만 지금은 배우는 입장이고 코드량이 많이 없어 빠르게 확인 가능하지만 나중에 실무에선 엄청난 코드량을 어떻게 확인 할 수 있을까? 야근각

테스트가 처음에는 귀찮을지 몰라도 나중을 생각한다면 꼭 Test를 하자! 그리고 log도 수시로 찍자

log찍는다고 뭐라 할 사람 없다!

 

//등록
	@PostMapping("/register")
	public void register(BoardVO board) {
		
		log.info("board:" + board);
		
		Long bno = service.register(board);
		
		log.info("BNO:" + bno);
	}

 

GET은 '서버로부터 정보를 조회하기 위해' URL를 보면 파리미터와 같이 넘어온다. ?가 찍히고 옆에 id 등등

 

POST는 '리소스를 생성,변경하기 위해' 쉽게 생각하면 카톡을 사용할 때 '링크' 만 보내는 것(브라우저로 바로 못 보낸다)

           내용이 눈에 보이지 않다고 안전하다고 생각하지만 그렇지 않음. (개발자도구로 확인가능) 반드시 암호화로..

 

GET, 동일한 요청을 여러번 전송하더라도 같은 응답 (list)

POST, 동일한 요청을 여러번 전송하더라도 응답이 항상 다를 수 있음 (Register,Modify,remove(delete))

 

그래서 등록이라 @PostMapping

 

 

 테스트

 

@Test
	  public void testRegister() throws Exception {
	    log.info(
	        mockMvc.perform(
	            MockMvcRequestBuilders.post("/board/register")
	            .param("title","Test 테스트")
                    .param("content","Content")
	    	    .param("writer","user10")
	    		)
	        .andReturn());
	  }

 

MockMvc, 가짜 객체를 만들어서 MVC동작을 재현 할 수 있는 클래스(테스트 굿굿)

 

.perform, 요청을 전송하는 역할

 

.param(info), 키=값의 파라미터를 전달

 

.andReturn, 말그대로임

 

 

파라미터 수집된것을 알 수 있다.

  Comments,     Trackbacks
[Spring] 컨트롤러 정의 및 설정

 

 

스프링 MVC 중에 C에 해당하는 컨트롤러 다른 것중에 제일 많이 일하는 녀석인거 같다.

 

사용자의 모든 요청은 컨트롤러에 전달되기도 하고 모델생성... 심지어 뷰도 생성하기도 한다.

 

뷰와 모델의 중간다리 역할...

 

이렇게 컨트롤러가 비중이 엄청 커서 MVC의 단점이기도 하다. 그래서 컴포넌트로 분리한다는데

 

컴포넌트분리라는 말도 그렇고 그 다음부터는 잘 모르겠다.

 

 

BoardController를 만들건데 

 

먼저 목록페이지 - > 등록/입력 처리 -> 조회 -> 수정/삭제 순으로 진행 할 예정이다.

 

 

package org.zerock.controller;

@Controller 
@RequiredArgsConstructor 
@RequestMapping("/board/*") 
@Log4j 
public class BoardController {
	
	private final BoardService service;

	@GetMapping("/list")
	public void list(Model model) {
		
		log.info("list............");
		
		model.addAttribute("list", service.getList());
		
	}
}

 

@Controller는 Model 객체를 만들어 데이터를 담고 View를 찾고, 스프링 MVC 컨트롤러 객체임을 명시해준다.

 

@RequiredArgsConstructor

어노테이션은 final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 만들어준다. (필드주입)

 

@RequestMapping("/board/*")

URL을 컨트롤러의 메서드와 매핑할 때 사용하는 스프링 프레임워크의 어노테이션이다. list나 view 등 보면 모두 링크가 board로 시작하기때문에 ()에 보드라고 씀.

 

 

@GetMapping은 /list 메소드 하나당 전부다 URL 하나씩 매핑 하는 것 (?freeId=asdasd )

 

중요한 Model부분 (request.setAttribute()와 비슷하다)

Model이라는 타입의 객체를 파라미터로 받을 수 있다.

 

전달할때는 데이터가 없지만, DB를 통해서 이것의 결과 값이 필요할때는 모델로 써야한다.

 

(DB에서 게시물을 가져와야하는데.. 스프링에선 Model을 사용한다 라고 생각하기)

 

 

 

 

 

 

 

  Comments,     Trackbacks