본문 바로가기
Java/JDBC

JDBC) MVC pattern2 (회원관리 프로젝트, 회원가입 처리)

by 박채니 2022. 5. 10.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


MVC pattern 2

run → 실행 메소드가 있는 실행 클래스

view → 사용자가 보게 될 메뉴 제공

controller → 사용자가 메뉴 선택하면 controller에서 요청을 받아 시작

* service → 업무로직 business logic 수행

dao(Data Access Object) → DB에 접근하기 위한 클래스

* JdbcTemplate → 각종 중복 코드 모음 (Connection 객체 생성, close, commit, rollback)

 

 Service 클래스
 - 업무로직 business logic 수행
 --------------SERVICE 시작--------------------
 1. jdbc driver class 등록 
 2. Connection객체 생성 & setAUtoCommit(false)
 -----------------DAO 시작---------------------
 3. PreparedStatment객체 생성 & 미완성쿼리 값대입
 4. 실행 & 결과 값 받기 (ResultSet, int)
 5. 자원반납(pstmt, rset)
 ------------------DAO 끝----------------------
 6. 트랜잭션 처리
 7. 자원반납(conn)
 ---------------SERVICE 끝---------------------


Member(VO)

package member.model.vo;

import java.sql.Date;
import java.sql.Timestamp;


/**
 * VO
 * - Value Object
 * - Database 테이블과 대응
 * - field는 테이블의 컬럼과 대응
 * - VO 객체 하나가 테이블의 레코드와 대응
 *
 */
public class Member {
	/*
		ID       NOT NULL VARCHAR2(20)  
		NAME     NOT NULL VARCHAR2(100) 
		GENDER            CHAR(1)       
		BIRTHDAY          DATE          
		EMAIL    NOT NULL VARCHAR2(500) 
		POINT             NUMBER        
		REG_DATE          TIMESTAMP(6) 
	 */
	
	private String id;
	private String name;
	private String gender;	// char여도 String으로 처리! 나중에 복잡해짐
	private Date birthday;
	private String email;
	private int point;
	private Timestamp regDate;
	
	public Member(String id, String name, String gender, Date birthday, String email, int point, Timestamp regDate) {
		super();
		this.id = id;
		this.name = name;
		this.gender = gender;
		this.birthday = birthday;
		this.email = email;
		this.point = point;
		this.regDate = regDate;
	}
	
	public Member(String id, String name, String gender, Date birthday, String email) {
		this.id = id;
		this.name = name;
		this.gender = gender;
		this.birthday = birthday;
		this.email = email;
	}

	public Member() {
		super();
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public int getPoint() {
		return point;
	}

	public void setPoint(int point) {
		this.point = point;
	}

	public Timestamp getRegDate() {
		return regDate;
	}

	public void setRegDate(Timestamp regDate) {
		this.regDate = regDate;
	}

	@Override
	public String toString() {
		return "Member [id=" + id + ", name=" + name + ", gender=" + gender + ", birthday=" + birthday + ", email="
				+ email + ", point=" + point + ", regDate=" + regDate + "]";
	}
}

 

MemberRun

package member.run;

import member.view.MemberMenu;

public class MemberRun {

	public static void main(String[] args) {
		new MemberMenu().mainMenu();
		
		System.out.println("이용해주셔서 감사합니다.");
	}
}

 

MemberMenu(View)

package member.view;

import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Scanner;

import member.controller.MemberController;
import member.model.vo.Member;

public class MemberMenu {
	
	private MemberController memberController = new MemberController();
	private Scanner sc = new Scanner(System.in);
	private SimpleDateFormat simpleRegDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
	
	public void mainMenu() {
		String menu = "\n******** 회원 정보 관리2-1 ********\n"
				+ "1. 전체 조회\n"
				+ "2. 아이디 조회\n"
				+ "3. 이름 검색 \n"
				+ "4. 회원가입\n"
				+ "5. 회원 정보 변경\n"
				+ "6. 회원 탈퇴\n"
				+ "0. 프로그램 종료\n"
				+ "*****************************\n"
				+ "선택 : ";
		
		while(true) {
			System.out.print(menu);
			String choice = sc.next();
			
			Member member = null;
			List<Member> members = null;
			int result = 0;
			String id = null;
			String name = null;
			
			switch(choice) {
			case "1" : 
				members = memberController.findAll();
				printMembers(members);
				break;
			case "2" : 
				id = findId();
				member = memberController.findById(id);
				printMember(member);
				break;
			case "3" : 
				name = findName();
				members = memberController.findByName(name);
				printMembers(members);
				break;
			case "4" : 
				member = inputMember();
				result = memberController.inputMember(member);
				displayResult(result, "회원가입");
				break;
			case "5" : 
				updateMemberInfo();
				break;
			case "6" : 
				id = findId();
				result = memberController.deleteMember(id);
				displayResult(result, "회원탈퇴");
				break;
			case "0" : return;
			default : System.out.println("잘못 입력하셨습니다.");
			}
		}
	}

	// 전체 회원 조회
	private void printMembers(List<Member> members) {
		if(members == null || members.isEmpty()) {
			System.out.println("> 조회된 결과가 없습니다.");
		} else {
			System.out.printf("%s\t%s\t%s\t%s\t%s\t\t%s\t%s\t\n",
					"id", "name", "gender", "birthday", "email", "point", "regDate");
			System.out.println("-------------------------------------------------------------------------");
			for(Member member : members) {
				System.out.printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n",
						member.getId(),
						member.getName(),
						member.getGender(),
						member.getBirthday(),
						member.getEmail(),
						member.getPoint(),
						simpleRegDateFormat.format(member.getRegDate())
					);
			}
			System.out.println("-------------------------------------------------------------------------");
		}
	}
	
	// 아이디 조회
	private String findId() {
		System.out.print("> 아이디를 입력하세요 : ");
		return sc.next();
	}
	
	// 회원 한 명 조회
	private void printMember(Member member) {
		if(member == null)
			System.out.println("> 조회된 결과가 없습니다.");
		else {
			System.out.println("-------------------------------------------");
			System.out.printf("%-10s : %s\n", "id", member.getId());
			System.out.printf("%-10s : %s\n", "name", member.getName());
			System.out.printf("%-10s : %s\n", "gender", member.getGender());
			System.out.printf("%-10s : %s\n", "birthday", member.getBirthday());
			System.out.printf("%-10s : %s\n", "email", member.getEmail());
			System.out.printf("%-10s : %s\n", "point", member.getPoint());
			System.out.printf("%-10s : %s\n", "regDate", simpleRegDateFormat.format(member.getRegDate()));
			System.out.println("-------------------------------------------");

		}
	}
	
	// 이름 검색
	private String findName() {
		System.out.print("> 이름을 입력하세요 : ");
		return sc.next();
	}
	
	// 회원가입
	private Member inputMember() {
		Member member = new Member();
		// 아이디 중복 검사
		while(true) {
			System.out.print("> 아이디를 입력하세요 : ");
			String id = sc.next();
			if(memberController.findById(id) != null) {
				System.out.printf("> [%s]는 사용 불가능한 아이디입니다. 다시 입력하세요.", id);
			} else {
				System.out.printf("> [%s]는 사용 가능한 아이디입니다 :)", id);
				member.setId(id);
				break;
			}
		}
		System.out.print("> 이름을 입력하세요 : ");
		member.setName(sc.next());
		System.out.print("> 성별을 입력하세요(M/F) : ");
		member.setGender(String.valueOf(sc.next().toUpperCase().charAt(0)));
		System.out.print("> 생년월일을 입력하세요(19990909) : ");
		String temp = sc.next();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
		try {
			member.setBirthday(new Date(sdf.parse(temp).getTime()));
		} catch (ParseException e) {
			e.printStackTrace();
		}
		System.out.print("> 이메일을 입력하세요 : ");
		member.setEmail(sc.next());

		return member;
	}
	
	// 회원 정보 수정
	private void updateMemberInfo() {
		String menu = "******* 회원 정보 변경 메뉴 *******\n"
				+ "1. 이름 변경\n"
				+ "2. 생일 변경\n"
				+ "3. 이메일 변경\n"
				+ "9. 메인 메뉴 돌아가기\n"
				+ "*******************************\n"
				+ "선택 : ";
		
		String id = findId();
		
		while(true) {
			Member member = memberController.findById(id);
			
			if(member == null) {
				System.out.println("> 조회된 회원이 없습니다.");
				return;
			}
			else {
				System.out.println("================== 기존정보 =================");
				printMember(member);
			}

			System.out.print(menu);
			String input = sc.next();
			
			int result = 0;
			SimpleDateFormat birthdayFormat = new SimpleDateFormat("yyyyMMdd");

			switch(input) {
			case "1" : 
				System.out.print("> 변경할 이름을 입력하세요 : ");
				String name = sc.next();
				result = memberController.updateMember("name", id, name);
				break;
			case "2" :
				System.out.print("> 변경할 생년월일을 입력하세요 : ");
				String temp = sc.next();
				Date birthday = null;
				try {
					birthday = new Date(birthdayFormat.parse(temp).getTime());
				} catch (ParseException e) {
					e.printStackTrace();
				}
				result = memberController.updateMember("birthday", id, birthday);
				break;
			case "3" :
				System.out.print("> 변경할 이메일을 입력하세요 : ");
				String email = sc.next();
				result = memberController.updateMember("email", id, email);
				break;
			case "9" : return;
			default : System.out.println("잘못 입력하셨습니다.");
			}
			
			if(result > 0) {
				displayResult(result, "정보변경");
				System.out.println("================== 변경 후 =================");
				printMember(memberController.findById(id));
			}
		}
	}
	
	
	private void displayResult(int result, String type) {
		if(result > 0)
			System.out.println("> " + type + " 성공!");
		else
			System.out.println("> " + type + " 실패!");
	}
}

 

MemberController

package member.controller;

import java.util.List;

import member.model.service.MemberService;
import member.model.vo.Member;

public class MemberController {
	private MemberService memberService = new MemberService();
	
	// 1. 전체 회원 조회
	public List<Member> findAll() {
		return memberService.findAll();
	}
	
	// 2. 아이디 조회
	public Member findById(String id) {
		return memberService.findById(id);
	}
	
	// 3. 이름 조회
	public List<Member> findByName(String name) {
		return memberService.findByName(name);
	}
	
	// 4. 회원가입
	public int inputMember(Member member) {
		return memberService.inputMember(member);
	}
	
	// 5. 정보 변경
	public int updateMember(String type, String id, Object changeVal) {
		return memberService.updateMember(type, id, changeVal);
	}
	
	// 6. 회원 탈퇴
	public int deleteMember(String id) {
		return memberService.deleteMember(id);
	}
}

 

MemberService

package member.model.service;

// 클래스 명 없이도 static 자원을 불러올 수 있음!
import static member.common.JdbcTemplate.close;
import static member.common.JdbcTemplate.commit;
import static member.common.JdbcTemplate.getConnection;
import static member.common.JdbcTemplate.rollback;

import java.sql.Connection;
import java.util.List;

import member.model.dao.MemberDao;
import member.model.vo.Member;


/**
 * Service 클래스
 * - 업무로직 business logic 수행
 * --------------SERVICE 시작--------------------
 * 1. jdbc driver class 등록 
 * 2. Connection객체 생성 & setAUtoCommit(false)
 * -----------------DAO 시작---------------------
 * 3. PreparedStatment객체 생성 & 미완성쿼리 값대입
 * 4. 실행 & 결과 값 받기 (ResultSet, int)
 * 5. 자원반납(pstmt, rset)
 * ------------------DAO 끝----------------------
 * 6. 트랜잭션 처리
 * 7. 자원반납(conn)
 * ---------------SERVICE 끝---------------------
 *
 */
public class MemberService {
	private MemberDao memberDao = new MemberDao();
	
	// 전체 회원 조회
	public List<Member> findAll() {
		Connection conn = getConnection();	// 커밋, 롤백에 대한 처리 필요없음!
		List<Member> members = memberDao.findAll(conn);
		close(conn);
		return members;
	}
	
	// 아이디 조회
	public Member findById(String id) {
		Connection conn = getConnection();
		Member member = memberDao.findById(conn, id);
		close(conn);
		return member;
	}
	
	// 이름 조회
	public List<Member> findByName(String name) {
		Connection conn = getConnection();
		List<Member> members = memberDao.findByName(conn, name);
		close(conn);
		return members;
	}
	
	// 회원가입
	public int inputMember(Member member) {
		int result = 0;
		Connection conn = null;
		
		// 예외 발생 여부에 따라 커밋인지 롤백인지 결정하므로 try~catch 필요
		try {
			conn = getConnection();
			result = memberDao.inputMember(conn, member);
			commit(conn);
		} catch (Exception e) {
			rollback(conn);
			e.printStackTrace();
		} finally {
			close(conn);
		}
		return result;
	}
	
	// 회원 정보 수정
	public int updateMember(String type, String id, Object changeVal) {
		Connection conn = null;
		int result = 0;
		
		try {
			conn = getConnection();
			result = memberDao.updateMember(conn, type, id, changeVal);
			commit(conn);
		} catch(Exception e) {
			rollback(conn);
			e.printStackTrace();
		} finally {
			close(conn);
		}
		return result;
	}
	
	// 회원 탈퇴
	public int deleteMember(String id) {
		Connection conn = null;
		int result = 0;
		
		// 예외 발생 여부에 따라 커밋인지 롤백인지 결정하므로 try~catch 필요
		try {
			conn = getConnection();
			result = memberDao.deleteMember(conn, id);
			commit(conn);
		} catch(Exception e) {
			rollback(conn);
			e.printStackTrace();
		}
		return result;
	}
}

import static 패키지명.클래스명.* 하면 해당 클래스의 모든 static 메소드들을 import 가능!

 

MemberDao

package member.model.dao;

import static member.common.JdbcTemplate.close;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import member.model.vo.Member;


/**
 * --------------SERVICE 시작--------------------
 * 1. jdbc driver class 등록 
 * 2. Connection객체 생성 & setAUtoCommit(false)
 * -----------------DAO 시작---------------------
 * 3. PreparedStatment객체 생성 & 미완성쿼리 값대입
 * 4. 실행 & 결과 값 받기 (ResultSet, int)
 * 5. 자원반납(pstmt, rset)
 * ------------------DAO 끝----------------------
 * 6. 트랜잭션 처리
 * 7. 자원반납(conn)
 * ---------------SERVICE 끝---------------------
 *
 */

public class MemberDao {
	// 전체 회원 조회
	public List<Member> findAll(Connection conn) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = "select * from member order by reg_date desc";
		
		try {
			pstmt = conn.prepareStatement(sql);
			rset = pstmt.executeQuery();
			while(rset.next()) {
				members.add(handleMemberResult(rset));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(rset);
			close(pstmt);
		}
		return members;
	}
	
	// 아이디 조회
	public Member findById(Connection conn, String id) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		Member member = null;
		String sql = "select * from member where id = ?";
		
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, id);
			rset = pstmt.executeQuery();
			while(rset.next()) {
				member = handleMemberResult(rset);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(rset);
			close(pstmt);
		}
		return member;
	}
	
	// 이름 조회
	public List<Member> findByName(Connection conn, String name) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = "select * from member where name like ?";
		
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "%" + name + "%");
			rset = pstmt.executeQuery();
			while(rset.next()) {
				Member member = handleMemberResult(rset);
				members.add(member);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(rset);
			close(pstmt);
		}
		return members;
	}

	
	// 회원 가입
	public int inputMember(Connection conn, Member member) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = "insert into member values(?, ?, ?, ?, ?, default, default)";
		
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getName());
			pstmt.setString(3, member.getGender());
			pstmt.setDate(4, member.getBirthday());
			pstmt.setString(5, member.getEmail());
			result = pstmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		return result;
	}
		
	// 회원 정보 수정
	public int updateMember(Connection conn, String type, String id, Object changeVal) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = "update member set % = ? where id = ?";
		
		try {
			pstmt = conn.prepareStatement(sql.replace("%", type));
			pstmt.setObject(1, changeVal);
			pstmt.setString(2, id);
			result = pstmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		return result;
	}
	
	// 회원 탈퇴
	public int deleteMember(Connection conn, String id) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = "delete from member where id = ?";
		
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, id);
			result = pstmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		return result;
	}

	// ResultSet의 현재 행을 Member VO객체로 변환
	public Member handleMemberResult(ResultSet rset) throws SQLException {
		Member member = new Member();
		member.setId(rset.getString("id"));
		member.setName(rset.getString("name"));
		member.setGender(rset.getString("gender"));
		member.setBirthday(rset.getDate("birthday"));
		member.setEmail(rset.getString("email"));
		member.setPoint(rset.getInt("point"));
		member.setRegDate(rset.getTimestamp("reg_date"));
		return member;
	}
}

※ MemberDao와 MemberService에서 사용한 자원들만 반납해야하는 점 주의!!!

 

JdbcTemplate

package member.common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * JDBC API를 사용하면서 중복된 코드를 static 메소드로 작성
 *
 */
public class JdbcTemplate {
	
	static final String driverClass = "oracle.jdbc.OracleDriver";
	static final String url = "jdbc:oracle:thin:@localhost:1521:xe"; // db접속프로토콜@ip:포트:db명(sid)
	static final String user = "student";
	static final String password = "비번";

	static {
		// driver class 등록 - application 실행 시 최초 1회만
		try {
			Class.forName(driverClass);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Connection 객체 생성
	 * setAutoCommit(false) - 트랙잭션을 직접 관리 (DQM, DML)
	 */
	public static Connection getConnection() {
		Connection conn = null;
		try {
			conn = DriverManager.getConnection(url, user, password);
			conn.setAutoCommit(false);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}
	
	public static void close(PreparedStatement pstmt) {
		try {
			if(pstmt != null && !pstmt.isClosed())
				pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void close(Connection conn) {
		try {
			if(conn != null && !conn.isClosed()) 
				conn.close();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void close(ResultSet rset) {
		try {
			if(rset != null && !rset.isClosed()) 
				rset.close();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void commit(Connection conn) {
		try {
			conn.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void rollback(Connection conn) {
		try {
			conn.rollback();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

driver class는 application 실행 시 최초 1회만 실행해도 되므로, static 블럭에 작성하였습니다.

 

이렇게 코드의 중복성을 제거 및 직관적으로 볼 수 있게 할 수 있습니다.