본문 바로가기

3월 ~ 5월) 자바/jdbc

34Day - SqlMinusApp / CallableStatementApp / !!PropertiesApp & user.properties / ConnectionPool & ConnectionPoolApp & db.properites / DataSourceApp / !!StudentDAO & StudentDTO & StudentDAOImpl / JdbcDAO

 SqlMinusApp 

package xyz.itwill.jdbc;

 

// 키보드로 SQL 명령을 입력받아 DBMS 서버에 전달하여 실행하고 실행결과를 출력하는 JDBC 프로그램 작성
// > 키보드로 INSERT,UPDATE,DELETE,SELECT 명령만 입력받아 실행되도록 작성
// > SQL 명령은 [exit] 명령을 입력하기 전까지 반복적으로 입력받아 실행 - 대소문자 미구분
// > 입력받은 SQL 명령이 잘못된 경우 에러 메세지 출력

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.*;


public class SqlMinusApp {
    public static void main(String[] args) throws Exception {
        //키보드로 SQL 명령을 입력받기 위한 입력스트림 생성
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        Connection con = ConnectionFactory.getConnection();
        Statement stmt = con.createStatement();
        ResultSet rs = null;

        System.out.println("SQLMinus 프로그램을 실행합니다.(종료 : exit)");

        while(true) {
            // 키보드로 SQL 명령을 입력받아 저장
            System.out.print("SQL> ");
            // 키보드로 입력받은 문자열의 앞과 뒤에 존재하는 모든 공백을 제거한 후 변수에 저장
            String sql = in.readLine().trim();

            // 키보드 입력값이 없는 경우 반복문을 처음부터 다시 실행
            if(sql == null || sql.equals("")) continue;

            // 키보드 입력값이 [exit]인 경우 반복문 종료 - 프로그램 종료
            if(sql.equalsIgnoreCase("exit")) break;

            try {
                // 입력받은 SQL 명령을 전달하여 실행하고 실행결과를 반환받아 출력
                if(stmt.execute(sql)) { // 전달되어 실행된 SQL 명령이 SELECT 명령인 경우
                    rs = stmt.getResultSet();

                    if(rs.next()) { // 검색행이 있는 경우
                        ResultSetMetaData rsmd = rs.getMetaData();

                        // 검색행의 컬럼의 갯수를 반환받아 저장
                        int columnCount = rsmd.getColumnCount();

                        System.out.println("===============================================");

                        // 검색행의 컬럼명을 반환받아 출력
                        for(int i = 1; i <= columnCount; i++) {
                            System.out.print(rsmd.getColumnLabel(i) + "\t");
                        }
                        System.out.println();
                        System.out.println("===============================================");
                        do {
                            for(int i = 1; i <= columnCount; i++) {
                                String columnValue = rs.getString(i);

                                if(rsmd.getColumnTypeName(i).equals("DATE")) { // 컬럼의 자료형이 DATE인 경우
                                    // [yyyy-MM-dd] 형식의 문자열로 분리하여 저장
                                    columnValue = columnValue.substring(0, 10); // [yyyy-MM-dd] 형식의 문자열로 분리하여 저장
                                }
                                if(columnValue == null) { // 컬럼값이 없는 경우
                                    columnValue = "";
                                }
                                System.out.print(columnValue + "\t");
                            }
                            System.out.println();
                        } while(rs.next());

                    } else { // 검색행이 없는 경우
                        System.out.println("검색된 결과가 없습니다.");
                    }
                } else { // 전달되어 실행된 SQL 명령이 INSERT,UPDATE,DELETE 명령인 경우
                    int rows=stmt.getUpdateCount();
                    System.out.println(rows+"개의 행을 "+sql.substring(0,6).toUpperCase()+" 하였습니다.");
                }
            } catch (SQLException e) { // 전달되어 실행된 SQL 명령이 잘못된 경우 SQLException 발생
                System.out.println("SQL 오류 = "+e.getMessage());
            }
        }

        ConnectionFactory.close(con, stmt, rs);
        System.out.println("SQLMinus 프로그램을 종료합니다.");
    }
}

 


실행문

SQLMinus 프로그램을 실행합니다.(종료 : exit)
SQL> select * from dept
===============================================
DEPTNO	DNAME	LOC	
===============================================
10	ACCOUNTING	NEW YORK	
20	RESEARCH	DALLAS	
30	SALES	CHICAGO	
40	OPERATIONS	BOSTON	
SQL> select empno,ename,sal from emp
===============================================
EMPNO	ENAME	SAL	
===============================================
7369	SMITH	800	
7499	ALLEN	1600	
7521	WARD	1250	
7566	JONES	2975	
7654	MARTIN	1250	
7698	BLAKE	2850	
7782	CLARK	2450	
7788	SCOTT	3000	
7839	KING	5000	
7844	TURNER	1500	
7876	ADAMS	1100	
7900	JAMES	950	
7902	FORD	3000	
7934	MILLER	1300	
SQL> select empno, ename, sal from emp where empno = 5000
검색된 결과가 없습니다.
SQL> select no, nema from student
SQL 오류 = ORA-00904: "NEMA": 부적합한 식별자

SQL> select no, name from student
===============================================
NO	NAME	
===============================================
1000	홍길동	
1500	정우성
2000	임꺽정	
2500	정대만	
SQL> exit
SQLMinus 프로그램을 종료합니다.

 

CallableStatementApp

package xyz.itwill.jdbc;

 

// 키보드로 학번을 입력받아 STUDENT 테이블에 저장된 학생정보 중 해당 학번의 학생정보를 삭제하는
// JDBC 프로그램 작성 - 저장 프로시저를 호출하여 학생정보를 삭제 처리 ▽


/*
// sql프로그램 - user - 워크시트에 작성하기
CREATE OR REPLACE PROCEDURE DELETE_STUDENT(VNO IN STUDENT.NO%TYPE
    ,VNAME OUT STUDENT.NAME%TYPE) IS
BEGIN
    SELECT NAME INTO VNAME FROM STUDENT WHERE NO=VNO;     // 삭제하기전 검색하고 vname에 null 넣어주는
    IF SQL%FOUND THEN
        DELETE FROM STUDENT WHERE NO=VNO;
        COMMIT;
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        VNAME := NULL;
END;
/
*/

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Types;

public class CallableStatementApp {
    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        System.out.println("<<학생정보 삭제>>");
        System.out.print("학번 입력 >>");
        int no = Integer.parseInt(in.readLine());

        System.out.println("-----------------------------------------------");

        Connection con=ConnectionFactory.getConnection();

        // Connection.prepareCall(String sql) : 저장 프로시저를 호출하는 명령을 전달하여 실행
        // 하기 위한 CallableStatement 객체를 반환하는 메소드
        // > 저장 프로시저를 호출하는 명령 - {call 저장프로시저명 (?,?, ...)}
        // > 저장 프로시저에서 사용한 ? 기호에는 반드시 setXXX() 메소드를 호출하여 값을 전달하거나
        // 전달하거나 registerOutParameter() 메소드를 호출하여 값을 제공받아 저장

        String sql = "{call delete_student (?,?)}";
        CallableStatement cstmt = con.prepareCall(sql);
        // CallableStatement.setXXX (int parmeterIndex, XXX value) : 저장 프로시저에 사용한
        // 매겨변수 중 IN 모드의 매개변수에 값을 전달하기 위한 메소드
        cstmt.setInt(1, no);

        // CallableStatement.registerOutParameter (int parameterIndex, int sqlType)
        // > 저장 프로시저에서 사용한 매개변수 중 OUT 모드의 매개변수에 저장된 값을 제공받기
        // > sqlType : SQL 자료형 - Type 클래스의 상수 사용
        cstmt.registerOutParameter(2, Types.NVARCHAR); // registerOutParameter 저장도 해줌

        // CallableStatement.execute() : 저장 프로시저를 호출하는 명령을 전달하여 실행하는 메소드
        cstmt.execute();

        // CallableStatement.getXXX (int parameterIndex) : 저장 프로시저에서 사용한 매개변수중
        // OUT 모드의 매개변수에 저장된 값을 반환하는 메소드
        String name = cstmt.getNString(2);

        if ( name == null) {
            System.out.println("[메세지]해당 학번의 학생정보를 찾을 수 없습니다.");
        } else {
            System.out.println("[메세지]" + name + "님을 삭제 하였습니다.");
        }

        ConnectionFactory.close(con, cstmt);

    }
}

<<학생정보 삭제>>
학번 입력 >>1500
----------------------------------------------
[메세지]정우성님을 삭제 하였습니다.

 


PropertiesApp (패키지 변경)

package xyz.itwill.dbcp;
// Properties 파일 : 프로그램 실행에 필요한 값을 제공하기 위한 텍스트 파일 - 확장자 : ~.propert
// > 프로그램을 변경하지않고 Properties파일의 내용을 변경하여 프로그램 실행 결과 변경 가능
// > 프로그램의 유지보스 효율성 증가
// > Properties 파일에서 제공되는 값은 문자열만 가능
// > Properties 파일에선 영문자, 숫자, 일부 특수문자를 제외한 나머지 문자는 유니코드로 변환되어 처리

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

// user.properties 파일에 저장된 값을 얻어와 출력하는 프로그램 작성
public class PropertiesApp {
    // ▽ throws IOException 하면 new FileInputStream 오류해제

    public PropertiesApp() throws IOException {

        // Properties 파일을 읽기위한 입력 스트림 생성
        // > Properties 파일의 경로를 제공받아 FileInputStream 클래스로 객체 생성
        // > 프로그램 배포시 파일경로에 대한 문제 발생가능 (같은경로가 아니라서) ▽ 사용X
        // FileInputStream in = new FileInputStream("src/xyz/itwill/dbcp/user.properties");

        // Object. getClass() : 현재 실행중인 클래스에 대한 Class 객체(Clazz)를 반환하는 메소드
        // Class.getClassLoader() : 클래스를 읽어 메모리에 저장된 ClassLoader 객체를 반환하는 메소드
        // ClassLoader.getResourceAsStream(String name) : 리소스 파일에 대한 입력스트림을 생성함
        InputStream in = getClass().getClassLoader().getResourceAsStream("xyz/itwill/dbcp/user.properties");

        // Properties 객체 생성 - 다수의 엔트리 (Entry - Key와 Value)저장
        // > Properties 클래스는 Map 인터페이스를 상속받은 자식클래스
        // > Properties 파일의 이름(Name)과 값(Value)을 하나의 엔트리(Entry) 로 저장하기 위한 객체
        Properties properties = new Properties();


        // Properties.load(InputStream in) : 입력스트림으로 Properties파일을 제공받아 파일에
        // 저장된 모든 이름과 값으로 Properties 객체에 엔트리를 추가하는 메소드
        properties.load(in);

        // properties.get(String key) : Properties 객체에 저장된 엔트리에서 맵키(MapKey)를
        // 전달받아 맵값(MapValue)를 반환하는 메소드
        String id = (String) properties.get("id");
        String password = (String) properties.get("password");
        String name = (String) properties.get("name");

        System.out.println("아이디 = " + id);
        System.out.println("비밀번호 = " + password);
        System.out.println("이름 = " + name);

    }


    public static void main(String[] args) throws IOException {
        new PropertiesApp();
    }
}

아이디 = abc123
비밀번호 = 123456
이름 = 임꺽정

 


user.properties (file)

#Comment - \uC124\uBA85\uBB38
#properties File > Configuration File
#Name = Value
id = abc123
password = 123456
name = \uC784\uAEBD\uC815

 


ConnectionPool  (붙여)

package xyz.itwill.dbcp;

 

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;

// Connection Pooling 기능을 제공한 클래스 - DBCP(DataBase Connection Pool) 클래스
public final class ConnectionPool {
    // Singleton
    private static ConnectionPool instance;

    // Storage for the unused connections.
    private Vector<Connection> free;

    // Storage for the allocated connections.
    private Vector<Connection> used;

    // Connection information.
    private String driver;
    private String url;
    private String user;
    private String password;

    // Initial Connections
    private int initialCons = 0;

    // Maximum number of concurrent connections allowed.
    private int maxCons = 0;

    // The number of connection that have been created.
    private int numCons = 0;

    // Whether to block until a connection is free when maxCons are in use.
    private boolean block;

    // Timeout waiting for a connection to be released when blocking.
    private long timeout;

    // Whether we should re-use connections or not
    private boolean reuseCons = true;

    /**
     * Creates a new <code>ConnectionPool</code> with defaults for
     * <code>Connection</code> characteristics. Specifies the url, user and
     * password for <code>Connections</code> and the characteristics of the
     * pool.
     *
     * @param url
     *
     *            The url for the database, as in jdbc:<em>subprotocol</em>:
     *            <em>subname</em>.
     * @param user
     *            The user to connect to the database as.
     * @param password
     *            The user's password.
     * @param initialCons
     *            The number of connections to create now.
     * @param maxCons
     *            The maximum number of connections allowed. A value <= 0 means
     *            "no limit". If maxCons > 0 and maxCons < initialCons then
     *            maxCons takes precedence.
     * @param block
     *            If a request for a connection should block until a connection
     *            is released when none are available and maxCons has been
     *            reached.
     * @param timeout
     *            Maximum time to wait for a connection to be released when
     *            maxCons are in use.
     * @exception SQLException
     *                if a connection could not be established, this is the
     *                exception thrown by DriverManager.
     * @see java.sql.Connection
     * @see java.sql.DriverManager
     * @see java.sql.DriverManager#getConnection(String, String, String)
     **/
    private ConnectionPool() throws SQLException {
        loadConf();
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // maxCons has precedence over initialCons
        if (maxCons > 0 && maxCons < initialCons)
            initialCons = maxCons;

        // Create vectors large enough to store all the connections we make now.
        free = new Vector<Connection>(initialCons);
        used = new Vector<Connection>(initialCons);

        // Create some connections.
        while (numCons < initialCons) {
            addConnection();
        }
    }

    public static ConnectionPool getInstance() throws SQLException {
        if (instance == null) {
            synchronized (ConnectionPool.class) {
                if (instance == null) {
                    instance = new ConnectionPool();
                }
            }
        }
        return instance;
    }

    private void loadConf() {
        ClassLoader cl = getClass().getClassLoader();
        InputStream in = cl.getResourceAsStream("xyz/itwill/dbcp/db.properties");
        Properties p = new Properties();
        try {
            p.load(in);
        } catch(IOException e) {
            e.printStackTrace();
        }
        this.driver = (String)p.get("driver");
        this.url = (String)p.get("url");
        this.user = (String)p.get("user");
        this.password = (String)p.get("passwd");
        this.initialCons = Integer.parseInt((String)p.get("initialCons"));
        this.maxCons = Integer.parseInt((String)p.get("maxCons"));
        this.block = Boolean.getBoolean((String)p.get("block"));
        this.timeout = Long.parseLong((String)p.get("timeout"));
    }

    /**
     * Closes all unallocated <code>connections</code>, allocated
     * <code>connections</code> are marked for closing when they are released.
     *
     * @see #freeConnection
     * @see java.sql.Connection#close
     **/
    @SuppressWarnings("unchecked")
    public synchronized void closeAll() {
        Enumeration<Connection> cons = ((Vector<Connection>)free.clone()).elements();
        while (cons.hasMoreElements()) {
            Connection con = (Connection) cons.nextElement();

            free.removeElement(con);
            numCons--;

            try {
                con.close();
            } catch (SQLException e) {
                // The Connection appears to be broken anyway, so we will ignore it
                e.printStackTrace();
            }
        }

        // Move allocated connections to a list of connections that are closed
        // when they are released.
        cons = ((Vector<Connection>) used.clone()).elements();
        while (cons.hasMoreElements()) {
            Connection con = (Connection) cons.nextElement();

            used.removeElement(con);
        }
    }

    /**
     * Gets the <code>block</code> property for the pool. The block values
     * specifies whether a <code>getConnection()</code> request should wait for
     * a <code>connection</code> to be release if the maximum allowed are all in
     * use.
     *
     * @return The block property.
     * @see #setBlock
     * @see #getConnection
     **/
    public boolean getBlock() {
        return block;
    }

    /**
     * Gets a <code>Connection</code> from the pool.
     *
     * @return A connection
     * @exception ConnectionPoolException
     *                if the maximum number of allowed connections are all in
     *                use, and, the "pool" is not blocking or the timeout
     *                expired when waiting.
     * @exception SQLException
     *                if all existing connections are in use and a new one could
     *                not be created, this is the exception thrown by
     *                DriverManger when attempting to get a new connection.
     * @see java.sql.DriverManager
     **/
    public Connection getConnection() throws SQLException {

        return getConnection(this.block, timeout);
    }

    /**
     * Gets a <code>Connection</code> from the pool.
     *
     * @param block
     *            If a request for a connection should block until a connection
     *            is released when none are available and maxCons has been
     *            reached, overrides the value specified at construction.
     * @param timeout
     *            Maximum time to wait for a connection to be released when
     *            maxCons are in use, overrides the values specified at
     *            construction.
     * @return A connection
     * @exception ConnectionPoolException
     *                if the maximum number of allowed connections are all in
     *                use, and, the "pool" is not blocking or the timeout
     *                expired when waiting.
     * @exception SQLException
     *                if all existing connections are in use and a new one could
     *                not be created, this is the exception thrown by
     *                DriverManger when attempting to get a new connection.
     * @see java.sql.DriverManager
     **/
    public synchronized Connection getConnection(boolean block, long timeout) throws SQLException {

        if (free.isEmpty()) {

            // None left, do we create more?
            if (maxCons <= 0 || numCons < maxCons) {
                addConnection();
            } else if (block) {
                try {
                    long start = System.currentTimeMillis();
                    do {
                        wait(timeout);
                        if (timeout > 0) {
                            timeout -= System.currentTimeMillis() - start;
                            if (timeout == 0) {
                                timeout -= 1;
                            }
                        }
                    } while (timeout >= 0 && free.isEmpty() && maxCons > 0 && numCons >= maxCons);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Did anyone release a connection while we were waiting?
                if (free.isEmpty()) {
                    /*
                     * OK, nothing on the free list, but someone may have
                     * released a connection that they closed, so the free list
                     * is empty but numCons is now < maxCons and we can create a
                     * new connection.
                     */
                    if (maxCons <= 0 || numCons < maxCons) {
                        addConnection();
                    } else {
                        throw new SQLException("Timeout waiting for a connection to be released");
                    }
                }
            } else {
                // No connections left and we don't wait for more.
                throw new SQLException("Maximum number of allowed connections reached");
            }
        }
        // If we get this far at least one connection is available.
        Connection con;

        synchronized (used) {

            con = (Connection) free.lastElement();
            // Move this connection off the free list
            free.removeElement(con);
            used.addElement(con);
        }
        return con;
    }

    /**
     * Gets the <code>maxCons</code> property for the pool. The maxCons values
     * specifies the maximum number of <code>Connections</code> that can be
     * allocated from the pool at any one time.
     *
     * @return The maxCons property.
     * @see #getConnection
     **/
    public int getMaxCons() {
        return maxCons;
    }

    /**
     * Gets the reuseConnections property for the pool.
     *
     * @see #setReuseConnections
     **/
    public boolean getReuseConnections() {
        return reuseCons;
    }

    /**
     * Gets the <code>timeout</code> property for the pool. The timeout values
     * specifies how long to wait for a <code>Connection</code> to be release if
     * the maximum allowed are all in use when <code>getConnection()</code> is
     * called and <code>block</code> is true.
     *
     * @return The timeout property.
     * @see #setTimeout
     **/
    public long getTimeout() {
        return timeout;
    }

    /**
     * Gets the <code>url</code> property for the pool. This property is the url
     * for <code>Connections</code> opened by this pool.
     *
     * @return The url property.
     **/
    public String getUrl() {
        return url;
    }

    /**
     * Release a connection back to the pool. Apps should take care not to use a
     * <code>Connection</code> after it has been released as it may well be in
     * use somewhere else, in which case the effects are undefined.
     * <p>
     * If an app has cause to close a <code>Connection</code> then it should
     * still be released so that the count of active transactions is correct and
     * a new <code>Connection</code> can be created to take its place.
     * <p>
     * A rollback is performed on the <code>Connection</code> so if autoCommit
     * is false any changes made that have not been committed will be lost.
     *
     * @param con
     *            The Connection to put back in the pool.
     * @exception UnownedConnectionException
     *                if the Connection did not come from this pool.
     * @see #getConnection
     **/
    public synchronized void freeConnection(Connection con) throws SQLException {

        boolean reuseThisCon = reuseCons;

        if (used.contains(con)) {
            // Move this connection from the used list to the free list
            used.removeElement(con);
            numCons--;
        } else {
            throw new SQLException("Connection " + con + " did not come from this ConnectionPool");
        }

        try {
            if (reuseThisCon) {
                free.addElement(con);
                numCons++;
            } else {
                con.close();
            }

            // Wake up a thread waiting for a connection
            notify();
        } catch (SQLException e) {
            /*
             * The Connection seems to be broken, but it's off the used list and
             * the connection count has been decremented.
             */
            // Just to be sure
            try {
                con.close();
            } catch (Exception e2) {
                // we're expecting an SQLException here
            }
            notify();
        }
    }

    /**
     * Sets the <code>block</code> property for the pool. The block values
     * specifies whether a <code>getConnection()</code> request should wait for
     * a <code>Connection</code> to be release if the maximum allowed are all in
     * use.
     * <p>
     * Setting <code>block</code> to false will have no effect on any connection
     * requests that have already begin to wait for a connection.
     *
     * @param block
     *            The block property.
     * @see #getBlock
     **/
    public void setBlock(boolean block) {
        this.block = block;
    }

    /**
     * Sets the reuseConnections property on the pool. If this property is false
     * then whenever a <code>Connection</code> is released it will be closed.
     *
     * @see #getReuseConnections
     * @see #freeConnection
     * @see java.sql.Connection
     * @see java.sql.Connection#close
     **/
    public synchronized void setReuseConnections(boolean reuseCons) {
        this.reuseCons = reuseCons;
    }

    /**
     * Sets the <code>timeout</code> property for the pool. The timeout values
     * specifies how long to wait for a <code>Connection</code> to be release if
     * the maximum allowed are all in use when <code>getConnection()</code> is
     * called and <code>block</code> is true.
     * <p>
     * Setting <code>timeout</code> will have no effect on any
     * <code>Connection</code> requests that have already begin to wait for a
     * <code>Connection</code>.
     *
     * @return The timeout property.
     * @see #getTimeout
     **/
    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    /**
     * Adds a new <code>Connection</code> to the pool.
     *
     * @exception SQLException
     *                if a connection could not be established.
     **/
    private void addConnection() throws SQLException {
        free.addElement(getNewConnection());
    }

    /**
     * Gets a new <code>Connection</code>.
     *
     * @exception SQLException
     *                if a connection could not be established.
     **/
    private Connection getNewConnection() throws SQLException {

        Connection con = null;

        //System.out.println("About to connect to " + url);
        try {
            con = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ++numCons;
        return con;
    }
}

 


ConnectionPoolApp (공통적으로 오류남 왜? >>  반환받을값이 2~3 개인데, 3개가 넘어버려서)

package xyz.itwill.dbcp;

 

// DBCP(DataBase Connection Pool) : 다수의 Connection 객체를 미리 생성하여 저장하고
// Connection 객체를 반환하는 기능을 제공하는 클래스
// > Connection 객체를 미리 생성하여 사용하므로 JDBC 프로그램의 실행 속도 증가
// > Connection 객체를 생성하기 위한 정보의 변경 용이 - 유지보수의 효율성 증가
// > Connection 객체의 갯수 제한 가능


import java.sql.Connection;
import java.sql.SQLException;

public class ConnectionPoolApp {
    public static void main(String[] args) throws SQLException {
        // ConnectionPool 클래스를 싱글톤 클리스이므로 new 연산자를 사용하여 객체를 생성하지
        // 않고 메소드를 호출하여 객체를 반환받아 사용
        // > ConnectoinPool 객체에는 Connection 객체가 2개 생성되어 저장
        ConnectionPool cp = ConnectionPool.getInstance();

        // ConnectionPool.getConnection() : ConnectionPool객체에 저장된 Connection객체 중 하나를
        // 반환하는 메소드 - 다른 사용자가 사용 불가능하도록 설정
        Connection con1 = cp.getConnection();
        System.out.println("con1 = " + con1);
        // ConnectionPool.freeConnection(Connection con) : Connection 객체를 ConnectionPool
        // 객체에 반환하는 메소드 - 다른 사용자가 사용 가능하므로 설정
        // cp.freeConnection(con1);

        Connection con2 = cp.getConnection();
        System.out.println("con2 = " + con2);
        // cp.freeConnection(con2);

        Connection con3 = cp.getConnection();
        System.out.println("con3 = " + con3);
        // cp.freeConnection(con3); // 주석처리안하면 제대로된 값이 나옴 (반환받을수있는값이 늘어나서)

        Connection con4 = cp.getConnection();
        System.out.println("con4 = " + con4);
        cp.freeConnection(con4); 

    }
}

con1 = oracle.jdbc.driver.T4CConnection@4b2c5e02
con2 = oracle.jdbc.driver.T4CConnection@6200f9cb
con3 = oracle.jdbc.driver.T4CConnection@5e17553a
Exception in thread "main" java.sql.SQLException: Maximum number of allowed connections reached
	at xyz.itwill.dbcp.ConnectionPool.getConnection(ConnectionPool.java:260)
	at xyz.itwill.dbcp.ConnectionPool.getConnection(ConnectionPool.java:197)
	at xyz.itwill.dbcp.ConnectionPoolApp.main(ConnectionPoolApp.java:35)

 db.properites 

#########################################
### ConnectionPool Configuration file ###
#########################################
url = jdbc:oracle:thin:@localhost:1521:XE
user = scott
passwd = tiger
driver = oracle.jdbc.driver.OracleDriver
initialCons = 2
maxCons = 3
block = true 
timeout = 10000

DataSourceApp

package xyz.itwill.dbcp;

 

// javax.sql.DataSource : DBCP 클래스를 작성하기 위한 상속받기 위한 인터페이스
// => DBCP 클래스의 메소드를 동일한 형식으로 작성하기 위한 규칙 제공

// UCP(Universal Connection Pool) 라이브러리로 제공되는 DBCP 클래스를 이용한 JDBC 프로그램 작성
// > https://www.oracle.com 사이트의 페이지에서 UCP 라이브러리 파일(ucp11.jar) 파일을 다운로드 받아 프로젝트에 빌드 처리

import java.sql.Connection;
import java.sql.SQLException;

import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;

public class DataSourceApp {
    public static void main(String[] args) throws SQLException { // throws SQLException 하면 setConnectionFactoryClassName 에러안뜸
        // PoolDataSource 객체를  (dbcp 객체)를 반환받아 저장
        // PoolDataSourceFactory.getPoolDataSource() : PoolDataSource 객체를 생성하여 반환하는
        PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();

        // PoolDataSource 객체에 저장될 Connection 객체를 생성하기 위한 메소드 호출

        // PoolDataSourceFactory.setConnectionFactoryClassName (String driver)
        // > PoolDataSource 객체의 JDBC Driver 클래스를 변경하는 메소드
        pds.setConnectionFactoryClassName("oracle.jdbc.driver.OracleDriver");

        // PoolDataSourceFactory.setUser (String user)
        // > PoolDataSource 객체의 사용자 이름을 변경하는 메소드
        pds.setURL("jdbc:oracle:thin:@localhost:1521:xe");

        // PoolDataSourceFactory.setUser(String user)
        // > PoolDataSource 객체의 사용자 이름을 변경하는 메소드
        pds.setUser("scott");

        // PoolDataSourceFactory.setPassword (String password)
        // > PoolDataSource 객체의 사용자 비밀번호를 변경하는 메소드
        pds.setPassword("tiger");

        // PoolDataSource 객체에 저장될 Connection 객체의 갯수를 제한하기 위한 메소드 호출
        // > 메소드 호출을 생략할 경우 기본값을 사용하여 Connection 갯수 제한
        pds.setInitialPoolSize(2); // 최초 생성되는 Connection 객체의 갯수 변경
        pds.setMaxPoolSize(3); // 최대 생성되는 Connection 객체의 갯수 변경

        // PoolDataSource.getConnection() : PoolDataSource 객체에 저장된 Connection 객체 중 하나를 반환하는 메소드
        Connection con1 = pds.getConnection();
        System.out.println("con1 = "+con1);
        // Connection 객체를 제거하면 자동으로 PoolDataSource 객체로 Connection 객체를 반환
        // con1.close();

        Connection con2 = pds.getConnection();
        System.out.println("con2 = " + con2);
        // con2.close();

        Connection con3 = pds.getConnection();
        System.out.println("con3 = " + con3);
        // con3.close();

        Connection con4 = pds.getConnection();
        System.out.println("con4 = " + con4);
        con4.close();

    }
}

StudentDAO (*interface)

package xyz.itwill.student;

 

// DAO 클래스가 상속받기 위한 인터페이스
// > 추상메소드를 선언하여 인터페이스를 상속받은 자식클래스 가 동일한 메소드가 선언되도록 메소드의 작성 규칙 제공
// > 프로그램에서 사용하는 DAO 클래스가 변경돼도 프로그램에 영향을 최소화 하기위해 인터페이스 선언
public interface StudentDAO {

    // 학생정보를 전달받아 STUDENT 테이블에 삽입하고 삽입행의 갯수를 반환하는 메소드
    int insertStudent (StudentDTO student);


    // 학생정보를 전달받아 STUDENT 테이블에 저장된 학생정보를 변경하고 변경행의 갯수를 반환하는 메소드
    int updateStudent (StudentDTO student);


    // 학번을 전달받아 STUDENT 테이블에 저장된 학생정보를 삭제하고 삭제행의 갯수를 반환하는 메소드
    int deleteStudent(int no);


    // 학번을 전달받아 STUDENT 테이블에 저장된 해당 학번의 학생정보를 검색하여 반환하는 메소드
    // > 단일행은 값 또는 DTO 객체 반환
    StudentDTO selectStudent(int no);

    // 이름을 전달받아 STUDENT 테이블에 저장된 해당 이름의 학생정보를 검색하여 반환하는 메소드
    // > 다중행은 List 객체 반환
    List<StudentDTO> selectNameStudentList(String name);

    // STUDENT 테이블에 저장된 모든 학생정보를 검색하여 반환하는 메소드
    List<StudentDTO> selectAllStudentList();
}

StudentDTO

package xyz.itwill.student;

 

// DTO (Data Transfer Object) 클래스 : DAO 클래스의 메소드에 필요한 정보를 매개변수로 전달하거나
// 메소드의 실행결과를 저장하여 반환하기 위한 클래스 - VO (Value Object) 클래스
// > 테이블의 컬럼과 1:1로 매핑되는 필드 선언 - Getter & Setter
// > 필드의 이름은 컬럼의 이름과 동일하게 작성하는 것을 권장

/*
이름       널?       유형
-------- -------- -------------
NO       NOT NULL NUMBER(4)
NAME              VARCHAR2(50)
PHONE             VARCHAR2(20)
ADDRESS           VARCHAR2(100)
BIRTHDAY          DATE
*/

// STUDENT 테이블에 저장된 하나의 행 (학생정보)을 저장하여 전달하기 위한 클래스
public class StudentDTO {
    private  int no;
    private  String name;
    private  String phone;
    private  String address;
    private  String birthday;

    //[Ctrl]+[Space] >> Constructor 선택
    public StudentDTO() {
        // TODO Auto-generated constructor stub
    }

    //[Alt]+[Shift]+[S] >> 팝업메뉴 >> [O] >> 필드 선택 >> Generate
    public StudentDTO(int no, String name, String phone, String address, String birthday) {
        super();
        this.no = no;
        this.name = name;
        this.phone = phone;
        this.address = address;
        this.birthday = birthday;
    }

            // 이클립스 :: [Alt]+[Shift]+[S] >> 팝업메뉴 >> [R] >> 필드 선택 >> Generate
    // 인텔리제이 :: alt + insert > getter and setter 해서 생성 ▽
    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

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

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getBirthday() {
        return birthday;
    }

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

    public String toString() {
        return  no + "\t" + name + "\t" + phone + "/t" + address + "\t" + birthday;
    }
}

StudentDAOImpl 

package xyz.itwill.student;

 

// DAO (Data Access Object) 클래스 : 저장매체에 행 정보를 삽입, 삭제, 변경, 검색하는 기능을 제공하는
// > 저장매체 : 정보를 행단위로 저장해 관리하기 위한 하드웨어 또는 소프트웨어 - DBMS
// > 인터페이스를 상속받아 작성하는 것을 권장 - 메소드 작성 규칙 제공 : 유지보수의 효율성 증가
// > 싱글톤 디자인 패턴을 적용하여 작성하는 것을 권장 - 프로그램에 하나의 객체만 제공되는 클래스

// STUDENT 테이블에 행을 삽입, 삭제,변경 검색하는 기능의 메소드를 제공하는 클래스
// > 메소드는 매개변수로 SQL 명령에 필요한 값을 객체(변수)로 전달받아 하나의 SQL 명령을
// DBMS 서버에 전달하여 실행하고 실행결과를 Java 객체(값)로 매핑 전환
// > JdbcDAO 클래스를 상속받아 DAO 클래스의 메소드에서 JdbcDAO 클래스의 메소드 호출 가능

 

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

public class StudentDAOImpl extends JdbcDAO implements StudentDAO {

    private static StudentDAOImpl _dao;

    private StudentDAOImpl() {
        // TODO Auto-generated constructor stub
    }

    static {
        _dao=new StudentDAOImpl();
    }

    public static StudentDAOImpl getDAO() {
        return _dao;
    }

    // alt + insert > implement methods 해서 생성 ▽
    // 학생정보를 전달받아 STUDENT 테이블에 삽입하고 삽입행의 갯수를 반환하는 메소드
    @Override
    public int insertStudent(StudentDTO student) {
        Connection con = null;
        PreparedStatement pstmt = null;
        int rows = 0;

        try {
            con = getConnection();

            String sql = "insert into student values (?,?,?,?,?)";
            pstmt = con.prepareStatement(sql);
            pstmt.setInt(1, student.getNo());
            pstmt.setString(2, student.getName());
            pstmt.setString(3, student.getPhone());
            pstmt.setString(4, student.getAddress());
            pstmt.setString(5, student.getBirthday());

            rows = pstmt.executeUpdate();
        } catch (SQLException e) {
            System.out.println("[에러]insertStudent() 메소드의 SQL 오류 = " + e.getMessage());
        } finally {
            close(con, pstmt);
        }
        return rows;
    }


    // 학생정보를 전달받아 STUDENT 테이블에 저장된 학생정보를 변경하고 변경행의 갯수를 반환하는 메소드
    @Override
    public int updateStudent(StudentDTO student) {
        Connection con = null;
        PreparedStatement pstmt = null;
        int rows = 0;
        try {
            con=getConnection();

            String sql="update student set name=?,phone=?,address=?,birthday=? where no=?";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, student.getName());
            pstmt.setString(2, student.getPhone());
            pstmt.setString(3, student.getAddress());
            pstmt.setString(4, student.getBirthday());
            pstmt.setInt(5, student.getNo());

            rows = pstmt.executeUpdate();
        } catch (SQLException e) {
            System.out.println("[에러]updateStudent() 메소드의 SQL 오류 = "+e.getMessage());
        } finally {
            close(con, pstmt);
        }
        return rows;
    }


    // 학번을 전달받아 STUDENT 테이블에 저장된 학생정보를 삭제하고 삭제행의 갯수를 반환하는 메소드
    @Override
    public int deleteStudent(int no) {
        Connection con = null;
        PreparedStatement pstmt = null;
        int rows = 0;
        try {
            con = getConnection();

            String sql = "delete from student where no=?";
            pstmt = con.prepareStatement(sql);
            pstmt.setInt(1, no);

            rows = pstmt.executeUpdate();
        } catch (SQLException e) {
            System.out.println("[에러]deleteStudent() 메소드의 SQL 오류 = "+e.getMessage());
        } finally {
            close(con, pstmt);
        }
        return rows;
    }


    // 학번을 전달받아 STUDENT 테이블에 저장된 해당 학번의 학생정보를 검색하여 반환하는 메소드
    @Override
    public StudentDTO selectStudent(int no) {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        StudentDTO student = null;
        try {
            con = getConnection();

            String sql = "select * from student where no=?";
            pstmt = con.prepareStatement(sql);
            pstmt.setInt(1, no);

            rs = pstmt.executeQuery();

            // ResultSet 객체에 저장된 검색행을 Java 객체(값)로 매핑 처리
            // 검색행이 0 또는 1인 경우 선택문 사용
            if(rs.next()) { // 검색행이 있는 경우
                student = new StudentDTO();
                // 처리행의 컬럼값을 반환받아 DTO 객체의 필드값으로 변경
                student.setNo(rs.getInt("no"));
                student.setName(rs.getString("name"));
                student.setPhone(rs.getString("phone"));
                student.setAddress(rs.getString("address"));
                student.setBirthday(rs.getString("birthday").substring(0, 10));
                
                studentList.add(student); // 이거안적으면 StudentGUIApp실행했을때, 학생목록안보여짐 
                
            }
        } catch (SQLException e) {
            System.out.println("[에러]selectStudent() 메소드의 SQL 오류 = "+e.getMessage());
        } finally {
            close(con, pstmt, rs);
        }
        // 검색행이 없는 경우 [null]를 반환하고 검색행이 있으면 DTO 객체 반환
        return student;
    }



    // 이름을 전달받아 STUDENT 테이블에 저장된 해당 이름의 학생정보를 검색하여 반환하는 메소드
    @Override
    public List<StudentDTO> selectNameStudentList(String name) {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<StudentDTO> studentList = new ArrayList<>();
        try {
            con = getConnection();

            String sql = "select * from student where name = ? order by no";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, name);

            rs = pstmt.executeQuery();

            // 검색행이 0개 이상인 경우 반복문 사용
            while(rs.next()) {
                // 하나의 검색행을 DTO 객체로 매핑 처리
                StudentDTO student = new StudentDTO();
                student.setNo(rs.getInt("no"));
                student.setName(rs.getString("name"));
                student.setPhone(rs.getString("phone"));
                student.setAddress(rs.getString("address"));
                student.setBirthday(rs.getString("birthday").substring(0, 10));

                // DTO 객체를 List 객체의 요소로 추가
                studentList.add(student);
            }
        } catch (SQLException e) {
            System.out.println("[에러]selectNameStudentList() 메소드의 SQL 오류 = "+e.getMessage());
        } finally {
            close(con, pstmt, rs);
        }
        return studentList;
    }




    // STUDENT 테이블에 저장된 모든 학생정보를 검색하여 반환하는 메소드 (혼자서 만들줄알아야함)
    @Override
    public List<StudentDTO> selectAllStudentList() {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<StudentDTO> studentList = new ArrayList<>();

        try {
            con = getConnection();

            String sql =  "select * from student order by no";
            pstmt = con.prepareStatement(sql);

            rs = pstmt.executeQuery();;

            while (rs.next()) {
                StudentDTO studen = new StudentDTO();
                studen.setNo(rs.getInt("no"));
                studen.setName(rs.getString("name"));
                studen.setPhone(rs.getString("phone"));
                studen.setAddress(rs.getString("address"));
                studen.setBirthday(rs.getString("birthday"). substring(0,10));
            }
        } catch (SQLException e) {
            System.out.println("[에러] selectAllStudent() 메소드의 SQL 오류 = " + e.getMessage());
        } finally {
            close(con ,pstmt, rs);
        }
        return studentList;
    }
}

 


JdbcDAO

package xyz.itwill.student;

 

// 모든 DAO 클래스가 상속받아 사용하기 위한 부모클래스
// > DBCP 객체를 생성하여 미리 Connection 객체를 생성하여 저장하고
// DBCP 객체로부터 Connection 객체를 반환하거나  JDBC 관련 객체를 매개변수로 전달받아 제거하는 메소드
// > 객체 생성이 목적이 아닌 상속을 목적으로 작성된 클래스이므로 추상클래스로 선언하는 것을 권장

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

import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;

public class JdbcDAO {
    // PoolDataSource 객체(DBCP 객체)를 저장하기 위한 필드
    private static PoolDataSource pds;


    static {
        // PoolDataSource 객체를 반환받아 필드에 저장
        pds = PoolDataSourceFactory.getPoolDataSource();
        try {
            // PoolDataSource 객체에 Connection 객체를 미리 생성하여 저장
            pds.setConnectionFactoryClassName("oracle.jdbc.driver.OracleDriver");
            pds.setURL("jdbc:oracle:thin:@localhost:1521:xe");
            pds.setUser("scott");
            pds.setPassword("tiger");
            pds.setInitialPoolSize(10);
            pds.setMaxPoolSize(15);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // PoolDataSource 객체(DBCP 객체)에 저장된 Connection 객체 중 하나를 반환하는 메소드
    public Connection getConnection() {
        Connection con = null;
        try {
            con = pds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }

    // 매개변수로 JDBC 관련 객체를 전달받아 제거하는 메소드
    public void close(Connection con) {
        try {
            // Connection 객체 제거 : PoolDataSource 객체에게 다시 Connection 객체를 되돌려주는 기능 구현
            if(con != null) con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void close(Connection con, PreparedStatement pstmt) {
        try {
            if(pstmt != null) pstmt.close();
            if(con != null) con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void close(Connection con, PreparedStatement pstmt, ResultSet rs) {
        try {
            if(rs != null) rs.close();
            if(pstmt != null) pstmt.close();
            if(con != null) con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}