최근 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 코드는 항상 같은 흐름을 가진다.
- DB 연결(Connection)
- SQL 준비(Statement / PreparedStatement)
- SQL 실행
- 결과 처리(ResultSet)
- 자원 해제(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 방식
매 실행마다:
- SQL 문자열 생성
- SQL 파싱
- 실행 계획 생성
- 실행
PreparedStatement 방식
- SQL 구조를 미리 컴파일
- 실행 계획 캐싱
- 값만 바꿔 재사용
구조는 재사용
값만 교체
→ 반복 쿼리에서 성능 향상
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 |
