이번에 자바로 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();
}
}
}
위 메서드의 수행 과정은 다음과 같다.
- url, user, password로 데이터베이스 커넥션 생성
- run 메서드를 실행할 때 받은 파라미터 sql로 PreparedStatement 객체 생성(pstmt)
- 또 하나의 파라미터인 params를, 반복문을 이용해서 params의 값을 pstmt에 동적으로 바인딩
이때, name,age처럼 타입이 여러개로 들어올 수 있기 때문에 Object로 하여금 setObject()를 사용했다. - 바인딩이 끝난 후 쿼리 실행
- 바인딩이 실패했을 때나 하지 않았을때 발생한 SQLException을 잡아 출력
- 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 |