[TIL] JDBC 동작원리 및 ResultSet

seaStamp·2023년 12월 13일
0

TIL

목록 보기
29/33
post-thumbnail

⛏️ 오늘의 삽질

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의 동작 순서를 복기해봤다.

JDBC란?

  • Java Database Connectivity

    • Java 앱과 DB 를 연결시켜주기 위해 만들어진 기술.
    • JPA또한 이 기술을 사용하여 구현되어 있다.
  • JDBC Driver 는 여러타입의 DB 와 연결할 수 있는 기능을 제공한다.
    jdbc 구조
    여기서 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에 값이 담긴 걸 확인할 수 있다.

+) 왜 resultSet.next()를 쓸까?

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의 구성 및 초기값

profile
우선은 부딪히고 보자

0개의 댓글