<MVC 패턴의 이해>
- MVC 패턴이란?
MVC(Model-View-Controller) 패턴은 애플리케이션을 세 개의 계층으로 분리하여 개발하는 소프트웨어 아키텍처 패턴이다. 각 관심사에 따라 레이어를 분리함으로써 코드의 재사용성을 높이고 유지보수를 용이하게 만든다.
- MVC 각 계층의 역할
Model 계층
- 데이터와 비즈니스 로직을 담당
- VO(Value Object): 데이터를 담는 객체
- DAO(Data Access Object): 데이터베이스 접근을 담당하는 객체
View 계층
- 사용자 인터페이스를 담당
- 사용자의 입력을 받고 결과를 출력하는 역할
- 콘솔 출력, 웹 페이지 등이 해당
Controller 계층
- 사용자의 요청을 처리하고 흐름을 제어
- View에서 받은 데이터를 가공하여 Model에 전달
- Model에서 받은 결과에 따라 적절한 View를 호출

- MVC 패턴을 사용하는 이유
MVC 패턴을 사용하지 않았을 때, 하나의 클래스에 모든 기능을 담아야 한다.
예시)
// MVC 패턴 미적용 시 - 하나의 클래스에서 모든 처리
public class MemberManager {
private Scanner sc = new Scanner(System.in);
public void memberManagementSystem() {
while(true) {
// ===== View 역할: 메뉴 출력 =====
System.out.println("========== 회원관리 프로그램 ==========");
System.out.println("1. 회원 추가 2. 회원 조회 3. 회원 수정 4. 회원 삭제 9. 종료");
System.out.print("메뉴 선택: ");
int choice = sc.nextInt();
sc.nextLine();
switch(choice) {
case 1: addMember(); break;
case 2: selectAllMembers(); break;
case 3: updateMember(); break;
case 4: deleteMember(); break;
case 9: System.out.println("프로그램 종료"); return;
}
}
}
public void addMember() {
// ===== View 역할: 입력 받기 =====
System.out.println("======== 회원 추가 ========");
.
.
.
// ===== Controller 역할: 입력값 검증 및 가공 =====
if(userId == null || userId.trim().isEmpty()) {
System.out.println("아이디는 필수 입력사항입니다.");
return;
}
.
.
.
.
// ===== Model 역할: DB 연결 및 데이터 처리 =====
Connection conn = null;
PreparedStatement pstmt = null;
int result = 0;
try {
.
.
.
.
.
}
// 실제 INSERT 쿼리 실행
String insertSql = "INSERT INTO MEMBER VALUES(SEQ_USERNO.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, ?, SYSDATE)";
pstmt = conn.prepareStatement(insertSql);
pstmt.setString(1, userId);
pstmt.setString(2, userPwd);
pstmt.setString(3, userName);
.
.
.
.
} catch (ClassNotFoundException e) {
.
.
.
.
}
// ===== View 역할: 결과 출력 =====
if(result > 0) {
System.out.println("========================================");
System.out.println(" 회원 등록 성공! ");
System.out.println("========================================");
System.out.println("등록된 정보:");
System.out.println("- 아이디: " + userId);
System.out.println("- 이름: " + userName);
System.out.println("- 성별: " + (gender.equals("M") ? "남성" : "여성"));
System.out.println("- 나이: " + age + "세");
System.out.println("- 이메일: " + email);
System.out.println("========================================");
} else {
System.out.println("========================================");
System.out.println(" 회원 등록 실패! ");
System.out.println(" 다시 시도해 주세요. ");
System.out.println("========================================");
}
}
public void selectAllMembers() {
// ===== View 역할: 조회 안내 메시지 =====
System.out.println("======== 전체 회원 조회 ========");
// ===== Model 역할: DB 연결 및 조회 =====
.
.
.
.
}
}
// ===== View 역할: 결과 출력 =====
if(memberList.isEmpty()) {
System.out.println("========================================");
System.out.println(" 등록된 회원이 없습니다. ");
System.out.println("========================================");
} else {
System.out.println("========================================");
System.out.println(" 전체 회원 목록 ");
System.out.println("========================================");
for(String member : memberList) {
System.out.println(member);
System.out.println("----------------------------------------");
}
System.out.println("총 " + memberList.size() + "명의 회원이 등록되어 있습니다.");
}
}
public void updateMember() {
// View, Controller, Model 역할이 모두 혼재...
// 수정 기능도 위와 유사하게 복잡한 구조...
}
public void deleteMember() {
// View, Controller, Model 역할이 모두 혼재...
// 삭제 기능도 위와 유사하게 복잡한 구조...
}
}
위와 같은 방식의 문제점:
- 단일 책임 원칙 위반: 하나의 클래스가 너무 많은 책임을 가짐
- 코드 중복: DB 연결 코드가 각 메서드마다 반복됨
- 유지보수 어려움: UI 변경 시 비즈니스 로직까지 수정 필요
- 테스트 어려움: UI, 비즈니스 로직, DB 로직이 결합되어 단위 테스트 불가능
- 재사용성 저하: 다른 프로젝트에서 특정 기능만 가져와 사용하기 어려움
- 확장성 문제: 새로운 기능 추가 시 기존 코드에 영향을 미칠 가능성 높음
<프로젝트 구조 및 패키지 설계>
1. DB 설계 예시.
CREATE TABLE MEMBER(
USER_NO NUMBER PRIMARY KEY,
USER_ID VARCHAR2(30) NOT NULL UNIQUE,
USER_PWD VARCHAR2(30) NOT NULL,
USER_NAME VARCHAR2(20) NOT NULL,
GENDER CHAR(1) CHECK(GENDER IN('M','F')),
AGE NUMBER,
EMAIL VARCHAR2(30),
PHONE CHAR(11),
ADDRESS VARCHAR2(100),
HOBBY VARCHAR2(50),
EMROLL_DATE DATE DEFAULT SYSDATE NOT NULL
);
CREATE SEQUENCE SEQ_USERNO NOCACHE;
2. 프로젝트 및 패키지 구조
com.kh.jdbc
├── run
│ └── Run.java // 메인 실행 클래스
├── view
│ └── MemberMenu.java // View 계층
├── controller
│ └── MemberController.java // Controller 계층
└── model
├── vo
│ └── Member.java // Value Object
└── dao
└── MemberDao.java // Data Access Object
<Model 계층 구현>
- VO(Value Object) 클래스 구현
Member.java
public class Member {
private int userNo;
private String userId;
private String userPwd;
private String userName;
private String gender;
private int age;
private String email;
private String phone;
private String address;
private String hobby;
private LocalDateTime emrollDate;
// 기본 생성자, 매개변수 생성자
// Getter/Setter 메서드
// toString() 오버라이딩
}
VO 클래스의 특징
- DB 테이블의 컬럼과 일대일 매핑되는 필드 구성
- 캡슐화를 위한 private 접근제한자 사용
- 데이터 접근을 위한 Getter/Setter 메서드 제공
DAO(Data Access Object) 클래스 구현
JDBC 사용 순서
- JDBC Driver 등록
- Connection 생성
- PreparedStatement 생성
- SQL문 실행
- 결과 처리
- 트랜잭션 처리
- 리소스 반납
MemberDao.java
회원 추가 기능 구현
public int insertMember(Member m) {
int result = 0;
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "INSERT INTO MEMBER VALUES(SEQ_USERNO.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, ?, SYSDATE)";
try {
// 1. JDBC Driver 등록
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2. Connection 생성
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","C##JDBC","JDBC");
conn.setAutoCommit(false);
// 3. PreparedStatement 생성 및 값 설정
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserId());
pstmt.setString(2, m.getUserPwd());
pstmt.setString(3, m.getUserName());
pstmt.setString(4, m.getGender());
pstmt.setInt(5, m.getAge());
pstmt.setString(6, m.getEmail());
pstmt.setString(7, m.getPhone());
pstmt.setString(8, m.getAddress());
pstmt.setString(9, m.getHobby());
// 4. SQL문 실행
result = pstmt.executeUpdate();
// 5. 트랜잭션 처리
if(result > 0) {
conn.commit();
} else {
conn.rollback();
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 리소스 반납
try {
if(pstmt != null) pstmt.close();
if(conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
전체 회원 조회 기능 구현
public ArrayList<Member> selectMemberList(){
ResultSet rset = null;
ArrayList<Member> list = new ArrayList<>();
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "SELECT * FROM MEMBER";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","C##JDBC","JDBC");
pstmt = conn.prepareStatement(sql);
rset = pstmt.executeQuery();
// ResultSet에서 데이터 추출하여 Member 객체에 담기
while(rset.next()) {
Member m = new Member();
m.setUserNo(rset.getInt("USER_NO"));
m.setUserId(rset.getString("USER_ID"));
m.setUserPwd(rset.getString("USER_PWD"));
m.setUserName(rset.getString("USER_NAME"));
m.setGender(rset.getString("GENDER"));
m.setAge(rset.getInt("AGE"));
m.setEmail(rset.getString("EMAIL"));
m.setPhone(rset.getString("PHONE"));
m.setAddress(rset.getString("ADDRESS"));
m.setHobby(rset.getString("HOBBY"));
m.setEmrollDate(rset.getTimestamp("EMROLL_DATE").toLocalDateTime());
list.add(m);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
try {
if(rset != null) rset.close();
if(pstmt != null) pstmt.close();
if(conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return list;
}
회원 정보 수정 기능 구현
public int updateMember(Member m) {
int result =0;
Connection conn = null;
PreparedStatement pstmt = null;
// 실행할 sql(sql뒤에 "" 안에 ; 없어야한다.)
String sql = "UPDATE MEMBER SET EMAIL=?, PHONE=?, ADDRESS=?, HOBBY=? WHERE USER_ID=?";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","C##JDBC","JDBC");
conn.setAutoCommit(false);
// 아prepareStatement> 직 미완성 sql문으로 ?의 값을 채어줘야한다.
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getEmail());
pstmt.setString(2, m.getPhone());
pstmt.setString(3, m.getAddress());
pstmt.setString(4, m.getHobby());
pstmt.setString(5, m.getUserId());
result = pstmt.executeUpdate();
if(result > 0) {
conn.commit();
}else {
conn.rollback();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
회원 탈퇴 기능 구현
public int deleteMember(Member m) {
int result =0;
Connection conn = null;
PreparedStatement pstmt = null;
// 실행할 sql(sql뒤에 "" 안에 ; 없어야한다.)
String sql = "DELETE FROM MEMBER WHERE USER_ID=? AND USER_PWD=?";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","C##JDBC","JDBC");
conn.setAutoCommit(false);
// 아prepareStatement> 직 미완성 sql문으로 ?의 값을 채어줘야한다.
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserId());
pstmt.setString(2, m.getUserPwd());
result = pstmt.executeUpdate();
if(result > 0) {
conn.commit();
}else {
conn.rollback();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
<Controller 계층 구현>
- Controller의 역할
Controller는 View에서 전달받은 데이터를 가공하여 DAO에 전달하고, DAO에서 받은 결과에 따라 적절한 View를 호출하는 역할을 담당한다.
MemberController.java
import java.util.List;
public class MemberController {
private MemberDao md;
public MemberController() {
super();
this.md = new MemberDao();
}
/*
* 사용자의 추가 요청을 처리하는 매서드
* userId ~ hobby : 사용자가 입력한 정보를 매개변수로 받음.
*/
public void insertMember(String userId, String userPwd, String userName, String gender, String age, String email, String phone,
String address, String hobby) {
//View로부터 전달받은 값을 바로 dao에 전달x
//vo에 잘 담아서 전달
Member m = new Member(userId, userPwd, userName, gender,
Integer.parseInt(age), email, phone, address, hobby);
int result = md.insertMember(m);
if(result > 0) {
//성공화면
new MemberMenu().displaySuccess("성공적으로 회원이 추가되었습니다.");
} else {
//실패화면
new MemberMenu().displaySuccess("회원추가에 실패하였습니다.");
}
}
// 회원을 전체 조회
public void selectMemberAll() {
List<Member> list = md.selectMemberList();
if(list.isEmpty()) {
new MemberMenu().displayNoData("회원 목록 조회결과가 없습니다.");
}else {
new MemberMenu().displayList(list, "회원 목록");
}
}
// userId, email, phone, address, hobby를 전달받아 Member를 수정하는 매서드
public void updateMember(String userId, String email, String phone, String address, String hobby) {
Member m = new Member();
m.setUserId(userId);
m.setEmail(email);
m.setPhone(phone);
m.setAddress(address);
m.setHobby(hobby);
int result = md.updateMember(m);
if(result > 0) {
new MemberMenu().displaySuccess("성공적으로 회원 정보를 수정하였습니다.");
} else {
new MemberMenu().displayFail("회원 정보를 수정하는데 실패하였습니다.");
}
}
public void deleteMember(String userId, String userPwd) {
Member m = new Member();
m.setUserId(userId);
m.setUserPwd(userPwd);
int result = md.deleteMember(m);
if(result > 0) {
new MemberMenu().displaySuccess("회원 탈퇴를 완료 했습니다.");
} else {
new MemberMenu().displayFail("잘못된 아이디 또는 비밀번호 입니다.");
}
}
}
<View 계층 구현>
- View의 역할
사용자와의 상호작용을 담당하며, 사용자의 입력을 받고 결과를 출력하는 역할을 수행한다.
import java.util.List;
import java.util.Scanner;
public class MemberMenu {
private Scanner sc;
private MemberController mc;
public MemberMenu() {
super();
this.sc = new Scanner(System.in);
this.mc = new MemberController();
}
/*
* 사용자가 보게 될 첫 화면 (메인화면)
*/
public void mainMenu() {
while(true){
System.out.println("========== 회원관리 프로그램 ==========");
System.out.println("1. 회원 추가");
System.out.println("2. 회원 전체 조회");
System.out.println("3. 회원 정보 변경");
System.out.println("4. 회원 탈퇴");
System.out.println("9. 프로그램 종료");
System.out.print("메뉴 입력 : ");
int selectNum = sc.nextInt();
sc.nextLine();
switch(selectNum) {
case 1: insertMember(); break;
case 2: mc.selectMemberAll(); break;
case 3: updateMember(); break;
case 4: deleteMember(); break;
case 9: System.out.println("프로그램을 종료합니다."); return;
default: System.out.println("잘못된 입력입니다. 다시 입력하세요.");
}
System.out.println();
}
}
public void insertMember() {
System.out.println("======== 회원 추가 ========");
// id~취미까지 전부 입력받아서 회원 추가
System.out.print("아이디 : ");
String userId = sc.nextLine();
System.out.print("비밀번호 : ");
String userPwd = sc.nextLine();
System.out.print("이름 : ");
String userName = sc.nextLine();
System.out.print("성별(M,F) : ");
String gender = sc.nextLine();
System.out.print("나이 : ");
String age = sc.next();
sc.nextLine();
System.out.print("이메일 : ");
String email = sc.nextLine();
System.out.print("전화번호(-제외) : ");
String phone = sc.nextLine();
System.out.print("주소 : ");
String address = sc.nextLine();
System.out.print("취미(,로 구분) : ");
String hobby = sc.next();
mc.insertMember(userId, userPwd, userName, gender, age, email, phone, address, hobby);
}
public void updateMember() {
System.out.println("======== 회원 정보 변경 ========");
//어떤 회원의 정보를 받아야 UPDATE 할 수 있는가 ? -> USED_ID (UNIQUE 제약조건 적합)
//변경할 정보를 입력 -> 이메일, 전화번호, 주소, 취미
System.out.print("정보를 수정할 아이디 : ");
String userId = sc.nextLine();
System.out.print("변경할 이메일 : ");
String email = sc.nextLine();
System.out.print("변경할 전화번호(-제외) : ");
String phone = sc.nextLine();
System.out.print("변경할 주소 : ");
String address = sc.nextLine();
System.out.print("변경할 취미(,로 구분) : ");
String hobby = sc.next();
mc.updateMember(userId, email, phone, address, hobby);
}
public void deleteMember() {
System.out.println("======== 회원 탈퇴 ========");
System.out.print("탈퇴할 아이디 : ");
String userId = sc.nextLine();
System.out.print("비밀번호 확인 : ");
String userPwd = sc.nextLine();
mc.deleteMember(userId, userPwd);
}
//============================응답 화면===============================
// 서비스요청 처리 후 성공했을 때 사용자가 보게될 화면
// msg : 기능 별 성공 메세지
public void displaySuccess(String msg) {
System.out.println("\n서비스 요청 성공 : " + msg);
}
public void displayFail(String msg) {
System.out.println("\n서비스 요청 실패 : " + msg);
}
public void displayNoData(String msg) {
System.out.println("\n" + msg);
}
public void displayList(List list, String title) {
System.out.println("======== " + title + " ========");
for(Object o : list) {
System.out.println(o);
}
}
}
<실행 결과>
========== 회원관리 프로그램 ==========
1. 회원 추가
2. 회원 전체 조회
3. 회원 정보 변경
4. 회원 탈퇴
9. 프로그램 종료
메뉴 입력 : 1
======== 회원 추가 ========
아이디 : hong123
비밀번호 : pass123
이름 : 홍길동
성별(M,F) : M
나이 : 25
이메일 : hong@test.com
전화번호(-제외) : 01012345678
주소 : 서울시 강남구 테헤란로
취미(,로 구분) : 독서,영화감상
서비스 요청 성공 : 성공적으로 회원이 추가되었습니다.

========== 회원관리 프로그램 ==========
1. 회원 추가
2. 회원 전체 조회
3. 회원 정보 변경
4. 회원 탈퇴
9. 프로그램 종료
메뉴 입력 : 2
======== 회원 목록 ========
[userNo=1, userId=hong123, userPwd=pass123, userName=홍길동, gender=M, age=25, email=hong@test.com, phone=01012345678, address=서울시 강남구 테헤란로, hobby=독서,영화감상, EMROLL_DATE=2025-08-27T15:54:48]
- MVC 패턴의 장점
- 관심사 분리: 각 계층이 독립적인 역할 수행
- 재사용성: Controller와 DAO의 메서드 재활용 가능
- 유지보수성: 특정 계층의 변경이 다른 계층에 미치는 영향 최소화
- 테스트 용이성: 각 계층별 독립적인 테스트 가능
정리 및 요약
MVC 패턴을 활용한 JDBC 프로그래밍을 통해 체계적인 데이터베이스 연동 애플리케이션 개발 방법을 학습했다. 각 계층의 명확한 역할 분리를 통해 확장 가능하고 유지보수하기 쉬운 코드 구조를 구현할 수 있었다.
특히 PreparedStatement를 활용한 안전한 SQL 실행, 명시적인 트랜잭션 처리, 체계적인 리소스 관리 등 실제 프로젝트에서 중요한 개발 원칙들을 실습을 통해 체득할 수 있었다.
향후 웹 애플리케이션 개발 시에도 이러한 MVC 패턴의 기본 원리가 동일하게 적용되므로, 이번 학습이 탄탄한 기반이 될 것이다.
'JDBC' 카테고리의 다른 글
| [JAVA_JDBC] 프로그램 제작 (2) | 2025.09.05 |
|---|---|
| JDBC_01) JDBC 데이터베이스 연결과 기본 CRUD 작업 (5) | 2025.08.26 |