신규 프로젝트나 운영 업무를 인계받다 보면, 아무런 가이드 문서 없이 돌아가고 있는 서버 하나만 덩그러니 놓여 있는 상황을 맞닥뜨리곤 한다. 이번 분석의 시작이 그랬다....

시스템 구성도나 서비스 명세서 하나 없이, 리눅스 서버 터미널과 한 판 씨름하며 소스 코드를 하나씩 까보고 프로세스를 역추적하여 Flask + Gunicorn + Nginx로 이어지는 서비스의 실체를 파헤친 과정을 기록으로 남긴다.

 


 

1. 프로세스 역추적: 서비스 찾기

가장 먼저 수행한 작업은 현재 서버에서 무엇이, 어떻게 돌고 있는지 확인하는 것이었다. 일단 리눅스 서버에 접속한 뒤 표준 명령어를 통해 서비스의 정체를 파악했다.

  • 실행 유닛 확인: systemctl list-units --type=service를 통해 관리되고 있는 서비스 목록을 훑었고, api-service라는 이름의 유닛을 발견했다. (물론 모니터링 서비스들도 있었다.)
  • 데몬 설정 분석: systemctl cat api-service 명령어로 서비스의 내부를 들여다보았다. 여기서 WorkingDirectory를 통해 소스 코드의 위치를, ExecStart를 통해 Gunicorn이 엔진으로 사용되고 있음을 확인했다.

2. Gunicorn과 Flask의 역할 모델

분석 결과, 이 서비스는 파이썬 웹 표준인 WSGI(Web Server Gateway Interface) 구조를 따르고 있었다.

자바 개발자의 시각에서 본 구조는 다음과 같다.

구성 요소 실제 역할 Java 진영 매칭
Flask 웹 프레임워크 Spring Boot
Gunicorn WSGI HTTP Server Embedded Tomcat
systemd 서비스 관리자 Windows Service / Daemon
  • Gunicorn (WAS): 단순한 실행 도구가 아니라, 직접 서비스 포트를 바인딩하고 요청을 처리하는 WAS 엔진, 별도의 관리 포트 없이 설정된 서비스 포트 하나를 점유하여 리스닝(Listening) 함
  • Flask (Logic): Gunicorn에 의해 로드되어 실행되는 비즈니스 로직

**WSGI(Web Server Gateway Interface, 위스기)

- 파이썬 애플리케이션(Flask, Django 등)과 웹 서버(Nginx, Apache 등) 사이에서 통신하기 위한 표준 프로토콜
- 자바 개발자에게 익숙한 개념으로 치면 서블릿 스펙(Servlet Spec)과 거의 동일한 역할
- WSGI 서버의 종류 (엔진)

  • Gunicorn: 파이썬에서 가장 많이 쓰는 WSGI 서버 (자바의 톰캣 역할)
  • uWSGI: 성능은 좋지만 설정이 복잡한 또 다른 WSGI 서버
  • Werkzeug: Flask 내장 서버 (개발용으로만 쓰고 운영에선 안 씀)

3. 포트와 프록시의 연결 고리

서버에는 외부 접속을 위한 Nginx가 앞단에 배치되어 있었다. 구성도 없이 netstat과 lsof만으로 파악한 통신 흐름은 다음과 같다.

  1. Nginx (Port 443): 외부 접속을 받는 유일한 통로(L7 Proxy)
  2. Internal Pass: Nginx 설정 파일(proxy_pass)을 확인한 결과, 내부에서 가동 중인 Gunicorn의 서비스 포트로 트래픽을 넘겨주고 있었다.
  3. Endpoint: 구니콘은 이 포트를 점유하여 대기하다가 Flask 앱의 엔드포인트로 요청을 전달한다.

4. 실전 분석 명령어

문서가 없는 환경에서 유효했던 핵심 명령어 셋

  • 포트 점유 프로세스 식별: sudo netstat -tnlp (포트별 PID 및 프로그램명 확인)
  • 포트 기반 서비스 정체 파악: sudo lsof -i :[Port] (해당 포트를 사용하는 실행 파일 경로 역추적)
  • 런타임 로그 분석: sudo journalctl -u [Service] -f (데몬이 뱉는 표준 출력 로그 실시간 모니터링)

5. 결론: "구조를 알면 소스가 보인다"

시스템 구성도가 없더라도 리눅스 서비스 데몬(systemd) -> WAS 엔진(Gunicorn) -> 프레임워크(Flask)로 이어지는 계층 구조를 이해하면 역추적은 생각보다 명확해진다. 자바 서비스 구조를 투영하여 분석한 결과, 엔진의 설정값(ExecStart)과 실제 소스 코드의 엔트리 포인트를 정확히 매칭시킬 수 있었고, 이를 통해 서비스 전체 흐름을 파악할 수 있었다.

 

 


 

TODO: 이제 파이썬 소스코드를 분석해야 한다. 하기 프로세스대로 할 예정. 힘내자!

 


1. 진입점(Entry Point) 식별 및 객체 초기화 분석
- 방법: ExecStart에 명시된 api_server:app 구조를 추적한다.
- 분석 포인트: api_server.py 내에서 Flask(__name__) 객체가 생성되는 시점을 찾고, 해당 파일에서 import 하는 설정 파일(Config)이나 초기화 로직(DB 연결, 로깅 설정 등)을 먼저 파악한다. 이는 자바의 ApplicationContext 로딩 과정을 분석하는 것과 같다.

2. API 엔드포인트 및 라우팅 맵핑(Controller) 추출
- 방법: grep -r "@app.route" . 또는 @blueprint.route를 검색한다.
- 분석 포인트: 스프링의 @RequestMapping 대신 사용되는 라우팅 데코레이터를 전수 조사한다.
- 소스가 git 관리가 안 되어 있고 서버에만 존재하여 험난할 듯 싶다.


3. 의존성 및 런타임 환경(Library) 분석
- 방법: requirements.txt 또는 Pipfile, pyproject.toml 존재 여부 확인.
- 분석 포인트: 자바의 pom.xml처럼 프로젝트에 사용된 외부 라이브러리 전체 리스트를 파악한다. 특히 메일 발송(smtplib, Flask-Mail), DB ORM(SQLAlchemy), 비동기 작업(Celery) 등 핵심 모듈의 버전을 확인하여 인프라와의 호환성을 검토한다.

 

 1. 태그 사용 시 주의사항 

1) HTML Tag는 중첩 사용 가능

 - 태그 안에 태그를 정의하는 방법(닫힘 순서에 주의). 안쪽 태그부터 닫자!

 

 2)  부모 자식관계의 태그는 다른 태그를 넣지 않음

 - <ol>이나 <ul>은 <li>만 갖는다. (<li>는 <ol>이나 <ul> 안에만 온다.)  

 - <dl>은 <dt>, <dd>만 갖는다.

 - <table>은 <thead>, <tbody>, <tfoot>, <tr>만 갖는다.

 - <tr>은 <th>나 <td>만 갖는다.

>> 다른 속성을 넣고 싶다면 자식 태그 안에서 넣으면 된다. <ol><li><string>..

 

3) 부모태그에 속성을 설정하면 자식 태그에 속성이 상속됨

  자식태그에서 동일 속성을 사용하면 부모에서 전달된 속성은 소멸됨

 

4) 주석 

<!-- 내용 -->

 

 

 

 2. tag 

1) 줄변경

- Web Browser는 엔터로 줄변경을 하지 않음

  • <br> : 한 줄 바꿈 / 줄을 변경하고 싶은 곳에서 <br>을 사용! <br/> 보통 이렇게 사용함
           : 문단 내에서 한 줄을 바꿀 때 사용
  • <p> : 문단을 만들 때 사용  
           <p> 문단 내용 </p>

2) 글 목록

- <h1>~<h6>까지 제공됨

- <h1> 큰 글자 / <h6> 작은 글자

- 자동 줄변경의 기능이 있음

 

3) markup

<b> : 진하게

<strong> : 진하게 / reader기에서 강하게 읽어들일 수 있음

<i> : 이탤릭

<u> : 밑줄 / HTML5에서 지원하지 않음 (웹 브라우저에서는 그려서 보여주기는 함)

<strike> : 취소선 / HTML5에서 지원하지 않음

<mark> : 형광펜 효과 (HTML5에서 새로 지원된 태그)

<big> : 글자 크게 (HTML5에서 지원하지 않음)

<small> : 글자 작게

<sup> : 윗첨자

<sub> : 아랫첨자

 

 

 

 3. 속성이 있는 tag 

- 태그에 사용자가 값을 넣어 다양하게 사용해야 할 때

- 형식)  <태그명 속성 = 값 속성 = 값,,,>

- 값은 3가지 형태로 넣음

  ① "로 묶어서 : 속성 = "값" // 값에 공백 포함 가능. 이걸 제일 많이 씀

  ② '로 묶어서 : 속성 = '값'  // 값에 공백 포함 가능

  ③ 그냥 값으로 : 속성 = 값 // 값에 공백 포함 불가

- 태그 속성을 정의하는 순서는 없음

- 속성은 여러 태그에서 같이 사용하는 공통속성과 특정 태그에서만 사용하는 속성이 제공됨

 

1) 글자 태그

- 글자 크기, 글자 색, 글자모양을 변경하는 태그

- font는 글자만 갖는다. 테이블 등을 font태그가 가질 수 없음

- HTML5에서 지원하지 않음

<font size="크기" color="색깔" face="글꼴">글자</font>

  > 크기 : 1~10 

  > 색 : 영어 (black, red)

           RGB (#000000~#FFFFFF) // 앞에서부터 00:R, 00:G, 00:B

  > 글꼴 : 컴퓨터에 설치된 글꼴 모두 사용가능.

     (단, 클라이언트에 존재하지 않는 글꼴을 사용하면 클라이언트의 브라우저 기본 글꼴로 보여줌. 해결하려면 web font 사용)

 

2) 선긋기 태그

- Web Browser의 넓이 100%가 기본 설정 (자동 줄변경)

- browser마다 다르게 보여짐 (사용자에게 보여지지 않고(? 보여짐..), 소스를 구분하는 용도로 사용함)

 <hr color = "색깔" size="높이"  width ="넓이"/>

   > 넓이 설정방법

① 비율 (숫자%)

 - 브라우저의 몇 %로 할 건지 지정

 - 가변 / 브라우저의 크기에 따라 변경됨

② 숫자

 - pixcel로 지정

 - 고정 / 브라우저의 크기와 상관없이 항상 고정

             

** 속성 중 하나인 size와 color는  font와 선긋기 등의 태그에 공통으로 사용되는 속성임 => 글로벌 속성이라 함

 

3) 이미지 태그

- 이미지를 넣어서 제공할 때 사용

- 이미지는 모든 확장자가 다 가능!

<img src="이미지 경로" width="넓이" height="높이" title="풍선도움말" alt="엑박떴을 때 보여줄 텍스트"
  border="테두리선두께" name=”이름명”/>

  > 이미지 크기변경(Resize)는 되도록 하지 말자. CPU 엄청 소모됨.

  > width나 height 하나만 바꿔도 나머지 하나가 알아서 맞춰서 바뀜

  > 이미지 경로 (!절대경로는 절대 사용하지 말자!) :

  • URL
    - 다른 서버의 이미지도 연결해서 보여줄 수 있음
    - 장점) HTML파일의 위치가 변경되더라도 이미지의 경로를 수정할 필요 없음
    - 단점) 경로가 길다.
    - 사용법) src =http://~/이미지명.확장자" - 제일 많이 씀!!
  • 상대경로
    - 서버에 존재하는 이미지만 연결해서 보여줄 수 있음
    - 장점) 경로를 짧게 표현할 수 있음
    - 단점) HTML 파일의 위치가 변경되면, 이미지의 경로를 수정해야 함
    -  사용법) src ="../이미지명.확장자"
    -  HTML 파일이 존재하는 위치로부터 경로를 연산함

4) 다른 HTML과 연결 (Hyper Text) 

- URL과 상대경로를 사용하여 다른 HTML 페이지와 연결 (Hyper Link)

- 사용법) <a href = "연결할 HTML의 주소" target = "연결된 HTML이 보여질 frame"  name ="이름"  id ="아이디"> 링크명 </a>

    > 연결할 HTML의 주소는 URL or 상대경로로 표현함

    > target을 " 아무거나 "로 주면 새창으로 뜨는데 보통 blank나 new로 줌

    > name 과 id는 글로벌 속성임

    > name속성은 한 페이지에서 특정 위치로 이동할 때 사용함

       : href="#name 속성명" => 링크 클릭 시 name 부분으로 이동함

- 연결된 페이지는 web browser 안의 내용을 모두 삭제하고 다시 그려서 보여줌 (화면 깜빡임이 발생함)

 

5) frame

- 하나의 웹 브라우저에서 여러 개의 HTML을 동시에 보여줄 때 

- frame, iframe (inner frame의 약자) 을 제공

  • frame : 페이지의 전체를 나눌 때 사용 / 페이지의 디자인을 만들 때 사용 / 요새 잘 안씀 / Ex. Java API
  • iframe : 페이지내의 일부분을 나눌 때 사용 / 페이지의 일부분을 변경하여 보여줄 때 사용 / 이걸 더 많이 씀

* iframe 

- 사용법) <iframe name = "이름" src ="최초 제공 페이지의 URL" frameborder="선의 두께" width ="넓이" height="높이" scrolling="스크롤바의 제공여부">  </iframe>

  > name은 태그에서 찾아갈 이름

  > scrolling은 yes/no/auto 중 하나로 설정 가능

- HTML안에 다른 HTML을 넣어서 보여주기 가능

- HTML안의 사진을 눌렀을 때 다른 HTML 보여주기 가능 >> 외부 HTML

 

6) 목록 태그 (list tag)

- ol, ul, dl 3가지의 목록을 제공

  • <ol> 
    - 순서 목록
    -  순서가 있는 데이터를 보여줄 때 
    - 숫자 / 알파벳 / 로마자의 순서를 제공하는 목록 태그
    - <li> : 목록을 보여줄 때 사용. 자동 줄바꿈이 됨
    - 사용법) <ol type = "시작할 값">               
                    <li> 목록 </li> ......        
              </ol>
       > ol에 시작할 값을 주지 않으면 숫자로 시작함    
       > li태그의 value 속성으로 번호를 건너뛸 수 있음. 중간 list 번호를 건너뛰고 싶으면 <li value = "설정할 값">
  • <ul> 
    - 도형 목록
    - 순서없는 목록
    - 사용법) <ul type="도형의 종류">          
                      <li> 데이터 </li> .......        
                </ul>
     
     > 도형 종류 : square, circle, disc // disc가 기본
  • <dl>
    - 설명 목록 - 어떤 대상에 대한 설명을 보여줄 때 
    - <li>를 쓰지 않음

    - 사용법) <dl>              
                      <dt> 제목 </dt>              
                      <dd> 설명 </dd> .....
    // dd는 들여쓰기가 된다.        
                 </dl>

 

7) 테이블 태그

- 표 만들기

 <table>, <tr>,<th>,<td>, <thead>, <tbody>, <tfoot>으로 구성되는 태그

    (_얘네는 필수, 나머진 선택)

      • <tabel>
        - 표를 시작할 때 
        - 높이는 행마다 / 넓이는 열마다 같음
        - 사용법) <table border = "선의 두께" bordercolor="선색" width ="테이블넓이" height ="테이블높이"
                         align ="수평정렬" bgcolor="바닥색" background="바닥에 들어갈 이미지" 
                         cellspacing="td(셀) 간 간격" cellpadding="td 내부 간격(여백)">
        *테이블 자체에 넓이와 높이를 설정하는 것보다 안쪽 td나 th에서 넓이와 높이를 설정하고 테이블에 적용하는 걸 권장함*
      • <tr>
        - 행을 만들 때
        - 안에 존재해야 함
        - 사용법) <tr bgcolor="바닥색" background="바닥에 들어갈 이미지" align="수평정렬" valign ="수직정렬">
        > 수평정렬 : align = "left,center,right"
        > 수직정렬 : valign = "top,middle,bottom"
      • <th>
        - 테이블 header

        - 컬럼의 제목을 만들 때
        - 입력되는 글자는 가운데 정렬, 진하게의 설정이 자동 설정됨
        - 사용법) <th bgcolor="바닥색" background="바닥에 들어갈 이미지" align="수평정렬" valign ="수직정렬"
                         width ="넓이" height ="높이" colspan ="합칠 칸의 수" rowspan="합칠 행의 수">
      • <td>
        - 컬럼의 데이터

        - 입력되는 글자가 왼쪽정렬에 일반 글자로 설정됨
        - <tr>안에 존재해야 함
        - <th>와 속성이 같음
        - 사용법) <td bgcolor="바닥색" background="바닥에 들어갈 이미지" align="수평정렬" valign ="수직정렬"
                         width ="넓이" height ="높이" colspan ="합칠 칸의 수" rowspan="합칠 행의 수">

      • <thead>, <tbody><tfoot>
        -  각각 사용자의 눈에 보이지 않고 "테이블의 제목/내용/결과 부분"임을 알려줄 때

 

'Development > HTML CSS' 카테고리의 다른 글

[HTML] WEB/ HTML  (0) 2021.10.23

 Web 

- World Wide Web / Web / W3

- 인터넷에서 문자, 그림, 미디어(소리, 영상)를 포함하는 문서(HTML 문서)를 HyperText 개념을 사용하여 검색하고 전송할 수 있는 서비스

- HTML을 요청하고 해석할 수 있는 프로그램과 (=Web browser) 요청한 HTML을 응답해줄 수 있는 프로그램 (=Web Server)으로 구성됨

 

* Web Server

- HTML을 저장하고 있다가 인터넷에서 HTML이 요청되면 HTML을 응답해주는 프로그램 (Apache HTTP Server)

 

* Web Container

- Web Server의 기능을 가지고 있음 (HTML 파일 응답 가능)

- JSP, Servlet을 저장하고 있다가 Web Client에서 요청이 오면 JSP나 Servlet을 HTML로 변환하여 응답해주는 프로그램

   (Apache Tomcat)

 

* Web Client

- Web browser를 사용하여 Web Server로 요청을 보내고, 응답받은 HTML을 Rendering(그려주는)하여 보여주는 프로그램

 

 

 

 HTML (Hyper Text Markup Language) 

- 1995년 Tim, berners Lee가 HTML 1.0 초안을 발표

- 마크업(Markup Language) 언어 : 본문에서 특정부분을 강조하여 보여줄 수 있는 언어

- Web에서 HTML을 주고받기 위한 통신 규약 : HTTP (Hyper Text Transmission Protocol)

- Tag 언어 : <태그명> 내용 </태그명>의 형식으로 제작되는 언어

- SGML에서 필요한 것만 정의하여 만든 언어

- HTML은 연산의 기능이 없으며, 컴파일 하지 않고 Web browser에서 Rendering 하는 언어

  -> 그냥 그리기만 하는 언어다!

 

* HTML 작성법

- DTD(Document Type Definition)가 정의된 마크업 언어

     > Tag가 미리 정의되어 있음

     > 사용할 HTML 태그를 정의한 문서

- HTML 문서 작성 시 DTD에 정의된 것만 사용할 수 있음 

 

* 작성시 주의

- 대소문자를 가리지 않음 (하지만 맞춰 써야 함!)

- 태그는 짝으로 작성함

- ‘<’와 태그명 사이에는 공백을 넣지 않음

   Ex) <ol   > : 인식 가능 / <    ol> : 인식 불가능

 

* 태그 구성

- <시작태그> 내용 </끝태그>  이 전체를 element 라고 함 (태그=element)

  <열림태그> 내용 </닫힘태그>

'Development > HTML CSS' 카테고리의 다른 글

[HTML] tag  (0) 2021.10.24

 

 Procedure 호출 

- Procedure : 쿼리문을 저장하고 필요한 곳에서 실행해야 할 때 사용하는 DBMS 객체

- DBMS에서 언어적인 요소(제어, 연산)를 가져야 할 때

- Oracle DBMS에서는 user_procedures 딕셔너리에서 생성된 Procedure를 확인할 수 있음

- CallableStatement 객체로 Procedure를 호출할 수 있음

- 자바에서 호출 시, SQL의 프로시저가 컴파일 되어있어야 함

 

* Oracle의 Procedure *

- PL/SQL : DBMS에서 언어적인 요소를 구현할 때 사용

- 기본문법(데이터형, 변수, 연산자, 제어문), cursor, function, procedure, trigger, package를 지원

- 컴파일을 한 후 실행함

    > 컴파일 : @파일명.sql

    > 함수 : 간접실행 (쿼리문에 함수를 포함시켜 실행하는 방법)

                   직접실행 (실행기를 사용하여 실행하는 방법 -> execute 또는 exec 사용)

- 작성법) 

    create or replace procedure 프로시저명 (매개변수,,,) 

    is  -- 변수 선언, record 선언(java의 VO같은 거), table 선언(java의 배열 같은 거), cursor 선언

    begin -- 코드 작성(연산, 제어, 쿼리작성 등등)

    end;

    /

- 선언 시 주의)

   > 매개변수 종류 : in parameter (프로시저 외부의 값을 프로시저 내부로 전달하는 일)  //  in은 생략하고 선언 가능

                                   our parameter (프로시저 안에서 처리한 값을 외부로 전달하는 일)

   > 매개변수명은 테이블의 컬럼명과 동일하면 절대 안됨! 식별될 수 있도록 선언 

        ex) sal로 쓰면 sal=sal 항상 참이 될 수 있으므로 in paramter면 in_sal로 선언

- 컴파일) @파일명.sql   -- DOS에서 sqlplus에서 로그인하고 하면 됨

- 실행)

   1) bind 변수 선언 (프로시저 내부의 처리된 값을 SQLPlus에서 저장할 변수)

        > 프로시저 내부의 처리된 값은 out parameter를 타고 나옴

        >  bind 변수는 일시적이라 실행될 때 마다 선언해주어야 함

             var | variable 데이터형 변수명 (크기) -- 크기는 넣어도 되고 안 넣어도 됨

    2) 프로시저를 실행 (직접실행)

        execute | exec 프로시저명 (,,, : 바인드변수명 ,,,) 

        > : in parameter로 들어가는 값 

        > : 바인드면수명 : out parameter로 나오는 값

    3) bind 변수에 저장된 값 출력 (procedure가 처리한 결과값)

        > print 바인드변수명 바인드변수명 바인드변수명 -- 띄어쓰기로 여러 개의 값 줄 수 있음

 

-프로시저 삭제) drop procedure 프로시저명;

 

 

 Java에서의 Procedure 호출 

* CallabeStatement

- procedure를 호출할 때 사용하는 객체

- PreparedStatement Interface의 하위 Interface

- PreparedStatement bind 변수를 사용하여 값을 입력할 수 있음

- SQLPlus의 bind 변수는 CallableStatement의 registerOutparameter로 처리

   > SQLPlus의 bind 변수 : var 변수명 데이터형(크기), procedure에서는 :변수명 으로 사용

 

- 사용법)

1) 쿼리문 생성객체 얻기

    CallabeStatement cstmt = con.prepareCall(프로시저명);

    // 프로시저명에는 이렇게 !  "{ call 프로시저명(?,?,…) }"

2) CallableStatement 바인드 변수에 값 설정

 ① in parameter에 대응되는 bind 변수 (프로시저에 값 할당)

      - cstmt.setXXX (인덱스, 값);

         number -> cstmt.setInt (인덱스, 값), cstmt.setDouble (인덱스, 값);

         varchar2, char -> cstmt.setString(인덱스, 값);

 ② out parameter에 대응되는 bind 변수 (SQLPlus의 bind 변수를 자바 형식으로 선언하는 것)

      - cstmt.registerOutParameter (인덱스, java.sql.Types.상수) ;

      - Oracle에서는 var 변수명 데이터형(크기) 이렇게 했던 걸 위처럼 처리

    * java.sql.Types 클래스 

       - DBMS의 bind변수를 정의할 때 사용하는 데이터형을 java에서 사용할 수 있도록 구현한 클래스 

       - 특정 DBMS에서만 제공하는 데이터형을 지원하지 않음

            > Oracle의 NUMBER 대신 Types.NUMERIC 사용 

            > Oracle의 VARCHAR2 -> Types.VARCHAR

3) 프로시저 실행 

 - CallableStatement는 실행 method가 없으므로, 부모 Interface의 실행 method를 사용함

     > executeQuery(), executeUpdate(), execute() 셋 중에서 실행했다는 의미로 excute()를 사용!

        (어차피 셋다 반환형(rs,int,boolean)이 제대로 나오지 않음)

 * 프로시저를 실행하면 바인드변수(registerOutParameter)에 프로시저가 실행된 결과가 저장됨

4) 실행결과가 저장된 값 받기

- ResultSet 안 씀.

- CallableStatement의 method 사용 : cstmt.getXXX (바인드변수의 인덱스)

 예) registerOutParameter(Types.NUMERIC) 일 때 : cstmt.getInt(인덱스); cstmt.getDouble(인덱스)

        registerOutParameter(Types.VARCHAR) 일 때 : cstmt.getString(인덱스);

 


# 오늘의 코딩 #

- Java에서 Oracle에 생성한 procedure를 사용해보자 

 

# 생성한 프로시저

- in parameter로 name을 받고, out parameter로 msg를 내보냄!

 

# Java에서 호출

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;

import javax.swing.JOptionPane;

import test.dao.DbConnection;

/**
 * 이름(varchar2)을 입력하여 (in parameter) 설정된 msg(varcahr2)를 받는 일 (out parameter)
 * @author user
 */
 
public class UseCallableStatement {

	public UseCallableStatement() throws SQLException {

		Connection con = null;
		CallableStatement cstmt = null;

		DbConnection db = DbConnection.getInstance(); // 중복된 일이라 method 만들어 놓음
		
		try {
			// 1.드라이버로딩& 2.커넥션얻기
			con = db.getConn();
			// 3.쿼리문 생성객체 얻기 (프로시저명은 proc_hello)
			cstmt = con.prepareCall("{call proc_hello (?,?)}");
			// 4.바인드 변수 설정
			// in parameter
			cstmt.setString(1, JOptionPane.showInputDialog("이름 입력"));
			// out parameter
			cstmt.registerOutParameter(2, Types.VARCHAR);
			// 5.쿼리수행 후 결과 얻기
			cstmt.execute();
			//프로시저가 실행된 결과는 Out Parameter에 저장됨
			String msg = cstmt.getString(2);
			System.out.println(msg);
			
		} finally {
			// 6.연결 끊기 : CallableStatement는 PreparedStatement의 자식이므로, 종료 method를 부모로 사용 가능
			db.closeDB(null, cstmt, con);
		} // end finally

	}// UseCallableStatement

	public static void main(String[] args) {

		try {
			new UseCallableStatement();
		} catch (SQLException e) {
			e.printStackTrace();
		} // end catch
	}// main

}// class

- name은 다이얼로그를 띄워서 입력받음 (동동)

 

# 출력 결과 #

-> 완료!! ㅎㅎ 

 

 

'Development > JDBC' 카테고리의 다른 글

[JDBC] Cursor 작성 예정  (0) 2021.10.21
[JDBC] ResultSetMetaData / Transaction 처리  (0) 2021.10.19
[JDBC] DAO / PreparedStatement 활용  (0) 2021.10.15
[Java/JDBC] Singleton Pattern  (0) 2021.10.14
[JDBC] SQL Exception / SQL Injection  (0) 2021.10.13

 1. ResultSetMetaData 

- data dictionary를 사용하지 않고, 실행되는 select 쿼리에 해당하는 컬럼정보를 얻을 때 사용하는 ineterface

- 조회된 inline view에 한하여 컬럼정보 조회 가능

- DBMS가 달라도 정보를 얻을 수 있음

   > Oracle의 user_cons_columns ,,, 등과 같은 다양한 data dictionary는 oracle DB에서만 존재/사용 가능

- ResultSet을 기반으로 생성되며, ResultSet은 연결 끊어야 하나 ResultSetMetadata는 연결 안 끊어도 됨

- 사용법) 

> ResultSet에서 ResultSetMetaData를 얻기 : ResultSetMetaData rsmd = getMetaData();

> 정보 얻기

  • 컬럼의 개수 : rsmd.getColumnCount()
  • 컬럼명 : rsmd.getColumnName(컬럼 인덱스) or getColumnLabel (oracle의 컬럼 index는 1부터 시작)
  • 컬럼 데이터형명 : rsmd.getColumnTypeName(컬럼의 인덱스)
  • 컬럼 데이터형크기 : rsmd.getPrecision(컬럼의 인덱스)
  • null 허용 : rsmd.isnullable(컬럼의 인덱스) // not null이면 0 null 허용이면 1

 

 

 2. Transaction 처리 

- 데이터베이스의 작업 단위 (insert, update, delete)

- 작업이 완료될 때 commit, 작업을 취소할 때 rollback 사용 

- java에서는 Connection Interface가 Transaction을 처리

    > Transaction 대상쿼리문 하나 당 작업완료 (auto commit)

- auto commit 문제 : 

    > Transaction 대상 쿼리문 하나로 작업이 완료되면 auto commit은 문제를 발생시키지 않지만,

       여러 개의 쿼리문이 하나의 Transaction을 구성하면 문제를 발생시킬 수 있음

    > 여러 개의 작업이 모두 성공했을 때에만 DB작업이 완료되어야 하고,

       여러 개의 작업 중 하나라도 실패하면 모든 DB작업이 취소되어야 함 

    > 여러 쿼리가 있을 때 Transaction으로 관리하자

- 작업방법)

1) Connection의 auto commit을 해제

     con.setAutoCommit(false) ;  // 개발자가 transaction 완료 후 commit 또는 rollback 해야 함

    *주의 : method 안에서 Connection을 종료하면 commit이 된 후 Connection이 종료되므로,

                 연결 끊기는 DB작업 method 밖에서 끊음 

     (Connection을 DB작업 method 외부에서 선언 <- method가 2개로 분리됨)

2) 쿼리문 작업 외부에 Connection 선언 

3) DB작업 method가 쿼리 수행 행수를 반환

4) DB작업 method가 개발자가 목표로 한 레코드 수를 반환했을 때에만 commit 또는 rollback을 수행하고 연결을 끊음

 

- rollback 처리시 차이점:

1) update/delete의 경우 : 쿼리문 수행 실패 시 0건 수행이므로 else 부분에서 rollback 처리

2) insert의 경우 : 쿼리문 수행 실패 시 SQLException 발생하므로 catch 부분에서 rollback 처리

 

 


# 오늘의 코딩 #

- Transaction을 처리해보자

- 여러 쿼리문을 실행하고 모두 수행되었을 경우에만 commit!

 

# DB 구성은 아래와 같다!

- TRANSACTION2 테이블의 주소컬럼의 크기를 작게 설정하여 INSERT 되지 않을 경우/될 경우를 TEST 예정

TRANSACTION1
TRANSACTION2

 

# Transaction Test할 클래스

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import test.dao.DbConnection;

/**
 * 쿼리문 여러개로 하나의 Transaction이 구성되는 경우의 처리
 * @author user
 */
 
public class TestTransaction {

	/**
	 * transaction을 처리할 쿼리문을 실행하고, 결과를 받아와서 transaction을 완료하는 method
	 */
	@SuppressWarnings("resource")
	public void transaction() {
		Connection con = null;
		try {
			// 2. 커넥션 얻기
			con = DbConnection.getInstance().getConn();
			// autocommit 해제
			con.setAutoCommit(false);
			// 3. 쿼리문 수행 후 결과 얻기
			int cnt = sqlJob(con); // 여러개의 쿼리문이 하나의 transaction을 구성
			// 쿼리문이 실행된 후 목표로 한 행수가 나오면 commit 아니면 rollback
			if (cnt == 2) {
				con.commit(); }// 트랜잭션 완료
		} catch (SQLException e) {
			try {
				 // insert로 트랜잭션이 구성되는 경우 catch 에서 rollback을 처리함
				con.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}//end catch
			e.printStackTrace();
		} finally {
			try {
				if (con != null) {
					con.close();
				} // end if
			} catch (SQLException e) {
				e.printStackTrace();
			} // end catch
		} // end finally
	}// transaction

	/**
	 * Connection을 받아, DB작업을 수행하는 method<br>
	 * transaction1 테이블과 transaction2 테이블에 이름과 주소를 입력하여 추가된 행수를 반환하는 일
	 * 
	 * @param con
	 * @return 쿼리문을 수행한 결과 횟수 (총합)
	 * @throws SQLException
	 */
	public int sqlJob(Connection con) throws SQLException {
		int allCnt = 0;  // 쿼리문 수행 성공 횟수를 담을 변수

		String name = "김동동";
		String addr = "서울시 동작구";

		// 쿼리문 수행객체 얻기
		// transaction1 테이블에 작업
		String insert1 = "insert into transaction1(name,addr) values (?,?)";
		PreparedStatement pstmt = con.prepareStatement(insert1);
		pstmt.setString(1, name);
		pstmt.setString(2, addr);

		int cnt1 = pstmt.executeUpdate();

		// transaction2 테이블에 작업
		String insert2 = "insert into transaction2(name,addr) values (?,?)";
		PreparedStatement pstmt2 = con.prepareStatement(insert2);
		pstmt2.setString(1, name);
		pstmt2.setString(2, addr);

		int cnt2 = pstmt2.executeUpdate();

		allCnt = cnt1 + cnt2;

		return allCnt;
	}// sqlJob

	public static void main(String[] args) {
		TestTransaction tt = new TestTransaction();
		tt.transaction();
	}// main

}// class

 

# 출력 결과 #

1) INSERT 정상 처리 (이름 : 김동동 / 주소 : 서울)

- 두 테이블 모두 정상 적으로 INSERT 처리되어 커밋 됨!

2) 오류 발생 시 에러 메시지(이름 : 김동동 / 주소 : 서울시 동작구)

- 모든 트랜잭션이 처리되지 않으면 커밋되지 않고 에러 발생!

 

 

 

 DAO 

- 실질적으로 DB에 접근하는 객체

- DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트 (보통 singleton 패턴으로 만듦)

 

 


# 오늘의 코딩 #

- 이번엔 SQL Injection이 발생하지 않는 PreparedStatement 활용해보자

- Singleton으로 만들어서 하나의 객체만 생성되고 그 객체가 DB와 연동, Login을 수행해보자

 

# Login VO 클래스

- Login 사용 클래스에서 설정한 ID와 PW를 저장하고 반환해줄 클래스

public class LoginVO {

	private String id, pass;

	public LoginVO() {
		super();
	}

	public LoginVO(String id, String pass) {
		this.id = id;
		this.pass = pass;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPass() {
		return pass;
	}

	public void setPass(String pass) {
		this.pass = pass;
	}

	@Override
	public String toString() {
		return "LoginVO [id=" + id + ", pass=" + pass + "]";
	}
	
}

 

# Login DAO 클래스

- DB와 연결되어 일을 할 클래스

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import test.dao.DbConnection; 

public class LoginDAO {
	
	public String usePreparedStatement(LoginVO loginVO) throws SQLException {
		String name =""; 
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		DbConnection db = DbConnection.getInstance();
		try {
		//1.드라이버로딩 & 2.Connection얻기 (중복된 일이라 따로 method 만들어놨음)
			con = db.getConn();
		//3.쿼리문 생성 객체 얻기
			StringBuilder selectName = new StringBuilder();
			selectName
			.append("	select	name ")
			.append("	from 	test_login	")
			.append("	where id = ? and pass = ? ");  // ? : 바인드 변수!!
			
			pstmt = con.prepareStatement(selectName.toString());
		//4.바인드 변수에 값 설정 (VO Class에서 가져오기)
			pstmt.setString(1, loginVO.getId());
			pstmt.setString(2, loginVO.getPass());
		//5.쿼리문 수행 후 결과 얻기
			rs = pstmt.executeQuery();
			
			if(rs.next()) {//입력된 id와 p/w에 일치하는 레코드가 존재
				name = rs.getString("name"); // 조회된 이름 가져와서 변수에 할당
			}//end if
			
		}finally{
		//6.연결 끊기 (얘도 method 만들어놨음)
			db.closeDB(rs, pstmt, con);
		}//end finally
		
		return name;
	}//usePreparedStatement
	
}//class

 

# Login을 수행할 클래스

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.sql.SQLException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;

@SuppressWarnings("serial")
public class TestLogin extends JFrame implements ActionListener {

	private JTextField jtfId;
	private JPasswordField jpfPass;
	private JLabel jlblOutput;
	
	public TestLogin() { 
		super("로그인");
		jtfId=new JTextField();
		jpfPass=new JPasswordField();
		jlblOutput=new JLabel("출력");
		
		jtfId.setBorder(new TitledBorder("아이디"));
		jpfPass.setBorder(new TitledBorder("비밀번호"));
		jlblOutput.setBorder(new TitledBorder("결과"));
		
		jpfPass.addActionListener(this);
		
		setLayout(new GridLayout(3,1));
		
		add(jtfId);
		add(jpfPass);
		add(jlblOutput);
	
		// 종료
		addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				dispose();
			}//windowClosing

			@Override
			public void windowClosed(WindowEvent e) {
				System.exit(JFrame.ABORT);
			}//windowClosed
		});
		setBounds(100, 100, 250, 300);
		setVisible(true);
	}//TestLogin
	
	public void usePreparedStatement() {
		LoginDAO ld = new LoginDAO();
		
		//아이디와 비밀번호를 받자
		LoginVO lv = new LoginVO(jtfId.getText(), new String(jpfPass.getPassword()));
		
		try {
			String name = ld.usePreparedStatement(lv);
			
			if("".equals(name)) {//이름이 조회되지 않음
				JOptionPane.showMessageDialog(this, "아이디나 비밀번호를 확인해주세요.");
				return;
			}//end if
			
			jlblOutput.setText(name + "님 반갑습니다.");
			
		} catch (SQLException e) {
			JOptionPane.showMessageDialog(this, "죄송합니다.");
			e.printStackTrace();
		}//end catch
		
	}//usePreparedStatement
	
	@Override
	public void actionPerformed(ActionEvent e) {
		usePreparedStatement(); // SQLInjection 공격불가
	}//actionPerformed

	public static void main(String[] args) {
		new TestLogin();
	}//main

}//class

 

# 출력 결과 #

(기존 DB 데이터의 ID와 비밀번호는 각각 WOO/4321)

 

> 로그인 성공시 화면

 

> 로그인 실패시 화면

 

 Singleton Pattern 

- JVM에서 하나의 인스턴스를 생성하고 사용하는 개발 패턴 (Design pattern)

- 메모리를 절감할 수 있음

- 참조하는 속도가 빠름

- Framework에서 주로 객체를 생성하고 사용하는 방식

- 아래 작성법이 class안에 들어가있으면 singleton 패턴인 클래스 라고 함

- Ex) Calendar Class의 getInstance()

- 작성법)

작성 코드
1. 객체화를 클래스 안에서만 할 수 있도록 작성함
 => 생성자의 접근지정자를 private로 지정

2.  객체를 생성하여 반환하는 일을 하는 method 작성
 => public static 클래스명 getInstance() {

3. 객체를 하나로 유지하고 반환하는 일.
      if ( 객체 == null ) { 객체 = new 생성자();  }
                return 객체;
    }
class Test{
        private static Test t;
        private Test(){
         }
       
        public static Test getInstance(){
                 if (t == null) {
                     t = new Test();  }
                 return t;       }    
        }

 


 

# 예시 #

- 싱글톤 패턴의 클래스

/**
 * 싱글톤 패턴으로 만드는 클래스 : 객체가 하나로만 사용되는 클래스
 * @author user
 */
public class Singleton {

	private static Singleton single;
	private Singleton() { // 접근지정자 private으로 클래스 외부에서 객체화 불가
		
	}// Singleton

	/**
	 * 생성된 객체를 얻기 위한 method
	 * @return
	 */
	public static Singleton getInstance() {
		// 객체를 하나로 생성 관리할 수 있는 코드
		if (single == null) {
			single = new Singleton();
		}//end if
		
		return single;
	}// getInstance

}// class

 

- 싱글톤 클래스 사용

public class UseSingleton {

	public static void main(String[] args) {
		//생성자가 private이기 때문에 클래스 외부에서 객체화 될 수 없음
//		Singleton single = new Singleton(); // 생성자가 보이지 않아서 생성 불가! getInstance 사용해야함
		
		//getInstance method를 통해서만 객체를얻음
		Singleton single = Singleton.getInstance();
		Singleton single2 = Singleton.getInstance();
		Singleton single3 = Singleton.getInstance();
		
		//몇개를 생성하든 객체는 하나이므로 주소는 똑같당
		System.out.println(single);
		System.out.println(single2);
		System.out.println(single3);
		
		//일반 class는 객체 생성 횟수만큼 heap 주소가 할당됨!
		UseStatement us = new UseStatement();
		UseStatement us2 = new UseStatement();
		UseStatement us3 = new UseStatement();
		
		System.out.println(us);
		System.out.println(us2);
		System.out.println(us3);
		
	}//main

}//class

 

# 출력 결과 #

 

> 객체를 하나만 생성해서 사용할 때 !! 

DB 연동 작업 시 한 객체만 DB에 접근/조작할 수 있도록 할 수 있음

+ Recent posts