초록꼬마의 devlog
article thumbnail

2021.11.29(월)

🌿 Properties를 이용한 JDBC

  • 기존 방식 = JDBC Driver 구문, 내가 접속할 DB의 url 정보, 계정명, 비밀번호를 Java source codes 내에 명시적으로 작성함 = 정적/hard coding 방식 → 나쁜 건 아님, 일 바쁘면/마감시한 촉박하면 이렇게 함, 사고날 일 없고, 편함
    • 문제점 = DBMS가 변경되었을 경우 ou 접속할 url, 계정명, 비밀번호가 변경되었을 경우, Java source codes 수정해야 함 -> 수정한 코드 내용을 반영시키고자 한다면 프로그램을 재구동해야/껐다 켜야 함 -> 사용자 입장에서 프로그램 사용 중 비정상적으로 종료되었다가 다시 구동될 수 있음 -> 유지/보수 불편
  • 해결 방식 = DB 관련된 정보들을 별도로 관리하는 외부 파일(.properties 확장자)로 만들어서 관리, 외부 파일로 key에 대한 value를 읽어들여서 반영시킴 = 동적 coding 방식 -> 유지/보수 용이
    • 설정과 관련된 정보가 외부 파일에 존재 -> DB 관리자 등 Java 코드 모르는/일반 컴퓨터 사용자도 접속 정보를 쉽게 변경 가능
    • "resources/driver.properties"라는 파일(정보)을 읽어들이는 시점 = Connection 객체 생성 시/getConnection() 할 때 = 정보가 필요할 때 -> 프로그램 껐다 켜지 않고도 새로운 정보를 프로그램에 (실시간으로) 반영 가능

✔️ properties 파일 생성

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

import src.com.kh.view.MemberView;

public class Run {
    public static void main(String[] args) {
        /*
        Properties prop = new Properties(); // Properties 객체 하나 생성 -> java.util에 있는 것 import

        // setProperty() -> Properties 파일 내용 입력
        prop.setProperty("A", "B");    // key 'A' + value 'B'
        prop.setProperty("driver", "oracle.jdbc.driver.OracleDriver"); // 현재 사용하고 있는 Oracle driver 정보를 "driver"라는 key 값에 대응하는 value 값으로 입력
        prop.setProperty("url", "jdbc:oracle:thin:@localhost:1521:xe"); // jdbc하면서 사용하고 있는 url = getConnection()의 첫번째 인자 = version + url? + port번호..
        prop.setProperty("username", "JDBC");
        prop.setProperty("password", "JDBC");

        try {
            // FileOutputStream 객체 생성 시 생성자 매개변수로 경로 제시 vs 별도의 경로 제시가 없으면 프로젝트 폴더 내에 만들어짐
            // resources(폴더) = 주로 프로젝트 내의 외부파일들을 저장하는 역할
            prop.store(new FileOutputStream("resources/driver.properties"), "driver.properties"); // Properties 객체 생성한 뒤, setProperty()로 값을 넣고 나서, store() -> Properties 파일(외부 매체)에/로 set한 값들을 내보내기; 2개의 매개변수를 받음 = Writer(stream) + String(주석, comments)
            // Properties 파일 생성 시 위 keys/values 순서 엉망으로 들어감 = Map 계열 특징 -> 파일 열고, 내용 수정하고(내용 순서 수정, \ 삭제 등) 저장하면 반영됨 -> 프로그램 재실행하면 위 set한 것처럼 파일 내용 다시 써짐 -> 파일 한 번 생성(및 수정)한 뒤에는 파일 생성 코드 주석 처리함

            prop.storeToXML(new FileOutputStream("resources/query.xml"), "query.xml"); // storeToXML() -> XML 파일로 내보내기
            // XML 파일에서는, Properties 파일과 달리, 공백, 줄바꿔쓰기/개행 등 내가 쓰고 싶은대로 쓸 수 있음
            // XML 파일로 쿼리문 저장하면, 스트림 만들고 바깥으로부터 파일 불러와서 쓰는 속도(내가 느끼지는 못하겠지만, 훨씬 느림) < 메소드 내에 쿼리문 작성해서 쓰는 속도
        } catch (IOException e) { // "surround with try/multi-catch" 예외 처리 자동완성
            e.printStackTrace();
        } 
        */

        new MemberView().mainMenu(); // MemberView 객체 만들어서 더 쓸 일 없으니까, 그냥 기본생성자로 이번 객체 만들고 그 객체의 mainMenu() 메소드에 접근함
    } // main() 종료
} // 클래스 영역 끝

✔️ properties 파일 읽기

public static Connection getConnection() {
    // 동적 코딩 방식을 적용하기 위해 + Properties 파일 읽기 위해, Properties 객체를 생성
    Properties prop = new Properties();

    // Connection 객체를 담을 그릇 생성
    Connection conn = null;

    // 연결시키기 = JDBC 단계1,2)
    try {
        // load() 메소드 -> 읽기 = prop 객체로부터 load() 메소드를 이용해서 각 키에 해당되는 value 가져오기
        prop.load(new FileInputStream("resources/driver.properties"));

        // Class.forName("oracle.jdbc.driver.OracleDriver"); // 못 박아놓고 사용 = 정적 코딩 방식
        Class.forName(prop.getProperty("driver"));

        // conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC"); // 못 박아놓고 사용 = 정적 코딩 방식
        conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("username"), prop.getProperty("password"));
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return conn; // 만든 Connection 객체를 이 메소드 호출한 곳으로 반환
} // getConnection() 종료

🌱 xml 파일 이용

1. 프로젝트 내 resources 폴더에 query.xml 파일 생성

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>

    <comment>query.xml</comment>

    <entry key="insertMember">
        INSERT INTO
                    MEMBER
        VALUES
              (SEQ_USERNO.NEXTVAL
               , ?
               , ?
               , ?
               , ?
               , ?
               , ?
               , ?
               , ?
               , ?
               , SYSDATE)
    </entry>

    <entry key="selectAll">
        <!--SELECT USERNO, USERID, USERPWD, USERNAME, EMAIL-->
        SELECT * 
         FROM MEMBER  
        ORDER BY USERNAME ASC    
    </entry>

    <entry key="selectByUserId">
        SELECT *
         FROM MEMBER 
        WHERE USERID = ?
    </entry>

    <entry key="selectByUserName">
        SELECT *
         FROM MEMBER 
        WHERE USERNAME LIKE ?
    </entry>

    <entry key="updateMember">
        UPDATE
                MEMBER
            SET 
                USERPWD = ?
              , EMAIL = ?
              , PHONE = ?
              , ADDRESS = ?
        WHERE
                USERID = ?
    </entry>

    <entry key="deleteMember">
        DELETE FROM MEMBER
         WHERE USERID = ?
    </entry>

</properties>

2. Dao 생성자에서 xml 파일 읽어옴

public class MemberDao {
    private Properties prop = new Properties(); // properties 객체 생성 + 이 클래스만 사용할 수 있도록 캡슐화(접근제한자 private)

    // 생성자 내부에 파일 호출하는 코드 작성 -> Dao 객체 생성 시 xml 파일 다시 읽어옴
    public MemberDao() {
        try {
            prop.loadFromXML(new FileInputStream("resources/query.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }   
}

3. Dao의 멤버 메서드에서 xml 파일의 key-value 값 가져와서 사용

public int insertMember(Connection conn, Member m) {
    // 0) 필요한 변수 먼저 세팅 
    int result = 0; // 처리된 결과/행의 개수를 담아줄 변수
    // Connection 객체는 Service에서 받음
    PreparedStatement pstmt = null; // SQL문 실행 후 결과를 받기 위한 변수

    // 미완성된 형태의, 실행할, SQL문
    String sql = prop.getProperty("insertMember");

    // 1,2)는 해서 넘겨받음

    try {
        // 3a) PreparedStatement 객체 생성 + SQL문을 미리 넘겨줌
        pstmt = conn.prepareStatement(sql);

        // 3b) 미완성된 SQL문일 경우 완성시켜주기
        // pstmt.setXXX(?의 위치, 실제값)
        pstmt.setString(1, m.getUserId());
        pstmt.setString(2, m.getUserPwd());
        ...
        pstmt.setString(9, m.getHobby());

        // 4,5) 완성된 SQL문을 DB에서 실행한 뒤, 결과/처리된 행의 개수 받기
        result = pstmt.executeUpdate();

    } catch (SQLException e) {
        e.printStackTrace();
    } finally { // 더 이상 쓸 일/필요 없는 것들의 자원 반납
        close(pstmt);
    }

        // 6) 트랜잭션 처리는 Connection 객체를 가지고 하는 바, Connection 객체를 만든 Service에서 할 것임
        // -> service로 돌아가서 트랜잭션 처리할 때 result 필요하므로 가지고 돌아감
        return result;
    } // insertMember() 종료

💻 Statement 이용 + PreparedStatement 및 Properties 이용 조별 실습

"제품 관리 프로그램을 만들어 보자"

  • 실습 파일: 01_JDBC_ProductManager(Statement 이용), 05_JDBC_MiniProject(PreparedStatement 이용)
  • 역할 분담: selectAll(); / insertProduct(); / selectByProductName(); / updateProduct(); (본인 담당) / deleteProduct();와 같이 각 메서드를 조원 1명이 담당 + 공통 모듈(VO, JDBC template, .properties, .xml 등)은 조원 1명이 담당해서 만들고 프로젝트 초반에 공유

📗 homework: JDBC/DBMS 평가 준비