📗 self-study/📗 KH정보교육원 당산지원

[JDBC/중요] Properties 객체 적용하여 공통 코드 빼놓기

천재강쥐 2022. 9. 8. 11:24

 

더보기

파일 입출력에 특화된 Properties를 이용하여 환경설정과 관련된 내용을 따로 빼서 만들어 보자

*내가 필요한 정보들을 외부 파일로 빼내는 작업을 추가할 예정

 

👉🏻 이 작업을 통해 코드의 오타를 줄일 수 있고(= 오류 확률 낮춤),

         어디서 오류가 발생했는지 더욱 쉽게 확인 가능함

 

*미리 설정해 놓을 작업

Template에서 진행했던 내용에서 환경설정과 관련된 내용을 따로 빼는 수정을 할 것이기 때문에

03_JDBC_Template_SERVICE 프로젝트 복사 후 04_JDBC_Properties로 copy하기

 


 

Properties 복습
Properties: Map 계열의 컬렉션 (key + value 세트로 담는 게 특징)
      주로 외부 설정 파일 읽어오기 또는 파일 형태로 출력하고자 할 때 사용
   (파일 입출력에 특화된 컬렉션: key, value 모두 String 타입으로 적어 주는 것을 권장)

 

 

<Run 클래스>

 

.properties, .xml 파일로 내보내기

package com.kh.run;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class Run {

	public static void main(String[] args) {
		
		// 프로그램 실행만을 담당
		// 사용자가 보게 될 첫 화면을 띄워 주고 끝
		
		// 방법 1. 생성 후 호출
		// MemberView mv = new MemberView();
		// mv.mainMenu();
		
		// 방법 2. 객체 생성과 동시에 호출
		// new MemberView().mainMenu();
		
		// .properties, .xml 파일로 내보내기
		Properties prop = new Properties();
		
		// setProperty("key", "value"): 문자열 형태로 Properties에 키 + 밸류 세트로 담을 수 있는 메소드
		prop.setProperty("List", "ArrayList");
		prop.setProperty("Set", "HashSet");
		prop.setProperty("Map", "HashMap"); // key 값이 중복되면 덮어씌워지기 때문에 얘는 없어짐
		prop.setProperty("Map", "Proerties"); // 총 3개의 키 + 밸류 세트가 들어 있음
		
		System.out.println(prop); // 키값 중복 X, 넣은 순서 유지 X
		
		// prop에 담긴 내용물들을 파일로 내보내기
		// store / storeToXML (출력스트림객체, 파일에대한설명문구)
		try {
			prop.store(new FileOutputStream("resources/test.properties"), "test.properties");
			prop.storeToXML(new FileOutputStream("resources/test.xml"), "test.xml");
			// java.io.FileNotFoundException: resources\test.properties (지정된 경로를 찾을 수 없습니다)
			// 파일 이름만 쓰면 없는 파일 이름이라도 잘 만들어지는데
			// 지금은 resources라는 폴더 안에 파일을 만들겠다라고 했기 때문에 resources 폴더가 없어서 오류 남!!
			
			// resources 폴더: 주로 프로젝트 내의 외부 파일들을 저장하는 역할의 폴더
			
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}

 

.properties 와 .xml의 쓰임

.properties: DB 접속 관련 내용을 담을 것

.xml: 쿼리문 관련(접속 정보) 내용을 담을 것

 

 

<test.properties 수정 - 샘플 예시문>

before
after

 

<test.xml 수정 - 샘플 예시문>

before
after

 

 

.propertise 파일 읽어들이기

load(입력스트림객체)

	Properties prop = new Properties();

        try {
            prop.load(new FileInputStream("resources/driver.properties"));

        } catch (IOException e) {
         e.printStackTrace();
        }

        // 출력
        // getProperty("키"): "밸류"
            System.out.println(prop.getProperty("driver")); // oracle.jdbc.driver.OracleDriver
            System.out.println(prop.getProperty("url")); // jdbc:oracle:thin:@localhost:1521:xe
            System.out.println(prop.getProperty("username")); // JDBC
            System.out.println(prop.getProperty("password")); // JDBC
            System.out.println(prop.getProperty("pass")); // null, 존재하지 않는 key값 제시 시 null 반환함!

 

.XML 파일 읽어들이기

loadFromXML(입력스트림객체)

	Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
		
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 출력
		System.out.println(prop.getProperty("select")); // SELECT * FROM
		System.out.println(prop.getProperty("insert")); // INSERT INTO MEMBER VALUES (1)
		System.out.println(prop.getProperty("update")); // UPDATE MEMBER SET USERID = 1

 

 


 

<JDBCTemplate 클래스>

 

*기존의 방식: jdbc.driver 구문, 접속할 DB의 url, 접속할 계정명/비밀번호 들을
     자바 소스코드 내에 직접 명시적으로 작성 => 정적 코딩 방식(하드코딩)


- 문제점: DBMS가 변경되었을 경우, 접속할 DB의 url 또는 계정명 / 비밀번호가 변경될 경우
 => 자바 코드를 수정해야 함
 수정된 내용을 적용시키고자 한다면 (반영시키고자 한다면)
 => 프로그램을 재구동 해야 함
 (사용자 입장에서 프로그램 사용 중 비정상적으로 종료되었다가 다시 구동해서 사용해야 함, 운영이나 유지보수에도 불편하다)


- 해결방식: DB와 관련된 정보들을 변도로 관리하는 외부 파일 (.properties)로 만들어서 접속 정보를 관리
                  외부파일로 읽어들여서 내용물을 반영시킬 것 

 

 

정적 코딩과 동적 코딩

정적 코딩: 내용의 수정이 있을 때 프로그램 중지 후 재실행 해야 적용

동적 코딩: 프로그램 실행 중에 내용이 바뀌어도 바로 적용

 

(+) 하드 코딩: 변경될 수 있는 변수 등의 값이 아닌 고정값을  박아 넣어 놓는 것

// 하드 코딩의 예시

Connection conn = null;

		Class.forName("oracle.jdbc.driver.OracleDriver");
			
		conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
        
// 서버가 바뀌었을 때 반드시 값을 변경해 줘야 함

 


 

<MemberDao 클래스>

 

* 기존의 방식: DAO 클래스에 사용자가 요청할 때마다 실행해야 하는 SQL문들을
                          자바 소스코드 내에 직접 명시적으로 작성함 => 정적 코딩 방식


- 문제점: SQL 구문을 수정해야 할 경우 자바 소스코드를 수정하는 셈
                 즉, 수정된 내용을 반영시키고자 할 경우 프로그램을 재구동해야 함


- 해결방식: SQL 문들을 별도로 관리하는 외부 파일(.xml)로 만들어서
                     실시간으로 이 파일에 기록된 SQL문들을 동적으로 읽어들여서 실행 => 동적 코딩 방식

 

	public int insertMember(Connection conn, Member m) {
		
		// INSERT문 => 처리된 행의 개수(int) => 트랜잭션 처리
		
		Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
			// 연결 통로를 만든 이 시점 이후로는 prop 객체에 query.xml로부터 읽어들인 key + value 세트들이 담겨 있을 것
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 0) 필요한 변수들 먼저 세팅 및 초기화 (선언 및 초기화)
		int result = 0; // 처리된 결과를 받을 수 있는 변수

		// Service 클래스에서 역할을 대신하므로 Connection 변수 선언 및 초기화가 필요 없어짐!!!
//		Connection conn = null; // 접속된 DB의 연결 정보를 담을 수 있는 변수
		
		// Statement stmt = null; // SQL문 (DELETE)을 실행 후 결과를 받을 수 있는 변수
		PreparedStatement pstmt = null;
		// 전역변수로 세팅하는 이유: try문에 넣어 버리면 try 닫히면 없어짐! finally에서 반납하고자 할 때 알아먹지 못하니까 
		
		// 실행할 SQL문 (완성된 형태로 만들어 둘 것)
		// => 끝에 세미콜론이 있으면 안 됨
		// INSERT INTO MEMBER
		// VALUES (SEQ_USERNO.NEXTVAL, 'XXX', 'XXX', 'XXX', 'X'
		//			XX, 'XXXX', 'XXXXX', 'XXXXXXXXXXX', 'XXXX', DEFAULT);
		
		/*
		String sql = "INSERT INTO MEMBER "
								+ "VALUES(SEQ_USERNO.NEXTVAL, "
										+ "'" + m.getUserId() + "'" + ", "
										+ "'" + m.getUserPwd() + "'" + ","
										+ "'" + m.getUserName() + "'" + ", "
										+ "'" + m.getGender() + "'" + ", "
										+ m.getAge() + ", "
										+ "'" + m.getEmail() + "'" + ", "
										+ "'" + m.getPhone() + "'" + ", "
										+ "'" + m.getAddress() + "'" + ", "
										+ "'" + m. getHobby() + "'" + ", DEFAULT)";
		
		 System.out.println(sql); // SQL 구문 확인 가능
		*/
		
		// 실행할 SQL문 (미완성된 형태로)=> PreparedStatement 버전
		// INSERT INTO MEMBER
		// VALUES (SEQ_USERNO.NEXTVAL, ?, ?, ?, ?
		//			?, ?, ?, ?, ?, DEFAULT);
		// 동적 바인딩이므로 문자열이면 문자열(홑따옴표로 감싸서), 숫자형이면 숫자로 넣어 줌
		
		// 정적 코딩 방식 sql은 잠깐 주석 처리!
		
		/*
		String sql = "INSERT INTO MEMBER "
				   + "VALUES (SEQ_USERNO.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT)";
		// 구멍의 개수 잘 확인할 것! 11개 중 2개의 컬럼값 빼고 9개의 물음표가 존재해야 함!
		*/
		
		// 동적 코딩 방식으로!
		
		String sql = prop.getProperty("insertMember");
		System.out.println(sql);
		
		// PreparedStatement의 유일한 단점
		// System.out.println(sql); // Statement는 이 시점에서 적용된 SQL 구문 확인 가능하지만 PreparedStatement는 그게 안 됨!
		//								=> 미완성된 SQL 상태이기 때문
		
		try {
			
			// Service 클래스에서 역할을 대신하므로 1, 2 과정이 필요 없어짐!!!
			
//			// 1) JDBC Driver 등록
//			Class.forName("oracle.jdbc.driver.OracleDriver");
//	
//			// ojdbc6.jar 파일 내에 있는 oracle.ojdbc.driver.OracleDriver 클래스를 등록
//			// => ojdbc6.jar 파일이 누락되었거나, 잘 추가되었지만 오타가 있는 경우
//			//	  ClassNotFoundException 발생!
//			// 	  1) .jar 파일 있는지 확인 2) 클래스명 확인
//				
//			// 2) Connection 객체 생성 (DB와 연결 --> url: DB의 주소지, 계정명, 비밀번호)
//			conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
//												// 구분자 잘 확인할 것!
			
			// 3_1) PreparedStatement 객체 생성(Connection 객체로부터)
			// stmt = conn.createStatement();
			pstmt = conn.prepareStatement(sql);
			
			// 3_2) 내가 담은 SQL문이 미완성된 형태일 경우 값 채우기
			// 	pstmt.setXXX(위치홀더의 순번, 실제채울값);
			//  pstmt.setString(위치홀더의 순번, 실제채울값); => ? 자리에 '실제채울값' (양 옆에 홑따옴표가 붙어서 들어감)
			//  pstmt.setInt(위치홀더의 순번, 실제채울값); => ? 자리에 실제 채울 값(양 옆에 홑따옴표가 붙지 않음)
			pstmt.setString(1,  m.getUserId()); // 'test02'
			pstmt.setString(2,  m.getUserPwd()); // 'pass02'
			pstmt.setString(3,  m.getUserName()); // '고길동'
			pstmt.setString(4,  m.getGender()); // 'M'
			pstmt.setInt(5,  m.getAge()); // 50
			pstmt.setString(6,  m.getEmail()); // 'aaa@naver.com'
			pstmt.setString(7,  m.getPhone()); // '01011114444'
			pstmt.setString(8,  m.getAddress()); // '서울시 강남구'
			pstmt.setString(9,  m.getHobby()); // '낮잠자기'
			// => 적어도 이 단계까지 완료가 되었다면 본격적으로 SQL문을 실행시킬 수 있음
 			
			// 4, 5) DB에 완성된 SQL문을 전달하면서 실행 후 결과 (처리된 행의 개수) 받기			
			// result = stmt.executeUpdate(sql);
			result = pstmt.executeUpdate();
			// => insert이기 때문에 executeUpdate로 받고, result에는 처리된 행의 개수가 들어 있음
			
			// Service 클래스에서 역할을 대신하므로 6_2) 트랜잭션 과정이 필요 없어짐!!!
			
//			// 6_2) 트랜잭션 처리
//			// => result 변수에 담긴 값 기준으로 조건 초기
//			if(result > 0) { // 1개의 행 insert => insert가 아주 잘되어 있음 (성공) => commit
//				conn.commit();
//				
//			} else { // 0개의 행 insert => insert에 실패했음 (실패) => rollback
//				conn.rollback();
//			}			

		} catch (SQLException e) {
			e.printStackTrace();
			
			// Service 클래스에서 역할을 대신하므로 ClassNotFoundException try~catch 과정이 필요 없어짐!!!
			
//		} catch (ClassNotFoundException e) {
//			e.printStackTrace();
//		}
		} finally {
			
			// try {
			// 7) 다 쓴 JDBC용 객체 자원 반납
			// => 반드시 작성해야 하는 코드이기 때문에 finally 블록 안에서 작성
			// => 생성된 순서의 역순으로 close
			
			// 생성 순서: Connection -> Statement
			// 닫는 순서: PreparedStatement -> Connection
				// stmt.close();
				// Service 클래스에서 역할을 대신하므로 conn.close() 없어도 됨!
				// conn은 서비스에서 닫아 줄 거고 대신 JDBCTemplate.close(pstmt)!
				// pstmt.close();
				// conn.close();
			// } catch (SQLException e) {
			//	 e.printStackTrace();
			// }
				JDBCTemplate.close(pstmt);
		}
		
		// 8) 결과 반환
		return result; // 처리된 행의 개수(성공했을 경우 1, 실패했을 경우 0)
	}

String sql = prop.getProperty("insertMember");

했으므로 query.xml해당 key값을 제시해 줘야 함

 

	public ArrayList<Member> selectAll(Connection conn) {
		
		// SELECT문 => ResultSet 객체 => 여러 행 조회 (Member 1개당 회원 1명): ArrayList 타입으로 묶기
		
		Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
			
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
		
		// 0) 필요한 변수들 먼저 세팅 (선언 및 초기화)
		ArrayList<Member> list = new ArrayList<>(); // 조회된 결과를 뽑아서 담아 줄 ArrayList를 생성 (텅 빈 리스트)
		// Connection conn = null; // 접속된 DB의 연결 정보를 담는 변수
		// Statement stmt = null; // SQL문 (DELETE)을 실행 후 결과를 받을 수 있는 변수
		PreparedStatement pstmt = null; // SQL문 실행 후 결과를 받기 위한 변수
		ResultSet rset = null; // SELECT문이 실행될 조회 결과값들이 처음에 실질적으로 담겨 올 객체 
		
		// 실행할 SQL문(완성된 형태로, 세미콜론은 빼고)
		// SELECT * FROM MEMBER
		
		// 얘는 정적 코딩 방식임!
		// String sql = "SELECT * FROM MEMBER";
		// => PreparedStatement 는 완성된 쿼리문을 애초에 보내 놔도 무방!!
		
		// 동적 코딩 방식을 소개합니다
		String sql = prop.getProperty("selectAll");
		
		try {

			// 1) JDBC Driver 등록
			// Class.forName("oracle.jdbc.driver.OracleDriver");
			
			// 2) Connection 객체 생성(url, 계정명, 비밀번호)
			// conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
			// 이미 try 블럭 안이므로 2번째 선택지인 "Add catch clause to surrounding try"를 선택해 주면 catch문만 생김 
			
			// 3_1) PreparedStatement 객체 생성 (Connection 객체로부터 얻어냄)
			//stmt = conn.createStatement();
			 pstmt = conn.prepareStatement(sql); // 애초에 SQL문을 담은 채로 생성
			 
			 // 3_2) 미완성된 쿼리문을 완성시키기
			 // => 3_1) 에서 완성된 쿼리문을 미리 넘겨 뒀기 때문에 이 과정은 생략
			 
			// 4, 5) SQL문 (SELECT) 실행해서 결과값 받아내기
			rset = pstmt.executeQuery();
			// => rset에는 조회된 결과물들이 다 담겨 있을 것임
			
			// 6_1) 현재 조회 결과가 담긴 ResultSet에서 한 행씩 뽑아서 VO 객체에 담기
			// => ResultSet 객체는 커서를 한 줄씩 아래로 옮겨서 현재 행의 위치를 나타내는 구조
			// => 이때, 커서를 rest.next() 메소드로 다음 줄로 옮겨 버리기
			while (rset.next()) { // 커서를 한 줄 아애로 움직여 주고
								  // 해당 행이 존재할 경우 true, 존재하지 않을 경우 false를 반환함
				// 적어도 이 중괄호 안에 들어와서 코드가 실행된다라는 것은
				// ResultSet 객체로부터 뽑아낼 데이터가 있다라는 뜻!
				
				// 현재 rset의 커서가 가리키고 있는 해당 행의 데이터를 하나씩 뽑아서 Member 객체에 담기
				// 한 행의 데이터 == 회원 한 명의 정보 == Member VO 객체 한 개
				Member m = new Member();
				
				// rset으로부터 어떤 컬럼에 해당하는 값을 뽑을 건지 제시해서
				// 해당 컬럼의 값들을 각 필드로 옮겨 주기
				// -> 해당 행의 해당 컬럼의 값을 자바의 지정된 자료형으로 가지고 옴
				// rset.getInt(컬럼명/컬럼순번): int형 값을 뽑아낼 때
				// rset.getString(컬럼명/컬럼순번): String 형 값을 뽑아낼 때
				// rset.getDate(컬럼명/컬럼순번): Date형 값을 뽑아낼 때
				// => 컬럼명은 대소문자를 가리지 않음
				// => 권장사항: 컬럼명으로 쓰고, 대문자로 쓰는 것을 권장! (가독성 높아져 협업 때 편함)
				
				// m.userNo = rset.getInt("USERNO"); --> private이라 직접 접근 불가
				m.setUserNo(rset.getInt("USERNO")); // 모든 컬럼에 대해 이 작업을 다 해 줘야 함 
				m.setUserId(rset.getString("USERID"));
				m.setUserPwd(rset.getString("USERPWD"));
				m.setUserName(rset.getString("USERNAME"));
				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.setEnrollDate(rset.getDate("ENROLLDATE"));
				// 컬럼명 오타 주의! get자료형 주의!
				// 한 행에 대한 모든 데이터 값들을 하나의 Member 객체로 옮겨 담기 끝!
				
				// 리스트에 해당 Member 객체를 담아 둘 것!
				list.add(m);
			}

		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			
			JDBCTemplate.close(rset);
			JDBCTemplate.close(pstmt);
		}
		
		// 8) 결과 반환
		return list; // 회원들의 정보가 담겨 있음!
					 // 만약 아무도 조회되지 않았다면 list.size() == 0 또는 list.isEmpty() == true 일 것
	}

 

	public Member selectByUserId(Connection conn, String userId) {
		
		// SELECT문 => ResultSet 객체 => 한 행 조회 (Member 하나로 결과 받기)
		
		Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
		
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 0) 필요한 변수들 먼저 세팅
		Member m = null; // 조회된 한 회원에 대한 정보를 담는 변수
		// Connection conn = null;
		// Statement stmt = null; // SQL문 (DELETE)을 실행 후 결과를 받을 수 있는 변수
		PreparedStatement pstmt = null; // SQL문 실행 후 결과를 받기 위한 변수
		ResultSet rset = null; // SELECT 문이 실행된 조회 결과들이 처음에 실질적으로 담길 객체

		// 실행할 SQL문 (완성된 형태, 세미콜론 X)
		// SELECT * FROM MEMBER WHERE USERID = 'XXXX'
		// String sql = "SELECT * FROM MEMBER WHERE USERID = '" + userId + "'";
		
		// 실행할 SQL문 (미완성된 형태, 세미콜론 X)
		// SELECT * FROM MEMBER WHERE USERID = ?
		
		//-- 정적 코딩 잘 가~
		// String sql = "SELECT * FROM MEMBER WHERE USERID = ?";
		
		//-- 동적 코딩 안녕?
		String sql = prop.getProperty("selectByUserId");
		
		try {
			// 1) JDBC Driver 등록
			// Class.forName("oracle.jdbc.driver.OracleDriver");
			// 잘못되면 ClassNotFoundException
			
			// 2) Connection 객체 생성(url, 계정명, 비밀번호)
			// conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
			
			// 3_1) PreparedStatement 객체 생성
			// stmt = conn.createStatement();
			pstmt = conn.prepareStatement(sql);
			
			// 3_2) 미완성된 쿼리문일 경우 완성 형태로 바꾸기
			pstmt.setString(1, userId); // 'user01'
			
			// 4, 5) SQL문 (SELECT)을 전달해서 실행 후 결과 받기
			// rset = stmt.executeQuery(sql);
			rset = pstmt.executeQuery();
			
			// 6_1) 현재 조회 결과가 담긴 ResultSet에서 한 행씩 뽑아서 VO 객체에 담기
			// while(rset.next()) { // 여러 행 조회일 경우 더 이상 뽑아낼 데이터가 없을 때까지 돌리기
			// -> ResultSet 특징: 커서가 제일 위의 행에서 기다리고 있음!
			// -> .next(): 바로 밑의 행에 값이 있다면 true, 없으면 false로 행을 뽑아냄
			
			if(rset.next()) { // 어차피 unique 제약 조건에 의해 한 행만 조회되므로 if로 돌리는 게 좋음
				
				// 적어도 이 중괄호 안에 들어왔다는 것은 조회된 결과가 있다는 것!
				// => 한 개의 행에 대해서 각 컬럼마다 값을 매번 뽑아서 VO 객체의 필드로 가공
				
				// 조회된 한 행에 대한 데이터값들을 뽑아서 하나의 Member 객체에 담기
				m = new Member(rset.getInt("USERNO")
							 , rset.getString("USERID")
							 , rset.getString("USERPWD")
							 , rset.getString("USERNAME")
							 , rset.getString("GENDER")
							 , rset.getInt("AGE")
							 , rset.getString("EMAIL")
							 , rset.getString("PHONE")
							 , rset.getString("ADDRESS")
							 , rset.getString("HOBBY")
							 , rset.getDate("ENROLLDATE"));
				
			}
			
			// 이 시점 기준으로 봤을 때
			// 만약 조회된 회원이 있다면 m이라는 변수에 Member 타입의 객체가 담겨 있음
			// 만약 조회된 회원이 없다면 m이라는 변수에 null 값이 담겨 있음
			
//		} catch (ClassNotFoundException e) {
//			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			
			// 7) JDBC 자원 반납(역순)
			JDBCTemplate.close(rset);
			JDBCTemplate.close(pstmt);
//			try {
//			// 7) 다 쓴 JDBC용 객체 반납 (생성된 역순)
//			// ResultSet -> Statement -> Connection
//				rset.close();
//				// stmt.close();
//				pstmt.close();
//				conn.close();
//			} catch (SQLException e) {
//				e.printStackTrace();
//			}

		}
		
		return m;
	}

 

	public ArrayList<Member> selectByUserName(Connection conn, String keyword) {
	// 다른 곳에서도 호출할 수 있어야 하므로 public
		
		// SELECT문 => ResultSet 객체 => 여러 행 (ArrayList<Member>)
		
		Properties prop = new Properties();
		
		try {
			
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
		// 0) 필요한 변수 먼저 세팅
		ArrayList<Member> list = new ArrayList<>(); // 조회된 결과를 담을 수 있는 텅 빈 리스트
		// Connection conn = null; // 접속된 DB의 정보를 담아 둘 수 있는 변수
		// Statement stmt = null; // SQL문 (DELETE)을 실행 후 결과를 받을 수 있는 변수
		PreparedStatement pstmt = null; // SQL문 (SELECT) 실행 및 결과를 받을 수 있는 변수
		ResultSet rset = null; // SELECT문 실행 후 결과들이 담길 변수
		
		// 실행할 SQL문(완성된 형태, 세미콜론 X)
		// SELECT * FROM MEMBER WHERE USERNAME LIKE '%XXX%'
		// String sql = "SELECT * FROM MEMBER WHERE USERNAME LIKE '%" + keyword + "%'";

		// 실행할 SQL문 (미완성된 형태, 세미콜론 X)
		// SELECT * FROM MEMBER WHERE USERNAME LIKE '%?%'
		// String sql = "SELECT * FROM MEMBER WHERE USERNAME LIKE '%?%'";
		// 구멍을 메꾸는 순간 문법에 맞지 않는 구문이 될 것임! (오류 발생)
		
		// 해결방법 1)
		// String sql = "SELECT * FROM MEMBER WHERE USERNAME LIKE '%' || ? ||'%'";
		// => 이 방법을 쓸 경우에는 메꿀 값을 그냥 제시하면 됨
		
		// 해결방법 2) 그냥 통째로 구멍을 뚫는 방법
		// String sql = "SELECT * FROM MEMBER WHERE USERNAME LIKE ?";
		// 단, 이 방법을 쓸 경우에는 메꿀 값 양 사이드에 "%" + keyword + "%" 이런식으로 메꿔 줘야 함
		
		String sql = prop.getProperty("selectByUserName");
		
		try {
			// 1) JDBC 드라이버 등록
			// Class.forName("oracle.jdbc.driver.OracleDriver");
			
			// 2) Connection 객체 생성
			// conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
			
			// 3_1) PreparedStatement 객체 생성
			// stmt = conn.createStatement();
			pstmt = conn.prepareStatement(sql);
			
			// 3_2) 미완성된 SQL문 완성시키기
			// 해결방법 1) 을 사용했을 경우
			// pstmt.setString(1,  keyword);
			// 해결방법 2) 를 사용했을 경우
			pstmt.setString(1,  "%" + keyword + "%");
			
			// 4, 5) SQL문 실행 후 결과 받기
			// rset = stmt.executeQuery(sql);
			rset = pstmt.executeQuery();
			
			// 6_1) 현재 조회 결과가 담긴 ResultSet에서 한 행씩 뽑아서 ArrayList에 담기
			
			while (rset.next()) {
				// 한 행에 담긴 데이터들을 한 Member 타입의 객체에 옮겨 담기 => 그 Member를 list에 담기
				
				// 표현법 1)
				list.add(new Member(rset.getInt("USERNO")
								  , rset.getString("USERNAME")
								  , rset.getString("USERPWD")
								  , rset.getString("USERNAME")
						  		  , rset.getString("GENDER")
								  , rset.getInt("AGE")
								  , rset.getString("EMAIL")
								  , rset.getString("PHONE")
								  , rset.getString("ADDRESS")
								  , rset.getString("HOBBY")
								  , rset.getDate("ENROLLDATE")));
				
				// 이 시점 기준으로 봤을 때
				// 조회된 회원이 없다면 list.size() == 0 또는 list.isEmpty() == true
				
//			표현법 2)			
//			Member m = new Member();		
//			m.setUserNo(rset.getInt("USERNO"));
//			m.setUserId(rset.getString("USERNAME"));
//			m.setUserPwd(rset.getString("USERPWD"));
//			m.setUserName(rset.getString("USERNAME"));
//			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.setEnrollDate(rset.getDate("ENROLLDATE"));
//			
//			list.add(m);		
			
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			
			// 7) 자원 반납 (역순)
			JDBCTemplate.close(rset);
			JDBCTemplate.close(pstmt);

		}
		
		// 8) 결과 반환
		return list; // 조회 결과가 없다면 list.size() == 0 또는 list.isEmpty() == true
	}

 

	public int updateMember(Connection conn, Member m) {
		
		// UPDATE문 => 처리된 행의 개수가 int형으로 반환 => 트랜잭션 처리
		
		Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 0) 필요한 변수 먼저 세팅
		int result = 0; // 처리된 행의 개수를 받을 수 있는 변수
		// Connection conn = null; // 접속된 DB의 정보를 담을 수 있는 변수
		// Statement stmt = null; // SQL문 (DELETE)을 실행 후 결과를 받을 수 있는 변수
		PreparedStatement pstmt = null; // SQL문(UPDATE) 실행 후 결과를 받을 수 있는 변수
		
		// 실행할 SQL문(완성된 형태, 세미콜론 X)
		/* UPDATE MEMBER
		 * SET USERPWD = 'XXXX'
		 * 	 , EMAIL = 'XXXXX'
		 *   , PHONE = 'XXXXX'
		 *   , ADDRESS = 'XXXXXX'
		 *  WHERE USERID = 'XXXX';
		 */
		
//		String sql = "UPDATE MEMBER "
//				   + "SET USERPWD = '" + m.getUserPwd() + "'"
//				   + ", EMAIL = '" + m.getEmail() + "'"
//				   + ", PHONE = '" + m.getPhone() + "'"
//				   + ", ADDRESS = '" + m.getAddress() + "' "
//				   + "WHERE USERID = '" + m.getUserId() + "'";
		
		// 실행할 SQL문 (미완성된 형태, 세미콜론 X)
		/*
		String sql = "UPDATE MEMBER "
					+ "SET USERPWD = ?"
					+ ", EMAIL = ?"
					+ ", PHONE = ?"
					+ ", ADDRESS = ?"
					+ " WHERE USERID = ?";
		*/
		
		String sql = prop.getProperty("updateMember");
		
		try {
			// 1) JDBC Driver 등록
			// Class.forName("oracle.jdbc.driver.OracleDriver");
			
			// 2) Connection 객체 생성
			// conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
			
			//(주목) conn.setAutoCommit(false);
			
			// 3_1) PreparedStatement 객체 생성
			// stmt = conn.createStatement();
			pstmt = conn.prepareStatement(sql);
			
			// 3_2) 미완성 SQL문을 완성된 형태로
			// pstmt: pstmt에
			// .set자료형: 자료형에 해당하는 값을 넣을 건데
			//             (몇번째구멍, 뭘넣을건지)
			pstmt.setString(1, m.getUserPwd());
			pstmt.setString(2, m.getEmail());
			pstmt.setString(3, m.getPhone());
			pstmt.setString(4, m.getAddress());
			pstmt.setString(5, m.getUserId());
			
			// 4, 5) SQL문 (UPDATE) 실행 후 결과 받기
			// result = stmt.executeUpdate(sql);
			//(주목) result = 를 안 쓸 경우 자동 커밋되므로 원래는 setAutoCommit(false)를 해 줘야 함
			// 근데 mybatis 가면 얘를 자동으로 1줄 메소드로 진행시켜 주기 때문에... 음...
			
			result = pstmt.executeUpdate();
			
			// 6_2) 트랜잭션 처리
			if(result > 0) { // 성공
				conn.commit();
			} else { // 실패
				conn.rollback();
			}

		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			
			// 7) 자원 반납
			JDBCTemplate.close(pstmt);
		}
		
		//  8) 결과 반환
		return result;
	}

 

public int deleteMember(Connection conn, String userId) {
		
		Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 0) 필요한 변수 선언
		int result = 0; // 처리된 행의 개수를 담을 변수
		// Connection conn = null; // 접속할 DB의 정보를 담는 변수
		// Statement stmt = null; // SQL문 (DELETE)을 실행 후 결과를 받을 수 있는 변수
		PreparedStatement pstmt = null;
		
		// 실행할 SQL문 (세미콜론 X)
		// DELETE FROM MEMBER WHERE USERID = 'XXX'
		// String sql = "DELETE FROM MEMBER WHERE USERID = '"
		//			 + userId + "'";
		
		// 실행할 SQL문 (세미콜론 X)
		// DELETE FROM MEMBER WHERE USERID = ?
		// String sql = "DELETE FROM MEMBER WHERE USERID = ?";
		
		String sql = prop.getProperty("deleteMember");
		
		try {
			// 1) JDBC Driver 등록
			// Class.forName("oracle.jdbc.driver.OracleDriver");
			
			// 2) Connection 객체 생성
			// conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
			
			// 3_1) PreparedStatement 객체 생성
			// stmt = conn.createStatement();
			pstmt = conn.prepareStatement(sql);
			
			// 3_2) 미완성 SQL문을 완성된 형태로
			pstmt.setString(1, userId);
			
			// 4, 5) SQL문 (DELETE문) 실행 후 결과 받기
			// result = stmt.executeUpdate(sql);
			result = pstmt.executeUpdate();
			
			// 6_2) 트랜잭션 처리
			if(result > 0) { // 성공
				conn.commit();
			} else { // 실패
				conn.rollback(); 
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			
			// 7) 자원 반납
			JDBCTemplate.close(pstmt);
			
		}
		// 8) 결과 반환
		return result;
		
	}

 

 

<MemberDao 클래스의 기본 생성자 이용하기>

👉🏻 코드를 더 줄여 보자!

 

 

 

public MemberDao() {

	System.out.println("MemberDao 기본생성자 호출됨");
    
}

모든 메뉴를 선택할 때마다 해당 출력문이 출력됨을 확인함
=> new MemberDao().xxxx(); 이기 때문에!

 

 

👉🏻 그렇다면 xxxx() 메소드에서 실행되는 중복 코드를 MemberDao()에 정의해 놓는다면?

 

	Properties prop = new Properties();
	
	// Service단에서
	// new MemberDao().xxxx(); 코드가 실행될 때마다 MemberDao 객체가 생성되면서 기본 생성자가 호출된 꼴
	
	public MemberDao() {
		
		// Dao의 모든 메소드들은 공통적으로
		// 제일 처음 Properties 객체를 생성 후 query.xml 파일로부터 모든 키-밸류 세트를 읽어들이는 코드를 가지고 있음
		
		try {
			prop.loadFromXML(new FileInputStream("resources/query.xml"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

 

👉🏻 이제 MemberDao 클래스의 모든 메소드에서 2가지 일을 하지 않아도 됨

1) Properties 객체 정의

2) prop.loadFromXML(new FileInputStream("resources/query.xml")); 로 통로 열기