CS/데이터베이스(Database)

[DB] JDBC 간단 정리: 설정부터 사용까지

배씌 2025. 4. 25. 10:05

JDBC란?

JDBC(Java Database Connectivity) 는 자바에서 데이터베이스와 연결하고, SQL 을 실행할 수 있게 해주는 표준 API 이다.

  • Java 프로그램 <-> DBMS 연결
  • SQL 실행, 트랜잭션 처리, 결과 조회 가능
  • JDBC 드라이버를 통해 DB와 연결

JDBC 기본 흐름

  1. JDBC 드라이버 로딩 - DBMS에 맞는 드라이버를 JVM에 로드함
  2. DB 연결 (Connection) - DB에 연결해 커넥션 객체를 얻음
  3. SQL 실행 (Statement/PreparedStatement) - 쿼리를 작성하고, 실행
  4. 결과 처리(ResultSet) - 쿼리 결과를 가져와 데이터를 읽는다
  5. 자원 해제 (Close) - 사용한 리소스 해제 (try-with-resource 구문 사용 가능)

JDBC 기본 예제

public class JdbcExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test_db";
        String username = "root";
        String password = "1234";

        try {
            Class.forName("com.mysql.cj.jdbc.Driver"); // 1. 드라이버 로딩
            Connection conn = DriverManager.getConnection(url, username, password); // 2. 연결
            Statement stmt = conn.createStatement(); // 3. Statement 생성
            ResultSet rs = stmt.executeQuery("SELECT * FROM users"); // 4. 쿼리 실행

            while (rs.next()) {
                System.out.println("이름: " + rs.getString("name"));
            }

            rs.close(); stmt.close(); conn.close(); // 5. 자원 정리

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

 

위 방법은 가장 기초적인 JDBC 활용 방법이다. Statement 를 사용한 것을 볼 수 있는데, Statement 는 단순 문자열을 조립하여 쿼리를 생성해야하기 때문에, 그리 많이 사용되지 않는다. 아래에서 나올 PreparedStatement 는 쿼리를 좀 더 안정적이고, 확장된 기능을 제공하기에 왠만하면 PreparedStatement 를 사용하자


매번 드라이버 로딩을 해야 하나?

위의 방식대로 하면, 쿼리를 작성할 때 마다 드라이버 로딩과 설정을 해야하는 번거로움이 발생한다. 따라서, DbUtil 과 같은 클래스를 생성하여, 드라이버 설정을 관리하고 static 메서드(getConnection) 로 드라이버 로딩을 하게 되면 번거로움을 줄일 수 있다.

public class DbUtils {
    private static final DataSource DATASOURCE;

    static {
        BasicDataSource ds = new BasicDataSource();
        ds.setUrl("jdbc:mysql://localhost:3306/test_db"); // db url 설정
        ds.setUsername("root");	// username 설정
        ds.setPassword("1234"); // password 설정
        ds.setInitialSize(5);
        ds.setMaxTotal(10);
        ds.setMaxWait(Duration.ofSeconds(5));
        DATASOURCE = ds;
    }

    public static Connection getConnection() throws SQLException {
        return DATASOURCE.getConnection();
    }
}

 


Statement vs PreparedStatement ?

두가지 모두 JDBC 에서 SQL 쿼리를 작성하고 실행할 때 쓰인다. 기능은 비슷하지만 사용법에 차이가 있고, 대부분의 경우에 보안성과 성능을 위해 PreparedStatement 를 사용한다는 것만 기억하자. -> (SQL Injection 방지)

https://developer.mozilla.org/ko/docs/Glossary/SQL_Injection

 

SQL 인젝션 (SQL Injection) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

SQL 주입은 사용자 입력의 유효성을 검사하지 못하는 웹 앱을 활용합니다. 해커는 백엔드 데이터베이스에서 실행하기 위해 웹앱을 통해 SQL 명령을 악의적으로 전달할 수 있습니다.

developer.mozilla.org

 

- Save (Statement 사용)

@Override
    public int save(Student student){
        String sql = "INSERT INTO jdbc_students(id, name, gender, age) VALUES ("
                + "'" + student.getId() + "', "
                + "'" + student.getName() + "', "
                + "'" + student.getGender() + "', "
                + "'" + student.getAge() + "')";

        try (
            Connection conn = DbUtils.getConnection();
            Statement stmt = conn.createStatement();
        )
        {
            int result = stmt.executeUpdate(sql);
            return result;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

 

- Save (PreparedStatement 사용)

@Override
    public int save(Student student){
        String sql = "INSERT INTO jdbc_students(id, name, gender, age) VALUES(?, ?, ?, ?)";

        try (
            Connection conn = DbUtils.getConnection();
            PreparedStatement pstmt = conn.prepareStatement(sql)
        ) {
            pstmt.setString(1, student.getId());
            pstmt.setString(2, student.getName());
            pstmt.setString(3, String.valueOf(student.getGender()));
            pstmt.setInt(4, student.getAge());

            return pstmt.executeUpdate();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

 

두 방법의 가장 큰 차이점은, Statement 는 해당 쿼리를 작성할 때 문자열을 조립하여 작성하는 방식이고, PreparedStatement 는 쿼리에 작성된 '?' 자리에 (set...) 메서드를 사용하여 값을 바인딩 해주므로, 가독성과 수정에도 용이하다.


데이터 처리 결과는 어떻게 다루는가?

SQL 쿼리를 사용하여 데이터를 처리하는 대표적인 예시 중 INSERT 와 SELECT 로 예를 들어보겠다.

 

PreparedStatement 는 기본적으로 아래 2개의 메서드를 사용하여 쿼리를 실행한다.

  • pstmt.executeUpdate() : INSERT, UPDATE, DELETE
  • pstmt.executeQuery() : SELECT

보통 executeQuery 메서드는 SELECT 쿼리를 작성할 때만 사용한다.

 

executeUpdate()

우선 executeUpdate 메서드는 위의 INSERT 예시 처럼, SQL 쿼리를 작성하고 값을 바인딩 해준 다음, 메서드를 실행시키면 해당 쿼리가 실행된다. 여기서 왜 반환값이 void 가 아닌 int 인지 몰랐는데, executeUpdate() 메서드는 쿼리 실행 후 영향 받은 행(row) 의 개수를 반환한다.

 

쉽게 말해, 데이터를 조회하는 쿼리를 제외한, 데이터 변경 쿼리를 실행할 때 사용되는데, SELECT 를 제외한 나머지 쿼리에 해당 메서드를 사용하는 이유가 이 때문이다. 이를 활용하여, 쿼리 성공 여부를 판단할 때 사용되기도 한다. (트랜잭션 처리 검증)

 

[쿼리 성공 여부 처리 검증]

int affectedRows = pstmt.executeUpdate();
if (affectedRows == 0) {
    throw new RuntimeException("변경된 행이 없습니다.");
}

 

executeQuery()

executeQuery 는 SELECT 문에만 사용하는데, 해당 메서드는 ResultSet 을 반환한다.

ResultSet 객체

ResultSet은 위처럼 쿼리 결과 집합을 담고 있는 객체인데 next(), getString("컬럼명"), getInt("컬럼명") 과 같은 메서드로 조회 결과에 대한 데이터 값을 가져올 수 있다.

 

[SELECT 쿼리문 예시]

@Override
    public Optional<Student> findById(String id){
        String sql = "SELECT * FROM jdbc_students WHERE id = ?";

        try (
            Connection conn = DbUtils.getConnection();
            PreparedStatement pstmt = conn.prepareStatement(sql)
        ) {
            pstmt.setString(1, id);
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()) {
                Student student = new Student(
                        rs.getString("id"),
                        rs.getString("name"),
                        Student.GENDER.valueOf(rs.getString("gender")),
                        rs.getInt("age")
                );
                return Optional.of(student);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return Optional.empty();
    }