본문 바로가기
Java/JDBC

JDBC) Properties 이용하여 정보 보호, SQL문 관리, 예외처리

by 박채니 2022. 5. 10.

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

 

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


Properties를 이용하여 정보 보호

※ Properties 관련 하위 링크 참고!!
https://chanychu.tistory.com/142

 

컬렉션/Map<K, V>) Properties, 설정 파일 쓰고 읽어오기

안녕하세요, 코린이의 코딩 학습기 채니 입니다. 개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다. 컬렉션이란? - 동일한 타입을 묶어 관리하는 자료 구조 컬렉션 프레임워크

chanychu.tistory.com

 

프로젝트 → ctrl+N → 폴더 생성 (resources) → 해당 폴더 내에 파일 생성(datasource.properties)

중요한 정보 중 하나인 드라이버 클래스, user이름, 비밀번호, url을 datasource.properties에서 관리할 것입니다.

 

###################################################
# datasource.properties
###################################################

driverClass = oracle.jdbc.OracleDriver
url = jdbc:oracle:thin:@localhost:1521:xe
user = student
password = 비번

해당 파일 내에 위와 같이 key = value로 작성해줍니다.

그 후 필요하다면 key 값을 이용하여 정보를 꺼내와 사용할 수 있습니다.

 

public class JdbcTemplate {
	
	static String driverClass;
	static String url; // db접속프로토콜@ip:포트:db명(sid)
	static String user;
	static String password;
	
	static {
		// datasource.properties의 내용을 Properties 객체로 불러오기
		Properties prop = new Properties();
		try {
			prop.load(new FileReader("resources/datasource.properties"));
			driverClass = prop.getProperty("driverClass");
			url = prop.getProperty("url");
			user = prop.getProperty("user");
			password = prop.getProperty("password");
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
		// driver class 등록 - application 실행 시 최초 1회만!
		try {
			Class.forName(driverClass);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

resources/datasource.properties 파일을 읽어와 getProperty("key값")을 이용하여 필요한 정보들을 꺼내와 사용하였습니다.

이렇게 중요한 정보에 대한 보호처리를 할 수 있습니다.

 

 

SQL문 관리

- MemberDao 객체 생성 시, member-sql.properties의 내용을 불러와 Properties 객체에 저장

- Dao의 각 메소드 호출 시 prop에서 SQL문을 가져와서 처리

 

위와 동일하게 resources 폴더 하위에 member-sql.properties 파일을 생성하여 SQL문을 꺼내와서 처리해보겠습니다.

###################################################
# member-sql.properties
###################################################
findAll = select * from member order by reg_date desc

 

public class MemberDao {
	private Properties prop;
	
	/**
	 * 1. MemberDao 객체 생성 시, member-sql.properties의 내용을 불러와 Properties 객체에 저장
	 * 2. Dao의 각 메소드 호출 시 prop에서 sql문을 가져와서 처리
	 */
	public MemberDao() {
		prop = new Properties();
		try {
			prop.load(new FileReader("resources/member-sql.properties"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
    	
	// 전체 조회
	public List<Member> findAll(Connection conn) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = prop.getProperty("findAll");	// 오타조심!
		
		try {
			// 1. pstmt 객체 생성 & 미완성 쿼리 값 대입
			pstmt = conn.prepareStatement(sql);
			// 2. 실행 & rset처리
			rset = pstmt.executeQuery();
			while(rset.next()) {
				Member member = handleMemberResultSet(rset);
				members.add(member);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 3. 자원반납
			close(rset);
			close(pstmt);			
		}
		return members;
	}

MemberDao의 객체가 생성될 때마다 해당 경로의 파일 내용을 가져오게됩니다.

메소드마다 필요한 SQL문을 key 값을 이용해 가져와 사용하게 됩니다.

 

public List<Member> findAll(Connection conn) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = prop.getProperty("findAll");	// 오타조심!
        
		System.out.println("sql@findAll : " + sql);
        
@콘솔출력값
sql@findAll : select * from member order by reg_date desc

이렇게 findAll에 대한 SQL문을 잘 가져오는 것을 확인할 수 있습니다.

 

 

예외 처리

JDBC의 대부분 메소드들은 모두 Checked Exception이기 때문에, 반드시 예외처리를 해줘야 합니다.

따라서 MemberDao에서 메소드 선언과 동시에 예외처리를 해주었는데요, MemberDao에서 예외처리를 해주었기 때문에 MemberService, MemberController, MemberMenu에서는 예외가 발생한 사실을 알지 못합니다.

예외가 발생하였을 때 사용자에게 안내 해주고 싶다면, 예외를 throws, throw로 던져야겠죠.

여간 귀찮은 일이 아니기 때문에 RuntimeException으로 커스텀 예외 클래스를 생성하여 처리 해줍니다.

 

그렇다면 만일 key값이 잘못 설정 되어 "findAllll"이라고 오타가 나 예외처리가 발생하였을 때는 사용자에게 "오류 발생, 관리자에게 연락 요망"이라는 메세지가,

사용자의 입력 값이 잘못 되었을 때는 "~로 인한 오류 발생 다시 입력" 식의 안내를 해보겠습니다.

 

MemberMenu

	public static void displayError(String msg) {
		System.err.println("> 오류가 발생했습니다. 관리자에게 연락해주세요 : " + msg);
	}
	
	public static void displayClient(String msg) {
		System.err.println("> " + msg);
	}

MemberMenu에서 오류 발생 시 실행할 static 메소드들을 생성해줍니다.

 

MemberDao

public List<Member> findAll(Connection conn) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = prop.getProperty("findAllllll");	// 오타조심!
		System.out.println("sql@findAll : " + sql);
		
		try {
			// 1. pstmt 객체 생성 & 미완성 쿼리 값 대입
			pstmt = conn.prepareStatement(sql);
			// 2. 실행 & rset처리
			rset = pstmt.executeQuery();
			while(rset.next()) {
				Member member = handleMemberResultSet(rset);
				members.add(member);
			}
		} catch (SQLException e) {
			throw new MemberException("회원 전체 조회 오류!", e);	// message:String, err:Throwable
				
		} finally {
			// 3. 자원반납
			close(rset);
			close(pstmt);			
		}
		return members;
	}

 

MemberException (커스텀 예외 클래스)

package member.model.exception;

public class MemberException extends RuntimeException {

	public MemberException() {
		super();
	}

	public MemberException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
	}

	public MemberException(String message, Throwable cause) {
		super(message, cause);
	}

	public MemberException(String message) {
		super(message);
	}

	public MemberException(Throwable cause) {
		super(cause);
	}
}

alt + shift + s → Generate Constructors from Superclass... 를 선택하여 생성자 생성

 

이렇게 커스텀 예외 클래스를 RuntimeException(언체크드)로 생성하여 예외 발생 시 throw 던져주었습니다.

 

MemberController

public List<Member> findAll() {
		List<Member> members = null;
		try {
			members = memberService.findAll();
		} catch(Exception e) {
			// 예외발생 시 후처리
			// 1. 로깅(개발자용)
			e.printStackTrace();
			
			// 2. 사용자 알림
			displayError(e.getMessage());
		}
		return members;
	}

MemberController에서 예외 발생 시 후처리를 해주어 사용자에게 알려주었습니다.

(MemberService에서는 따로 던져주지 않아도 됨!)

 

******** 회원 정보 관리3 ********
1. 전체 조회
2. 아이디 조회
3. 이름 검색 
4. 회원가입
5. 회원 정보 변경
6. 회원 탈퇴
7. 탈퇴 회원 조회
0. 프로그램 종료
*****************************
선택 : 1
	at member.view.MemberMenu.mainMenu(MemberMenu.java:44)
	at member.run.MemberRun.main(MemberRun.java:8)
Caused by: java.sql.SQLException: 실행할 SQL 문은 비어 있거나 널일 수 없음
	at oracle.jdbc.driver.PhysicalConnection.prepareStatementInternal(PhysicalConnection.java:1743)
	at oracle.jdbc.driver.PhysicalConnection.prepareStatement(PhysicalConnection.java:1721)
	at oracle.jdbc.driver.PhysicalConnection.prepareStatement(PhysicalConnection.java:1648)
	at member.model.dao.MemberDao.findAll(MemberDao.java:95)
	... 4 more
> 오류가 발생했습니다. 관리자에게 연락해주세요 : 회원 전체 조회 오류!

key값을 "findAllll"로 오타가 났을 때 위처럼 사용자에게 오류 발생 안내를 해줄 수 있습니다.

 

 

만일 사용자가 이메일을 다른 사용자가 사용 중인 이메일로 변경하려고 할 때, 예외 처리를 해보겠습니다.

(이메일은 Unique 제약 조건이 걸려있음)

MemberDao

// 회원 정보 변경
	public int updateMember(Connection conn, String type, String id, Object input) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = prop.getProperty("updateMember");
		String finalSql = null;
		
		try {
			switch(type) {
			case "name" : 
				finalSql = sql.replace("%", type);
				pstmt = conn.prepareStatement(finalSql);
				pstmt.setString(1, (String)input);
				break;
			case "birthday" :
				finalSql = sql.replace("%", type);
				pstmt = conn.prepareStatement(finalSql);
				pstmt.setDate(1, (Date)input);
				break;
			case "email" :
				finalSql = sql.replace("%", type);
				pstmt = conn.prepareStatement(finalSql);
				pstmt.setString(1, (String)input);
				break;
			}
			pstmt.setString(2, id);
			result = pstmt.executeUpdate();
		} catch (Exception e) {
			if(e.getMessage().contains("UQ_MEMBER_EMAIL"))
				throw new MemberEmailNotUniqueException("회원 이메일 중복 오류 발생!", e);
			else
				throw new MemberException("회원 정보 수정 오류 발생!", e);
		} finally {
			close(pstmt);
		}
		return result;
	}

만약 "UQ_MEMBER_EMAIL"이라는 문장이 포함 되어있다면 MemberEmailNotUniqueException(커스텀예외클래스)를 throw, 그게 아니라면 MemberException을 throw해줍니다.

 

MemberSerivce

// 회원 정보 변경
	public int updateMember(String type, String id, Object input) {
		Connection conn = null;
		int result = 0;
		
		try {
			conn = getConnection();
			result = memberDao.updateMember(conn, type, id, input);
			commit(conn);
		} catch(Exception e) {	// commit, rollback에 대한 try~catch이므로 MemberException 예외는 던져줘야함
			rollback(conn);
			throw e;	// 이렇게 던져줘도 됨
		} finally {
			close(conn);
		}
		return result;
	}

updateMember는 MemberService 클래스에서 commit, rollback으로 인해 try~catch절을 사용해주었기 때문에, Controller로 다시 던져주는 작업이 필요합니다.

 

MemberController

// 정보 변경
	public int updateMember(String type, String id, Object input) {
		int result = 0;
		
		try {
			result = memberService.updateMember(type, id, input);
		} catch(MemberEmailNotUniqueException e) { 
			// 2. 사용자 오류 알림			
			displayClient("사용 불가능한 이메일 주소입니다.");
		}
		catch (Exception e) {
			// 1. 로깅
//			e.printStackTrace();
			displayError(e.getMessage());
		}
		return result;
	}

 

******** 회원 정보 관리3 ********
1. 전체 조회
2. 아이디 조회
3. 이름 검색 
4. 회원가입
5. 회원 정보 변경
6. 회원 탈퇴
7. 탈퇴 회원 조회
0. 프로그램 종료
*****************************
선택 : 5
> 아이디를 입력하세요 : honggd
================== 기존정보 =================
-------------------------------------------
id         : honggd
name       : 홍길동
gender     : M
birthday   : 1999-01-19
email      : honggd@abc.com
point      : 1000
regDate    : 2022-05-02 17:11
-------------------------------------------
******* 회원 정보 변경 메뉴 *******
1. 이름 변경
2. 생일 변경
3. 이메일 변경
9. 메인 메뉴 돌아가기
*******************************
선택 : 3
> 변경하실 이메일을 입력하세요 : gogd@naver.com
> 사용 불가능한 이메일 주소입니다.
> 정보변경 실패!

이렇게 분기처리 해주었습니다.


MemberMenu (View)

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******** 회원 정보 관리3 ********\n"
				+ "1. 전체 조회\n"
				+ "2. 아이디 조회\n"
				+ "3. 이름 검색 \n"
				+ "4. 회원가입\n"
				+ "5. 회원 정보 변경\n"
				+ "6. 회원 탈퇴\n"
				+ "7. 탈퇴 회원 조회\n"
				+ "0. 프로그램 종료\n"
				+ "*****************************\n"
				+ "선택 : ";
		
		while(true) {
			System.out.print(menu);
			String choice = sc.next();
			
			Member member = null;
			int result = 0;
			List<Member> members = null;
			String id = null;
			String name = null;
			
			switch(choice) {
			case "1" : 
				// select * from member
				members = memberController.findAll();
				printMembers(members);
				break;
			case "2" : 
				id = insertId();
				member = memberController.findById(id);
				printMember(member);
				break;
			case "3" : 
				name = insertName();
				members = memberController.findByName(name);
				printMembers(members);
				break;
			case "4" : 
				// insert into member values(?, ?, ?, ?, ?, ?, ?)
				member = inputMember();
				result = memberController.insertMember(member);
				displayResult(result, "회원가입");
				break;
			case "5" : 
				updateMemberInfo();
				break;
			case "6" : 
				id = insertId();
				result = memberController.deleteMember(id);
				displayResult(result, "회원탈퇴");
				break;
			case "7" :
				members = memberController.findAllDeleted();
				printDeleteMembers(members);
				break;
			case "0" : return;
			default : System.out.println("잘못 입력하셨습니다.");
			}
		}
	}

	public static void displayError(String msg) {
		System.err.println("> 오류가 발생했습니다. 관리자에게 연락해주세요 : " + msg);
	}
	
	public static void displayClient(String msg) {
		System.err.println("> " + msg);
	}

	/**
	 * 1. 전체 회원 조회
	 */
	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%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 insertName() {
		System.out.print("> 이름을 입력하세요 : ");
		return sc.next();
	}
	
	// 5. 아이디 입력
	private String insertId() {
		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("-------------------------------------------");
		}
	}
	
	/**
	 * 4. 회원가입
	 * 아이디, 이름, 성별, 생일, 이메일
	 * 포인트, 가입일 - 기본값처리
	 */
	private Member inputMember() {
		Member member = new Member();
		System.out.print("> 아이디를 입력하세요 : ");
		member.setId(sc.next());
		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();
		// 문자열 -> java.util.Date -> java.sql.Date
		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;
	}

	/**
	 * 5. 회원 정보 변경 메뉴
	 */
	private void updateMemberInfo() {
		String menu = "******* 회원 정보 변경 메뉴 *******\n"
					+ "1. 이름 변경\n"
					+ "2. 생일 변경\n"
					+ "3. 이메일 변경\n"
					+ "9. 메인 메뉴 돌아가기\n"
					+ "*******************************\n"
					+ "선택 : ";
		String id = insertId();
		Member member = memberController.findById(id);
		if(member == null) {
			System.out.println("> 조회된 회원이 없습니다.");
			return;
		} else {
			System.out.println("================== 기존정보 =================");
			printMember(member);
		}
		
		while(true) {
			System.out.print(menu);
			String input = sc.next();
			
			int result = 0;
			Date birthday = null;
			SimpleDateFormat sdf = 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("> 변경할 생년월일을 입력하세요(19990909) : ");
				String temp = sc.next();
				try {
					birthday = new Date(sdf.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("================== 변경 후 =================");
				member = memberController.findById(id);
				printMember(member);
			} else displayResult(result, "정보변경");
		}
	}
	
	// 탈퇴회원 조회
	private void printDeleteMembers(List<Member> members) {
		if(members == null || members.isEmpty()) {
			System.out.println("> 조회된 결과가 없습니다.");
		} else {
			System.out.printf("%s\t%s\t%s\t%s\t\t%s\t%s\t%s\t\t\t%s\t\n",
							"id", "name", "gender", "birthday", "email", "point", "regDate", "delDate");
			System.out.println("---------------------------------------------------------------------------------------------------------");
			for(Member member : members) {
				System.out.printf("%s\t%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()),
								member.getdelDate()
							);
			}
			System.out.println("---------------------------------------------------------------------------------------------------------");
		}
	}
	
	/**
	 * DML 처리 결과 화면 출력 메소드
	 */
	private void displayResult(int result, String type) {
		if(result > 0) 
			System.out.printf("> %s 성공!\n", type);
		else
			System.out.printf("> %s 실패!\n", type);
	}
}

 

MemberController

package member.controller;

import static member.view.MemberMenu.*;

import java.util.List;

import member.model.exception.MemberEmailNotUniqueException;
import member.model.exception.MemberIdNotPrimayException;
import member.model.service.MemberService;
import member.model.vo.Member;

public class MemberController {
	private MemberService memberService = new MemberService();

	public int insertMember(Member member) {
		int result = 0;
		try {
			result = memberService.insertMember(member);
		} catch(MemberIdNotPrimayException e) { 
			displayClient("중복된 아이디가 있습니다.");
		}
		catch (Exception e) {
			displayError(e.getMessage());
		}
		return result;
	}

	public List<Member> findAll() {
		List<Member> members = null;
		try {
			members = memberService.findAll();
		} catch(Exception e) {
			// 예외발생 시 후처리
			// 1. 로깅(개발자용)
			e.printStackTrace();
			
			// 2. 사용자 알림
			displayError(e.getMessage());
		}
		return members;
	}
	
	// 아이디 조회
	public Member findById(String id) {
		Member member = null;
		try {
			member = memberService.findById(id);
		} catch(Exception e) {
			e.printStackTrace();
			displayError(e.getMessage());
		}
		return member;
	}
	
	// 이름 조회
	public List<Member> findByName(String name) {
		List<Member> members = null;
		
		try {
			members = memberService.findByName(name);
		} catch(Exception e) {
			e.printStackTrace();
			displayError(e.getMessage());
		}
		return members;
	}
	
	// 정보 변경
	public int updateMember(String type, String id, Object input) {
		int result = 0;
		
		try {
			result = memberService.updateMember(type, id, input);
		} catch(MemberEmailNotUniqueException e) { 
			// 2. 사용자 오류 알림			
			displayClient("사용 불가능한 이메일 주소입니다.");
		}
		catch (Exception e) {
			// 1. 로깅
//			e.printStackTrace();
			displayError(e.getMessage());
		}
		return result;
	}
	
	// 회원 탈퇴
	public int deleteMember(String id) {
		int result = 0;
		try {
		 	result = memberService.deleteMember(id);
		} catch(Exception e) {
			displayError(e.getMessage());
		}
		return result;
	}
	
	// 탈퇴한 회원 조회
	public List<Member> findAllDeleted() {
		List<Member> members = null;
		
		try {
			members = memberService.findAllDeleted();
		} catch(Exception e) {
			displayError(e.getMessage());
		}
		return members;
	}
}

 

MemberService

package member.model.service;

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

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

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

public class MemberService {
	private MemberDao memberDao = new MemberDao();
	
	/**
	 * DML
	 */
	public int insertMember(Member member) {
		int result = 0;
		Connection conn = null;
		
		// 예외 발생 여부에 따라 커밋인지 롤백인지 결정하므로 try~catch 필요
		try {
			conn = getConnection();
			result = memberDao.insertMember(conn, member);
			commit(conn);
		} catch(Exception e) {
			rollback(conn);
			throw e;
		} finally {
			close(conn);
		}
		return result;
	}

	/**
	 * DQL
	 */
	public List<Member> findAll() {
		Connection conn = getConnection();	// 커밋, 롤백에 대한 처리가 불필요하므로, try~catch도 없어도 됨!
		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 updateMember(String type, String id, Object input) {
		Connection conn = null;
		int result = 0;
		
		try {
			conn = getConnection();
			result = memberDao.updateMember(conn, type, id, input);
			commit(conn);
		} catch(Exception e) {	// commit, rollback에 대한 try~catch이므로 MemberException 예외는 던져줘야함
			rollback(conn);
			throw e;	// 이렇게 던져줘도 됨
		} finally {
			close(conn);
		}
		return result;
	}
	
	// 회원 탈퇴
	public int deleteMember(String id) {
		Connection conn = null;
		int result = 0;
		
		try {
			conn = getConnection();
			result = memberDao.deleteMember(conn, id);
			commit(conn);
		} catch(Exception e) {
			rollback(conn);
			e.printStackTrace();
			throw e;
		} finally {
			close(conn);
		}
		return result;
	}
	
	// 탈퇴한 회원 조회
	public List<Member> findAllDeleted() {
		Connection conn = null;
		
		conn = getConnection();
		List<Member> members = memberDao.findAllDeleted(conn);
		close(conn);
		
		return members;
	}
}

 

MemberDao

package member.model.dao;

import static member.common.JdbcTemplate.close;

import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import member.model.exception.MemberEmailNotUniqueException;
import member.model.exception.MemberException;
import member.model.exception.MemberIdNotPrimayException;
import member.model.vo.Member;

public class MemberDao {
	private Properties prop;
	
	/**
	 * 1. MemberDao 객체 생성 시, member-sql.properties의 내용을 불러와 Properties 객체에 저장
	 * 2. Dao의 각 메소드 호출 시 prop에서 sql문을 가져와서 처리
	 */
	public MemberDao() {
		prop = new Properties();
		try {
			prop.load(new FileReader("resources/member-sql.properties"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// 회원가입
	public int insertMember(Connection conn, Member member) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = prop.getProperty("insertMember");
		
		try {
			// 1. PreparedStatment객체 생성 & 미완성쿼리 값대입
			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());
			// 2. 실행 & 결과 값 받기 (ResultSet, int)
			result = pstmt.executeUpdate();
		} catch (SQLException e) {
			if(e.getMessage().contains("PK_MEMBER_ID")) 
				throw new MemberIdNotPrimayException("회원 아이디 중복 오류 발생!", e);
			else 
				throw new MemberException("회원 가입 오류 발생!", e);
		} finally {
			// 3. 자원반납(pstmt)
			close(pstmt);
		}
		return result;
	}
	
	/**
	 * 예외가 발생하면, CheckedException이므로 우선 예외 처리 후, 컨트롤러까지 예외를 던져야함
	 * 컨트롤러에서 흐름을 분기할 수 있도록!
	 * - 예외를 더 정확히 표현할 수 있는 커스텀 예외(RuntimeException)로 처리 
	 */
	// 전체 조회
	public List<Member> findAll(Connection conn) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = prop.getProperty("findAll");	// 오타조심!
		System.out.println("sql@findAll : " + sql);
		
		try {
			// 1. pstmt 객체 생성 & 미완성 쿼리 값 대입
			pstmt = conn.prepareStatement(sql);
			// 2. 실행 & rset처리
			rset = pstmt.executeQuery();
			while(rset.next()) {
				Member member = handleMemberResultSet(rset);
				members.add(member);
			}
		} catch (SQLException e) {
			throw new MemberException("회원 전체 조회 오류!", e);	// message:String, err:Throwable
				
		} finally {
			// 3. 자원반납
			close(rset);
			close(pstmt);			
		}
		return members;
	}
	
	// 아이디 조회
		public Member findById(Connection conn, String id) {
			PreparedStatement pstmt = null;
			ResultSet rset = null;
			String sql = prop.getProperty("findById");
			Member member = null;
			
			try {
				pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, id);
				rset = pstmt.executeQuery();
				while(rset.next()) {
					member = handleMemberResultSet(rset);
				}
			} catch (SQLException e) {
				throw new MemberException("아이디 조회 오류!", e);
			} 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 = prop.getProperty("findByName");
		
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "%" + name + "%");
			rset = pstmt.executeQuery();
			while(rset.next()) {
				Member member = handleMemberResultSet(rset);
				members.add(member);
			}
		} catch (SQLException e) {
			throw new MemberException("이름 조회 오류 발생!", e);
		} finally {
			close(rset);
			close(pstmt);
		}
		return members;
	}
	
	// 회원 정보 변경
	public int updateMember(Connection conn, String type, String id, Object input) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = prop.getProperty("updateMember");
		String finalSql = null;
		
		try {
			switch(type) {
			case "name" : 
				finalSql = sql.replace("%", type);
				pstmt = conn.prepareStatement(finalSql);
				pstmt.setString(1, (String)input);
				break;
			case "birthday" :
				finalSql = sql.replace("%", type);
				pstmt = conn.prepareStatement(finalSql);
				pstmt.setDate(1, (Date)input);
				break;
			case "email" :
				finalSql = sql.replace("%", type);
				pstmt = conn.prepareStatement(finalSql);
				pstmt.setString(1, (String)input);
				break;
			}
			pstmt.setString(2, id);
			result = pstmt.executeUpdate();
		} catch (Exception e) {
			if(e.getMessage().contains("UQ_MEMBER_EMAIL"))
				throw new MemberEmailNotUniqueException("회원 이메일 중복 오류 발생!", e);
			else
				throw new MemberException("회원 정보 수정 오류 발생!", e);
		} finally {
			close(pstmt);
		}
		return result;
	}
	
	// 회원 탈퇴
	public int deleteMember(Connection conn, String id) {
		PreparedStatement pstmt = null;
		int result = 0;
		String sql = prop.getProperty("deleteMember");
		
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, id);
			result = pstmt.executeUpdate();
		} catch (SQLException e) {
			throw new MemberException("회원 탈퇴 오류 발생!", e);
		} finally {
			close(pstmt);
		}
		return result;
	}
	
	// 탈퇴한 회원 조회
	public List<Member> findAllDeleted(Connection conn) {
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		List<Member> members = new ArrayList<>();
		String sql = prop.getProperty("findAllDeleted");
		
		try {
			pstmt = conn.prepareStatement(sql);
			rset = pstmt.executeQuery();
			
			while(rset.next()) {
				Member member = handleMemberResultSet(rset);
				member.setDelDate(rset.getTimestamp("del_date"));
				members.add(member);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new MemberException("탈퇴한 회원 조회 오류 발생!", e);
		} finally {
			close(rset);
			close(pstmt);
		}
		return members;
	}
	
	/**
	 * ResultSet의 현재 행을 Member VO객체로 변환하는 메소드
	 */
	public Member handleMemberResultSet(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;
	}
}

 

JdbcTemplate

package member.common;

import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * JDBC API를 사용하면서 중복된 코드를 static 메소드로 작성
 *
 */
public class JdbcTemplate {
	
	static String driverClass;
	static String url; // db접속프로토콜@ip:포트:db명(sid)
	static String user;
	static String password;
	
	static {
		// datasource.properties의 내용을 Properties 객체로 불러오기
		Properties prop = new Properties();
		try {
			prop.load(new FileReader("resources/datasource.properties"));
			driverClass = prop.getProperty("driverClass");
			url = prop.getProperty("url");
			user = prop.getProperty("user");
			password = prop.getProperty("password");
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
		// 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(Connection conn) {
		try {
			if(conn != null && !conn.isClosed())
				conn.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void close(PreparedStatement pstmt) {
		try {
			if(pstmt != null && !pstmt.isClosed())
				pstmt.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이 null이 아니고, 닫혀있지 않다면 커밋처리
			if(conn != null && !conn.isClosed())	// isClosed() : 반환되었다면 true리턴
				conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public static void rollback(Connection conn) {
		try {
			if(conn != null && !conn.isClosed())
				conn.rollback();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
}

 

member-sql.properties

###################################################
# member-sql.properties
###################################################
findAll = select * from member order by reg_date desc
updateMember = update member set % = ? where id = ?
insertMember = insert into member values(?, ?, ?, ?, ?, default, default)
findById = select * from member where id = ?
findByName = select * from member where name like ?
deleteMember = delete from member where id = ?
findAllDeleted = select * from member_del order by del_date desc