[DB] JDBC 간단 정리: 설정부터 사용까지
JDBC란?
JDBC(Java Database Connectivity) 는 자바에서 데이터베이스와 연결하고, SQL 을 실행할 수 있게 해주는 표준 API 이다.
- Java 프로그램 <-> DBMS 연결
- SQL 실행, 트랜잭션 처리, 결과 조회 가능
- JDBC 드라이버를 통해 DB와 연결
JDBC 기본 흐름
- JDBC 드라이버 로딩 - DBMS에 맞는 드라이버를 JVM에 로드함
- DB 연결 (Connection) - DB에 연결해 커넥션 객체를 얻음
- SQL 실행 (Statement/PreparedStatement) - 쿼리를 작성하고, 실행
- 결과 처리(ResultSet) - 쿼리 결과를 가져와 데이터를 읽는다
- 자원 해제 (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은 위처럼 쿼리 결과 집합을 담고 있는 객체인데 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();
}