waveofmymind
기록하는 습관
waveofmymind
전체 방문자
오늘
어제
  • 분류 전체보기 (124)
    • 📝 정리 (5)
    • 🌊TIL (9)
    • 💻CS (1)
      • 자료구조 (1)
    • 📙Language (9)
      • ☕Java (6)
      • 🤖Kotlin (3)
    • 🍃Spring (28)
    • 👨🏻‍💻알고리즘 (67)
      • 프로그래머스 (59)
      • 백준 (3)
    • 👷DevOps (4)
      • 🐳Docker (2)
      • 🤵Jenkins (1)

블로그 메뉴

  • 홈
  • Spring
  • Java
  • 알고리즘

공지사항

인기 글

태그

  • BFS
  • chat GPT
  • sql
  • 트랜잭션
  • AOP
  • 힙
  • 코틀린
  • til
  • LeetCode
  • kotlin
  • JDBC
  • Connection
  • 스프링 부트
  • CORS
  • 완전탐색
  • 트랜잭션 전파
  • 스택
  • 챗GPT
  • spring
  • 스프링
  • resultset
  • Open AI
  • Spring Security
  • 스프링 시큐리티
  • mybatis
  • SpringAOP
  • 다이나믹 프로그래밍
  • 통합테스트
  • spring boot
  • kotest

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
waveofmymind

기록하는 습관

[JDBC] Connection과 PreparedStatement, 그리고 ResultSet
📙Language/☕Java

[JDBC] Connection과 PreparedStatement, 그리고 ResultSet

2023. 4. 24. 10:43

이번에 자바로 ORM 기술을 이용하지 않고 JDBC를 구현하는 미션을 하게 되어 

자바 코드에서 DB에 쿼리를 발생시키기까지 모든 단계를 구현해야 했다.

 

그중 가장 핵심인 Connection과 PreparedStatement를 제대로 알 필요가 있다고 생각해서 정리하게 되었다.

 

Connection

데이터베이스와의 세션을 정의한다.

따라서 MySQL 워크벤치에서 커넥션을 생성할 때처럼, 연결을 위한 정보들이 필요하다.

url, username, password이 그것이다.

DriverManager.getConnection() 메서드를 사용해서 생성할 수 있으며,

나는 SampleDb라는 클래스에 해당 커넥션 정보들을 필드로 두고, 생성자를 통해 주입받을 수 있도록 했다.

@Data
public class SimpleDb {

    private final String url;
    private final String user;
    private final String password;
    private final String dbName;
    private boolean devMode;

    public SimpleDb(String host, String user, String password, String dbName) {
        this.url = "jdbc:mysql://" + host + ":3306/" + dbName + "?useUnicode=yes&characterEncoding=UTF-8";
        this.user = user;
        this.password = password;
        this.dbName = dbName;
    }

그리고 위 커넥션 정보를 이용해서 아래와 같이 커넥션을 생성한다.

Connection conn = DriverManager.getConnection(url,user,password);

PreparedStatement

커넥션을 통해 데이터베이스와 연결을 생성했으면, 쿼리를 만들어야한다.

이때, 쿼리를 데이터베이스에 날리기 전에 준비해 주는 객체가 바로 PreparedStatement이다.

? 를 이용해서 동적으로 값을 넣을 수 있다.

Statement vs PreparedStatement
Statement도 쿼리 작업을 실행하기 위한 객체이다.
특히 정적인 쿼리를 처리할 때 사용되며, 반드시 값이 미리 입력되어야만 처리가 가능하다.
PreparedStatement는 그에 반해 동적인 쿼리를 처리할 수 있다.

위에서 생성했던 커넥션의 prepareStatement() 메서드를 사용해서 객체를 생성할 수 있으며,
SQL 쿼리를 파라미터로 받는다.

특정 유저 테이블에 insert를 하는 쿼리를 String으로 생성했다면 다음과 같다.

String query = "INSERT INTO users (name, age) VALUES (?, ?)";

그럼 위 쿼리를 파라미터로 받는 prepareStatement() 메서드를 사용하면 된다.

Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql);

이제? 에 값을 바인딩해야 하는데, 이는 PreparedStatement의 setter를 사용해서 인덱스, 값을 바인딩하면 된다.

이때 인덱스는 1부터 시작한다.

위의 name, age에 각각?,?라고 되어있고, name은 String, age는 int라고 가정하자.

pstmt.setString(1, "wave");
pstmt.setInt(2,30);

위와 같이 바인딩할 수 있다.

만약 바인딩할 파라미터가 있는데 값을 바인딩하지 않으면 아래와 같이 SQLException이 발생한다.

java.sql.SQLException: No value specified for parameter 1

그리고 값을 할당했다면, executeUpdate() 또는 executeQuery() 메서드를 사용해서 쿼리를 실행할 수 있다.

그래서 만약, 파라미터를 동적으로 받을 경우, 아래와 같이 run 메서드를 사용했다.

public void run(String sql, Object... params) {
        try (Connection conn = DriverManager.getConnection(url, user, password); //1
             PreparedStatement pstmt = conn.prepareStatement(sql)) { //2

            for (int i = 0; i < params.length; i++) { //3
                pstmt.setObject(i + 1, params[i]);
            }

            pstmt.executeUpdate(); //4

        } catch (SQLException e) {
            if (devMode) {
                e.printStackTrace();
            }
        }
    }

위 메서드의 수행 과정은 다음과 같다.

  1. url, user, password로 데이터베이스 커넥션 생성
  2. run 메서드를 실행할 때 받은 파라미터 sql로 PreparedStatement 객체 생성(pstmt)
  3. 또 하나의 파라미터인 params를, 반복문을 이용해서 params의 값을 pstmt에 동적으로 바인딩
    이때, name,age처럼 타입이 여러개로 들어올 수 있기 때문에 Object로 하여금 setObject()를 사용했다.
  4. 바인딩이 끝난 후 쿼리 실행
  5. 바인딩이 실패했을 때나 하지 않았을때 발생한 SQLException을 잡아 출력
  6. try-with-resources를 사용하고 있으므로 자동으로 자원이 close된다.

ResultSet

위 PreparedStatement(줄여서 pstmt)를 사용한 결과를 사용하기 위해서는 ResultSet을 사용한다.

 

위 run에서는 결과값을 이용하지 않았기에 함수 반환값을 void로 하여 pstmt.executeUpdate()만 호출하고 끝냈지만,

pstmtm.executeUpdate()의 반환값을 확인해보면 ResultSet 객체이다.

 

그리고 다음과 같은 기능을 가진다.

  • 저장된 값을 한 행 단위로 불러올 수 있다.
  • 한 행에서 값을 가져올 때 타입을 지정해 불러올 수 있다.

사용 방법은 다음과 같다.

 

rs.next() 메서드를 호출해서 선택되는 행을 바꿀 수 있으며,

다음 행이 있을 경우 true, 없을 경우 false를 반환한다.

 

쉽게 말하면, rs.next()를 실행하면 커서가 변경되고, 해당 커서의 값을 가져오고 싶을 때 get타입()을 사용해서 값을 불러올 수 있다.

 

get타입()의 파라미터는 컬럼의 숫자 또는 컬럼의 이름이 된다.

예를 들어, Users라는 테이블에 첫번째 컬럼이 name, 두번째 컬럼이 age일 경우,

  • name의 값을 가져오고 싶을 경우 -> getString(1), getString("name")
  • age의 값을 가져오고 싶을 경우 -> getInt(2), getInt("age")

와 같이 사용할 수 있다.

 

코드로 예를 들면

while(rs.next()) {
System.out.println(rs.getInt(1) + "\t" + rs.getString(2)); 
}

rs.next()가 true이면 해당 컬럼을 출력하고, false이면 반복문을 탈출한다.

즉, 쿼리로 조회된 모든 값이 출력되는 것이다.

 

 

 

'📙Language > ☕Java' 카테고리의 다른 글

[Java] url 변수 동적으로 받도록 개선하기  (0) 2023.05.18
[트러블 슈팅] url 패턴이 같을 경우에 Controller 메서드 구별하기  (0) 2023.05.17
[JDBC] 동적으로 파라미터 바인딩 하기  (0) 2023.04.26
[Java] 스트림(Stream) 잘 사용하기  (0) 2023.03.15
[Java] Jackson Databind 이용하기  (0) 2023.03.02
    '📙Language/☕Java' 카테고리의 다른 글
    • [트러블 슈팅] url 패턴이 같을 경우에 Controller 메서드 구별하기
    • [JDBC] 동적으로 파라미터 바인딩 하기
    • [Java] 스트림(Stream) 잘 사용하기
    • [Java] Jackson Databind 이용하기
    waveofmymind
    waveofmymind

    티스토리툴바