일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 오라클주별데이터
- 스프링과 스프링부트 차이점
- 자바왕초보
- 자바 왕기초
- 스프링 구글차트
- 스프링 Ioc
- 자바왕기초
- 스프링 구글차트로 기간별 현황 조회하기
- 썸머노트
- 스프링
- 스프링 에러
- HTML
- 오라클통계
- 스프링 Ioc Container
- 오라클월별데이터
- 제이쿼리
- Spring Boot가 해결하려고 했던 문제
- CSS
- 세션
- 자바
- 스프링 제어역전
- 오라클
- 자바기초
- 자바 기초
- 오라클일별데이터
- 오라클클라우드에 젠킨스 설치하기
- java
- maven
- jsp
- 스프링 부트가 해결하려고 했던 문제
- Today
- Total
Just Do it
Part 3) 기본적인 웹 게시물 관리 (Ch. 12) 본문
Part 3) 기본적인 웹 게시물 관리 (Ch. 12)
Seojoo21 2022. 2. 18. 21:34* 이 카테고리의 글은 <코드로 배우는 스프링 웹 프로젝트> 에서 각 파트별로 진행하는 프로젝트의 흐름을 보기 위해 각 장의 내용을 간단히 요약한 것이다.
* 출처: 코드로 배우는 스프링 웹 프로젝트 개정판, 구멍가게 코딩단, 남가람북스
- 목록 페이지는 기본적으로 페이징 처리가 필요하다.
- 일반적으로 페이징 처리는 크게 번호를 이용하거나 '계속 보기'의 형태로 구현된다. 번호를 이용한 페이징 처리는 과거 웹 초기부터 이어오던 방식이고, '계속 보기'는 Ajax와 앱이 등장한 이후에 '무한 스크롤'이나 '더 보기'와 같은 형태로 구현된다.
- 예제에서 목록 페이지는 전통적인 번호를 이용하는 방식으로 처리하게 된다.
- 오라클에서 페이징 처리하는 것은 MySQL에 비해서 추가적인 지식이 필요하므로 이에 대한 학습을 선행해야 한다.
**이번 장은 Oracle SQL Developer를 통해 Oracle에 대해 공부한다.
12.1. order by의 문제
- 데이터베이스를 이용할 때 웹이나 애플리케이션에 가장 신경 쓰는 부분은 1) 빠르게 처리 되는 것, 2) 필요한 양만큼만 데이터를 가져오는 것이다.
- 빠르게 동작하는 SQL을 위해서는 먼저 order by를 이용하는 작업을 가능하면 하지 말아야 한다.
12.1.1 실행 계획과 order by
- 오라클의 페이징 처리를 제대로 이해하기 위해서 반드시 알아두어야 하는 것이 실행 계획이다.
- 실행 계획은 말 그대로 'SQL을 데이터베이스에서 어떻게 처리할 것인가?'에 대한 것이다.
- SQL 데이터베이스에 전달되면 데이터베이스는 여러 단계를 거쳐 해당 SQL을 어떤 순서와 방식으로 처리할 것인지 계획을 세운다.
12.2 order by 보다는 인덱스
- 데이터가 많은 상태에서 정렬 작업이 문제가 된다는 사실을 알았다면 이 문제를 어떻게 해결해야할까?
- 가장 일반적인 해결책은 '인덱스(index)를 이용해서 정렬을 생략하는 방법'이다.
- 결론부터 말하자면 '인덱스'라는 존재가 이미 정렬된 구조이므로 이를 이용해서 별도의 정렬을 하지 않는 방법이다.
- 데이터베이스에서 PK(고유키)는 상당히 중요한 의미를 가지는데, 흔히 말하는 '식별자'의 의미와 '인덱스'의 의미를 가진다.
- '인덱스'는 말그대로 '색인'이다. 데이터베이스에서 인덱스를 이해하는 가장 쉬운 방법은 데이터베이스의 테이블을 하나의 책이라고 생각하고 어떻게 데이터를 찾거나 정렬하는지를 생각하는 것이다. 색인은 사람들이 쉽게 찾아볼 수 있게 알파벳 순서나 한글 순서로 정렬한다. 이를 통해 원하는 내용을 위에서부터 혹은 반대로 찾아나가는데 이를 스캔(scan)한다고 한다.
- 데이터베이스에 테이블을 만들 때 PK를 부여하면 지금까지 얘기한 '인덱스'라는 것이 만들어진다. 데이터베이스를 만들 때 PK를 지정하는 이유는 '식별'이라는 의미가 있지만, 구조상으로는 '인덱스'라는 객체가 만들어지는 것을 의미한다. TBL_BOARD 테이블은 BNO라는 칼럼을 기준으로 인덱스를 생성하게 된다. (인덱스는 PK와 ROWID로 구성되어있다.)
- 식별키로 만들어진 인덱스의 경우 BNO 값이 순서대로 정렬된 것을 볼 수 있다. 하지만 실제 테이블은 책장에 책을 막 넣은 것처럼 중간에 순서가 섞여 있는 경우가 대부분이다.
- 인덱스와 실제 테이블을 연결하는 고리는 ROWID라는 존재이다. ROWID는 데이터베이스 내의 주소에 해당하는데 모든 데이터는 자신만의 주소를 가지고 있다.
- 내용이 많고 인덱스가 존재한다면 데이터베이스는 인덱스를 찾고 인덱스에서 ROWID를 찾아서 접근하는 방식을 이용해 SQL이 요청한 데이터를 찾는다.
- 예를 들어 SQL문이 WHERE BNO=100 을 줘서 BNO 값이 100인 데이터를 찾아달라고 하면, 데이터베이스는 먼저 인덱스를 이용해서 100번 데이터가 어디에 있는지 ROWID를 찾아내고, ROWID를 통해 테이블에 접근한다.
- Oracle SQL Developer의 계획 설명을 보면 데이터베이스의 실행 계획을 알 수 있다.
12.3 인덱스를 이용하는 정렬
- 인덱스에서 가장 중요한 개념 중 하나는 '정렬이 되어 있다는 점'이다. 정렬이 이미 되어있는 상태이므로 데이터를 찾아내서 이들을 SORT하는 과정을 생략할 수 있다.
- 데이터의 양이 많고 정렬이 필요한 상황이라면 우선적으로 생각하는 것이 '인덱스'를 작성하는 것이다.
- 데이터의 양이 수만개 이상일 때는 정렬을 안할 수 있는 방법에 대해서 고민해야한다.
12.3.1 인덱스와 오라클 힌트(hint)
- 웹페이지의 목록은 주로 시간의 역순으로 정렬된 결과를 보여준다. 최신 데이터가 가장 중요하기 때문에 시간의 역순으로 정렬해서 최신 게시물들을 보여주게 된다. 이 경우 개발자의 입장에서는 정렬을 안 하는 방식으로 select문을 실행하고 싶어한다.
- 오라클은 select문을 전달할 때 '힌트(hint)'라는 것을 사용할 수 있다. 힌트는 말 그대로 데이터베이스에 '지금 내가 전달한 select문을 이렇게 실행해 주면 좋겠습니다'라는 힌트이다. 힌트는 특이하게도 select문을 어떻게 처리하는지에 대한 얘기일 뿐이므로 힌트 구문에서 에러가 나도 전혀 SQL 실행에 지장을 주지 않는다. 따라서 힌트를 이용한 select문을 작성한 후에는 실행 계획을 통해서 개발자가 원하는대로 SQL이 실행되는지를 확인해야한다.
- 게시물 목록은 반드시 시간의 역순으로 나와야만 하기 때문에 SQL에서는 'order by bno desc'와 같은 구문을 추가할 수 있다. 문제는 이 조건은 데이터베이스 상황에 따라서 테이블의 모든 데이터를 정렬하는 방식으로 동작할 수 있다는 점이다.
- 반면에 힌트는 개발자가 데이터베이스에 어떤 방식으로 실행해 줘야 하는지를 명시하기 때문에 조금 강제성이 부여되는 방식이다.
SELECT * FROM TBL_BOARD ORDER BY BNO DESC;
SELECT /*+INDEX_DESC (TBL_BOARD PK_BOARD) */ * FROM TBL_BOARD ;
- 위의 두 SQL은 동일한 결과를 생성한다.
- 두번째 select문은 order by 조건이 없어도 동일한 결과가 나온 것에 주목해야 한다. select문에서 힌트를 부여했는데 (/*+...*/ 구문) 힌트의 내용이 'tbl_board 테이블에 pk_board 인덱스를 역순으로 이용해줄 것'이므로 실행 계획에서 이를 활용하고 있는 것을 확인할 수 있다.
12.3.2 힌트 사용 문법
- select문을 작성할 때 힌트는 잘못 작성되어도 실행할 때는 무시되기만 하고 별도의 에러는 발생하지 않는다.
- 힌트 문법은 아래와 같다.
SELECT
/*+ 힌트 이름(파라미터) */ 열 이름,...
FROM TABLE NAME;
- 힌트 구문은 /*+ 로 시작하고 */ 로 마무리된다.
- 힌트 자체는 SQL로 처리되지 않기 때문에 위의 그림처럼 뒤에 열 이름이 나오더라도 별도의 ','로 처리되지 않는다.
12.3.3. FULL 힌트
- 힌트 중에는 해당 select문을 실행할 때 테이블 전체를 스캔할 것을 명시하는 FULL힌트가 있다.
- FULL 힌트는 테이블의 모든 데이터를 스캔하기 때문에 데이터가 많을 때는 상당히 느리게 실행된다.
- 예를 들어 tbl_board 테이블을 FULL 스캔하도록 하고, 이 상태에서 정렬을 하려면 다음과 같이 작성할 수 있다.
SELECT /*+FULL(TBL_BOARD)*/ * FROM TBL_BOARD ORDER BY BNO DESC;
12.3.4. INDEX_ASC, INDEX_DESC 힌트
- 흔히 목록 페이지에서 가장 많이 사용하는 힌트는 인덱스와 관련된 'INDEX_ASC, INDEX_DESC' 힌트이다.
- 이 힌트는 주로 'order by'를 위해서 사용한다고 보면 된다. 인덱스 자체가 정렬을 해 둔 상태이므로 이를 통해서 SORT 과정을 생략하기 위한 용도이다.
- 'INDEX_ASC, INDEX_DESC' 힌트는 테이블 이름과 인덱스 이름을 같이 파라미터로 사용한다.
SELECT /*+ INDEX_ASC(TBL_BOARD PK_BOARD) */ * FROM TBL_BOARD WHERE BNO>0;
- INDEX_ASC, INDEX_DESC를 이용하는 경우에는 동일한 조건의 order by 구문을 작성하지 않아도 된다.
12.4 ROWNUM과 인라인뷰
- 페이징 처리를 위해서 역순으로 게시물의 목록을 조회하는 작업이 성공했다면, 이제는 전체가 아닌 필요한 만큼의 데이터를 가져오는 방식에 대해서 알아야 한다.
- 오라클 데이터베이스는 페이지 처리를 위해 ROWNUM이라는 특별한 키워드를 사용해서 데이터에 순번을 붙여 사용한다.
- ROWNUM은 쉽게 생각해서 SQL이 실행된 결과에 넘버링을 해준다고 생각하면 된다.
- 모든 SELECT문에는 ROWNUM이라는 변수를 이용해서 해당 데이터가 몇번째로 나오는지 알아낼 수 있다.
- ROWNUM은 실제 데이터가 아니라 테이블에서 데이터를 추출한 후에 처리되는 변수이므로 상황에 따라서 그 값이 매번 달라질 수 있다.
- 아무 조건을 적용하지 않고 ROWNUM을 적용하면 다음과 같이 SQL문을 작성하여 결과를 얻을 수 있다.
- ROWNUM은 테이블에는 존재하지 않고, 테이블에서 가져온 데이터를 이용해서 번호를 매기는 방식으로 위의 결과는 가장 먼저 가져올 수 있는 데이터들을 꺼내서 번호를 붙여주고 있다. 이때 번호는 현재 데이터베이스의 상황에 따라서 저장된 데이터를 로딩하는 것이기 때문에 실습 환경에 따라 다른 값이 나온다.
12.4.1 인덱스를 이용한 접근 시 ROWNUM
-ROWNUM의 의미가 테이블에서 데이터를 가져오면서 붙는 번호라는 사실을 기억해보면 결국 문제는 테이블에 어떤 순서로 접근하는가에 따라서 ROWNUM 값은 바뀔 수 있다는 뜻이 된다.
- 만일 게시물의 역순으로 테이블을 접근하게 된다면 ROWNUM은 데이터에 접근하는 순서이기 때문에 가장 먼저 접근하는 데이터가 1번이 되므로 이를 이용하면 테이블의 BNO의 역순으로 접근해서 BNO 값이 가장 큰 데이터가 ROWNUM 값이 1이 되도록 작성할 수 있다.
- 이 방식을 이용하면 각 게시물을 정렬하면서 순번을 매겨줄 수 있는데, 10개씩 페이징을 한다는 전제로 1페이지의 경우에는 위의 그림에서 RN이라는 칼럼의 값이 1부터 10에 해당된다고 볼 수 있다.
12.4.2 페이지 번호 1, 2의 데이터
- 한 페이지당 10개의 데이터를 출력한다고 가정하면 ROWNUM 조건을 WHERE 구문에 추가해서 다음과 같이 작성할 수 있다.
SELECT /*+INDEX_DESC(TBL_BOARD PK_BOARD) */
ROWNUM AS RN, BNO, TITLE FROM TBL_BOARD
WHERE ROWNUM <= 10; -- WHERE 구문에는 ROWNUM 관련 조건을 줄 수 있다.
- 1페이지의 데이터를 구했다면 흔히 동일한 방식으로 2페이지 데이터를 구할 수 있을 것(WHERE 절에 WHERE ROWNUM>10 AND ROWNUM <=20; 을 입력하면 될 것)이라고 생각하지만 그렇게 되지 않고 실제로 아무 결과가 나오지 않는다. 이유가 무엇일까.
- ROWNUM을 실행할 때마다 ROWNUM은 다시 1부터 시작한다. 즉 ROWNUM 값은 항상 1로 만들어지고 없어지는 과정이 반복된다는 것이다. 따라서 SQL을 작성할때 ROWNUM 조건은 반드시 1이 포함되어야 한다.
- SQL에 ROWNUM 조건이 1이 포함되도록 다음과 같이 수정해보면 결과가 나오는 것을 볼 수 있다.
12.4.3 인라인뷰(In-line View) 처리
- 10개씩 목록을 출력하는 경우 2페이지의 데이터 20개를 가져오는 데는 성공했지만, 1페이지의 내용이 같이 출력되는 문제가 있으므로 마지막으로 이 문제를 수정해야한다.
- 이 문제를 해결하기 위해서는 인라인뷰라는 것을 이용하는데 인라인뷰를 쉽게 설명하자면, 'SELECT문 안쪽 FROM에 다시 SELECT문'으로 이해할 수 있다.
- 인라인뷰는 논리적으로는 어떤 결과를 구하는 SELECT문이 있고, 그 결과를 다시 대상으로 삼아서 SELECT를 하는 것이다.
- 데이터베이스에서는 테이블이나 인덱스와 같이 뷰(View)라는 개념이 존재한다. 뷰(View)는 일종의 '창문'같은 개념으로 복잡한 SELECT 처리를 하나의 뷰로 생성하고, 사용자들은 뷰를 통해서 복잡하게 만들어진 결과를 마치 하나의 테이블처럼 쉽게 조회한다는 개념이다.
- 인라인뷰는 이러한 뷰의 작성을 별도로 작성하지 않고 말 그대로 FROM 구문 안에 바로 작성하는 형태이다.
- 외부에서 SELECT문은 인라인뷰로 작성된 결과를 마치 하나의 테이블처럼 사용한다. 예를 들어, 위의 경우 20개의 데이터를 가져오는 SQL을 하나의 테이블처럼 간주하고 바깥쪽에서 추가적인 처리를 하는 것이다.
- 만일 위의 결과를 하나의 테이블로 보면 해당 테이블은 RN, BNO, TITLE, CONTENT라는 칼럼을 가지는 테이블이 된다. 이 경우 이 테이블에서 원하는 것은 RN 칼럼 값이 10보다 큰 데이터만 가져오면 된다.
- 인라인뷰를 적용한 2페이지 데이터의 처리는 아래와 같이 작성될 수 있다.
SELECT BNO, TITLE, CONTENT
FROM (
SELECT /*+INDEX_DESC(TBL_BOARD PK_BOARD) */
ROWNUM AS RN, BNO, TITLE, CONTENT
FROM TBL_BOARD
WHERE ROWNUM <= 20 -- 역순으로 데이터 20개를 가져온다.
)
WHERE RN > 10;
- 기존의 SQL과 비교해보면 20개의 데이터를 가져온 후 (인라인뷰(내부에 있는 SELECT문)에 해당) 2페이지에 해당하는 10개만을 추출하는 방식으로 구현된다. (외부에 있는 SELECT문)
- 이 과정을 정리하면 다음과 같은 순서이다.
1) 필요한 순서로 정렬된 데이터에 ROWNUM을 붙인다.
2) 처음부터 해당 페이지의 데이터를 'ROWNUM <= 30'과 같은 조건을 이용해서 구한다.
3) 구해놓은 데이터를 하나의 테이블처럼 간주하고 인라인뷰로 처리한다.
4) 인라인뷰에서 필요한 데이터만을 남긴다.
'신입 개발자가 되기 위해 공부했던 독학 자료들 > 코드로 배우는 스프링 웹프로젝트' 카테고리의 다른 글
Part 3) 기본적인 웹 게시물 관리 (Ch 15) (0) | 2022.02.24 |
---|---|
Part 3) 기본적인 웹 게시물 관리 (Ch 13 & 14) (0) | 2022.02.20 |
Part 3) 기본적인 웹 게시물 관리 (Ch 11) (0) | 2022.02.17 |
Part 3) 기본적인 웹 게시물 관리 (Ch 9 & 10) (0) | 2022.02.16 |
Part 3) 기본적인 웹 게시물 관리 (Ch7 & 8) (0) | 2022.02.16 |