JDBC_02) MVC 패턴
2025. 8. 27. 15:57

    <MVC 패턴의 이해>

- MVC 패턴이란?

MVC(Model-View-Controller) 패턴은 애플리케이션을 세 개의 계층으로 분리하여 개발하는 소프트웨어 아키텍처 패턴이다. 각 관심사에 따라 레이어를 분리함으로써 코드의 재사용성을 높이고 유지보수를 용이하게 만든다.

 

- MVC 각 계층의 역할

Model 계층

  • 데이터와 비즈니스 로직을 담당
  • VO(Value Object): 데이터를 담는 객체
  • DAO(Data Access Object): 데이터베이스 접근을 담당하는 객체

View 계층

  • 사용자 인터페이스를 담당
  • 사용자의 입력을 받고 결과를 출력하는 역할
  • 콘솔 출력, 웹 페이지 등이 해당

Controller 계층

  • 사용자의 요청을 처리하고 흐름을 제어
  • View에서 받은 데이터를 가공하여 Model에 전달
  • Model에서 받은 결과에 따라 적절한 View를 호출

MVP 패턴 아키텍처 ❘ 출처: https://itwiki.kr/w/MVC_%ED%8C%A8%ED%84%B4

 

 

- 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 역할이 모두 혼재...
        // 삭제 기능도 위와 유사하게 복잡한 구조...
    }
}

위와 같은 방식의 문제점:

  1. 단일 책임 원칙 위반: 하나의 클래스가 너무 많은 책임을 가짐
  2. 코드 중복: DB 연결 코드가 각 메서드마다 반복됨
  3. 유지보수 어려움: UI 변경 시 비즈니스 로직까지 수정 필요
  4. 테스트 어려움: UI, 비즈니스 로직, DB 로직이 결합되어 단위 테스트 불가능
  5. 재사용성 저하: 다른 프로젝트에서 특정 기능만 가져와 사용하기 어려움
  6. 확장성 문제: 새로운 기능 추가 시 기존 코드에 영향을 미칠 가능성 높음

    <프로젝트 구조 및 패키지 설계>

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 사용 순서

  1. JDBC Driver 등록
  2. Connection 생성
  3. PreparedStatement 생성
  4. SQL문 실행
  5. 결과 처리
  6. 트랜잭션 처리
  7. 리소스 반납

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