JDBC

2025. 3. 19. 18:42기반기술/서버 개발

1. JDBC란?

  • JDBC는 자바에서 데이터베이스와 연결하여 데이터를 주고받을 수 있도록 해주는 API이다. 
  • 자바 애플리케이션에서 SQL을 실행할 수 있도록 도와주는 역할
  • java.sql package의 interface와 class를 활용한다. 
  • JDBC 주요 기능
    • 데이터베이스 연결 : DriverManager, Connection
    • SQL 실행 : Statement, PreparedStatement
    • 결과 가져오기 : ResultSet
    • 트랜잭션 처리 : commit, rollback
    • 연결 종료 : close (Connection, Statement, ResultSet 모두 자원 반납 필수임) 

 

2. 데이터베이스 연결

1) DriverManager Class

  • 데이터 원본(= Database)에 JDBC driver를 통하여 Connection을 만드는 역할을 한다.
  • 반드시 예외 처리를 해야 한다. 
  • 직접 instance 생성이 불가하고, DriverManager 클래스의 getConnection() 메소드를 사용하여 Connection instance를 생성할 수 있다.

2) Connection Class

  • 특정 데이터 원본(= Database)과 연결된 Connection을 나타낸다.
  • 쿼리문을 실행할 수 있는 Statement 혹은 PreparedStatement 객체를 생성할 수 있는 기능을 제공한다.
  • Connection 객체 자원은 사용 후 반드시 반납해야 한다.
  • Connection 객체를 생성하는 코드 또한 중복 작성하게 되므로, Template 클래스의 static 메소드로 Connection 객체를 생성하거나 반납하는 코드를 작성하여 공통으로 사용하도록 하는 것이 일반적이다.

 

3. SQL 실행 

1) Statement

  • SQL문을 저장하고 실행한 뒤 결과를 받아 반환해주는 메소드들이 묶여 있는 타입의 클래스이다.
  • Statement 객체 생성 및 사용
    • Connection class의 createStatement() 메소드를 호출하여 Statement instance를 생성한다.
    • 생성한 instance의 executeQuery() 메소드를 호출하여 SQL문 수행한다. (SQL문을 String 형태로 인자로 전달한다.)

2) PreparedStatement

  • PreparedStatement는 placeholder '?'를 활용한 하나의 문자열 형태로 쿼리를 작성한다.
  • 완성된 쿼리문과 미완성된 쿼리문(= 위치홀더를 사용한 쿼리문)을 모두 사용할 수 있다.
  • PreparedStatement는 인수가 많아 특정 값만 바꾸어 여러 번 실행하는 상황에 유용하다.
  • 장점: 수행 속도가 빠르다. SQL injection 공격에 대하여 안전한다.

 

4. ResultSet

  • SELECT문 수행 성공 시 반환한 결과값을 받아오는 객체이다.
  • SQL문에 의해 생성된 결과 테이블을 담고 있다.
  • Connection과 Statement도 close()로 자원 반납하듯, ResultSet도 close()를 통해 자원을 반납해야 한다.
  • Method
    • get자료형(”컬럼명”) : ResultSet의 현재 커서 위치에 존재하는 로우에서 인자로 전달한 컬럼의 결과 값을 가지고 온다.
    • next() : ResultSet의 커서 위치를 하나 내리면서 다음 행이 존재하면 true 존재하지 않으면 false를 반환한다.

 

5. JDBC 활용 예시 

//MySQL 설정
//build.gradle에 mysql dependencies 추가 

dependencies {
    // https://mvnrepository.com/artifact/com.mysql/mysql-connector-j
    implementation 'com.mysql:mysql-connector-j:9.2.0'
    ...
}
//설정 정보를 저장하는 파일 따로 분리
//jdbc-config.properties 

url=jdbc:mysql://localhost:3306/companydb
user=practice
password=practice
//JDBC Template 파일
public class JDBCTemplate {
    public static Connection getConnection(){
        Properties properties = new Properties();
        Connection con = null;
        try {
            // url, user, password와 같은 설정 정보는  
            // 유지보수성을 위해 파일 내 리터럴 값으로 작성하지 않고 
            // 별도의 properties 파일로 분리하여 관리하는 것이 좋다.
            properties.load(new FileReader("src/main/java/com/hnjee/config/jdbc-config.properties"));
            String url = properties.getProperty("url");
            con = DriverManager.getConnection(url, properties);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return con;
    }

    //Connection을 닫는 개념은 별도의 메소드로 분리하고 실제 닫는 시점은 Service 계층에서 진행
    public static void close(Connection con){
        try {
            if(con != null && !con.isClosed()) con.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(ResultSet rset){
        try {
            if(rset != null && !rset.isClosed()) rset.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static void close(Statement stmt){
        try {
            if(stmt != null && !stmt.isClosed()) stmt.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
//Statement 활용 

import static com.hnjee.common.JDBCTemplate.close;
import static com.hnjee.common.JDBCTemplate.getConnection;

//사번을 Scanner로 입력 받아서 사원의 정보를 출력하는 프로그램
//사원의 정보: emp_id, emp_name, salary
//없는 사번이면 "해당 사원의 조회 결과가 없습니다" 출력
public class Application2 {
    public static void main(String[] args) {
        Connection con = getConnection();
        Statement stmt = null;
        ResultSet rset = null;

        Scanner sc = new Scanner(System.in);
        System.out.print("사번을 입력하세요:");
        int empId = sc.nextInt();

        try {
            stmt = con.createStatement();
            rset = stmt.executeQuery("SELECT * FROM employee WHERE emp_id ="+empId);
            if(rset.next()){
                int emp_id = rset.getInt("emp_id");
                String emp_name = rset.getString("emp_name");
                int salary = rset.getInt("salary");
                System.out.println(emp_id+", "+emp_name+", "+salary);
            } else{
                System.out.println("해당 사원의 조회 결과가 없습니다");
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            close(con);
            close(stmt);
            close(rset);
        }
    }
}
//PreparedStatement 활용

import static com.hnjee.common.JDBCTemplate.close;
import static com.hnjee.common.JDBCTemplate.getConnection;

//사번을 Scanner로 입력 받아서 사원의 정보를 출력하는 프로그램
//사원의 정보: emp_id, emp_name, salary
//없는 사번이면 "해당 사원의 조회 결과가 없습니다" 출력
public class Application2 {
    public static void main(String[] args) {
        Connection con = getConnection();
        PreparedStatement pstmt = null;
        ResultSet rset = null;

        Scanner sc = new Scanner(System.in);
        System.out.print("사번을 입력하세요:");
        int empId = sc.nextInt();

        try {
            //쿼리 세팅
            //PreparedStatement는 placeholder '?'를 활용한 하나의 문자열 형태로 쿼리를 작성한다.
            pstmt = con.prepareStatement(
                    "SELECT emp_id, emp_name, salary " +
                            "FROM employee " +
                            "WHERE emp_id = ? and ent_yn = ?"
            );
            //파라미터 세팅
            //쿼리 실행 전 placeholder의 내용을 인덱스 번호를 통해 설정한다.
            pstmt.setInt(1, empId);
            pstmt.setString(2, "N");

            //쿼리 실행
            rset = pstmt.executeQuery();

            if(rset.next()){
                int emp_id = rset.getInt("emp_id");
                String emp_name = rset.getString("emp_name");
                int salary = rset.getInt("salary");
                System.out.println(emp_id+", "+emp_name+", "+salary);
            } else{
                System.out.println("해당 사원의 조회 결과가 없습니다");
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            close(con);
            close(pstmt);
            close(rset);
        }
    }
}