(수십명이 동시에 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
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)
);
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' );
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)이다
--테이블 생성--
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 >>
-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
-검색에 필요한 내용을 담는 Criteria =DTO = 커맨드 객체 - 클라이언트가 전달해주는 파라미터 데이터를 주입 받기 위해 사용되는 객체
@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
@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
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
로그
@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 부분 변경 과 특히 list.jsp에 페이징 처리에 대하여 pageMaker로 전달
이렇게 이동
*현재 게시판과 관련된 모든 링크, 버튼 등을 click하여 이동시, 그리고 Request Mapping table에 있는 모든 URI에 페이징처리와 관련된 기준값(criteria) 전송 파라미터들을 마치 우리의 스마트폰처럼 가지고 다녀야한다!!
페이지 메이커가 무엇인가
셋팅된 pageMaker에는 페이징을 위한 버튼의 값들이 들어있고 ModelAndView를 이용해 jsp에 넘겨준다