[멋사 클라우드 5기] Day 14 - Modeling, JDBC 이해하기

2026. 2. 9. 19:10·Learning Log

최근 Java와 백엔드 관련 여러 개념들을 배우고 있었는데,
이번 실습을 통해 그 개념들을 조금 더 구체화할 수 있었다.
헷갈리던 부분들도 정리되면서 이해가 한층 또렷해진 느낌이다.
Workbench에서 EER Diagram을 직접 그려보며 모델링에 대해 고민해보고,
JDBC를 이용해 자바와 데이터베이스를 연결한 뒤 DML을 실행해보는 방식으로 실습을 진행했다.
오늘도 중요한 개념들이 많이 나와 정리해본다.


1. 주문 도메인으로 이해하는 데이터 모델링과 정규화

데이터베이스 설계를 배우면서 아래와 같은 의문점들이 생겼다.

  • 중간 테이블이 진짜 필요한 걸까?
  • 배열로 한 번에 넣으면 안 되나?
  • 정규화는 도대체 뭘 해주는 걸까?

주문 시스템을 예제로 
데이터 모델링과 정규화에 대한 이해를 넓혀보자...


1-1. 주문과 메뉴는 왜 다대다 관계일까?

도메인:

  • 하나의 주문(order)에는 여러 메뉴(menu)가 담길 수 있다
  • 하나의 메뉴(menu)는 여러 주문(order)에 등장할 수 있다

예시:

주문 포함된 메뉴
주문 1 치킨, 콜라
주문 2 콜라

이 관계를 보면:

  • 주문 1 → 메뉴 여러 개 (1:N)
  • 콜라 → 주문 여러 개 (1:N)

즉,

주문 ↔ 메뉴는 서로 여러 개를 가질 수 있는 관계
→ 다대다(M:N) 관계


1-2. 그럼 orders 테이블에 menu_ids 배열을 넣으면 안 될까?

예를 들어 이렇게 설계할 수도 있다.

menus
------------------------
menu_id | name
------------------------
1       | 치킨
2       | 콜라
orders
--------------------------------
order_id | menu_ids
--------------------------------
1        | [1, 2]
2        | [2]

겉보기에는 간단해 보이지만, 실제로는 심각한 문제가 생긴다.


문제 1. 검색이 매우 느려진다

“콜라(menu_id = 2)가 포함된 주문을 찾아라”

배열 구조에서는:

  • 모든 주문을 하나씩 열어서
  • 배열 안에 2가 있는지 확인해야 한다

→ 전체 테이블 스캔 발생
→ 데이터가 많아지면 성능 급락


문제 2. 데이터 무결성을 보장할 수 없다

예:

  • menus에서 2번(콜라)을 삭제
  • 그런데 orders의 배열에는 여전히 2가 남아 있음

→ 존재하지 않는 메뉴를 참조하는
유령 데이터 발생


문제 3. 수량이나 옵션을 저장하기 어렵다

예:

  • 치킨 2마리
  • 콜라 1개

배열 구조에서는 표현이 애매해진다.

[1, 1, 2] ?
[[1,2], [2,1]] ?

→ 구조가 점점 복잡해짐


1-3. 그래서 등장하는 중간 테이블 (order_items)

다대다 관계는 DB에서 직접 표현할 수 없기 때문에
중간 테이블을 사용해 1:N 관계로 쪼갠다.

orders
menus
order_items   ← 중간 테이블

예:

order_items
--------------------------------------
order_id | menu_id | quantity
--------------------------------------
1        | 1       | 2
1        | 2       | 1
2        | 2       | 1

이제:

  • 주문 1에 치킨 2개, 콜라 1개 저장 가능
  • 검색, 집계, 무결성 모두 해결됨

1-4. 정규화란 무엇인가?

데이터 중복을 줄이고, 이상현상을 막기 위해 테이블을 나누는 과정

 

정규화의 목적:

  • 중복 제거
  • 데이터 일관성 유지
  • 수정/삭제 시 오류 방지

정규화를 통해 데이터를 안전하고 일관되게 관리할 수 있다


1-5. 정규화 1 → 2 → 3 단계 예시

수강 시스템을 예제로 보자.
아직 정규화 이전이기 때문에 개선의 여지가 있다.

비정규 테이블

enrollments
-----------------------------------------
student_id | student_name | subjects
-----------------------------------------
1          | 철수         | 수학, 영어
2          | 영희         | 영어

 

가장 눈에띄는 문제점은 subjects에 여러 값이 들어있는 것이다.


1NF (제1정규형)

한 칸에는 하나의 값만

enrollments
-----------------------------------------
student_id | student_name | subject
-----------------------------------------
1          | 철수         | 수학
1          | 철수         | 영어
2          | 영희         | 영어

→ 1NF 만족

하지만... 철수 이름이 여러 번 반복된다


2NF (제2정규형)

복합키의 일부에만 의존하는 컬럼 제거

PK: (student_id, subject)

현재 enrollments 테이블에서는 student_id 와 subject의 조합으로만 유일성이 보장되는 상황이다.

(2NF는 복합키가 있을 때만 의미가 있는 정규형이기 때문에 위와 같은 상황으로 가정)

그런데...

student_name은 student_id만 알면 결정됨

key로 사용되는 column이 아닌 student_name은 사실 student_id에 의존한다.

복합키인 student_id와 subject 둘 다 필요한게 아닌, subject_id만 알면된다는 점에서
→ 부분 종속 발생
→ 2NF 위반

→ subject는 불필요한 column 으로 제거 대상이다

해결

students
-------------------------
student_id | student_name
-------------------------
1          | 철수
2          | 영희
enrollments
-------------------------
student_id | subject
-------------------------
1          | 수학
1          | 영어
2          | 영어

→ 2NF 만족


3NF (제3정규형)

컬럼이 다른 컬럼에 의존하면 안 됨
(이행 종속 제거)

 

students 테이블에서 아래 같은 상황을 가정해보자

students
-----------------------------------------
student_id | department_id | department_name
-----------------------------------------
1          | 10            | 컴퓨터공학과

의존 관계:

student_id → department_id
department_id → department_name

student_id는 PK로 사용되는 column 이다.
department_id
는 student_id를 알면 알 수 있는 값이다. ✅

department_name은 department_id를 알아야 알 수 있는 값이다. ⚠️

그러면 department_name -> department_id -> student_id 라는 간접(이중) 의존관계가 형성된다.
→ 3NF 위반


해결

departments
------------------------------
department_id | department_name
------------------------------
10            | 컴퓨터공학과
students
------------------------------
student_id | department_id
------------------------------
1          | 10

→ 3NF 만족


1-6. 주문 시스템에서 정규화 적용 결과

정규화된 구조:

restaurants
menus
orders
order_items

핵심 역할

테이블 역할
restaurants 음식점 정보
menus 현재 메뉴 정보
orders 주문 대표 정보
order_items 주문 상세 (메뉴, 수량, 가격 등)

1-7. order_items에 가격이나 메뉴명을 저장해도 될까?

order_items
----------------------------------------------
order_id | menu_id | menu_name | unit_price
----------------------------------------------
1        | 10      | 치킨      | 18000

menu_id를 통해서 menu의 가격이나 이름을 알 수 있음에도 불구하고,

menu_name, unit_price 라는 column이 존재하고 있다. 중복아닐까?
이건 정규화 위반이 아니다. 의도된 설계다.

 

왜냐? 두 column은 스냅샷으로 사용되고 있기 때문이다.

컬럼 의미
menus.price 현재 가격
menus.name 현재 메뉴명
order_items.unit_price 주문 당시 가격
order_items.menu_name 주문 당시 메뉴명

메뉴명이나 가격이 menus 테이블에서 변경되더라도,

과거 주문 당시의 정보는 변경되지 않는다.

이처럼 과거 기록 보존을 목적으로 저장하는 것은 합리적이고 의도된 설계일 수 있다.


1-8. 그럼 NoSQL에서는 배열을 써도 될까?

NoSQL은 철학이 다르다.

RDBMS NoSQL
정규화 중심 읽기 성능 중심
테이블 분리 문서에 한 번에 저장
JOIN 사용 문서 단위 조회

NoSQL 주문 예시

{
  "order_id": 1,
  "items": [
    { "menu_name": "치킨", "price": 20000, "qty": 1 },
    { "menu_name": "콜라", "price": 2000, "qty": 2 }
  ]
}

특징:

  • 한 번의 조회로 주문 전체 확인 가능
  • 구조가 유연함
  • 대신 데이터 중복이 많아짐

 

2.JDBC 개념과 내부 설계, 성능까지 한 번에 이해하기

DB 모델링과 SQL을 배웠다면 Java로 데려와서 동작을 확인해봐야 한다.

이때 필요한 것이 JDBC다.


2-1. JDBC란 무엇인가

JDBC (Java Database Connectivity) 는

자바 프로그램에서 데이터베이스에 연결하고
SQL을 실행할 수 있게 해주는 표준 API

 

쉽게 말하면:

  • 자바 코드에서
  • DB에 접속하고
  • SQL을 실행하고
  • 결과를 받아오는

공식 통신 규격이다.


 

JDBC가 필요한 이유

DB는 종류가 다양하다.

  • MySQL
  • Oracle
  • PostgreSQL
  • SQL Server 등

만약 DB마다 자바 코드가 달라진다면 유지보수가 매우 어려워진다.

그래서 JDBC는 이런 구조를 만든다.

자바 코드
   ↓
JDBC 인터페이스
   ↓
DB 드라이버 (MySQL, Oracle 등)
   ↓
데이터베이스

자바 코드는 JDBC만 사용하고
실제 DB 연결은 드라이버가 담당한다.


2-2. JDBC 기본 동작 흐름

JDBC 코드는 항상 같은 흐름을 가진다.

  1. DB 연결(Connection)
  2. SQL 준비(Statement / PreparedStatement)
  3. SQL 실행
  4. 결과 처리(ResultSet)
  5. 자원 해제(close)
Connection con =
    DriverManager.getConnection(url, user, password);

PreparedStatement ps =
    con.prepareStatement(
        "SELECT * FROM users WHERE id = ?"
    );

ps.setInt(1, 1);

ResultSet rs = ps.executeQuery();

while (rs.next()) {
    System.out.println(rs.getString("name"));
}

rs.close();
ps.close();
con.close();

 


2-3. JDBC 안에 숨어 있는 디자인 패턴

JDBC는 여러 가지 객체지향 설계 패턴이 적용된 구조다.

 

✔️ Strategy 패턴 (인터페이스 기반 설계)

JDBC의 핵심 객체들은 전부 인터페이스다.

Connection
Statement
PreparedStatement
ResultSet

실제 구현체는 DB 드라이버가 제공한다.

Connection (인터페이스)
   ↑
MySQLConnection
OracleConnection
PostgresConnection
  • 자바 코드는 Connection 인터페이스만 사용
  • 실제 동작은 DB마다 다른 구현체가 수행

Strategy 패턴 

행동을 인터페이스로 분리하고
실행 시점에 구현체를 선택하는 구조

효과

  • DB 교체가 쉬움
  • 코드 변경 최소화
  • 확장성 향상

✔️ Factory 패턴 (DriverManager)

JDBC 코드:

Connection con =
    DriverManager.getConnection(url, user, pass);

 

  • new Connection()을 통해 직접 생성하지 않음
  • DriverManager가 알아서 적절한 드라이버를 선택
  • Connection 객체를 생성해 줌

객체 생성을 대신 처리하는
→ Factory 패턴


 

2-4. PreparedStatement의 성능과 보안 이점

JDBC에서 권장되는 방식

Statement 대신 PreparedStatement 사용

 

❌ 잘못된 방식 (Statement)

String sql =
    "SELECT * FROM users WHERE id = " + id;

Statement stmt = con.createStatement();
stmt.executeQuery(sql);

만약 사용자가 이런 값을 보내면:

id = 1 OR 1=1
SELECT * FROM users WHERE id = 1 OR 1=1

→ 전체 사용자 조회
→ SQL Injection 발생

→ ☠️☠️☠️


✅ 안전한 방식 (PreparedStatement)

PreparedStatement ps =
    con.prepareStatement(
        "SELECT * FROM users WHERE id = ?"
    );

ps.setString(1, id);

이 경우

SELECT * FROM users WHERE id = '1 OR 1=1'

→ 문자열로 처리

SQL 구조와 데이터가 완전히 분리된다.


👍 성능이 좋아지는 이유

Statement 방식

매 실행마다:

  1. SQL 문자열 생성
  2. SQL 파싱
  3. 실행 계획 생성
  4. 실행

PreparedStatement 방식

  1. SQL 구조를 미리 컴파일
  2. 실행 계획 캐싱
  3. 값만 바꿔 재사용
구조는 재사용
값만 교체

→ 반복 쿼리에서 성능 향상


2-5. JDBC 트랜잭션과 AutoCommit

기본 동작

JDBC는 기본적으로:

autoCommit = true

SQL 한 줄 실행할 때마다 자동 commit

 

그래서 이런 일이 발생한다

환경 설정
Workbench AutoCommit OFF
JDBC 기본값 AutoCommit ON

→ Workbench에서는 commit 안 하면 반영 안 됨
→ JDBC에서는 바로 반영됨


트랜잭션 직접 제어 방법

기본 패턴:

Connection con = DriverManager.getConnection(...);

try {
    con.setAutoCommit(false);  // 트랜잭션 시작

    // 여러 SQL 실행

    con.commit();              // 성공 시 반영
} catch (Exception e) {
    con.rollback();            // 실패 시 되돌림
} finally {
    con.close();
}

실전 예시: 계좌 이체

try {
    con.setAutoCommit(false);

    // A 계좌 출금
    PreparedStatement ps1 =
        con.prepareStatement(
            "UPDATE account SET balance = balance - 1000 WHERE id = 1"
        );
    ps1.executeUpdate();

    // B 계좌 입금
    PreparedStatement ps2 =
        con.prepareStatement(
            "UPDATE account SET balance = balance + 1000 WHERE id = 2"
        );
    ps2.executeUpdate();

    con.commit();
} catch (Exception e) {
    con.rollback();
}

의미:

  • 둘 다 성공해야 commit
  • 하나라도 실패하면 rollback

→ 트랜잭션의 원자성 보장

'Learning Log' 카테고리의 다른 글

[멋사 클라우드 5기] Day 16 - 브라우저의 동작 원리: 주소창에서 화면 렌더링까지  (0) 2026.02.12
[멋사 클라우드 5기] Day 15 - MVC & JDBC 구조 톺아보기  (0) 2026.02.11
[멋사 클라우드 5기] Day 13 - DML, DDL, DCL, TCL  (0) 2026.02.08
[멋사 클라우드 5기] Day 12 - Join, Subquery, Union  (0) 2026.02.06
[멋사 클라우드 5기] Day 11 - DBMS 기초(2), DQL  (0) 2026.02.05
'Learning Log' 카테고리의 다른 글
  • [멋사 클라우드 5기] Day 16 - 브라우저의 동작 원리: 주소창에서 화면 렌더링까지
  • [멋사 클라우드 5기] Day 15 - MVC & JDBC 구조 톺아보기
  • [멋사 클라우드 5기] Day 13 - DML, DDL, DCL, TCL
  • [멋사 클라우드 5기] Day 12 - Join, Subquery, Union
allluck777
allluck777
allluck777
    • 분류 전체보기 (44) N
      • AWS (0)
      • Network (0)
      • Linux (0)
      • Docker (0)
      • Project (4)
        • CloudNote (4)
      • Learning Log (36) N
      • Lecture (3)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (3)
  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.6
allluck777
[멋사 클라우드 5기] Day 14 - Modeling, JDBC 이해하기
상단으로

티스토리툴바