JPA심화 강의를 보며 JDBC를 테스트 하는 강의내용을 타이핑 하던 중
강의 자료를 그대로 따라했다 생각했는데 다르게 실행되는 걸 발견했다.
코드를 처음부터 끝까지 하나하나 따라 가봤는데 뭐가 잘못되었던 걸까?
public Integer insertAccount(AccountVO vo) {
var id = -1;
try {
String[] returnId = {"id"};
conn = DriverManager.getConnection(url, username, password);
stmt = conn.prepareStatement(ACCOUNT_INSERT, returnId);
stmt.setString(1, vo.getUsername());
stmt.setString(2, vo.getPassword());
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
id = rs.getInt(1);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return id;
}
@Test
@DisplayName("JDBC DAO 삽입/조회 실습")
void jdbcDAOInsertSelectTest() throws SQLException {
// given
AccountDAO accountDAO = new AccountDAO();
// when
Integer id = accountDAO.insertAccount(new AccountVO("new user", "new password"));
// then
var account = accountDAO.selectAccount(id);
assert account.getUsername().equals("new user");
}
강의를 들었으면서도 못 알아채는 나에게 필요한건 강의 재복습.. 일단 오류 발생의 원인은 코드에 대한 이해도 부족으로 판단하여 코드 하나하나에 주석을 달아보고, 보내는 쿼리문, 연결문을 재수정하였다.
public Integer insertAccount(AccountVO vo) {
var id = -1; // 새로운 계정의 ID를 저장할 변수 초기화
try {
String[] returnId = {"id"}; // 자동 생성된 키 값을 담을 배열
conn = DriverManager.getConnection(url, username, password); // 데이터베이스 연결
// 쿼리 준비
stmt = conn.prepareStatement(ACCOUNT_INSERT, returnId);
stmt.setString(1, vo.getUsername()); // 첫 번째 매개변수 설정
stmt.setString(2, vo.getPassword()); // 두 번째 매개변수 설정
try (ResultSet rs = stmt.getGeneratedKeys()) { // 자동 생성된 키 가져오기
if (rs.next()) { // 결과 집합에서 다음 행으로 이동
id = rs.getInt(1 ); // 첫번째 칼럼에 자동 생성된 ID 값 저장
}
}
} catch (SQLException e) { // SQL 예외 처리
e.printStackTrace(); // 예외 정보 출력
}
return id; // 새로 생성된 계정의 ID 반환
}
원래라면 test코드 내에서 반환되는 id값이 존재해야하는데 뭐가 안되었던걸까 생각해보며 JDBC의 동작 순서를 복기해봤다.
Java Database Connectivity
JDBC Driver 는 여러타입의 DB 와 연결할 수 있는 기능을 제공한다.
여기서 JDBC Driver Manager는 런타임 시점에
- Connection(연결) 을 생성하여 쿼리를 요청할 수 있는 상태를 만들어주고
- Statement(상태) 를 생성하여 쿼리를 요청하게 해주고
- ResultSet(결과셋) 을 생성해 쿼리 결과를 받아올 수 있게 해줍니다.
⚠️ 사용후에는 각각 close() 를 호출해서 자원 해제를 시켜줘야 한다.
지금 오류가 발생한 부분은 4b 부분인데, 코드를 다시 확인해보니 쿼리를 실행하는 Update구문이 빠져있었다.
당연히 삽입된 데이터가 없기에 id가 Null로 반환되는 것.
public Integer insertAccount(AccountVO vo) {
var id = -1;
try {
String[] returnId = {"id"};
conn = DriverManager.getConnection(url, username, password);
stmt = conn.prepareStatement(ACCOUNT_INSERT, returnId);
stmt.setString(1, vo.getUsername());
stmt.setString(2, vo.getPassword());
stmt.executeUpdate(); // 추가한 부분
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
id = rs.getInt(1);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return id;
}
이렇게 수정하니 실행이 된다.
resultSet에 값이 담긴 걸 확인할 수 있다.
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
id = rs.getInt(1);
}
}
코드를 해석하면서 처음에 포인터를 다음 행으로 넘기는 이유가 이해가 안갔는데 pgResultSet파일의 next() 메서드를 보고 이유를 알았다.
protected int currentRow = -1; // Index into 'rows' of our currrent row (0-based)
처음 생성했을 때 currentRow값이 0이라고 생각했던것과 달리 (위의 디버깅 값으로도 알 수 있듯이) -1값으로 초기화를 시켜주기 때문에 next()를 실행했던 것이다.
너무나도 사소한 실수였지만 이번기회로 체회할 수 있었던것
1. JDBC 동작 원리 (excute()를 해야 실행이 된거다..)
2. pgResultSet의 구성 및 초기값