이전 글에서는 JSP 기반 로그인 시스템을 구축하면서 쿠키와 세션, DB 연동까지 구현했습니다. 이번에는 이 시스템을 한 단계 발전시켜 다음과 같은 기능을 추가했습니다.

  • DB에 회원 정보 등록 (회원가입 기능)
  • 비밀번호 암호화 (SHA-256)
  • 로그인 실패 시도 횟수 제한 처리

1. 회원가입 기능 (DB에 사용자 등록)

1-1. 사용자 테이블 설계

MySQL Workbench에서 다음과 같은 테이블을 생성합니다.

CREATE TABLE user (
  id VARCHAR(50) PRIMARY KEY,
  pwd VARCHAR(255) NOT NULL,
  name VARCHAR(50),
  fail_count INT DEFAULT 0
);

1-2. 회원가입 폼 (register.jsp)

<form action="register" method="post">
  ID: <input type="text" name="id"><br>
  PWD: <input type="password" name="pwd"><br>
  이름: <input type="text" name="name"><br>
  <input type="submit" value="회원가입">
</form>

1-3. RegisterController.java

@Controller
public class RegisterController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(String id, String pwd, String name) throws Exception {
        String encPwd = encrypt(pwd);
        String sql = "INSERT INTO user (id, pwd, name) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, id, encPwd, name);
        return "redirect:/login.jsp";
    }

    private String encrypt(String pwd) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(pwd.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hash);
    }
}

 

2. 비밀번호 암호화 (SHA-256)

2-1. 암호화 로직 재사용

로그인 시에도 같은 방식으로 비밀번호를 암호화하여 비교해야 합니다.

private String encrypt(String pwd) throws Exception {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] hash = md.digest(pwd.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(hash);
}

 

3. 로그인 실패 처리

3-1. 사용자 인증 로직 수정 (LoginController.java)

@Controller
public class LoginController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(String id, String pwd, Model model, HttpSession session) throws Exception {
        String sql = "SELECT * FROM user WHERE id = ?";
        List<Map<String, Object>> users = jdbcTemplate.queryForList(sql, id);

        if (users.isEmpty()) {
            model.addAttribute("error", "존재하지 않는 사용자입니다.");
            return "login";
        }

        Map<String, Object> user = users.get(0);
        String encPwd = encrypt(pwd);
        String dbPwd = (String) user.get("pwd");
        int failCount = (Integer) user.get("fail_count");

        if (failCount >= 5) {
            model.addAttribute("error", "로그인 5회 이상 실패. 계정 잠김.");
            return "login";
        }

        if (dbPwd.equals(encPwd)) {
            jdbcTemplate.update("UPDATE user SET fail_count = 0 WHERE id = ?", id);
            session.setAttribute("id", id);
            return "redirect:/index.jsp";
        } else {
            jdbcTemplate.update("UPDATE user SET fail_count = fail_count + 1 WHERE id = ?", id);
            model.addAttribute("error", "비밀번호가 일치하지 않습니다.");
            return "login";
        }
    }

    private String encrypt(String pwd) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(pwd.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hash);
    }
}

4. 로그인 화면에서 오류 메시지 출력

login.jsp 수정

<c:if test="${not empty error}">
  <p style="color:red">${error}</p>
</c:if>

 

5. 테스트 시나리오 요약

  1. 회원가입 페이지에서 회원 등록 (비밀번호는 암호화되어 저장됨)
  2. 올바른 비밀번호로 로그인 → 성공
  3. 틀린 비밀번호 5회 입력 → 계정 잠김
  4. 로그인 성공 시 실패 횟수 초기화
  5. 암호화된 비밀번호를 직접 DB에서 확인 가능
기본적인 login.jsp → userInfo.jsp 데이터 전달부터 시작해, LoginController.java를 도입한 MVC 구조 변경, 최종적으로는 쿠키를 활용한 ID 기억 기능까지 구현했습니다.

 

1단계: 기본 JSP 기반 로그인 (Form 데이터 전달)

구현 흐름

  1. login.jsp에 form을 만들고 ID와 비밀번호를 입력하는 텍스트박스를 추가합니다.
  2. form의 action 속성을 userInfo.jsp로 지정하여 데이터를 전달합니다.
  3. userInfo.jsp에서는 request.getParameter("id"), request.getParameter("pwd")를 통해 입력값을 확인합니다.

실습 흐름

<!-- login.jsp -->
<form action="userInfo.jsp" method="post">
  ID: <input type="text" name="id" />
  PWD: <input type="password" name="pwd" />
  <button type="submit">로그인</button>
</form>
<!-- userInfo.jsp -->
ID: <%= request.getParameter("id") %><br>
PWD: <%= request.getParameter("pwd") %>

 

2단계: LoginController.java 추가 (Spring MVC 기반)

구현 흐름

  1. login.jsp의 form의 action을 /login으로 변경하고 LoginController.java를 생성합니다.
  2. @Controller와 @RequestMapping을 통해 /login 요청을 처리합니다.
  3. 전달받은 ID와 PWD를 임의로 검증 후, 일치하면 userInfo.jsp, 실패하면 redirect:/login.jsp로 이동합니다.

핵심 코드

//LoginController.java
// 로그인 요청을 처리하는 컨트롤러 클래스
@Controller
public class LoginController {

    // "/login" URL 요청을 처리할 메서드 정의
    @RequestMapping("/login")
    public String login(HttpServletRequest request, Model model) {
        
        // 클라이언트에서 전달된 id와 pwd 파라미터 추출
        String id = request.getParameter("id");
        String pwd = request.getParameter("pwd");

        // 간단한 조건 검사 (고정된 값으로 로그인 성공 여부 판단)
        if ("asdf".equals(id) && "123".equals(pwd)) {
            // 로그인 성공 시 모델에 데이터 담기
            model.addAttribute("id", id);
            model.addAttribute("pwd", pwd);

            // userInfo.jsp로 포워딩
            return "userInfo";
        } else {
            // 로그인 실패 시 다시 login.jsp로 리다이렉트
            return "redirect:/login.jsp";
        }
    }
}
<!--userInfo.jsp-->
<!-- 모델에서 전달된 id, pwd 출력 -->
ID: ${id} <br>
PWD: ${pwd}

 

3단계: 쿠키를 이용한 ID 기억 기능

구현 흐름

  1. login.jsp에 "ID 기억하기" 체크박스를 추가합니다.
  2. 사용자가 체크 후 로그인하면 ID를 쿠키에 저장합니다.
  3. 로그인 페이지를 다시 방문할 경우 쿠키로부터 ID를 읽어 input에 미리 채워 넣고, 체크박스도 체크된 상태로 표시합니다.
  4. 사용자가 체크하지 않은 채 로그인하면 쿠키는 삭제됩니다.

핵심 코드

<!--login.jsp-->
<%
  // 쿠키에서 rememberId 값을 읽어오기
  Cookie[] cookies = request.getCookies();
  String savedId = "";
  boolean remember = false;

  if (cookies != null) {
      for (Cookie c : cookies) {
          if ("rememberId".equals(c.getName())) {
              savedId = c.getValue(); // 저장된 ID 가져오기
              remember = true;        // 체크박스 체크 상태로 표시
          }
      }
  }
%>

<!-- 로그인 폼 -->
<form action="login" method="post">
  <!-- 저장된 ID가 있다면 input에 미리 채우기 -->
  ID: <input type="text" name="id" value="<%= savedId %>" /><br>
  
  <!-- 비밀번호 입력 -->
  PWD: <input type="password" name="pwd" /><br>
  
  <!-- ID 기억하기 체크박스, 쿠키가 있으면 체크 -->
  <input type="checkbox" name="remember" <%= remember ? "checked" : "" %> /> ID 기억<br>
  
  <!-- 로그인 버튼 -->
  <button type="submit">로그인</button>
</form>
//LoginController.java
// 로그인 요청을 처리하고 쿠키 생성/삭제를 수행하는 컨트롤러
@RequestMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response, Model model) {
    
    // 파라미터 추출
    String id = request.getParameter("id");
    String pwd = request.getParameter("pwd");
    String remember = request.getParameter("remember");

    // 로그인 성공 조건 확인
    if ("asdf".equals(id) && "123".equals(pwd)) {
        // 모델에 데이터 추가
        model.addAttribute("id", id);
        model.addAttribute("pwd", pwd);

        // ID 기억 체크박스 체크 여부 확인
        if (remember != null) {
            // 쿠키 생성 (유효기간: 7일)
            Cookie cookie = new Cookie("rememberId", id);
            cookie.setMaxAge(60 * 60 * 24 * 7);
            response.addCookie(cookie);
        } else {
            // 체크 해제 시 쿠키 삭제
            Cookie cookie = new Cookie("rememberId", null);
            cookie.setMaxAge(0);
            response.addCookie(cookie);
        }

        // 로그인 성공 → userInfo.jsp로 포워딩
        return "userInfo";
    } else {
        // 로그인 실패 → 다시 login.jsp로 이동
        return "redirect:/login.jsp";
    }
}

영화관 관리 시스템을 위한 ERD(Entity–Relationship Diagram)는 지점, 상영관, 좌석, 영화, 상영시간, 고객, 티켓, 담당자, 관리자 총 9개의 테이블로 구성됩니다.
각 테이블의 역할과 주요 속성, 그리고 테이블 간 관계를 단계별로 정리해 보겠습니다.

 

1. 테이블별 설명

1.1. Theater (지점 테이블)

  • 목적: 하나의 영화관 체인(지점)을 관리
  • 주요 속성
    • theater_id (PK): 지점 고유번호
    • theater_name: 지점 이름
    • location: 지점 위치 정보
  • 특징:
    • 여러 상영관(Screen)을 포함(1:N)

 

1.2. Screen (상영관 테이블)

  • 목적: 각 지점 내 개별 상영관을 관리
  • 주요 속성
    • screen_id (PK): 상영관 고유번호
    • theater_id (FK): 소속 지점
    • screen_name: 상영관 이름(예: “4관”)
  • 제약조건
    • theater_id → Theater(theater_id) (1:N)
    • 담당자(Employee) 1:1 매핑 (각 상영관에 직원 1명 배정)

 

1.3. Seat (좌석 테이블)

  • 목적: 각 상영관의 개별 좌석 정보를 관리
  • 주요 속성
    • seat_id (PK): 좌석 고유번호
    • screen_id (FK): 소속 상영관
    • seat_location: 좌석 위치(예: “A1”)
    • seat_type: 좌석 종류(예: “VIP”, “일반”)
    • price: 좌석별 가격

 

1.4. Movie (영화 테이블)

  • 목적: 상영할 영화 정보 관리
  • 주요 속성
    • movie_id (PK): 영화 고유번호
    • movie_title: 영화 제목
    • running_time: 상영시간(분)
    • start_date: 개봉일
    • end_date: 종료일

 

1.5. ScreenPeriod (상영시간 테이블)

  • 목적: 특정 상영관에서 특정 영화가 상영되는 시간 관리
  • 주요 속성
    • period_id (PK): 상영시간 고유번호
    • screen_id (FK): 상영관
    • movie_id (FK): 상영 영화
    • start_time: 상영 시작 시각 (DATETIME)
    • end_time: 상영 종료 시각 (DATETIME)
  • 제약조건
    • 같은 상영관 내 시간 겹침 금지 (DB EXCLUDE 제약 또는 애플리케이션 로직)

 

1.6. Customer (고객 테이블)

  • 목적: 티켓 구매 고객 정보 관리
  • 주요 속성
    • customer_id (PK): 고객 고유번호
    • phone_number: 실제 휴대폰 번호

 

1.7. Ticket (티켓 테이블)

  • 목적: 고객이 구매한 티켓 정보 관리
  • 주요 속성
    • ticket_id (PK): 티켓 고유번호
    • period_id (FK): 상영시간
    • seat_id (FK): 좌석
    • customer_id (FK): 구매 고객
    • purchase_time: 구매 시각
    • masked_phone: 출력용 마스킹 번호 (VARCHAR)
  • 제약조건
    1. 복합 UNIQUE(period_id, seat_id)
      • 같은 상영시간에 같은 좌석 이중 예약 방지
    2. 구매 시점 제한
      • purchase_time ≤ start_time – 10분 (DB CHECK 또는 애플리케이션 검증)

 

1.8. Employee (담당자 테이블)

  • 목적: 상영관별 책임자(직원) 관리
  • 주요 속성
    • employee_id (PK): 직원 고유번호
    • screen_id (FK): 담당 상영관
    • name: 직원 이름
    • position: 직책/직급
  • 제약조건
    • screen_id에 UNIQUE 제약: 상영관당 한 명만 배정 (1:1)

 

1.9. Admin (관리자 테이블)

  • 목적: 시스템 전반을 관리하는 관리자 계정 관리
  • 주요 속성
    • admin_id (PK): 관리자 고유번호
    • name: 관리자 이름
  • 특징
    • 물리적 FK 관계는 없으며, 단순 조회용

 

2. 테이블 간 관계성

관계 설명
Theater → Screen (1:N) 한 지점에 여러 상영관을 배치
Screen → Seat (1:N) 상영관별로 좌석을 여러 개 보유
Screen → ScreenPeriod (1:N) 상영관별로 일일 여러 회차 관리 (시간 겹침 금지)
Movie → ScreenPeriod (1:N) 한 영화가 여러 상영관·회차에서 동시 상영 가능
ScreenPeriod → Ticket (1:N) 한 회차에 여러 티켓(좌석) 판매
Seat → Ticket (1:1) 한 티켓은 오직 하나의 좌석에 매핑
Customer → Ticket (1:N) 한 고객이 여러 티켓을 구매 가능
Screen → Employee (1:1) 상영관당 담당 직원을 1명 배정
Admin (단순 조회권한) 모든 상영관·좌석 예약 현황을 조회

01. 데이터 모델링 이론

📌 데이터 모델링을 하는 이유

  • 데이터를 읽고 쓰기 편한 구조로 만들기 위해

📐 모델링 과정 3단계

  • 분석 - 개념 모델링(업무 파악, 기존 시스템 분석)
  • 설계 - 논리 모델링
  • 구현 - 물리 모델링

🧱 엔티티

  • 엔티티란?
    업무 구현을 위해 필요한 집합(=속성들의 그룹)

 

  • 엔티티가 될 수 있는 조건 3가지
    - 둘 이상의 인스턴스
    - 둘 이상의 속성
    - 식별자를 가지고 있어야 함

🧱 관계

  • ER(Entity Relationship) 모델 : 엔티티 간의 관계
  • 관계 형성을 위해 고려해야할 사항 5가지
    • 관계수
    • 식별자 상속
    • 관계의 종류
    • 관계 테이블 사용 유무
    • 선택성
  • 관계수 : A라는 엔티티의 인스턴스 1개가 B라는 엔티티 몇 개의 인스턴
  • 데이터는 **테이블(릴레이션)**로 표현
  • 테이블 = 행(튜플) + 열(속성)
  • 기본 키, 외래 키, 무결성 제약조건 등을 통해 관계 유지

02. 개념 모델링

🧭 데이터 모델링 접근 방법

  • Top-down: 업무에서 데이터 구조로
  • Bottom-up: 기존 데이터 구조에서 모델 도출
  • Inside-out: 주요 엔터티에서 확장
  • Hybrid: 위 방법을 조합해 유연하게 설계

🧱 개념 모델링

  • 사용자의 요구를 바탕으로 시스템이 다뤄야 할 주요 개체와 관계 정의
  • 기술적 제약 없이 업무 중심으로 설계
  • 주요 산출물: ERD(개념 수준)

03. 논리 모델링

🧠 논리 모델링이란?

  • 개념 모델을 바탕으로 실제 DBMS에 맞게 논리적 구조화
  • 정규화 수행, 키 설정, 관계 명확화

📌 엔티티 정의 및 상세화

  • 업무 규칙에 맞는 엔티티 도출
  • 각 엔티티에 고유한 기본 키 설정

🔗 관계 도출 및 정의

  • 1:1, 1:N, N:M 관계 정의
  • N:M 관계는 교차 엔티티로 풀어서 설계

🏷 속성 도출 및 정의

  • 각 엔티티에 필요한 속성 도출
  • 도메인(데이터 타입, 길이) 지정
  • NULL 여부, 기본값 설정

📏 데이터 표준화

  • 용어 사전, 도메인 사전 기반으로 표준화된 명칭/정의 사용
  • 컬럼명, 단위, 코드값 등 일관성 확보

04. 물리 모델링

⚙️ 물리 모델링이란?

  • 논리 모델을 기반으로 실제 DB 구현에 필요한 구조 설계
  • 성능, 저장 공간, 시스템 특성까지 고려

🧱 테이블 설계

  • 논리 엔티티를 테이블로 매핑
  • 기본 키 설정, 제약조건 명시

🔗 관계 설계

  • 외래 키(FK)로 관계 설정
  • CASCADE, RESTRICT 등 참조 동작 정의

🏷 컬럼 설계

  • 타입, 길이, NULL 여부, 디폴트 값 등 상세 지정
  • 문자셋/정렬 방식 고려 (예: UTF-8, Korean_CI_AS 등)

🔒 데이터 무결성 설계

  • 도메인 무결성: 타입, 제약조건
  • 참조 무결성: FK 제약
  • 엔터티 무결성: PK의 유일성/NOT NULL

🚀 성능을 고려한 데이터 구조

  • 정규화 + 필요한 경우 비정규화 적용
  • 중복/요약 테이블 도입

🧾 물리 설계

  • 테이블스페이스, 스토리지 설정, I/O 분산 설계
  • 데이터 배치 고려 (핫스팟 방지)

🔍 인덱스 설계

  • 조회가 잦은 컬럼에 B-Tree 인덱스
  • 다중 컬럼 인덱스 시 선택도 고려
  • 쓰기 부하를 고려한 인덱스 최소화 전략 필요

🧩 파티션 설계

  • 대용량 테이블을 논리적으로 분리
  • Range, List, Hash 파티션 전략
  • 파티션 키는 자주 사용되는 조건 컬럼 기준으로 선택

✅ 정리 요약

개념 모델링 업무 중심의 엔티티·관계 도출, ERD 작성
논리 모델링 정규화, 키/관계/속성 상세화, 표준화 적용
물리 모델링 테이블·컬럼·인덱스·파티션 설계 + 성능·무결성 고려
 

✍ 느낀점

이론으로만 봤을 땐 모델링이 그저 그림 그리는 작업처럼 보일 수 있지만,
직접 DB 설계를 해보면 "모델링이 곧 설계의 절반"이라는 말이 와닿습니다.
처음부터 탄탄하게 설계된 모델은 성능 튜닝을 덜 필요로 하고,
확장성과 유지보수 측면에서도 훨씬 효율적입니다.

정답이 하나가 아니라서 매 프로젝트마다 다른 결정을 내려야 한다는 점이 어렵기도 하고, 재미있는 부분이기도 했습니다.
앞으로는 실무 예제를 기반으로 다시 한 번 정리해보려 합니다. 모델링은 결국 경험인 것 같습니다.

1. DDL (Data Definition Language)

1.1 CREATE – 새로운 데이터베이스 객체(테이블, 뷰, 시퀀스, 프로시저 등)를 생성한다.

1.2 ALTER – 기존 데이터베이스 객체의 정의를 변경한다. (예: 컬럼 추가/수정, 스토리지 변경 등)

1.3 DROP – 데이터베이스 객체를 완전히 삭제한다.

1.4 TRUNCATE – 테이블의 모든 데이터를 즉시 제거하고, 공간을 초기화한다. (DDL로 분류되며 COMMIT/ROLLBACK 대상이 아님)

1.5 RENAME – 객체의 이름을 변경한다.

1.6 COMMENT – 테이블·컬럼 등에 설명을 추가한다.


2. DML (Data Manipulation Language)

2.1 SELECT – 데이터를 조회한다. (Projection, Selection, Join 등 포함)

2.2 INSERT – 새 행(row)을 테이블에 추가한다.

2.3 UPDATE – 기존 행의 값을 수정한다.

2.4 DELETE – 행을 삭제한다.

2.5 MERGE – 조건에 따라 INSERT 또는 UPDATE를 수행하는 ‘upsert’ 명령.

2.6 LOCK TABLE – 지정한 테이블에 명시적 잠금을 건다.


3. TCL (Transaction Control Language)

3.1 COMMIT – 현재 트랜잭션에서 수행한 변경 사항을 영구적으로 반영한다.

3.2 ROLLBACK – 트랜잭션 내 변경 사항을 취소하여 마지막 COMMIT 시점으로 되돌린다.

3.3 SAVEPOINT – 중간 지점을 지정하여, 이후 ROLLBACK TO SAVEPOINT가 가능하게 한다.

3.4 SET TRANSACTION – 트랜잭션의 격리 수준 및 읽기 전용/쓰기 가능 모드 등을 설정한다.


4. DCL (Data Control Language)

4.1 GRANT – 사용자(또는 롤)에게 권한을 부여한다.

4.2 REVOKE – 부여했던 권한을 회수한다.


5. Session / System Control 명령

5.1 ALTER SESSION – 현재 세션의 환경 설정을 변경한다. (예: NLS_DATE_FORMAT, CURRENT_SCHEMA)

5.2 ALTER SYSTEM – 데이터베이스 인스턴스 전역 설정을 변경하거나 상태를 제어한다. (예: 파라미터 수정, 아카이브 로그 모드 전환 등)


6. 보조(Utility) 명령·툴

6.1 DESCRIBE – 객체 구조(컬럼, 데이터형 등)를 빠르게 확인한다. (SQL*Plus/SQLcl 내부 명령)

6.2 EXPLAIN PLAN – SQL 실행 계획을 저장·조회한다.

6.3 SET ROLE – 현재 세션에 적용할 ROLE을 설정·해제한다.

6.4 CALL / EXECUTE – PL/SQL 프로시저·함수를 즉시 실행한다.

6.5 DBMS_OUTPUT.PUT_LINE – PL/SQL 블록에서 디버깅 메시지를 출력한다.

6.6 ANALYZE – 통계 생성·검증·삭제 등 객체 분석을 수행한다. (새 버전에서는 DBMS_STATS 패키지 사용 권장)


7. 고급 객체·데이터 제어

7.1 CREATE SEQUENCE – 순차적인 숫자를 자동 생성하는 시퀀스를 만든다.

7.2 CREATE SYNONYM – 객체에 대한 별칭을 정의하여 스키마·경로를 단순화한다.

7.3 CREATE VIEW – SELECT 결과를 논리적 테이블처럼 재사용하도록 뷰를 정의한다.

7.4 CREATE INDEX – 쿼리 성능 향상을 위해 인덱스를 생성한다.

7.5 FLASHBACK TABLE / DATABASE – 과거 시점으로 테이블이나 데이터베이스를 복구한다.

7.6 AUDIT / NOAUDIT – 특정 명령 또는 객체 액세스를 감사(Audit) 대상으로 지정하거나 해제한다.


8. 데이터 이동·백업 관련

8.1 EXPORT / IMPORT (Data Pump) – 대량 데이터 및 메타데이터를 덤프 파일로 내보내거나 가져온다. (expdp / impdp 유틸리티)

8.2 RMAN (Recovery Manager) – 백업·복구 자동화 도구로, 데이터베이스·아카이브 로그 백업 및 복원을 수행한다.


9. 참조용 뷰·패키지

9.1 USER_ / ALL_ / DBA_ – 메타데이터 조회용 데이터 사전 뷰 전접두사. (예: ALL_TABLES, DBA_INDEXES)

9.2 DBMS_STATS – 객체 통계 정보를 수집·관리하여 옵티마이저 성능을 최적화한다.

9.3 UTL_FILE, UTL_MAIL, UTL_HTTP – 외부 파일·메일·HTTP 호출 등 PL/SQL 유틸리티 패키지.


10. 참고 & 권장 학습 순서

  1. 필수: DDL, DML, TCL, DCL
  2. 세션·시스템 제어 및 통계 수집(DBMS_STATS)
  3. 백업·복구(RMAN) 및 데이터 이동(EXPDP/IMPDP)
  4. 보안(AUDIT), 고급 기능(FLASHBACK, PL/SQL 패키지)

 

추가적으로 스터디 활동 중 알게 된 내용을 블로그에 정리했습니다.

DB 설계나 쿼리 작성에 관심 있다면 참고하시면 좋을 것 같습니다.

2025.05.04 - [Study/DB] - 데이터 분석 전에 꼭 알아야 할 SQL 실무 상식

 

[재사용 목적의 디자인 패턴]

1. Singleton Pattern (싱글턴 패턴)

"하나의 회의실을 여러 사람이 나눠 쓴다."
프로그램에서 자주 쓰는 객체를 여러 번 만들지 않고 단 하나만 만들어 공유해서 쓰는 방식이에요.
예를 들어, 회사에서 회의실이 하나라면 사람들이 동시에 쓰지 못하고 하나를 기다려가며 공유하듯이요.

 

❗ 문제

  • 자주 쓰이는 객체를 여러 번 생성하면 메모리 낭비가 크고, 설정 충돌 가능성도 있음. (예: 설정 객체, 로깅 시스템)

✅ 해결

  • 객체를 하나만 만들고, 어디서든 접근할 수 있게 전역 인스턴스를 제공함.
  • 생성자를 private으로 막고, getInstance() 메서드로만 접근.

 

🔧 Java 예제 설명

public class ConfigManager { // 설정을 관리하는 싱글턴 클래스 정의

    private static ConfigManager instance = new ConfigManager(); // 클래스 내부에 자기 자신 타입의 정적 인스턴스 생성 (처음에 1개만 생성)

    private ConfigManager() {}  // 생성자를 private으로 선언하여 외부에서 new로 객체를 만들 수 없도록 제한

    public static ConfigManager getInstance() { // 외부에서 인스턴스를 요청할 때 사용하는 public 메서드
        if (instance == null) // 혹시 모를 null 상황을 방지하기 위한 체크 (여기선 필요 없음. 이미 위에서 생성됨)
            instance = new ConfigManager(); // null일 경우에만 새로 생성 (지연 초기화 방식)
        return instance; // 인스턴스를 반환
    }
}
  • 객체를 new로 여러 개 만들 수 없고, 오직 getInstance()로만 하나의 객체를 받아서 사용해요.

 

🏢 실무 사례

환경설정 객체(ConfigManager)
시스템 전체에서 환경 설정을 읽어올 때는 어디서든 같은 설정을 읽어야 하기 때문에, Singleton을 사용해 하나의 설정 인스턴스를 공유합니다.

 

🔗구조

  1. 싱글턴 클래스는 정적 메서드 get­Instance를 선언합니다. 이 메서드는 자체 클래스의 같은 인스턴스를 반환합니다.
  2. 싱글턴의 생성자는 항상 클라이언트 코드에서부터 숨겨져야 합니다. get­Instance 메서드를 호출하는 것이 Singleton 객체를 가져올 수 있는 유일한 방법이어야 합니다.

2. Flyweight Pattern (플라이웨이트 패턴)

"모든 직원이 같은 회사 로고 이미지를 공유해 쓰는 것처럼, 같은 데이터를 공유해서 메모리를 아끼는 패턴입니다."

 

❗ 문제

  • 동일한 데이터를 가진 수천 개 객체를 생성하면 메모리 낭비가 심각함. (예: 폰트, 그래픽 요소)

✅ 해결

  • 공통 속성을 가진 객체를 공유하여 재사용.
  • 내부 상태는 공유하고, 외부 상태만 각자 다르게 관리.

 

🔧 Java 예제 설명

// 글꼴(Font) 객체 정의 클래스
class Font {
    private final String fontName; // 글꼴 이름을 저장하는 필드 (변경 불가)

    Font(String fontName) { // 생성자: 글꼴 이름을 받아서 초기화
        this.fontName = fontName;
    }
}

// Flyweight 객체를 관리하는 팩토리 클래스
class FontFactory {
    private Map<String, Font> pool = new HashMap<>(); 
    // 글꼴 이름(String)을 키로, Font 객체를 값으로 저장하는 Map (객체 재사용 풀)

    Font getFont(String fontName) {
        // 요청된 글꼴 이름이 이미 pool에 있으면 기존 객체를 반환,
        // 없으면 새로 생성하고 pool에 추가한 후 반환
        return pool.computeIfAbsent(fontName, Font::new);
        // == if (!pool.containsKey(fontName)) pool.put(fontName, new Font(fontName));
        //    return pool.get(fontName);
    }
}
  • FontFactory는 같은 이름의 글꼴을 한 번만 생성해서 공유합니다.

 

🏢 실무 사례

문서 편집기에서 글꼴 객체: 수천 글자가 동일한 폰트를 사용할 때, 각 글자마다 객체를 만들지 않고 하나의 폰트를 공유합니다.

 

🔗구조

① Flyweight 객체 정의

  • Flyweight 클래스는 반복적으로 사용되는 공통 데이터(repeatingState)를 저장해.
  • 예: 글자 모양, 도형의 스타일 같은 것.
  • 여기엔 operation(uniqueState)이라는 메서드가 있어. 호출 시 변하는 값(uniqueState)을 받아서 처리해.

 

② 공장에서 Flyweight 객체 만들기 (또는 재사용)

  • FlyweightFactory는 Flyweight 객체들을 캐시에 저장하고 관리해.
  • getFlyweight(repeatingState)를 호출하면:
    • 이미 같은 repeatingState가 있으면 → 재사용
    • 없으면 → 새로 만들고 저장

 

③ Context 객체 생성

  • Context는 변하는 값(uniqueState)Flyweight 객체(공통 데이터)를 함께 보관해.
  • 여기서 생성자에서:
this.uniqueState = uniqueState;
this.flyweight = factory.getFlyweight(repeatingState);
  • 변하는 값은 자기 내부에 저장하고,
  • 공통 값은 공장에서 받아와 저장함.

 

④ 행동 실행 (operation 호출)

  • Context.operation()을 호출하면,
    • 내부의 flyweight에게 operation(uniqueState)를 전달해 실행
  • 즉, 공유 객체 + 개인 정보 → 원래 객체의 동작을 만들어냄!

 

⑤ 클라이언트가 Context를 사용

  • Client(사용자)는 직접 Flyweight를 만들지 않아.
  • 대신 필요한 정보를 전달해서 Context를 생성함.
  • 클라이언트는 Context를 통해 간접적으로 Flyweight를 사용하게 돼.

 

⑥ FlyweightFactory: 공유 객체 관리자

  • 공장은 Flyweight 객체들을 메모리 내에서 재사용할 수 있도록 관리해주는 역할을 해.
  • 클라이언트가 Context를 만들 때마다 이 공장을 거치게 해서, 공통 객체는 한 번만 만들고 계속 재사용할 수 있도록 보장해줌.

3. Prototype Pattern (프로토타입 패턴)

"복사해서 쓰는 문서 템플릿처럼, 기존 객체를 복제해서 새 객체를 만드는 패턴입니다."

 

❗ 문제

  • 복사하려는 객체의 구체 클래스나 내부 필드를 알 수 없고, 일부 필드는 비공개(private)일 수도 있음.
  • 외부에서 객체를 복사하려면 클래스에 의존하게 되어 유연성이 떨어짐.

✅ 해결

  • 복제는 객체 자신에게 위임한다.
  • clone() 메서드를 포함한 공통 인터페이스를 정의하여, 클래스 정보 없이도 객체를 복제할 수 있게 함.

 

🔧 Java 예제 설명

// 복제 가능한 문서 클래스 정의
class Document implements Cloneable { 
    String content; // 문서의 내용 (복제 대상이 되는 필드)

    // clone 메서드를 오버라이딩하여 객체 복제를 가능하게 함
    public Document clone() throws CloneNotSupportedException {
        // Object 클래스의 protected clone() 메서드를 호출하여 얕은 복제 수행
        return (Document) super.clone();
        // 복제된 객체를 Document 타입으로 형변환하여 반환
    }
}
  • 기존 Document 객체를 clone()으로 복사해서 새로운 객체를 만듭니다.

 

🏢 실무 사례

게임 캐릭터 생성 시 템플릿 복제: 기본 캐릭터를 템플릿으로 만들어 놓고, 복사해서 새로운 캐릭터를 생성

 

🔗구조

① Prototype (인터페이스 또는 추상 클래스)

  • 복제 기능을 정의한 틀이야.
  • 모든 복제 가능한 클래스는 이 clone() 메서드를 따라야 해.
  • 즉, "나를 복제하려면 clone을 구현해야 해요!" 라고 약속하는 인터페이스.

 

② ConcretePrototype & SubclassPrototype (복제 대상 객체들)

  • 실제 복사할 진짜 객체들이야.
  • 내부 필드를 clone() 메서드를 통해 복사함.
  • ConcretePrototype은 단순한 복제, SubclassPrototype은 상속받은 상태까지 포함한 복제를 함.

예시 설명:

return new ConcretePrototype(this); // 이 객체를 복사한 새 객체를 리턴!

 

 

③ Client (사용자)

  • 복제하려는 객체를 알고 있고,
  • 단순히 existing.clone()만 하면 복제 완료!
  • 클라이언트는 복사 로직을 몰라도 돼. 알아야 할 건 단 하나: "이 객체는 복제 가능하다."

[불변과 가변의 분리 목적의 디자인 패턴]

4. Template Method Pattern (템플릿 메서드 패턴)

"요리 레시피는 정해져 있지만, 재료만 다르게 만드는 방식이에요. 전체 흐름은 같지만 일부만 바꿀 수 있도록 설계한 패턴입니다."

 

: 템플릿 메서드는 부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드​(재정의)​할 수 있도록 하는 행동 디자인 패턴입니다.

 

❗ 문제

  • 비슷한 구조의 알고리즘이 여러 곳에 반복되며 중복 코드가 발생.
  • 알고리즘의 일부만 바꾸고 싶을 때 코드 복사 외에 마땅한 방법이 없음.

✅ 해결

  • 공통 알고리즘 구조를 상위 클래스에 두고, 가변적인 부분만 하위 클래스에서 오버라이딩하여 처리.

 

🔧 Java 예제 설명

abstract class DataProcessor { // 데이터 처리 흐름의 템플릿을 정의하는 추상 클래스

    public void process() { // 전체 데이터 처리 절차를 정의한 템플릿 메서드
        readData();         // 1단계: 데이터 읽기
        processData();      // 2단계: 데이터 처리
        saveData();         // 3단계: 데이터 저장
    }

    abstract void readData();     // 서브클래스가 구현할 데이터 읽기 단계
    abstract void processData();  // 서브클래스가 구현할 데이터 처리 단계
    abstract void saveData();     // 서브클래스가 구현할 데이터 저장 단계
}
  • process()는 공통된 처리 흐름이고, 구체적인 작업은 하위 클래스가 구현합니다.

 

🏢 실무 사례

CSV, Excel 파일 처리기: 전체 처리 로직은 같고, 읽고 파싱하는 부분만 서로 다름

 

🔗구조

  1. 추상 클래스는 알고리즘의 단계들의 역할을 하는 메서드들을 선언하며, 이러한 메서드를 특정 순서로 호출하는 실제 템플릿 메서드도 선언합니다. 단계들은 abstract로 선언되거나 일부 디폴트 구현을 갖습니다.
  2. 구상 클래스들은 모든 단계들을 오버라이드할 수 있지만 템플릿 메서드 자체는 오버라이드 할 수 없습니다.

5. Strategy Pattern (전략 패턴)

"게임 캐릭터가 무기를 바꾸듯이, 동작 방식(전략)을 상황에 맞게 바꾸는 패턴이에요."

: 전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴입니다.

 

❗ 문제

  • 동작(로직)을 바꾸려면 매번 if-else 조건문을 써야 하며, 로직이 하드코딩되어 유연하지 않음.

✅ 해결

  • 실행 알고리즘을 전략(Strategy) 객체로 분리하여 필요할 때 바꿔 끼울 수 있도록 설계.

 

🔧 Java 예제 설명

interface PaymentStrategy { // 결제 방법을 바꿀 수 있게 만든 인터페이스 (전략의 틀)
    void pay(int amount);   // 금액을 받아서 결제하는 메서드
}

class CardPayment implements PaymentStrategy { // 카드로 결제하는 방법 (전략 1)
    public void pay(int amount) { 
        System.out.println("Card: " + amount); // 카드 결제 실행
    }
}

class Order { // 결제 방법을 사용하는 주문 클래스 (전략을 사용하는 쪽)
    private PaymentStrategy strategy; // 어떤 결제 방법을 쓸지 저장

    public Order(PaymentStrategy strategy) { // 결제 방법을 외부에서 넣어줌
        this.strategy = strategy;
    }

    public void checkout(int amount) { // 결제 실행
        strategy.pay(amount); // 지금 선택된 결제 방법으로 결제
    }
}
  • PaymentStrategy를 바꿔주기만 하면 결제 방식이 달라집니다.

 

🏢 실무 사례

결제 방식 선택 시스템: 카드, 간편결제, 포인트 등 유연한 결제 처리

 

🔗구조

① Context: 전략을 사용하는 환경

  • 전략(=어떤 행동 방식을 담은 객체)을 내부에 들고 있음.
  • setStrategy()로 전략을 바꿀 수 있음.
  • doSomething()을 호출하면 내부 전략의 execute()가 실행됨.
  • 핵심: 자기가 뭘 해야 할지는 모르고, 전략에게 위임함.

 

② Strategy 인터페이스 (전략의 틀)

  • 다양한 전략들이 구현해야 하는 공통 메서드 정의 (execute(data)).
  • 어떤 전략이든 이 인터페이스만 따르고 있으면 Context에서 쓸 수 있어.

 

③ ConcreteStrategies: 실제 전략들

  • Strategy 인터페이스를 구현한 구체적인 전략 객체.
  • 예: BubbleSort, QuickSort, AES암호화, RSA암호화 등 서로 다른 실행 방식.

 

④ Client: 전략을 선택하는 사용자

  • 클라이언트가 SomeStrategy를 만들고 Context에 넘겨줌.
  • 그다음 Context.doSomething()을 호출하면, 전략이 실행됨.
  • 이후에 다른 전략으로 교체해서 다시 실행할 수도 있어.

 

⑤ 실행 흐름 예시

str = new SomeStrategy();
context.setStrategy(str);
context.doSomething();   // → SomeStrategy가 실행됨

other = new OtherStrategy();
context.setStrategy(other);
context.doSomething();   // → 전략이 바뀌어 OtherStrategy 실행
  • 전략을 바꿔도 Context 코드는 절대 안 바뀜!
  • 바꾸는 건 플러그만 교체하는 느낌이야.

6. Bridge Pattern (브릿지 패턴)

"리모컨(추상화)과 TV 본체(구현체)를 분리해서, 서로 독립적으로 교체 가능한 구조입니다."

 

❗ 문제

  • 추상 개념과 구체 구현이 강하게 결합되어 있어 둘 중 하나만 바꾸기 어려움.

✅ 해결

  • 추상화(Abstraction)구현(Implementor)을 분리해서 독립적으로 확장 가능하게 만듦.

이 접근 방식을 따르면, 색상 관련 코드를  Red  및  Blue 라는 두 개의 자식 클래스들이 있는 자체 클래스로 추출할 수 있습니다. 그런 다음  Shape  클래스는 색상 객체들 중 하나를 가리키는 참조 필드를 받습니다. 이제 모양은 연결된 색상 객체에 모든 색상 관련 작업을 위임할 수 있습니다. 이 참조는  Shape  및  Color  클래스들 사이의 브리지​(다리) 역할을 할 것입니다. 이제부터 새 색상들을 추가할 때 모양 계층구조를 변경할 필요가 없으며 그 반대의 경우도 마찬가지입니다.

 

🔧 Java 예제 설명

interface DrawingAPI { // 그리는 방법을 정의하는 인터페이스 (구현을 따로 분리함)
    void drawCircle(int x, int y, int r); // 원을 그리는 기능
}

abstract class Shape { // 도형의 기본 틀을 정의하는 클래스 (추상적인 역할)
    protected DrawingAPI api; // 그리는 방법을 따로 가지고 있음 (연결 다리 역할)

    public Shape(DrawingAPI api) { // 그리는 방법을 생성할 때 넣어줌
        this.api = api;
    }

    abstract void draw(); // 도형을 그리는 동작은 하위 클래스가 직접 만듦
}
  • Shape는 무엇을 그릴지 정의하고, DrawingAPI는 어떻게 그릴지 구현합니다.

 

🏢 실무 사례

UI 렌더링 구조: 하나의 버튼 추상화가 Windows/Mac/Android에서도 다르게 구현됨

 

🔗구조

① Abstraction (기능의 추상화)

  • 클라이언트가 사용하는 기능을 정의한 클래스야.
  • 내부에 i: Implementation 이라는 구현체를 갖고 있어.
  • 메서드를 호출하면 실제 구현체의 기능을 호출하게 돼.

 

② Implementation (인터페이스)

  • 실제 동작을 정의하는 인터페이스.
  • method1(), method2()처럼 실제 기능을 추상적으로 정의해.
  • Abstraction에서는 어떤 구현이 오든 이 인터페이스만 보고 호출함.

 

③ Concrete Implementations (구현체)

  • Implementation 인터페이스를 구현한 실제 클래스들이야.
  • 예: SamsungTV, LGTV, YouTubePlayer, NetflixPlayer 등
  • 실제 동작을 여기서 수행함.

 

④ Refined Abstraction (확장된 기능)

  • Abstraction을 상속해서 기능을 더 확장한 클래스야.
  • 필요한 경우에만 만들면 돼. (featureN() 같은 고급 기능 추가 가능)

 

⑤ Client (사용자)

  • 클라이언트는 Abstraction만 알고 있음.
  • 실제 구현체가 뭐든 상관없이 feature1()처럼 기능만 호출하면 됨.
  • 내부적으로는 Abstraction이 구현체의 method1() 같은 걸 대신 호출해줘.

 

사진 및 내용 출처: https://refactoring.guru/ko/design-patterns/bridge

 

디자인 패턴 핵심 정리


재사용 (Reuse)

  • Singleton
    하나의 인스턴스만 생성 → 전역에서 동일 객체 재사용
  • Flyweight
    공유 가능한 객체를 풀(pool)로 관리 → 여러 클라이언트가 n개의 인스턴스 재사용
  • Prototype
    기존 객체를 복제(clone())하여 새로운 객체 생성 → 객체 생성 비용 절감

불변과 가변의 분리

  • Template Method
    알고리즘의 불변 골격(템플릿)을 상위 클래스에 정의
    → 가변 부분은 하위 클래스에서 구현
  • Strategy
    가변 로직(알고리즘)을 객체로 캡슐화 → 런타임에 주입(포함)으로 변경 가능
  • Bridge
    Strategy + Template Method 결합
    → 구현부(가변)와 추상부(불변)를 분리하여 독립 확장
  • Decorator
    핵심 기능은 유지하면서 부가기능을 동적으로 조합
    → 상속+포함으로 기능 확장
  • Proxy
    Decorator와 유사하게 원본 객체에 ‘같은 옷’을 입힘
    → 접근 제어, 지연 로딩, 캐싱 등

기타 패턴

  • Iterator
    컬렉션 내부 구조를 모르는 채로 순회만 수행 → “묻지마 반복”
  • Future
    비동기 작업 결과를 담을 빈 상자(프라미스) 먼저 반환
    → 나중에 실제 결과 채워넣기

✅ 요약 정리

Singleton 인스턴스 1개 재사용
Flyweight n개의 공유 객체 재사용
Prototype clone()으로 객체 복제
Template Method 불변 골격(상위) + 가변 내용(하위)
Strategy 가변 로직을 객체 주입(포함)
Bridge Strategy + Template Method 결합
Decorator 핵심 기능 + 부가기능 동적 조합
Proxy 원본에 같은 옷 입히기(접근 제어, 캐싱 등)
Iterator “묻지마” 컬렉션 순회
Future 빈 상자 반환 → 나중에 결과 채움(비동기 처리)

✍️ 느낀점

패턴 하나하나를 보면서 ‘아, 이거 나도 썼던 방식이네’ 싶었던 순간이 꽤 많았습니다.
특히 Decorator랑 Proxy는 실제 코드에서 거의 비슷한 맥락으로 쓰여서, 그 차이를 명확히 이해하니 리팩터링 할 때 헷갈리던 부분이 선명해졌습니다.
Bridge 패턴처럼 두 가지 패턴을 결합하는 형태는 처음엔 복잡해 보이지만, 막상 적용하면 구조가 더 유연해져서 다음 프로젝트에서는 꼭 써보고 싶다는 생각이 들었습니다.
결국 디자인 패턴은 ‘내 코드를 읽는 사람을 위한 친절한 설계도’ 같아 보였습니다.
패턴을 알면 코드가 더 깔끔해지고, 코드 리뷰어 간의 공감대도 빠르게 형성될 것 같았습니다.

 

✅ 인터페이스 (Interface)

정의

  • 추상 메서드의 집합
  • 구현이 없는 껍데기 역할
  • 추상 클래스보다 추상화 수준이 높음
  • 다중 상속 가능
  • 두 대상(클래스 간)의 중간 매개체 역할

✅인터페이스의 장점

  1. 표준화 가능
    → 다양한 클래스들이 동일한 방식으로 동작하도록 강제
  2. 관계 형성 및 유연한 변경 가능
    → 코드 결합도를 낮추고 유연한 설계 가능
  3. 독립적인 개발 가능
    → 구현체가 달라도 인터페이스만 맞추면 개발 가능

✅ 인터페이스 타입의 활용

  • 매개변수 타입:
    → 인터페이스 타입으로 선언하면,
    해당 인터페이스를 구현한 객체를 모두 받을 수 있음
  • 반환타입:
    → 인터페이스를 구현한 클래스의 객체를 반환 가능
    → 유연한 코드 작성 가능 (다형성 활용)
void connect(USB device); // USB를 구현한 어떤 장치든 연결 가능
USB getDevice();          // 다양한 USB 구현체 반환 가능

 


🎨 디자인 패턴 (Design Patterns)

1. Singleton 패턴

  • 정의: 프로그램 전체에서 인스턴스를 하나만 만들도록 보장
  • 목적: 동일 객체를 재사용해 리소스 절약
public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

2. Flyweight 패턴

  • 정의: 공유 가능한 객체를 여러 개 재사용
  • 사용처: 게임 캐릭터, 글꼴, UI 아이콘 등 경량 객체

3. Prototype 패턴

  • 정의: 기존 객체를 복제하여 새 객체 생성
  • 방법: clone() 메서드를 활용
  • 사용처: 객체 생성 비용이 클 때 유용

🧷 불변과 가변의 분리

1. Template Method 패턴

  • 정의: 고정된 틀(불변)은 상위 클래스에,
    가변적 내용은 하위 클래스에서 완성
  • 예시: 알고리즘의 골격은 정해져 있으나, 세부 내용은 다름

2. Strategy 패턴

  • 정의: 가변적인 부분을 객체로 주입(포함)
  • 특징: 실행 중 전략 변경 가능 → 유연성↑
  • 사용처: 정렬 방식, 할인 정책, 경로 탐색 등

3. Future 패턴

  • 정의: 미래에 완성될 값을 담을 빈 객체를 먼저 반환
  • 비유: 케이크 상자를 먼저 받고, 나중에 케이크를 채움
  • 사용처: 비동기 처리, 멀티스레딩 환경

✅ 요약 정리

인터페이스 추상 메서드의 집합, 다중 상속 가능, 중간 역할
장점 표준화, 유연한 변경, 독립 개발 가능
활용 매개변수/반환타입으로 다형성 구현
Singleton 인스턴스 1개 재사용
Flyweight 다수 객체 공유 재사용
Prototype 객체 복제 (clone())
Template Method 불변 틀 + 가변 내용 (상속 기반)
Strategy 가변 로직 주입 (포함 기반)
Future 비동기용 미리 반환 객체 (케이스 상자 개념)

+ Recent posts