728x90
300x250

[컴퓨터 - PC활용] Golden - Benthic Software(오라클 전용 - MS윈도우) 프로그램


이번에 소개할 프로그램은 오라클(Oracle)사에서 판매하고 있는 사용 쿼리(질의/Query) 도구에 대해서 소개하려고 한다.


좋은 프로그램인가요?

= SQL 쿼리만 다루고자 하는 경우에는 좋을 수도 있다.

완벽하게 좋다고는 볼 수 없다. 제한된 사항, MS윈도에서만 동작하는 한계점 등은 어떻게 할 방법이 없기 때문이다.


고민해볼 만한 주제:

우분투, 리눅스, CentOS, LinuxMint 등 다 기종 운영체제에서는 가능한지?


(작성중)


개인적으로는 그렇게 딱히 추천하고 싶진 않으나, 하나의 장점은 테이블 구조 등 불필요한 정보의 노출을 최소화하는 프로그램이라고 보면 적합할 거 같다.

장단점이 있으니 글을 연재하도록 하겠다.



1. 소개


Benthic Software - High quality SQL query and database tools ( benthicsoftware.com )

이 사이트를 접속하도록 한다.



그림 1. Golden 7 사이트



그림 2. Golden 7 사이트




그림 3. Golden 7 사이트




2. Golden 설치 사양


Benthic Software의 "Golden"을 설치하기 위해서는 아래와 같은 사양이 필요하다.


[설치]

- Microsoft Windows 7, 10
- Oracle Instants Clients (필수)

- Golden


[기타 환경설정]

tnsnames.ora




3. Golden 다운로드 받기


아래의 그림처럼 Golden 7을 선택한다.

클릭한다.


그림 4. Golden 7 설치하기


기다리면, Golden7setup712_64bit.exe 프로그램이 설치된 것을 확인할 수 있다.




4. Golden 설치하기


설치파일을 실행하면, 아래의 그림을 볼 수 있다.



그림 5. Golden 7 설치하기(1)


"I accpet the agreement"를 체크한다.

"Next"를 누른다.



그림 6. Golden 7 설치하기(2)


"Next"를 누른다.



그림 7. Golden 7 설치하기(3)


"Next"를 누른다.




그림 8. Golden 7 설치하기(4)


"Next"를 누른다.



그림 9. Golden 7 설치하기(5)


"Install"을 누른다.



그림 10. Golden 7 설치하기(6)


설치 마법사를 기다린다.



그림 11. Golden 7 설치하기(7)


"Finish"를 누른다.



5. Oracle Instant Clients 설치하기


오라클 공식 홈페이지에 접속하면, Oracle Instant Clients를 내려받을 수 있다.

Oracle Software Downloads | Oracle (https://www.oracle.com/downloads/)



그림 12. Oracle 공식 홈페이지



그림 13. Oracle 공식 홈페이지


해당하는 윈도우 버전을 내려받으면 된다.

참고로 Oracle Instant Client는 다양한 운영체제를 지원하고 있다.


Instant Client는 오라클 로그인없이 내려받을 수 있도록 배포하고 있다.


-> Instant Client for Microsoft Windows (x64)



6. Oracle Instant Clients 설치하기


instantclient_19_9 폴더를 C:\ 또는 D:\ 드라이브에 설치한다.



그림 14. instantclient_19_9 설치하기


설치가 완료되었으면, 다음은 tnsnames.ora 파일을 설치하는 방법에 대해서 소개하겠다.




7. Tnsnames.ora


초기에는 아마 없을 수도 있다. 

Oracle Express Edtion이나 Oracle Databases를 설치하지 않는 이상 또는 설치파일만 내려받았을 때 제공하는 파일이라서

없을 수가 있다.


하나 예제로 첨부하도록 하겠다.


[참고사항] - Oracle XE 11g Edition을 설치하면 제공하는 경로
C:\oraclexe\app\oracle\product\11.2.0\server\network\ADMIN


XE =

  (DESCRIPTION =

    (ADDRESS = (PROTOCOL = TCP)(HOST = Sample)(PORT = 1521))

    (CONNECT_DATA =

      (SERVER = DEDICATED)

      (SERVICE_NAME = XE)

    )

  )


EXTPROC_CONNECTION_DATA =

  (DESCRIPTION =

    (ADDRESS_LIST =

      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1))

    )

    (CONNECT_DATA =

      (SID = PLSExtProc)

      (PRESENTATION = RO)

    )

  )


ORACLR_CONNECTION_DATA = 

  (DESCRIPTION = 

    (ADDRESS_LIST = 

      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1)) 

    ) 

    (CONNECT_DATA = 

      (SID = CLRExtProc) 

      (PRESENTATION = RO) 

    ) 

  ) 



[첨부(Attachments)]

tnsnames.zip


이 파일이 있어야만 golden(Benthic Software) 제품을 실행할 수 있다.



그림 15. tnsnames.ora - 복사, 붙여넣기 하기


tnsnames.ora 파일을 "복사, 붙여넣기"한다.




8. Golden 실행하기


골든 실행하는 방법은 매우 간단하다.



그림 16. Golden 7 실행하기


평가버전은 "Agree"를 누르고 진행하면 된다.

참고로 30일 체험판이다.

관심이 있다면, 알아보면 도움이 될 수는 있다.



그림 17. Golden 7 실행하기


이렇게 프로그램이 실행되면, 잘 된 것이다.

xe를 선택하고 계정 정보를 입력하고 로그인을 하면, Golden 7을 쉽고 빠르게 사용할 수 있다.



그림 18. 데이터베이스 선택하기 - Golden



9. Golden - 데이터베이스 질의 조회


Oracle XE 11g를 예제로 접속하였다.


select * from USER_OBJECTS;


이러한 명령어를 치면 아래의 테이블이 조회된다.



그림 19. 질의 조회하기




10. 참고자료(References)


1. Benthic Software - High quality SQL query and database tools, https://www.benthicsoftware.com/index.html, Last Modified 2020-12-05, Accessed by 2020-12-05.

2. Oracle Instant Client Downloads, https://www.oracle.com/database/technologies/instant-client/downloads.html, Last Modified 2020-12-05, Accessed by 2020-12-05.

반응형
728x90
300x250
[PC활용] Oracle(오라클) - Cmd 명령어로 sqlplus가 동작하지 않을 때 해결 방법


Oracle을 PC에서 설치하여 sqlplus로 작업을 하고자 했을 때 초기 순정 설치했을 때는 sqlplus 명령어가 잘 되지만, 
살다보면 셋팅값을 변경해서 안 켜지는 경우가 있을 수도 있다.


이럴 때 해결하는 방법에 대해서 소개하려고 한다.


[작성환경]

- MS 윈도우 10

- Oracle Databases 설치된 상태


[적용대상]

- Oracle Databases



1. 제어판 - 시스템 속성


몇 가지 간단한 설정으로 다시 사용할 수 있다.


그림 1. 시스템 속성


컴퓨터 -> 속성을 클릭한다. (마우스 오른쪽 버튼)

고급 시스템 설정을 클릭한다.

고급 탭을 클릭한다.

환경 변수(N)을 클릭한다.





그림 2. 시스템 속성


시스템 변수의 "PATH"를 편집한다.

예) C:\oraclexe\app\oracle\product\11.2.0\server\bin

오라클 설치한 경로를 찾아가보면 아래의 그림처럼 sqlplus의 위치를 찾을 수 있다.



[추가 방법]

-> 1. (컴퓨터 -> 속성 -> 환경변수 -> 시스템 변수 -> 새로만들기 

      "ORACLE_HOME" -> C:\oraclexe\app\oracle\product\11.2.0\server

   2. 시스템 변수 (Path 편집) -> %ORACLE_HOME%\bin





그림 3. 오라클 서버 프로그램 폴더

해당 경로에 있는 sqlplus의 모습이다.

반응형
728x90
300x250
[Oracle Databases] 번외글 - 게시판 페이징 관련 로직 쿼리


게시글 중 페이징네이션과 관련한 글을 실습하다보면, 쿼리 문제에 당면하는 일들이 벌어질 수 있다.

그래서 따로 몇 가지 중요 포인트를 정리해보았다.


1. [JSP] 17. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (1), 2020-09-30
- https://yyman.tistory.com/1428


2. [JSP] 18. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (2), 2020-09-30
https://yyman.tistory.com/1429


- 사용 프로그램: SQL Developer (Oracle)

- DB: Oracle Databases [11, 19g]


이 글이 어렵게 이해되는 경우를 위해서 아주 친절하게 SQL Developer에서 어떤 작업을 했는지 소개하도록 하겠다.



그림 1. Oracle SQL Developer에서의 작업 모습의 예

그림 1은 쿼리 넣고 태스트를 하는 모습이다. 예를 들면, Java, C#, C++, PHP, .NET 등의 코드를 삽입하기 전에 미리 확인을 해볼 수 있다.

이런 작업을 수 차례 반복해서 해보고 프로그래밍 코드에 적용하면 된다.


더 좋은 전문적인 프로그램이 있으면, 또는 편리한 프로그램이 있으면 다른 프로그램을 사용해도 무방하다.



1. 게시판 페이징 로직


게시글 "[JSP] 18. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (2), 2020-09-30"에 적혀져 있는 SQL 쿼리문이다.

물론 이 쿼리는 오라클 형태로 작성된 쿼리이다.


-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (BOARD)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE board
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    subject VARCHAR2(30),
    memo NCLOB,
    count NUMBER,
    regidate DATE
);


-- Sequence 정의
CREATE SEQUENCE board_sequence
START WITH 1
INCREMENT BY 1;


-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER board_trigger
BEFORE INSERT
    ON board
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT board_sequence.nextval INTO :NEW.ID FROM dual;
END;


/* 데이터 추가 */
INSERT INTO board (name, subject, memo, count, regidate) VALUES ('홍길동', '안녕하세요.', '메모메모', '0', '2020-09-29 11:11:00');

/* 데이터 등록 후 커밋할 것(대량 정보 처리 후) */
COMMIT;

-- 싱글 쿼리 (페이징)
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from board order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1

-- 특정 싱글 쿼리 SQL
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from board where subject like '%야해해%' order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1


[파일명: board-tbl-oracle_개선후.sql]


[첨부(Attachments)]

board-tbl-oracle_개선후.zip


이 쿼리처럼 세상에 있는 문제가 단일로 처리되면 간단하게 구성해도 된다.

하지만, 그렇지 않다는 점이다.




2. 다중 테이블 문제


두 개의 테이블을 두었다.


하나는 메뉴와 가격에 대한 테이블이다.

하나는 가게 정보에 대한 테이블이다.


CREATE TABLE foodmenu_tbl
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    price NUMBER,
    store_id NUMBER,
    cnt NUMBER,
    regidate DATE
);

-- Table 생성 (FOODSTORE_TBL)
CREATE TABLE foodstore_tbl
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    address VARCHAR2(30),
    regidate DATE
);


두 개 테이블을 하나로 합치면, foomenu_tbl의 stord_id와 foodstore_tbl의 id가 참조 관계를 형성하는 것을 알 수 있다.

모델링 프로그램으로 살펴볼 때는 외래키 지정해도 무방하다.


아무튼, 두 가지 이상 테이블을 조인하여 사용했을 때 쿼리에 대한 것이다.

100만 건 이상 넘어갔을 때는 느려지긴 하지만, 성능이 우수한 쿼리이다. 
(다른 방법을 찾아봐야 할듯. 이 주제에서는 생략함)


-- 2020-10-07 SQL Paging Upgrade

-- 조회 관련 태스트
select f1.id, f1.name, f1.price, f2.name as storename from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id
select count(*) from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id


-- Oracle 11 - 자동번호 생성 테이블 정의

-- Table 생성 (FOODMENU_TBL)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE foodmenu_tbl
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    price NUMBER,
    store_id NUMBER,
    cnt NUMBER,
    regidate DATE
);

-- Sequence 정의
CREATE SEQUENCE foodmenu_sequence
START WITH 1
INCREMENT BY 1;


-- Trigger 생성

-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER foodmenu_trigger
BEFORE INSERT
    ON foodmenu_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT foodmenu_sequence.nextval INTO :NEW.ID FROM dual;
END;



----------------------------------------------------------------------


-- Table 생성 (FOODSTORE_TBL)
CREATE TABLE foodstore_tbl
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    address VARCHAR2(30),
    regidate DATE
);


-- Sequence 정의
CREATE SEQUENCE foodstore_sequence
START WITH 1
INCREMENT BY 1;

-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER foodstore_trigger
BEFORE INSERT
    ON foodstore_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT foodstore_sequence.nextval INTO :NEW.ID FROM dual;
END;


-- Sequence 정의
CREATE SEQUENCE foodstore_sequence
START WITH 1
INCREMENT BY 1;

-- Sequence 삭제 //
drop sequence foodname_sequence;

-- Trigger 생성
-- BEFORE INSERT on '테이블명'
-- NEW.ID (Table의 id를 가리킴)
CREATE OR REPLACE TRIGGER foodstore_trigger
BEFORE INSERT
    ON foodstore_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT foodstore_sequence.nextval INTO :NEW.ID FROM dual;
END;


-- 뷰 생성하기
create or replace view foodmenu_view as
select f1.id, f1.name, f1.price, f2.name as storename from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id order by f1.id desc;


-- 페이징 SQL(뷰 방식)

SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * FROM FOODMENU_VIEW
) Z WHERE ROWNUM <= 20
) WHERE RNUM >= 11;



-- 페이징 SQL(뷰 원본으로 작성)
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT f1.id, f1.name, f1.price, f2.name as storename from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id order by f1.id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1;



-- 특정 쿼리 페이징 SQL(뷰 원본으로 작성)
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT f1.id, f1.name, f1.price, f2.name as storename from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id and f1.name like '%무봉리%' order by f1.id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1



-- 싱글 쿼리 (페이징)
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from foodmenu_tbl order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1



-- 특정 싱글 쿼리 SQL
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from foodmenu_tbl where name like '%야해해%' order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1



-- Sample Insert 문
-- 태스트 코드(foodmenu_tbl)
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴1', 1500, 1, 0, '2020-01-01');
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴2', 1400, 1, 0, '2020-01-01');
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴3', 1300, 1, 0, '2020-01-01');
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴4', 1100, 1, 0, '2020-01-01');
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴5', 1200, 1, 0, '2020-01-01');
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴6', 1300, 1, 0, '2020-01-01');
insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values('사슴7', 1500, 1, 0, '2020-01-01');



-- 상점(foodstore_tbl)
-- 태스트 코드
insert into foodstore_tbl(name, address, regidate) values('치즈집1', '천사1', '2020-01-03');
insert into foodstore_tbl(name, address, regidate) values('치즈집2', '천사2', '2020-01-03');
insert into foodstore_tbl(name, address, regidate) values('치즈집3', '천사3', '2020-01-03');
insert into foodstore_tbl(name, address, regidate) values('치즈집4', '천사4', '2020-01-03');
insert into foodstore_tbl(name, address, regidate) values('치즈집5', '천사5', '2020-01-03');
insert into foodstore_tbl(name, address, regidate) values('치즈집6', '천사6', '2020-01-03');


-- 삭제 관련 Delete Query

delete from foodmenu_tbl;
select * from foodmenu_tbl where name = '야해해';


[파일명: food_tbl_sample_boardQueries.sql]


[첨부(Attachments)]

food_tbl_sample_boardQueries.zip




* 맺음글(conclusion)


게시판 페이징 관련해서 학습 또는 자습할 때, 고민을 하면서 쿼리를 날려보고 하면 좋을 듯 싶다.

반응형
728x90
300x250
[Spring-Framework] 37. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java - AOP)


이번에 소개할 내용은 지난 36번 글에 이어서 AOP를 추가로 적용한 방법에 대해 소개하려고 한다.

어노테이션 방식의 트랜젝션 구축이 이해가 되지 않으면, 이전 글을 참고하면 도움이 된다.


1. [Spring-Framework] 36. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java 설정), 2020-10-10

- https://yyman.tistory.com/1461


IDE: Eclipse 2020-06

- Spring Framework 4.2.4 Releases

- Spring-JDBC

- Spring-TX

- Spring-Core

- AspeetJ Weaver

- AspectJ


DB: Oracle Databases 11g (Express Edition)



1. 프로젝트 구성도(비교)


AOP 작업을 추가한 프로젝트와 기존의 프로젝트의 구성을 비교해놓은 것이다.

이 글에서는 이전의 작성된 객체 파일을 그대로 복사 붙여넣기하여 AOP패키지에 적용하였다.


LogAdvisor.java 파일 말고는 추가 코드를 작성하진 않았다.

MainTestAOP.java도 복사, 붙여넣기해서 만든 것이다. 가리키고 있는 명칭만 변경하였다.



 

 



그림 1, 그림 2. 
(Spring 트랜젝션 + Spring JDBC) + AOP

그림 3, 그림 4. 이전 프로젝트
(Spring 트랜젝션 + Spring JDBC)



2. pom.xml 설정하기


((중략) - 추가된 사항)
    
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>

  
    
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency> 
  

  <!-- AspectJWeaver 추가 -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency>
  





3. LogAdvisor.java(com.website.example.aop)


package com.website.example.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Service;


//관점, 서비스
@Aspect
@Service
public class LogAdvisor {


   // 2단계 - 전 단계 시야)
 
  @Before("execution(* com.website.example.aop.service.AccountServiceAOP.*(..))")
  // @Before("execution(public void sum())")
  // 반환값 없어도 무방
  
   public void logBefore() {
           System.out.println("전 단계");


   }

 
}


파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip



4. AccountServiceAOP.java(com.website.example.aop.service) - 예(복사, 붙여넣기)


코드 자체를 복사 붙여넣기 한 것이다.

AOP를 왜 하는지 철학을 이해하라고 하나 예로 보여주는 것이다.



package com.website.example.aop.service;

import java.sql.SQLException;


import com.website.example.vo.AccountVO;


public interface AccountServiceAOP {

 void accountCreate(AccountVO vo) throws SQLException;
 void accountTransfer(String sender, String receiver, int money) throws SQLException;
 
}




5. AccountServiceImplAOP.java(com.website.example.aop.service) - 예(복사, 붙여넣기)


하나 어노테이션 @Repository를 달아주었다. 그거 말고는 동일하다.



package com.website.example.aop.service;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.vo.AccountVO;


@Repository
@Transactional
public class AccountServiceImplAOP implements AccountServiceAOP{

 private AccountDAO accountDAO;
 private DataSource ds = null;
 
 public AccountServiceImplAOP(DataSource ds) {
  this.accountDAO = new AccountDAOImpl(ds);
  this.ds = ds;
 }

 @Override
 @Transactional(propagation=Propagation.NEVER)
 public void accountCreate(AccountVO vo) throws SQLException {
  accountDAO.createAccount(vo); 
  System.out.println("create CurrentTransactionName: " + TransactionSynchronizationManager.getCurrentTransactionName());
 }
 
 @Override
 @Transactional
 public void accountTransfer(String sender, String receiver, int money) throws SQLException {
  
     int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크
     
        if(balance >= money){ // 보내는 돈이 잔액보다 많으면
     
         System.out.println("transfer CurrentTransactionName: " + TransactionSynchronizationManager.getCurrentTransactionName() );
         
   accountDAO.minus(sender, money);
   accountDAO.plus(receiver, money);
   
        } else{

         System.out.println("돈 없음");
         //throw new NoMoneyException();
        }
  
 }


}




6. RootConfigAOP.java(com.website.example.common) - 예(복사, 붙여넣기)



package com.website.example.common;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@Import({DBConfigAOP.class})
@ComponentScan(basePackages = {"com.website.example"})
@EnableAspectJAutoProxy
//@ComponentScan(basePackages = {"com.local.example.beans", "com.local.example.advisor"})
public class RootConfigAOP {


}




7. DBConfigAOP.java(com.website.example.common) - 예(복사, 붙여넣기)


package com.website.example.common;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.aop.service.AccountServiceAOP;
import com.website.example.aop.service.AccountServiceImplAOP;


@Configuration
@EnableTransactionManagement
public class DBConfigAOP {

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
   
    @Bean
    public DataSource dataSource() {
     
     DataSource dataSource = new MyDataSourceFactory().getOracleDataSource();
     
     /* Apache DBCP
     
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/testDb?useUnicode=true&characterEncoding=utf8");
        dataSource.setUsername("test");
        dataSource.setPassword("test123!@#");
       
        */
        return dataSource;
    }
   
    @Bean
    public AccountDAO accountDAOImpl() {
     
     AccountDAO dao = new AccountDAOImpl(dataSource());
     return dao;
    }
   
    @Bean
    public AccountServiceAOP accountServiceImplAOP() {
     
     AccountServiceAOP service = new AccountServiceImplAOP(dataSource());

     return service;
    }
   
}




8. MainTestAOP.java(com.website.example.unit) - 예(복사, 붙여넣기)



package com.website.example.unit;


import java.sql.SQLException;

import java.sql.Timestamp;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.common.RootConfigAOP;

import com.website.example.dao.AccountDAOImpl;
import com.website.example.aop.service.AccountServiceAOP;
import com.website.example.aop.service.AccountServiceImplAOP;

import com.website.example.vo.AccountVO;


class MainTestAOP {

 @Test
 void test() throws SQLException {
  
       @SuppressWarnings("resource")
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfigAOP.class);

  
        AccountServiceAOP service = (AccountServiceAOP) applicationContext.getBean("accountServiceImplAOP");
  
  AccountVO vo = new AccountVO();
  
  // 1. 계정 생성
  vo.setName("홍길동2");
  vo.setBalance(10000);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 11:05:20"));
  service.accountCreate(vo);
  
  // 2. 계정 생성
  vo.setName("홍길자2");
  vo.setBalance(0);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 23:05:20"));
  service.accountCreate(vo);
  
  // 3. 거래 처리
  service.accountTransfer("홍길동", "홍길자", 500);
  
  
 }


}




* 맺음글(Conclusion)


AOP를 쉽고 간단하게 트랜젝션과 연결해서 사용하는 방법에 대해서 소개하였다.



반응형
728x90
300x250
[PC 활용] 오라클 - 반복문으로 다중 입력 시(예:insert into) 퍼짐 현상


이 글은 JSP, Java, PHP, C#를 예로 들어서 개발을 수행했을 때, 태스트로 "DML(Insert 문)"으로 DB에 Query 호출을 반복문으로 하였을 때 초기 환경 상태에서의 오라클 데이터베이스에서 일어나는 반응에 대한 글이다.


[PseudoCode]


public void insertMember(){


    (연결자)
    Connection conn = .....;
    
    try{
        conn.open(); // (연결)

        for ( 1 to 10000 ){


           String sql = "insert into ........";

           conn.executeQuery(sql);

        }


     }catch(Exception e){

     }finally{
           conn.close(); // (종료)
     }

    


}


슈도 코드) 문제가 되는 영역


이런 문제를 처리하고자 했을 때, 발생되는 오류를 소개하려고 한다.


[태스트 환경]

1. MS Windows 10

2. Oracle 11g (Express Edition, XE)



1. 프로그래밍 도구에서 호출되는 오류명


ora-12519 TNS:no appropriate handler found


이러한 오류가 발생한다면, 다음과 같이 입력하여 해결하면 된다.



2. 해결 방법


명령어를 몇 가지 요약하였다.


1. 명령 프롬프트 실행하기

   cmd


2. 관리자 계정 접속 명령어

   sqlplus "/as sysdba"


3. 프로세스 조회하기
   SQL> SELECT * FROM v$resource_limit where resource_name='processes';


4. 프로세스 조정하기 크기
   SQL> ALTER SYSTEM SET PROCESSES=500 SCOPE=spfile; -- 200으로 늘려줌


5. 서버 재시작
   SQL> shutdown immediate; --셧다운
   SQL> startup; --재시작



그림 1. cmd 명령어 - 시작 메뉴


시작 메뉴에서 cmd를 입력한다.



그림 2. sqlplus


sqlplus "/as sysdba"


이렇게 입력해도 관리자 권한으로 오라클에 접속할 수 있다.


SQL> SELECT * FROM v$resource_limit where resource_name='processes';


프로세스 조회 명령어로 프로세스 자원 한계를 확인하자.

프로세스가 100으로 작게 설정되어 있으니 조금 더 늘려보도록 하자.



그림 3. sqlplus


SQL> ALTER SYSTEM SET PROCESSES=500 SCOPE=spfile;


프로세스 500으로 수정을 해주었다.

변경되었는지 조회를 해보자.


SQL> SELECT * FROM v$resource_limit where resource_name='processes';


조회를 해보니, 변경되지 않았다.

그래서 commit을 안 해서 발생한 문제인지 commit을 입력하였다.


SQL> commit;


그리고 다시 조회를 해본다.


SQL> SELECT * FROM v$resource_limit where resource_name='processes';


변경되지 않았다.

오라클 DB를 재부팅해보면 해결되는 문제인지 시도해보았다.



그림 4. sqlplus


SQL> shutdown immediate;

SQL> startup;


서버를 다시 시작하였다.

그리고 프로세스 조회 명령어를 입력하였다.


SQL> SELECT * FROM v$resource_limit where resource_name='processes';


변경된 프로세스 내용을 확인할 수 있다.

반응형
728x90
300x250
[PC 활용] 오라클 XE 11(Oracle Express Edition 11g) - 설치 및 로그인


윈도우 10에서 오라클 XE를 설치하는 방법에 대해서 소개하려고 한다.

Standard Edition, Enterprise Edition에 비해서 설치 프로그램 용량도 적고, 간편하다.

다만, 아파치 톰캣을 사용하는 개발자라면, 포트 충돌이 날 수 있다.



[작성 환경]

1. MS윈도우 10

2. 포멧 후 깨끗한 상태



1. 설치하기


설치하는 방법은 다음과 같다.




그림 1. 오라클 설치하기


setup.exe를 실행한다.



그림 2. 오라클 설치하기


잠시 기다린다.



그림 3. 오라클 설치하기


Next를 누른다.



그림 4. 오라클 설치하기


"I accpet the terms......"을 체크한다.

Next를 누른다.




그림 5. 오라클 설치하기


Next를 누른다.



그림 6. 오라클 설치하기


이 비밀번호는 Oracle XE에서는 매우 중요한 암호가 된다. 기억해두자.

암호를 입력한 후에는 Next를 누른다.




그림 7. 오라클 설치하기


설치 환경설정을 살펴본다.

Oracle HTTP에서도 8080포트를 사용하는 것을 살펴볼 수 있다.


Install을 누른다.




그림 8. 오라클 설치하기


기다린다.



그림 9. 오라클 설치하기


Finish를 누르면 설치가 완료된 것이다.




2. 명령 프롬프트(cmd) 작업 - Sqlplus


설치를 완료하였다면, 바로 사용가능한 것이 아니라 몇 가지 계정 설정을 해줘야 한다.



그림 10. sqlplus 작업을 위한 명령 프롬프트 실행하기


시작 메뉴에서 cmd를 입력한다.



그림 11. sqlplus 작업을 위한 명령 프롬프트 실행하기


system이라고 입력한다.

아까 입력했던 패스워드를 입력한 후 엔터를 누른다.



그림 12. sqlplus 작업을 위한 명령 프롬프트 실행하기


경로: C:\oraclexe\app\oracle\product\11.2.0\server\rdbms\admin\
찾아야 하는 파일명: scott.sql


해당 파일을 찾아야 한다.



그림 13. sqlplus 작업을 위한 명령 프롬프트 실행하기


SQL> @C:\oraclexe\app\oracle\product\11.2.0\server\rdbms\admin\scott.sql
SQL> alter user scott identified by tiger;

SQL> commit;


3줄 명령어를 입력한다. (SQL>은 입력하는 게 아님)


scott 계정을 활성화한 것이다.



4. scott 계정 접속하기


앞서 작업한 scott 계정을 로그인해볼 것이다.



그림 14. sqlplus 작업을 위한 명령 프롬프트 실행하기


SQL> conn scott/tiger
SQL> show user;

SQL> select * from tab;


3줄 명령어는 다음과 같다.

1줄은 scott/tiger(비밀번호)로 연결하라는 것이다.

2줄은 접속중인 계정이 무엇인지 확인하는 명령어이다.

3줄은 tab 테이블을 조회하는 명령이다. (SELECT문)



그림 15. sqlplus 작업을 위한 명령 프롬프트 실행하기


SQL> exit


exit는 연결을 끊는다는 명령어이다.


c:\Users\{사용자계정명}>sqlplus
Enter user-name: system
Enter password: (비밀번호 입력하기)


관리자 계정으로 접속하는 명령어를 3줄로 요약한 것이다. 
(xe버전 방법은 standard, enterprise의 방법은 미세한 차이가 있을 수도 있음.)



5. HR 계정 - 활성화 하기


Oracle XE 11g에서 제공하는 연습용 계정 HR을 활성화 하는 방법이다.


SQL> alter user hr account unlock;



그림 16. sqlplus 작업을 위한 명령 프롬프트 실행하기


간단한 명령으로 연습용 HR 계정을 활성화 시킬 수 있다.


- 참고로 Oracle 공식 사이트에서 제공하는 메뉴얼도 있으니 참고하면 좋겠다.

https://docs.oracle.com/cd/E17781_01/admin.112/e18585/toc.htm#XEGSG120

반응형
728x90
300x250

[JSP] 18. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (2)


2부에서는 페이징, DB구현부, 모델, 서비스 영역에 대해서 소개하겠다.


1. [JSP] 18. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (1)

https://yyman.tistory.com/1428




14. 페이징네이션 


실제경로: /target/generated-sources/annotations/com/smile/web/model

패키지명: com.smile.web.logic

파일명: Paging.java


아마 이거 하나면, 현존에 있는 페이지 기법은 충분히 소화되지 않겠냐는 생각이다.

물론 정답은 아니다. 오류가 있을 수도 있다.


이러한 복잡한 문제에 직면했을 때, 수학을 공부 많이 해야 한다고 생각한다.

머리가 좋으신 분들이 많으시니깐 더 좋은 방법이 있다면, 개선해봐도 좋을 듯 싶다.


package com.smile.web.logic;


public class Paging {

    private long pageSize; // 게시 글 수

    private long firstPageNo; // 첫 번째 페이지 번호

    private long prevPageNo; // 이전 페이지 번호

    private long startPageNo; // 시작 페이지 (페이징 네비 기준)

    private long pageNo; // 페이지 번호

    private long endPageNo; // 끝 페이지 (페이징 네비 기준)

    private long nextPageNo; // 다음 페이지 번호

    private long finalPageNo; // 마지막 페이지 번호

    private long totalCount; // 게시 글 전체 수

    private long dbStartNum; // db 조회 (시작번호)

    private long dbEndNum; // db 조회 (종료번호)


    /**

     * @return the pageSize

     */

    public long getPageSize() {

        return pageSize;

    }


    /**

     * @param pageSize the pageSize to set

     */

    public void setPageSize(long pageSize) {

        this.pageSize = pageSize;

    }


    /**

     * @return the firstPageNo

     */

    public long getFirstPageNo() {

        return firstPageNo;

    }


    /**

     * @param firstPageNo the firstPageNo to set

     */

    public void setFirstPageNo(long firstPageNo) {

        this.firstPageNo = firstPageNo;

    }


    /**

     * @return the prevPageNo

     */

    public long getPrevPageNo() {

        return prevPageNo;

    }


    /**

     * @param prevPageNo the prevPageNo to set

     */

    public void setPrevPageNo(long prevPageNo) {

        this.prevPageNo = prevPageNo;

    }


    /**

     * @return the startPageNo

     */

    public long getStartPageNo() {

        return startPageNo;

    }


    /**

     * @param startPageNo the startPageNo to set

     */

    public void setStartPageNo(long startPageNo) {

        this.startPageNo = startPageNo;

    }


    /**

     * @return the pageNo

     */

    public long getPageNo() {

        return pageNo;

    }


    /**

     * @param pageNo the pageNo to set

     */

    public void setPageNo(long pageNo) {

        this.pageNo = pageNo;

    }


    /**

     * @return the endPageNo

     */

    public long getEndPageNo() {

        return endPageNo;

    }


    /**

     * @param endPageNo the endPageNo to set

     */

    public void setEndPageNo(long endPageNo) {

        this.endPageNo = endPageNo;

    }


    /**

     * @return the nextPageNo

     */

    public long getNextPageNo() {

        return nextPageNo;

    }


    /**

     * @param nextPageNo the nextPageNo to set

     */

    public void setNextPageNo(long nextPageNo) {

        this.nextPageNo = nextPageNo;

    }


    /**

     * @return the finalPageNo

     */

    public long getFinalPageNo() {

        return finalPageNo;

    }


    /**

     * @param finalPageNo the finalPageNo to set

     */

    public void setFinalPageNo(long finalPageNo) {

        this.finalPageNo = finalPageNo;

    }


    /**

     * @return the totalCount

     */

    public long getTotalCount() {

        return totalCount;

    }


    /**

     * @param totalCount the totalCount to set

     */

    public void setTotalCount(long totalCount) {

        this.totalCount = totalCount;

        this.makePaging();

        this.makeDbPaging();

    }


    /**

     * 페이징 생성 (setTotalCount에서 호출함)

     */

    private void makePaging() {

   

        if (this.totalCount == 0) 

        return; 

        

        // 게시 글 전체 수가 없는 경우

        

        if (this.pageNo == 0) 

        this.setPageNo(1); // 기본 값 설정

        

        if (this.pageSize == 0) 

        this.setPageSize(10); // 기본 값 설정


        long finalPage = (totalCount + (pageSize - 1)) / pageSize; // 마지막 페이지

        

        if (this.pageNo > finalPage) 

        this.setPageNo(finalPage); // 기본 값 설정


        if (this.pageNo < 0 || this.pageNo > finalPage) 

        this.pageNo = 1; // 현재 페이지 유효성 체크


        boolean isNowFirst = pageNo == 1 ? true : false; // 시작 페이지 (전체)

        boolean isNowFinal = pageNo == finalPage ? true : false; // 마지막 페이지 (전체)


        long startPage = ((pageNo - 1) / 10) * 10 + 1; // 시작 페이지 (페이징 네비 기준)

        long endPage = startPage + 10 - 1; // 끝 페이지 (페이징 네비 기준)


        if (endPage > finalPage) { // [마지막 페이지 (페이징 네비 기준) > 마지막 페이지] 보다 큰 경우

            endPage = finalPage;

        }


        this.setFirstPageNo(1); // 첫 번째 페이지 번호


        if (isNowFirst) {

            this.setPrevPageNo(1);    // 이전 페이지 번호

        } else {

            this.setPrevPageNo(((pageNo - 1) < 1 ? 1 : (pageNo - 1))); // 이전 페이지 번호

        }


        this.setStartPageNo(startPage); // 시작 페이지 (페이징 네비 기준)

        this.setEndPageNo(endPage); // 끝 페이지 (페이징 네비 기준)


        if (isNowFinal) {

            this.setNextPageNo(finalPage); // 다음 페이지 번호

        } else {

            this.setNextPageNo(((pageNo + 1) > finalPage ? finalPage : (pageNo + 1))); // 다음 페이지 번호

        }


        this.setFinalPageNo(finalPage); // 마지막 페이지 번호

        

    }

    

    private void makeDbPaging() {

   

    long current = this.getPageNo();

    long weight = this.getPageSize();

    long endNum = 0;

   

    this.setDbEndNum( current * weight );

   

    endNum = this.getDbEndNum();

    this.setDbStartNum( (endNum - weight) + 1 );

   

    }


    /**

     * @return the dbStartNum

     */

public long getDbStartNum() {

return dbStartNum;

}



    /**

     * @param dbStartNum the dbStartNum to set

     */

public void setDbStartNum(long dbStartNum) {

this.dbStartNum = dbStartNum;

}


    /**

     * @return dbEndNum

     */


public long getDbEndNum() {

return dbEndNum;

}



    /**

     * @param dbEndNum the dbEndNum to set

     */

public void setDbEndNum(long dbEndNum) {

this.dbEndNum = dbEndNum;

}

    

}


파일명: Paging.java


[첨부(Attachments)]

Paging.zip




15. Board.java - 모델(Model)


실제경로: /target/generated-sources/annotations/com/smile/web/model

패키지명: com.smile.web.model

파일명: Board.java


게시판 모델에 대한 설계이다.


package com.smile.web.model;


import java.sql.Timestamp;


public class Board {


private long id;

private String name;

private String subject;

private String memo;

private long count;

private Timestamp regidate;

public long getId() {

return id;

}

public void setId(long id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getSubject() {

return subject;

}

public void setSubject(String subject) {

this.subject = subject;

}

public String getMemo() {

return memo;

}

public void setMemo(String memo) {

this.memo = memo;

}


public long getCount() {

return count;

}


public void setCount(long count) {

this.count = count;

}


public Timestamp getRegidate() {

return regidate;

}


public void setRegidate(Timestamp regidate) {

this.regidate = regidate;

}

}



파일명: Board.java


[첨부(Attachments)]

Board.zip




16. BoardService.java - Service 영역


실제경로: /target/generated-sources/annotations/com/smile/web/service

패키지명: com.smile.web.service

파일명: BoardService.java


게시판 서비스에 필요한 영역을 구현한 것이다.


package com.smile.web.service;


import java.util.List;


import com.smile.web.model.Board;


public class BoardService {

private static BoardService service = null;

private static BoardServiceImpl dao = null;

private BoardService() {}

public static BoardService getInstance() {


        if(service == null){

        service = new BoardService();

    dao = BoardServiceImpl.getInstatnce();

        }


        return service;

}

public List<Board> getBoardList(long startNum, long endNum){

return dao.getBoardList(startNum, endNum);

}

public long getTotalCount() {

return dao.getTotalCount();

}

public List<Board> getBoardKeywordList(String keyword, long startNum, long endNum){

return dao.getBoardKeywordList(keyword, startNum, endNum);

}


public long getTotalKeywordCount(String keyword) {

return dao.getTotalKeywordCount(keyword);

}

}



파일명: BoardService.java


[첨부(Attachments)]

BoardService.zip





16. BoardServiceImpl.java - Service 영역(DAO)


실제경로: /target/generated-sources/annotations/com/smile/web/service

패키지명: com.smile.web.service

파일명: BoardServiceImpl.java


DAO 영역에 관한 것이다. (실제 DB를 처리하는 영역)


package com.smile.web.service;


import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;


import com.smile.web.db.DBFactory;

import com.smile.web.model.Board;


public class BoardServiceImpl {

private static BoardServiceImpl boardDAO;

private static DBFactory dbPool;

private BoardServiceImpl() {}

public static BoardServiceImpl getInstatnce() {

if ( boardDAO == null ) {

boardDAO = new BoardServiceImpl();

dbPool = new DBFactory();

}

return boardDAO;

}

public List<Board> getBoardList(long startNum, long endNum){

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    List<Board> boardList = new ArrayList<Board>();


    String sql = "select * from (select /*+INDEX_DESC(tbl_board pk_board) */ rownum rn, A.*" + 

    " from board A order by id desc) " + 

    "where rn >= ? and rn <= ?";


    //System.out.println(sql);

    //System.out.println(startNum + "/" + endNum);

   

    try {

    conn = dbPool.getConnection();

    pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1, startNum);

    pstmt.setLong(2, endNum);

   

    rs = pstmt.executeQuery();


    while ( rs.next() ) {

    // 데이터가 존재할 때, 노드 생성

    node = new Board();

   

    node.setId(rs.getLong(2));

    node.setSubject(rs.getNString(3));

    node.setMemo(rs.getNString(4));

    node.setName(rs.getNString(5));

   

    boardList.add(node);

   

    System.out.println("rs:" + rs.getLong(1));

   

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return boardList;

}

public List<Board> getBoardKeywordList(String keyword, long startNum, long endNum){

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    String allKeyword = "%" + keyword + "%";

   

    List<Board> boardList = new ArrayList<Board>();


    String sql = "select * " + 

    "from (select /*+INDEX_DESC(tbl_board pk_board) */ rownum rn, A.* " + 

    "from board A where subject like ? order by id desc) " + 

    "where rn >= ? and rn <= ?";

   

    //System.out.println(sql);

   

    try {

    conn = dbPool.getConnection();

    pstmt = conn.prepareStatement(sql);

    pstmt.setNString(1, allKeyword);

    pstmt.setLong(2, startNum);

    pstmt.setLong(3, endNum);

   

    rs = pstmt.executeQuery();


    while ( rs.next() ) {

    // 데이터가 존재할 때, 노드 생성

    node = new Board();

   

    node.setId(rs.getLong(2));

    node.setSubject(rs.getNString(3));

    node.setMemo(rs.getNString(4));

    node.setName(rs.getNString(5));

   

    boardList.add(node);

   

    //System.out.println("rs:" + rs.getLong(1));

   

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return boardList;

}

public long getTotalCount() {

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    long cnt = 0;


    String sql = "select count(*) from board";

   

    System.out.println("sql:" + sql);


    try {

    conn = dbPool.getConnection();

    //System.out.println( conn.getSchema() );

    pstmt = conn.prepareStatement(sql);

   

    rs = pstmt.executeQuery();

    //System.out.println( rs.getFetchSize() );


    if ( rs.next() ) {

    cnt = rs.getLong(1);

        System.out.println("전체갯수:" + cnt + "/" + rs.getLong(1));

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return cnt;

}

public long getTotalKeywordCount(String keyword) {

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    String allKeyword = "%" + keyword + "%";

   

    long cnt = 0;


    String sql = "select count(*) from board where subject like ?";

   

    //System.out.println("sql:" + sql + "/키:" + keyword);


    try {

    conn = dbPool.getConnection();

    /// System.out.println( conn.getSchema() );

    pstmt = conn.prepareStatement(sql);

    pstmt.setNString(1, allKeyword);

   

    rs = pstmt.executeQuery();

    /// System.out.println( rs.getFetchSize() );


    if ( rs.next() ) {

    cnt = rs.getLong(1);

        System.out.println("특정 갯수:" + cnt + "/" + rs.getLong(1));

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return cnt;

}


}


파일명: BoardServiceImpl.java


[첨부(Attachments)]

BoardServiceImpl.zip



17. 데이터베이스 - 응용 영역


이 부분이 의외로 어려울 수 있다고 본다. 머리를 조금 써야 한다.


[개선 후 SQL 파일 내용]


-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (BOARD)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE board
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    subject VARCHAR2(30),
    memo NCLOB,
    count NUMBER,
    regidate DATE
);

-- Sequence 정의
CREATE SEQUENCE board_sequence
START WITH 1
INCREMENT BY 1;

-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER board_trigger
BEFORE INSERT
    ON board
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT board_sequence.nextval INTO :NEW.ID FROM dual;
END;


/* 데이터 추가 */
INSERT INTO board (name, subject, memo, count, regidate) VALUES ('홍길동', '안녕하세요.', '메모메모', '0', '2020-09-29 11:11:00');

/* 데이터 등록 후 커밋할 것(대량 정보 처리 후) */
COMMIT;


-- 싱글 쿼리 (페이징)
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from board order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1

-- 특정 싱글 쿼리 SQL
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from board where subject like '%야해해%' order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1


파일명: board-tbl-oracle_개선후.sql (변경해도 되는 쿼리)  /  board-tbl-oracle_개선전.sql (이 글의 16번 소스코드에 적용된 페이징 쿼리)


(16번 파일의 페이징 쿼리를 수정해서 사용해도 됨.)

-> 개선 전 쿼리라고 해서 파일을 두 개로 두었음.


[첨부(Attachments)]

board-tbl-oracle.zip (업데이트: 2020-10-11)


페이징 로직 설계가 되었다면, 다음 계획해야 할 작업이 실질적으로는 이 작업이다.

의외로 작업을 하다보면, 골치가 아픈 문제들이 많이 생긴다.


[Oracle Databases 코너에 쿼리 관련 작업에 대해서 추가로 보충하여 소개하도록 하겠다.]

- 단일 테이블 게시물 쿼리 방법, 뷰 테이블 쿼리 방법, 테이블 조인(2개 이상) 사용 방법


-> 태스트 결과로는 "Java 페이징 로직"은 크게 문제가 없다. 
    다만, 나머지 부분은 쿼리라고 본다.



[쿼리 관련 - 보충 글]
1. [Oracle Databases] 번외글 - 게시판 페이징 관련 로직 쿼리, 2020-10-11.
- https://yyman.tistory.com/1466




18. 뷰 - css


css 하나 만들어준다. 보안 영역에 만들지 말고 외부 영역에 만들어준다.



그림 23. style.css 프로젝트 위치


생성할 폴더: 

/src/main/webapp/board

/src/main/webapp/board/css


생성할 파일

/src/main/web/app/board/css/style.css


사실 style.css을 먼저 만들고 나서 작업하는 건 절대적으로 아니다.

웹 페이지를 보고 하나 하나 왕복 화면 전환 등 복합적으로 하면서 수많은 태스트과정을 거쳐서 작성하게 된다.

css 작업을 하는데 있어서도 어떻게 하면 깔끔하면서 표준을 지킬지 고민을 많이 해봐야 한다고 본다.


@charset "utf-8";


/* 1. 제목 */

h3{

font-size:20px;

font-family:'Nanum Gothic';

text-align:center;

}


/* 2. 게시판 */

/* 게시판 목록 출력 */

.board_list{

border-top:1px solid #e2e2e2;

border-bottom:1px solid #e2e2e2;

font-size:12px;

font-family:'Nanum Gothic';

width:900px;

margin:auto;

text-align:center;

}


/* 제목 */

.board_list th{


border-right:1px solid #666;

border-bottom:1px solid #666;

font-size:15px;

font-family:'Nanum Gothic';

height:20px;

text-align:center;

background-color:#eeeeee;

}


/* 내용 */

.board_list td{


border-right:1px solid #e2e2e2;

font-size:15px;

font-family:'Nanum Gothic';

height:20px;

text-align:center;

}


/* 3. 페이징 네이션 */

.paginate{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

}


.paginate .first{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .prev{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .next{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .last{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .choice{


font-size:20px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


/* 4. 링크 */

a{

text-decoration:none;

color:#666;

}


/* 5. 검색 영역 */

.searchArea{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

}


파일명: style.css


[첨부(Attachments)]

style.zip




19. 뷰(view) - jsp파일 작성하기


이번에 소개할 것은 include를 실제 적용한 코드이다.



그림 24. board 폴더 내 파일들 (작업할 부분)


생성할 폴더: 

/src/main/webapp/WEB-INF/views/

/src/main/webapp/WEB-INF/views/board


생성할 파일

/src/main/webapp/WEB-INF/views/board/list.jsp

/src/main/webapp/WEB-INF/views/board/search.jsp

/src/main/webapp/WEB-INF/views/board/paging.jsp


list.jsp를 호출하면, search.jsp, paging.jsp도 함께 호출이 된다.

기능을 전문적으로 분리하여 설계한 것이다.




20. 뷰(view) - list.jsp


list.jsp에 관한 것이다.

코드를 보고 한눈에 알 수 없는 사람들을 위해서 실제 동작 결과화면을 소개하겠다.



그림 25. list.jsp 화면 영역 구성도



<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

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

<%@ page import = "java.util.*" %>

<%@ page import = "com.smile.web.model.*" %>

<%@ page session="false" %>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>게시물 목록</title>

<style>

@import url('https://fonts.googleapis.com/css?family=Nanum+Gothic:400,700,800');


</style>

<link href="css/style.css" rel="stylesheet" type="text/css" />

</head>

<body>

<%

List<Board> boardList = (List<Board>)request.getAttribute("list");

%>


<h3>게시물 목록</h3>


<!-- 목록 출력 -->

<table class="board_list">

<tr>

<th style="width:15%;">

번호(Num)

</th>

<th>

제목(Subject)

</th>

<th style="width:13%;">

글쓴이(Author)

</th>

<th style="width:13%; border-right:none;">

조회수(Count)

</th>

</tr>

<%

for(Board board:boardList){

%>

<tr>

<td style="width:15%;">

<%= board.getId() %>

</td>

<td>

<%= board.getSubject() %>

</td>

<td style="width:13%;">

<%= board.getName() %>

</td>

<td style="width:13%; border-right:none;">

<%= board.getCount() %>

</td>

</tr>

<%

}

%>

</table>


<!-- 페이징 -->

<jsp:include page="/WEB-INF/views/board/paging.jsp">

<jsp:param name="customURL" value="${pagingUrl}" />

    <jsp:param name="firstPageNo" value="${paging.firstPageNo}" />

    <jsp:param name="prevPageNo" value="${paging.prevPageNo}" />

    <jsp:param name="startPageNo" value="${paging.startPageNo}" />

    <jsp:param name="pageNo" value="${paging.pageNo}" />

    <jsp:param name="endPageNo" value="${paging.endPageNo}" />

    <jsp:param name="nextPageNo" value="${paging.nextPageNo}" />

    <jsp:param name="finalPageNo" value="${paging.finalPageNo}" />

</jsp:include>


<!-- 검색 -->

<jsp:include page="/WEB-INF/views/board/search.jsp" />


</body>

</html>


파일명: list.jsp


[첨부(Attachments)]

list.zip



21. 뷰(view) - paging.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

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



<div class="paginate">

    <a href="${param.customURL}page=${param.firstPageNo}" class="first">처음 페이지</a>

    <a href="${param.customURL}page=${param.prevPageNo}" class="prev">이전 페이지</a>

    <span>

        <c:forEach var="i" begin="${param.startPageNo}" end="${param.endPageNo}" step="1">

            <c:choose>

                <c:when test="${i eq param.pageNo}"><a href="${param.customURL}page=${i}" class="choice">${i}</a></c:when>

                <c:otherwise><a href="${param.customURL}page=${i}">${i}</a></c:otherwise>

            </c:choose>

        </c:forEach>

    </span>

    <a href="${param.customURL}page=${param.nextPageNo}" class="next">다음 페이지</a>

    <a href="${param.customURL}page=${param.finalPageNo}" class="last">마지막 페이지</a>

</div>



파일명: paging.jsp


[첨부(Attachments)]

paging.zip




22. 뷰(view) - search.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

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


<div class="searchArea">

<form id="searchForm" action="list.do" method='get'>

<select name="type">

<option value="T">제목</option>

<option value="C">내용</option>

<option value="W">작성자</option>

</select>

<input type="text" name="keyword">

<button class="">검색</button>

</form>

</div>


파일명: search.jsp


[첨부(Attachments)]

search.zip




23. 데이터베이스 작업시 성능 - 꼭 파악하면서 작업해보기


오늘 날 게시판 뿐만 아니라 DB의 비중이 매우 중요한 시대에 직면해 있다.

성능 측정 꼭 해보기 바란다.

동작도 물론 중요한데, 결과는 정확한지 등 많이 고민하고 수 십번, 수 백법 이상 찍어봐야 한다.



그림 26. 데이터베이스 작업 모습 - 오라클 SQL Developer




* 맺음글(Conclusion)


페이징네이션 기반의 게시판 프로젝트에 대해서 살펴보았다.




* 참고자료(References)


1. [Oracle] 오라클 데이터타입(DataType) 총정리, https://coding-factory.tistory.com/416, Accessed by 2020-09-29, Last Modified 2019-11-03.


2. [JSP]include 와 forward 의 페이지 이동, https://jerryjerryjerry.tistory.com/31, Accessed by 2020-09-29, Last Modified 2018-04-13.


3. 오라클 페이징 쿼리, 오라클 paging 방법 - 개발자 삽질 일기, https://programmer93.tistory.com/4, Accessed by 2020-09-29, Last Modified 2019.


4. [OracleDB] 페이징(Paging) 처리하는 법, https://m.blog.naver.com/wideeyed/221796538283, Accessed by 2020-09-29, Last Modified 2020-02-04.

반응형
728x90
300x250

[JSP] 17. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (1)


Command 패턴과 Front Controller 패턴을 적용한 페이징네이션과 검색에 대해서 소개하려고 한다.

불필요한 기능은 다 제거하고 순수한 페이징네이션과 검색 그리고 오라클 연동에 대해서 다뤄보았다.


IDE: Spring-Tool Suite 4-4.7.2 RELEASES

Databases: Oracle Databases 19g

 - Maven 3.6.3/1.16.0.20200610-1735

 - javax.servlet-api (4.0.1) - servlet(서블렛)

 - jstl (1.2) - jstl 태그 적용

 - taglibs (1.1.2) - c태그 적용

 - HikariCP (3.4.2) - 커넥션 풀

 - com.oracle.database.jdbc - ojdbc8 (19.7.0.0)



[쿼리 관련해서 조금 더 보충한 글이다.]
1. [Oracle Databases] 번외글 - 게시판 페이징 관련 로직 쿼리, 2020-10-11.
- https://yyman.tistory.com/1466




1. 프로젝트 구조


작성할 프로젝트를 미리 살펴보면 조금 작업해야 할 양이 의외로 상당하다는 것을 알 수 있다.



그림 1, 그림 2. 프로젝트 작성 구조도




2. 게시판 - 설계


데이터베이스 설계는 매우 간단하게 작성하였다.


그림 3. Board - 테이블 설계(1)




그림 4. Board 게시판 설계(2)





3. 사용자 인터페이스 - 결과


이번 주제에서 다뤄볼 프로젝트의 완성 모습이다.

참고로 페이징네이션 로직에 대해서 다 기억할 수 없다.


다만 어떤 흐름인지는 대략적으로 알고는 있으면 하는 바람이다.

워낙 페이징 로직에 대해서 전문적으로 잘 연구하시는 분들이 많이 계시니깐 그건 골고루 참고도 해보고 개선도 해보고 하면 될 것 같다.



그림 5. list.do - 첫 화면



그림 6. list.do - 7번 페이지 / 기초



그림 7. list.do - 10106번 페이지 / 기초


10106번 페이지를 통해서 알 수 있는 것은 약 10106 * 10 = 101,106개의 DB가 존재한다는 것을 알 수 있다.

그림 8부터는 응용부분에 가까워진다.



그림 8. list.do - 키워드 검색 및 페이징 처리(1) / 심화


의외로 응용력이 많이 요구되는 부분이다.



그림 9. list.do - 키워드 검색 및 페이징 처리(2) / 심화


키워드 검색을 했을 때 "친"은 약 13개가 있었다는 사실을 유추할 수 있다.



그림 10. list.do - 키워드 검색 및 페이징 처리(3)


참고로 list.do 반응에 대해서 여러 조건으로 튜닝이 필요하다.



4. 프로젝트 생성하기


프로젝트는 Maven Project로 생성하여 진행하였다.



그림 11. Maven Project 생성하기(1)


File -> New -> Maven Project를 클릭한다.



그림 12. Maven Project 생성하기(2)


org.apache.maven.archetypes | maven-archetype-web을 선택하고 Next를 누른다.



그림 13. Maven Project 생성하기(3)


프로젝트의 Group Id, Artifact Id를 입력한다.

Finish를 누른다.




5. POM.xml - 설정하기


다음은 pom.xml 파일에 대한 설정이다.


<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>


  <groupId>com.boardMaven</groupId>

  <artifactId>web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>war</packaging>


  <name>board Maven Webapp</name>

  <!-- FIXME change it to the project's website -->

  <url>http://www.example.com</url>


  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven.compiler.source>1.7</maven.compiler.source>

    <maven.compiler.target>1.7</maven.compiler.target>

  </properties>


  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>4.11</version>

      <scope>test</scope>

    </dependency>

    

<!-- Servlet -->

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>

<!-- JSTL -->

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<version>1.2</version>

</dependency>

<!-- taglibs -->

<dependency>

<groupId>taglibs</groupId>

<artifactId>standard</artifactId>

<version>1.1.2</version>

<scope>compile</scope>

</dependency>


<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->

<dependency>

    <groupId>com.zaxxer</groupId>

    <artifactId>HikariCP</artifactId>

    <version>3.4.2</version>

</dependency>

<dependency>

<groupId>com.oracle.database.jdbc</groupId>

<artifactId>ojdbc8</artifactId>

<version>19.7.0.0</version>

</dependency>

  </dependencies>


  <build>

    <finalName>board</finalName>

    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->

      <plugins>

        <plugin>

          <artifactId>maven-clean-plugin</artifactId>

          <version>3.1.0</version>

        </plugin>

        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->

        <plugin>

          <artifactId>maven-resources-plugin</artifactId>

          <version>3.0.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-compiler-plugin</artifactId>

          <version>3.8.0</version>

        </plugin>

        <plugin>

          <artifactId>maven-surefire-plugin</artifactId>

          <version>2.22.1</version>

        </plugin>

        <plugin>

          <artifactId>maven-war-plugin</artifactId>

          <version>3.2.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-install-plugin</artifactId>

          <version>2.5.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-deploy-plugin</artifactId>

          <version>2.8.2</version>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip



6. 프로젝트의 Build-Path, Properties 수정하기


프로젝트의 자바 버전에 대한 환경설정이다.



그림 14. 프로젝트의 마우스 오른쪽 버튼 모습


프로젝트를 클릭한다.

마우스 오른쪽 버튼을 누른다.

Properties를 클릭한다.



그림 15. Build-Path 환경설정


JRE System Library를 JavaSE-14버전으로 변경한다. (14버전으로 변경)

Apply를 누른다.



그림 16. Project Facets 환경 설정


Project Facets의 Java 버전을 14로 변경한다.

Apply를 누른다.

Apply and close를 누른다.



7. Resource 폴더와 db.properties 만들기


Resource 폴더와 db.properties를 생성할 것이다.



그림 17. /src/main 폴더 - 오른쪽 버튼 메뉴 모습


src/main 폴더를 마우스 오른쪽으로 클릭한다.

New-> Folder를 클릭한다.


폴더명: Resource



그림 17. /src/main/resource 폴더 - 오른쪽 버튼 메뉴 모습


src/main/resource 폴더를 마우스 오른쪽으로 클릭한다.

New-> File를 클릭한다.


파일명: db.properties



그림 18. /src/main/resource 폴더와 db.properties 모습


성공적으로 만들어진 것을 확인할 수 있다.



그림 19. db.propeties 모습


jdbcUrl=jdbc:oracle:thin:@localhost:1521:orcl

dataSourceClassName=oracle.jdbc.driver.OracleDriver

dataSource.user=userName

dataSource.password=password

cachePrepStmts=true

prepStmtCacheSize=250

prepStmtCacheSqlLimit=2048


파일명: db.propeties


[첨부(Attachments)]

db.zip




9. Servlet 생성하기


servlet 생성하고 나서 web.xml 수정을 진행하면 타이핑을 적게 해도 되는 장점이 생긴다.



그림 20. 프로젝트 오른쪽 버튼 - 메뉴 모습


프로젝트를 선택한다.

마우스 오른쪽 버튼을 클릭한다.

New->Servlet을 클릭한다.



그림 21. Servlet 생성하기


Java package명을 입력한다. ("예: com.smile.web.controller // 이런 형식으로 작성하면 됨.)

Class name을 입력한다. ("예: FrontController" )

Finish를 누른다.



10. web.xml - 수정하기


charset 초기값을 추가하였다.

Servlet 2.5 문서 양식에서 3.1 양식으로 변경하였다.


<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 

id="WebApp_ID" version="3.0">

 

  <display-name>Archetype Created Web Application</display-name>

  

  <servlet>

  <servlet-name>FrontController</servlet-name>

  <servlet-class>com.smile.web.controller.FrontController</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet-mapping>

  <servlet-name>FrontController</servlet-name>

  <url-pattern>*.do</url-pattern>

  </servlet-mapping>

  

</web-app>


파일명: web.xml


[첨부(Attachments)]

web.zip




10. 폴더 2개(views, settings), 파일 하나 만들기(root-servlet.xml)


추가적으로 만들어줄 폴더와 파일이 있다.


폴더1: /src/main/webapp/WEB-INF/settings

폴더2: /src/main/webapp/WEB-INF/views

파일: /src/main/webapp/WEB-INF/settings/root-servlet.xml


Settings 폴더는 큰 의미는 없지만, 활용할 여지를 고려하여 생성하였다.
이 프로젝트에서는 root-servlet.xml은 직접 사용되진 않는다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
</beans>


파일명: root-servlet.xml


[첨부(Attachments)]

root-servlet.zip



그림 22. 작업된 프로젝트의 모습




11. FrontController.java - 서블렛(Servlet)


실제경로: /target/generated-sources/annotations/com/smile/web/controller

패키지명: com.smile.web.controller

파일명: FrontController.java


FrontController를 list만 정의하였다.

의외로 코드를 살펴보면, 간단하다는 것을 알 수 있다.


package com.smile.web.controller;


import java.io.IOException;

import java.sql.SQLException;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import com.smile.web.db.DBFactory;


public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

private String charset = null;

/**

* @see HttpServlet#doGet(HttpServletRequest req, HttpServletResponse res)

*/

protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}


/**

* @see HttpServlet#doPost(HttpServletRequest req, HttpServletResponse res)

*/

protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}

// FrontController 패턴 & Command 패턴

protected void doAction(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

charset = sc.getInitParameter("charset");


req.setAttribute("charset", charset);

req.setCharacterEncoding(charset);

res.setContentType("text/html; charset=" + charset);


String uri = req.getRequestURI();

String conPath = req.getContextPath();

String command = uri.substring(conPath.length());


Controller subController = null;


if(command.equals("/board/list.do")){

System.out.println("-----------------------------");

System.out.println("게시판 목록");

System.out.println("-----------------------------");

req.setAttribute("controllerName", "list");

subController = new com.smile.web.controller.board.ListController();

subController.execute(req, res);

}

}


}



파일명: FrontController.java


[첨부(Attachments)]

FrontController.zip




12. Controller.java - 인터페이스


실제경로: /target/generated-sources/annotations/com/smile/web/controller

패키지명: com.smile.web.controller

파일명: Controller.java


인터페이스에 관한 것이다. (forward 정의 관련한 것)


package com.smile.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public interface Controller {

public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;


}


파일명: Controller.java


[첨부(Attachments)]

Controller.zip



12. /Board/ListController.java - 클래스


실제경로: /target/generated-sources/annotations/com/smile/web/controller/board

패키지명: com.smile.web.controller.board

파일명: ListController.java


게시판 목록에 관한 컨트롤러 명세이다.


package com.smile.web.controller.board;


import java.io.IOException;

import java.net.URLEncoder;

import java.util.List;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import com.smile.web.controller.Controller;

import com.smile.web.logic.Paging;

import com.smile.web.model.Board;

import com.smile.web.service.BoardService;

import com.smile.web.util.HttpUtil;


public class ListController implements Controller {


@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

String controllerName = (String) req.getAttribute("controllerName");

BoardService service = BoardService.getInstance();

List<Board> boardList = null;

// long totalCount = boardList.size();

long currentPage = 1; // 기본값

long pageSize = 10;

long totalCount = service.getTotalCount();

long startNum, endNum;

String keyword = null;

String pagingUrl = controllerName + ".do?";

// 페이지 번호 존재할 때

if (req.getParameter("page") != null){

currentPage = Integer.valueOf( req.getParameter("page") );

}

// 키워드가 존재할 때

if (req.getParameter("keyword") != null) {

keyword = req.getParameter("keyword");

totalCount = service.getTotalKeywordCount(keyword);

// 키워드 값이 하나라도 존재할 때

if ( keyword.length() > 0) {

pagingUrl = pagingUrl + "keyword=" + URLEncoder.encode(keyword, "UTF-8") + "&";

}

}

        Paging paging = new Paging();

        /*

        paging.setPageNo(1);

        paging.setPageSize(10);

        paging.setTotalCount(totalCount);

        */

        

        paging.setPageNo(currentPage);

        paging.setPageSize(pageSize);

        paging.setTotalCount(totalCount);


        System.out.println("현재페이지번호:" + currentPage);

        System.out.println("페이지크기:" + pageSize);

        System.out.println("키워드:" + keyword);

        System.out.println("페이징URL:" + pagingUrl);

        

        startNum = paging.getDbStartNum();

        endNum = paging.getDbEndNum();


        

// 키워드가 존재할 때

if (keyword != null) {

// 키워드 값이 하나라도 존재할 때

if ( keyword.length() > 0) {

boardList = service.getBoardKeywordList(keyword, startNum, endNum);

}

else {

boardList = service.getBoardList(startNum, endNum);

}

}

else {

        boardList = service.getBoardList(startNum, endNum);

}

        

        req.setAttribute("paging", paging);

        req.setAttribute("list", boardList);

        req.setAttribute("pagingUrl", pagingUrl);

HttpUtil.forward(req, res, "/WEB-INF/views/board/" + controllerName + ".jsp");

}


}



파일명: ListController.java


[첨부(Attachments)]

ListController.zip



13. HttpUtil.java - 클래스


실제경로: /target/generated-sources/annotations/com/smile/web/util

패키지명: com.smile.web.controller.util

파일명: HttpUtil.java


RequestDispatcher와 forward에 대한 정의이다.

추가로 업로드 기능에 대한 명세도 있다.


package com.smile.web.util;


import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.PrintWriter;


import javax.servlet.RequestDispatcher;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



public class HttpUtil extends HttpServlet {

private static final long serialVersionUID = 1L;

private static String charset = null;


public static void forward(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {

try {

RequestDispatcher dispatcher = req.getRequestDispatcher(path);

dispatcher.forward(req, res);

}catch(Exception e) {

e.printStackTrace();

}

}

public static void fileUpload(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {


charset = (String) req.getAttribute("charset");

System.out.println(charset);

PrintWriter out = res.getWriter();

// 파일 업로드된 경로

String root = req.getSession().getServletContext().getRealPath("/");

String savePath = root + "upload" + File.separator + "upload";

// 서버에 실제 저장된 파일명

String filename = "1600955663095" ;

System.out.println("파일 실제 폴더경로:" + savePath);

// 실제 내보낼 파일명

String orgfilename = "license한글.txt" ;

req.setCharacterEncoding(charset);

res.setCharacterEncoding(charset);

InputStream in = null;

OutputStream os = null;

File file = null;

boolean skip = false;

String client = "";

try{

    // 파일을 읽어 스트림에 담기

    try{

        file = new File(savePath, filename);

        in = new FileInputStream(file);

    }catch(FileNotFoundException fe){

        skip = true;

    }

    client = req.getHeader("User-Agent");

    // 파일 다운로드 헤더 지정

    res.reset() ;

    res.setContentType("application/octet-stream");

    res.setHeader("Content-Description", "JSP Generated Data");

    if(!skip){

        // IE

        if(client.indexOf("MSIE") != -1){

            res.setHeader ("Content-Disposition", "attachment; filename="+new String(orgfilename.getBytes("KSC5601"),"ISO8859_1"));

        }else{

            // 한글 파일명 처리

            orgfilename = new String(orgfilename.getBytes("KSC5601"),"iso-8859-1");

            res.setHeader("Content-Disposition", "attachment; filename=\"" + orgfilename + "\"");

            res.setHeader("Content-Type", "application/octet-stream; charset=utf-8");

        }  

        res.setHeader ("Content-Length", ""+file.length() );

        os = res.getOutputStream();

        

        byte b[] = new byte[(int)file.length()];

        int leng = 0;

        while( (leng = in.read(b)) > 0 ){

            os.write(b,0,leng);

        }

    }else{

    // 한글 깨짐 - 해결

    res.setContentType("text/html;charset=" + charset);

        out.println("<html><head>");

        out.println("<script language='javascript'>alert('파일을 찾을 수 없습니다.');history.back();</script>");

        out.println("</head><body></body></html>");

    }

    in.close();

    os.close();

}catch(Exception e){

e.printStackTrace();

}

    

}


}



파일명: HttpUtil.java


[첨부(Attachments)]

HttpUtil.zip



13. DBFactory.java - 커넥션 풀 적용


실제경로: /target/generated-sources/annotations/com/smile/web/db

패키지명: com.smile.web.controller.db

파일명: DBFactory.java


HikariCP 3.4.2를 적용한 코드이다.


package com.smile.web.db;



import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import java.util.Properties;

import java.io.IOException;


import java.io.InputStream;


import java.io.Reader;

import java.util.Properties;

import java.sql.Connection;

import java.sql.SQLException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.zaxxer.hikari.HikariConfig;

import com.zaxxer.hikari.HikariDataSource;


import oracle.jdbc.pool.OracleDataSource;


public class DBFactory {


private static final Logger logger = LoggerFactory.getLogger(DBFactory.class);


    private static String CLASSNAME;

    private static String JDBC_URL;

    private static String USERNAME;

    private static String PASSWORD;

    private static String CACHE_PREP_STMTS;

    private static HikariDataSource ds;


    private HikariConfig config;


    

    public DBFactory() {


    InputStream inputStream;

    config = new HikariConfig();


        String resource = "db.properties";

        Properties properties = new Properties();


        try {


        inputStream = getClass().getClassLoader().getResourceAsStream(resource);

            properties.load(inputStream);


            System.out.println("jdbcurl:" + properties.getProperty("jdbcUrl"));

            System.out.println("className" + properties.getProperty("dataSourceClassName"));


            CLASSNAME = properties.getProperty("dataSourceClassName");

            JDBC_URL = properties.getProperty("jdbcUrl");

            USERNAME = properties.getProperty("dataSource.user");

            PASSWORD = properties.getProperty("dataSource.password");

            CACHE_PREP_STMTS = properties.getProperty("cachePrepStmts");


            config.setDriverClassName(CLASSNAME);

            config.setJdbcUrl( JDBC_URL );

            config.setUsername( USERNAME );

            config.setPassword( PASSWORD );

            

            config.addDataSourceProperty( "cachePrepStmts" , CACHE_PREP_STMTS );

            config.addDataSourceProperty( "prepStmtCacheSize" , "250" );

            config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );

            

            ds = new HikariDataSource( config );

            

        } catch (IOException e) {

            e.printStackTrace();

        }


    }


    public Connection getConnection() throws SQLException {

   

    /*

        try(Connection con = ds.getConnection()){

            System.out.println("연결상태확인:" + con);

            

            String sql = "SELECT * from board where id > 4 and id < 10";

            

            PreparedStatement pstmt = con.prepareStatement(sql);

            ResultSet rs = pstmt.executeQuery();

            

            while (rs.next()) {

                System.out.println("진짜 연결되었는가:" + rs.getString(2));

            }

        }

        catch(Exception e) {

        System.out.println("연결실패확인:" + e.getMessage());

        }

    */

   

        return ds.getConnection();

    }

    

    public void close(Connection conn, PreparedStatement ps, ResultSet rs) {


if ( rs != null ) {


try {

rs.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

close(conn, ps); // Recursive 구조 응용(재귀 함수)

} // end of if


}


public void close(Connection conn, PreparedStatement ps) {


if (ps != null ) {


try {

ps.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


if (conn != null ) {

try {

conn.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


}

    

}


파일명: DBFactory.java


[첨부(Attachments)]

DBFactory.zip



* 2부에서는 핵심적인 로직 등에 대해서 소개하겠다.


2부에서 만나요.


1. [JSP] 17. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (2), 2020-09-30

https://yyman.tistory.com/1429


반응형

+ Recent posts