파일 입출력에 특화된 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 수정 - 샘플 예시문>
<test.xml 수정 - 샘플 예시문>
.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")); 로 통로 열기