103~104일차 Spring 게시판 페이징처리

쿠우·2022년 9월 12일
0

- Oracle sql에 GENERATED절

(수십명이 동시에 insert를 시도했을 때 데이터 순번에 대한 처리를 멀티쓰레드에 대해 이해와 함께 복잡하게 해야하는데 이를 오라클이 해주겠다는 기능)

  • 기본문법
-- Basic syntax:
--  
--  GENERATED [ ALWAYS | BY DEFAULT [ ON NULL ] ] AS IDENTITY [ (identity options) ]
--
--  (1) GENERATED ALWAYS
--      a. Oracle always generates a value for the identity column.
--      b. Attempt to insert a value into the identity column will cause an error.

--  (2) GENERATED BY DEFAULT
--      a. Oracle generates a value for the identity column
--         if you provide no value.
--      b. If you provide a value, Oracle will insert that value into
--         the identity column.
--      c. For this option, Oracle will issue an error 
--         if you insert a NULL value into the identity column.

--  (3) GENERATED BY DEFAULT ON NULL
--      a. Oracle generates a value for the identity column
--         if you provide a NULL value or no value at all.
--
--
--  * identity options:
--      (1) START WITH intial_value
--          controls the initial value to use for the identity column.
--          The default initial value is 1.
--      (2) INCREMENT BY interval_value
--          defines the interval between generated values.
--          By default, the interval value is 1.
--      (3) CACHE n | NOCACHE
  • 예제1 : 테이블 생성
CREATE TABLE t (
    --  New feature added from Oracle12c and above.
    ID NUMBER 
        -- GENERATED ALWAYS AS IDENTITY
        -- GENERATED BY DEFAULT AS IDENTITY
        GENERATED BY DEFAULT ON NULL AS IDENTITY
        -- (
            START WITH 1
            INCREMENT BY 1
            NOCACHE
        -- )
        PRIMARY KEY,
        
    text VARCHAR2(50)
);
  • 예제1 결과
    -"ADMIN"."ISEQ$$_~".nextval 에 대해 > 시퀀스 객체
  • 예제2 : 데이터 추가
INSERT INTO t ( text ) 
VALUES ( 'TEXT_1' );

INSERT INTO t ( text ) 
VALUES ( 'TEXT_2' );

INSERT INTO t ( text ) 
VALUES ( 'TEXT_3' );

INSERT INTO t ( text ) 
VALUES ( 'TEXT_4' );
  • 예제2 결과 : 순서에 대한 값이 ID로 들어감

https://m.blog.naver.com/kang_sok/221840061270
https://dzzienki.tistory.com/81

-인조키 만드는 방법들-

  • 부분범위처리=> Sub-range Query => Inline View (from 절에 나오는 서브쿼리)
    *LIMIT 절 => 오픈소스 기반의 데이터베이스에서 예전부터 지원하던 절(clause)
  • 옛날에는 인라인뷰와 rownum을 이용해서 페이징처리를 했지만 (https://gent.tistory.com/170)
    12c 버전부터, 페이징 처리를 좀 더 원할하게 구현할 수 있도록,
    새로운 문법으로 Top-N Query와 Range Query가 소개된다.
    이 Query 핵심은 아래 2개의 새로운 절(clause)이다
  • 예제와 결과
    -rn은 rownum이용 / bno로 출력되는 값은 우리가 사용할 방법
--테이블 생성--
CREATE TABLE tbl_test (
    bno     NUMBER
            GENERATED BY DEFAULT ON NULL AS IDENTITY
                START WITH 1
                INCREMENT BY 1
                NOCACHE
            PRIMARY KEY,

    val     CHAR(1)
);

--더미데이터 --
INSERT INTO tbl_test (val)
SELECT dummy
FROM   dual
CONNECT BY level <= 100;

--인조키 고고싱--
SELECT
    -- /*+ index_desc(tbl_test) */
    /*+ index_desc(tbl_test tbl_test_pk) */
    -- /*+ index_asc(tbl_test) */
    -- /*+ index_asc(tbl_test tbl_test_pk) */
    rownum AS rn, bno, val
FROM
    tbl_test
-- ORDER BY
    -- bno DESC
OFFSET &v_offset ROW 
FETCH NEXT &v_length ROWS ONLY;

(1)Identity Column => PK가 인조키(대리키)인 경우, 자동으로 순번을 생성 (generated 절)
(우리가 값을 넣어야하는 경우에는 오라클 내부 시퀀스 객체를 사용해 넣는다. )

(2) OFFSET 절 (시작레코드의 위치)
시작점을 정한다. (0부터 시작)

(3) FETCH절 --(LENGTH의 의미)
기준을 주고 부합하는 행을 뽑아낸다.
FETCH [시작점][개수/비율] [중복선택]
(보통 시작점은 FIRST가 아니라 NEXT로 함 FIRST는 OFFSET절 사용못함)

*테이블의 종류 (광의적 2가지):
(1) "기준" 테이블 : 이미 값들이 정해져 있는 데이터를 저장하는 테이블
특징- 보통은 pk컬럼을 만들지 않는다.
예: 코드 테이블(1:정회원, 2:준회원 , 3: 비회원)
(2) "데이터" 테이블 : 비지니스 데이터를 지정하는 테이블

Pagination - 매 페이지마다 반복적으로 표시해야한다
<< prev 1 2 3 4 5 next >>

- SQL Mapper부분

-BoardMapper.java 에 selectListWithPaging으로 페이징 처리적용된 게시물 목록 조회 쿼리메소드 만들고
BoardMapper.xml에 resultType은 BoardVO로 추가

    <select 
        id="selectListWithPaging"
        resultType="org.zerock.myapp.domain.BoardVO">

        <!-- OFFSET/FETCH 절이 적용된 DQL문장 생성 -->
		SELECT /*+ index_desc(tbl_board) */  *
		FROM tbl_board
		
		OFFSET (#{currPage} - 1) * #{amount} ROWS
		FETCH NEXT #{amount} ROWS ONLY
    </select>


    <select
        id="getTotalCount"
        resultType="int">
        SELECT /*+ index(tbl_board) */ count(bno) 
        FROM tbl_board
    </select>
  pk 존재 => primary Key = UK + NN

- domain패키지

-검색에 필요한 내용을 담는 Criteria =DTO = 커맨드 객체 - 클라이언트가 전달해주는 파라미터 데이터를 주입 받기 위해 사용되는 객체

  • Criteria
@Log4j2
@Data
public class Criteria {
	
	private int currPage = 1;			// 현재 표시할 페이지번호
	private int amount = 20;			// 한 페이지당 보여줄 레코드 건수
	private int pagesPerPage = 10;		// 한 페이지당 보여줄 페이지목록의 길이	
	
	private String type;				// 검색유형
	private String keyword;				// 검색어
	
	
	public String getPagingUri() {
		log.debug("getPagingUri() invoked.");
		
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("");
		
		builder.queryParam("currPage", this.currPage);
		builder.queryParam("amount", this.amount);
		builder.queryParam("pagesPerPage", this.pagesPerPage);
		builder.queryParam("type", this.type);
		builder.queryParam("keyword", this.keyword);
		
		log.info("\t+ pagingUri: " + builder.toUriString());
		
		return builder.toUriString();
	} // getPagingUri
	
} // end class
  • PageDTO

@ToString
@Getter
public class PageDTO {

	private Criteria cri;
	
	private int totalAmount;		// 총 레코드 건수

	private int startPage;			// 한 페이지당 페이지목록의 시작번호
	private int endPage;			// 한 패이지당 페이지목록의 끝번호
	private int realEndPage;		// 총 레코드 건수에 기반한 총 페이지 수
	
	private int offset;				// 현재 페이지에 해당하는 레코드의 시작번호
	
	private boolean prev;			// 다음 페이지목록의 존재여부
	private boolean next;			// 이전 페이지목록의 존재여부
	
	
	
	public PageDTO(Criteria cri, int totalAmount) {
		this.cri = cri;
		
		this.totalAmount = totalAmount;
		
		//----------------------------------------------------------//
		//--Step.0 : 페이징 처리를 위한 공통변수 생성하기
		//----------------------------------------------------------//
		int currPage = cri.getCurrPage();
		int amount = cri.getAmount();
		int pagesPerPage = cri.getPagesPerPage();

		//----------------------------------------------------------//
		//--Step.1 : 현재 페이지에서 보여줄 페이지목록의 끝페이지번호 구하기
		//----------------------------------------------------------//
		// (공식) 끝페이지번호 = (int) Math.ceil( (double) 현재페이지번호 / 페이지목록길이 ) x 페이지목록길이
		//----------------------------------------------------------//
		this.endPage = (int) Math.ceil( (currPage * 1.0) / pagesPerPage ) * pagesPerPage;

		//----------------------------------------------------------//
		//--Step.3 : 현재 페이지의 페이지번호목록의 시작번호 구하기
		//----------------------------------------------------------//
		// (공식) 시작페이지번호 = 끝페이지번호 - ( 페이지목록길이 - 1 )
		//----------------------------------------------------------//
		this.startPage = this.endPage - ( pagesPerPage - 1 );

		//----------------------------------------------------------//
		//--Step.4 : 총페이지수 구하기
		//----------------------------------------------------------//
		// (공식) 총페이지수 = (int) Math.ceil( (double) 모든행의개수 / 한페이지당행의수 )
		//----------------------------------------------------------//
		this.realEndPage = (int) Math.ceil( (totalAmount * 1.0) / amount );
		
		if(this.realEndPage < this.endPage) {
			this.endPage = this.realEndPage;
		} // if

		//----------------------------------------------------------//
		//--Step.5 : 이전 페이지번호목록으로 이동가능여부(prev) 구하기
		//----------------------------------------------------------//
		// (공식) 이전페이지목록이동가능여부 = 시작페이지번호 > 1
		//----------------------------------------------------------//
		this.prev = this.startPage > 1;

		//----------------------------------------------------------//
		//--Step.6 : 다음 페이지번호목록으로 이동가능여부(next) 구하기
		//----------------------------------------------------------//
		// (공식) 다음페이지목록이동가능여부 = 끝페이지번호 < 총페이지수
		//----------------------------------------------------------//
		this.next = this.endPage < realEndPage;

		//----------------------------------------------------------//
		//--Step.7 : 현재 페이지에 표시할 목록의 시작 offset 구하기
		//----------------------------------------------------------//
		// (공식) 시작 offset = (현재페이지번호 - 1) x 한페이지당행의수
		//----------------------------------------------------------//
		this.offset = ( currPage - 1 ) * amount;
	} // constructor
	

} // end class

-TEST하기!

-mapper

mapper 인터페이스랑 xml 이랑 test랑 domain위주로 봤음 일단(영속)

//	@Disabled
	@Test
	@Order(6)
	@DisplayName("1. BoardMapper.selectListWithPaging test.")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testSelectListWithPaging() throws DAOException {
		log.trace("testSelectListWithPaging() invoked.");
		
		Criteria cri = new Criteria();
		cri.setCurrPage(3);
		cri.setAmount(10);
		
		@Cleanup("clear")
		List<BoardVO> list = this.mapper.selectListWithPaging(cri);
		
		for(BoardVO vo : list ) {
			log.info("\t+ vo : {}", vo);
		} // enhanced for
	} // testSelectListWithPaging
	

//	@Disabled
	@Test
	@Order(7)
	@DisplayName("5. BoardMapper.getTotalCount() test.")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testGetTotalCount() throws DAOException {
		log.trace("testGetTotalCount() invoked.");
		
		log.info("\t+ totalCount: {}", this.mapper.getTotalCount());
	} // testGetTotalCount
	

이런 로그가 찍힌다.

-서비스

서비스랑 구현객체에 페이징처리된 목록의 서비스 메소드추가(비즈니스)

//	@Disabled
	@Test
	@Order(6)
	@DisplayName("6. BoardService.getListPerPage")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testGetListPerPage() throws ServiceException {
		log.trace("testGetListPerPage() invoked.");
		
		Criteria cri = new Criteria();
		cri.setCurrPage(2);
		cri.setAmount(10);
		
		@Cleanup("clear")
		List<BoardVO> list = this.service.getListPerPage(cri);
		
		for( BoardVO vo  : list ) {
			log.info("\t+ vo: {}", vo);
		} // enhanced for
	} // testGetListPerPage
	
	
////	@Disabled
	@Test
	@Order(7)
	@DisplayName("7. BoardService.getTotal")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testGetTotal() throws ServiceException {
		log.trace("testGetTotal() invoked.");
		
		log.info("\t+ total: {}", this.service.getTotal());
	} // testGetTotal

서비스 부분에서 추가해준 부분

	@Override
	public List<BoardVO> getListPerPage(Criteria cri) throws ServiceException {
		log.trace(" getListPerPage({}) invoked.", cri);	
		
		try {
			return this.mapper.selectListWithPaging(cri);
		} catch (Exception e) {
			throw new ServiceException(e);
		} // try-catch
	}//getListPerPage

	@Override
	public int getTotal() throws ServiceException {
		log.trace(" getTotal() invoked.");	
		
		try {
			return this.mapper.getTotalCount();
		} catch (Exception e) {
			throw new ServiceException(e);
		} // try-catch
	}//getTotal

로그

-Controller

	@Test
	@Order(2)
	@DisplayName("testGet")
	@Timeout(value=2, unit =TimeUnit.SECONDS)
	void testGet() throws Exception {
		log.trace("testGet() invoked.");
		
		
		MockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(ctx);
		MockMvc mockMvc = mockMvcBuilder.build() ;
		
		
		MockHttpServletRequestBuilder reqBuilder = MockMvcRequestBuilders.get("/board/list");
		
		reqBuilder.param("bno", "84");
		reqBuilder.param("currPage", "3");
		
		ModelAndView modelAndView = 
				mockMvc
				.perform(reqBuilder)
				.andReturn()
				.getModelAndView();
		log.info("\t + modelAndView : {} " , modelAndView);

		
	}// testGet

Controller 부분에서 list 매핑된 메소드를 변경해준 부분 ()

	@GetMapping("/list")
	public void list(Criteria cri,Model model) throws ControllerException {
		log.trace("list() invoked.");
		
		try {
			List<BoardVO> list = this.service.getListPerPage(cri);
			
			model.addAttribute("list", list);	// JSP로 전달할 모델 데이터를 상자에 넣음
			
			PageDTO pageDTO = new PageDTO(cri, this.service.getTotal());
			model.addAttribute("pageMaker", pageDTO);
			
		} catch(Exception e) {
			throw new ControllerException(e);
		} // try-catch
	} // list

결과 로그 :ModelAndView 안에 넣은 pageMaker가 중요

modelAndView : ModelAndView [
  view="board/list";
  model={criteria=Criteria(currPage=3, amount=20, pagesPerPage=10, type=null, keyword=null),
  org.springframework.validation.BindingResult.criteria=org.springframework.validation.BeanPropertyBindingResult: 0 errors,
  list=[
    BoardVO(bno=275, title=TITLE_275, content=CONTENT_275, writer=writer_275, insertTs=2022-08-22 11:54:34.0, updateTs=null),
    BoardVO(bno=274, title=TITLE_274, content=CONTENT_274, writer=writer_274, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=273, title=TITLE_273, content=CONTENT_273, writer=writer_273, insertTs=2022-08-22 11:54:34.0, updateTs=null),
    BoardVO(bno=272, title=TITLE_272, content=CONTENT_272, writer=writer_272, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=271, title=TITLE_271, content=CONTENT_271, writer=writer_271, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=270, title=TITLE_270, content=CONTENT_270, writer=writer_270, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=269, title=TITLE_269, content=CONTENT_269, writer=writer_269, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=268, title=TITLE_268, content=CONTENT_268, writer=writer_268, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=267, title=TITLE_267, content=CONTENT_267, writer=writer_267, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=266, title=TITLE_266, content=CONTENT_266, writer=writer_266, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=265, title=TITLE_265, content=CONTENT_265, writer=writer_265, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=264, title=TITLE_264, content=CONTENT_264, writer=writer_264, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=263, title=TITLE_263, content=CONTENT_263, writer=writer_263, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=262, title=TITLE_262, content=CONTENT_262, writer=writer_262, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=261, title=TITLE_261, content=CONTENT_261, writer=writer_261, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=260, title=TITLE_260, content=CONTENT_260, writer=writer_260, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=259, title=TITLE_259, content=CONTENT_259, writer=writer_259, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=258, title=TITLE_258, content=CONTENT_258, writer=writer_258, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=257, title=TITLE_257, content=CONTENT_257, writer=writer_257, insertTs=2022-08-22 11:54:34.0, updateTs=null), 
    BoardVO(bno=256, title=TITLE_256, content=CONTENT_256, writer=writer_256, insertTs=2022-08-22 11:54:34.0, updateTs=null)
  ], 
  pageMaker=PageDTO(cri=Criteria(currPage=3, amount=20, pagesPerPage=10, type=null, keyword=null), 
  totalAmount=312, startPage=1, endPage=10, realEndPage=16, offset=40, prev=false, next=true), org.springframework.validation.BindingResult.pageMaker=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
] 

-JSP

-JSP 부분 변경 과 특히 list.jsp에 페이징 처리에 대하여 pageMaker로 전달

이렇게 이동

*현재 게시판과 관련된 모든 링크, 버튼 등을 click하여 이동시, 그리고 Request Mapping table에 있는 모든 URI에 페이징처리와 관련된 기준값(criteria) 전송 파라미터들을 마치 우리의 스마트폰처럼 가지고 다녀야한다!!

결론: 페이징처리의 핵심 클래스(객체)는 pageMaker 다 !!!!!!

페이지 메이커가 무엇인가

셋팅된 pageMaker에는 페이징을 위한 버튼의 값들이 들어있고 ModelAndView를 이용해 jsp에 넘겨준다

profile
일단 흐자

0개의 댓글