본문 바로가기
스프링

[Spring] ResultSet next()를 왜 사용할까?

by itstime0809 2023. 9. 7.
728x90

ResultSet

 순수 JDBC를 활용하여 쿼리문을 정의한 다음, 지정된 쿼리문을 실행하게 된다면 자연스럽게 쿼리문 조건에 맞는 값이 반환될 것이라는 것은 코드를 짜보지 않아도 대략적인 그림은 머릿속에서 그려질 수 있다. 하지만 어떻게 데이터를 받는지에 대해서 명확하게 알지 못한다면, 데이터를 어떤 방식으로 처리해야 하는지에 대한 문제도 해결하기 어려울 수 있다. 그래서 순수 JDBC를 사용할 때 ResultSet을 어떻게 활용하길래 데이터를 받아올 수 있는지 그리고 next()를 사용해서 데이터 체크를 왜 하는지, 어떻게 동작하는지에 대해서 알아보려고 한다.

 

ResultSet이란? 

public interface ResultSet extends Wrapper, AutoCloseable

 ResultSet은 인터페이스로 정의되어 있다. 인터페이스 안에 여러 메소드들이 존재하고 next() 메서드도 그중 하나다. 그럼 ResultSet이 정확히 무슨 역할을 하는 것일까?

 

 

A table of data representing a database result set, which is usually generated by executing a statement that queries the database.

The ResultSet interface provides getter methods ( getBoolean, getLong, and so on) for retrieving column values from the current row. Values can be retrieved using either the index number of the column or the name of the column. In general, using the column index will be more efficient. Columns are numbered from 1. For maximum portability, result set columns within each row should be read in left-to-right order, and each column should be read only once.

 

 

 ResultSet은 database result set을 표현하는 데이터 테이블 이라는 표현을 사용하고 있다, 데이터 테이블.. 이게 무슨 뜻일까 쉽게 생각해 보면 데이터 베이스 구조를 축소화시켜둔 테이블이라고 생각해 보면 이해가 빠를 수 있다. 뒤에 문장은 실행하는 문장에 의해 생성된 결과들이라고 되어 있기 때문에 여기서 실행하는 문장이라고 하는 것은 SQL Query가 되겠다. 따라서 특정 쿼리문을 통해 얻은 결과 값을 테이블 형태로 제공하는 것. 정도로 보면 될 것 같다. 그리고 빨간색 문장을 보게 되면 여러 getter 메서드들을 제공해 주는데 현재 row로부터 값들을 검색하거나 혹은 탐색하기 위함 정도로 생각해 보면 될 것 같다. 현재 row의 값을 getter로 얻어 올 수 있는 메서드들을 제공해준다는 의미다. 결국 ResultSet은 우리가 실행 하고자 했던 SQL Query문에 의해서 얻어진 결과값들의 집합 객체라고 표현할 수 있다. 이러한 객체에는 쿼리문 수행을 통해 얻은 데이터베이스에 저장되어 있는 값들이 될 것이고 이러한 값들이 테이블 형태로 존재한다고 생각해 보면 된다. 

 

 그럼 이러한 ResultSet은 어떻게 row들을 움직일 수 있을까? 라는 질문을 해볼 수 있다. 왜냐하면 게시판을 만든다고 했을 때, 게시물이 하나가 아닌 이상 여러 개의 게시물이 있다고 가정하면, 여러 개의 게시물들은 다양한 값들을 독립적인 형태로 지니고 있다. 각각의 값은 중복될 수 있을지 모르지만, 최소한 id의 값은 중복이 되지 않는다. (autoincrement 설정했다고 가정) 결국 이러한 여러 개의 게시물들을 다 보여주지 않는 이상은 특정 게시물을 확인해 보고 싶기도 할 수 있고, 혹은 특정 id 값만을 조회하여 게시물을 보여주고 싶을 수도 있다. 이럴때 ResultSet에 들어있는 결과 값들의 테이블을 컨트롤할 수 있는 것이 cursor라는 개념이다. 이 cursor 개념은 '화살표'와 같다고 생각하면 된다.

 

 

 A ResultSet cursor is initially positioned before the first row

 

 

 위 문장은 next() 함수의 주석 설명란에서 가져온 문장인데, 보면 cursor의 초기 위치는 first row 이전으로 정의되어 있다고 한다. 따라서 이를 해석할 때 주의 해야 될 점은 첫 번째 row를 가리키고 있는 것이 아니라 첫 번째 이전을 가리키고 있기 때문에 값의 범위 밖을 가리키고 있다고 생각하면 된다. 그래서 우리는 이러한 cursor를 활용해 특정 값을 가지고 있는 row로 향하게 만든다. 그때 사용 되는 함수가 바로 next() 함수 이다. 참고로 결과값을 받아 왔는데 next()를 하지 않을 경우 에러가 발생 된다. 그 이유도 초기 테이블의 cursor위치는 범위 밖을 가리키고 있기 때문에 내가 얻고자 하는 값을 가리키지 않기 때문이다. 잠깐 중간 정리를 해보자면, ResultSet은 쿼리문에 의해 생성된 결과값들의 집합 객체이며, ResultSet객체가 가지고 있는 값들은 쿼리문에 의해 얻게 된 값들이고 이러한 값들이 하나의 테이블을 이루고 있다.

 

next()

 근본적인 질문으로 다시 돌아와 쿼리문을 통해 데이터를 받는 방법은 ResultSet 객체로 받는 다는 것을 알게 되었다. 그렇다면 next() 함수를 사용해서 데이터는 왜 체크하는 걸까를 생각해 볼 수 있다. next() 함수는 아래와 같이 정의한다.

 

 

Moves the cursor forward one row from its current position. A ResultSet cursor is initially positioned before the first row; the first call to the method next makes the first row the current row; the second call makes the second row the current row, and so on.

 

 

 요약해 보자면 커서를 움직이는 것이다 현재 position으로 부터, 초기에는 첫 번째 row의 이전에 위치해 있고, 이 메서드가 한번 불릴 때마다 cursor는 다음 row위치로 이동하게 된다. (이 부분을 잘 생각해 보아야한다.) 그렇게 한 번씩 호출 될 때마다 결과값들의 집합이 적어도 한개 존재한다면 row들을 순차적으로 탐색이 가능하다. row의 개수가 1 이상이라면 해당 row를 가리키는 cursor는 row의 개수만큼 가리킬 수 있다. 또한 next() 함수는 다음 row가 존재할 때 true를 리턴하게 되고, 더 이상 row가 존재하지 않는다면 false를 리턴한다.

 

 그렇다는 건 row가 한개일 경우에도 값은 당연히 리턴한다는 것이다. next()의 원리를 이해하기 힘들었던 부분 중 하나가 바로 이 부분이다. 앞서 (이 부분을 잘 생각해 보아야 한다.) 라고 정의했던 부분과 연관되어 있는데 우선 next()가 어떻게 데이터를 체크하는지에 대해서 먼저 정의해보고 몇 가지 예시를 가지고 이해해 보려고 한다.

 

 

String sql = "select * from post where id = ?";
        Post post = new Post();
        ResultSet rs = null;
        
        try (Connection con = dataSource.getConnection();
             PreparedStatement pstmt = con.prepareStatement(sql);) {

            pstmt.setInt(1, id);
            

            rs = pstmt.executeQuery();

            if (rs.next()) {
                log.info("해당 id" + id);
                post.setId(rs.getInt("id"));
                post.setUsername(rs.getString("username"));
                post.setTitle(rs.getString("title"));
                post.setContent(rs.getString("content"));
                post.setLikeCount(rs.getInt("likeCount"));
                post.setViewCount(rs.getInt("viewCount"));
                post.setCurrentDatetime(rs.getString("currentDatetime"));
            }
        }  catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }

 

 

 위 코드는 순수 JDBC를 이용해서 특정 post의 값을 가지고 오는 예시다. 보면 executeQuery() 메소드를 이용해서 쿼리문을 실행하고 있고 쿼리문을 통해 얻어진 값들은 ResultSet을 통해 얻을 수 있음을 보이고 있다. 그리고 밑에 if (rs.next())를 통해서 데이터를 확인하는데 위 코드는 정상 작동되는 코드이다. 따라서 rs.next() if문에 진입하면 해당 id log가 출력 되게 된다. 그렇다는 건 적어도 하나의 row는 존재하기 때문에 if문이 작동 되는 것이다. 왜냐하면 next()는 row가 없으면 false를 리턴하기 때문에 위에서는 따로 처리되지 않았지만, false를 리턴한다. 따라서 만약 if문에 정상진입 했다면 row는 적어도 한 개가 존재하는 것이다. 

 

 근데 이상하지 않은가? 쿼리문을 수행해서 결과 값을 얻었다고 해도 그 값이 내가 원한 값을 반환 한다는 것은 위 코드에서 어떻게 알 수 있는가? 만약 그 값이 내가 원한 값이 아니라면 결국 반환되는 Post 객체는 클라이언트에서 의도하지 않은 게시물이 될 것이다. 그럼 이렇게 체크하는 게 올바른 방법이라고 말할 수 있는가?

 

 아마 이 질문에 대해서 의구심을 품게 될 경우, 예상컨데 대부분 모든 게시물을 가지고 올 것이다라는 전제를 잘못 두고 생각하고 있을 것이다. 혹은 혼동 하고 있거나 next()함수를 통해서 값을 체크한다고 전제하에 생각하고 있을 수 있다. 우선 특정 id값만을 조회해서 가지고 온다는 것은 쿼리문에 이상이 없는 한 id값에 value가 매핑되어 쿼리문이 실행된다. 그러면 해당 쿼리에 대해 값이 존재하지 않는 id 값이라면 위 코드대로 라면 ResultSet 에 next() 함수는 반환된 row가 없기 때문에 false를 리턴하게 되어 post객체를 리턴하지 못한다. 따라서 next()를 통해서 cursor가 이동이 가능하다면 즉 적어도 한개의 row가 존재한다면 if문에 정상 진입하여 값을 리턴하게 된다는 것이다. 결국 적어도 한 개의 row가 존재한다는 것은 쿼리문에 의해 적합한 값이 존재한다는 것이고, 그 값을 cursor의 이동으로 체크하게 된다면, 해당 값을 ResultSet이 담고 있고 cursor는 next()에 의해서 초기 위치에서 다음 row로 이동하게 되기 때문에 그 값은 올바른 값을 리턴한다. 앞서 전제를 두었어야 하는데 (쿼리문이 잘못되지 않았다) 라는 가정이 필요할 것 같다.

 

 따라서 rs.next()로 값을 확인한다는 것은 결국에 해당 값이 정상적으로 테이블 형태의 row로 접근이 가능하냐는 뜻이 되며, 이는 row에 접근이 가능하다는 것이고 그 값은 쿼리문에 의한 적합한 값이다. 따라서 클라이언트의 요구사항에 만족한다. 또한 next()함수를 통해서 값을 체크 한다는 것에 대한 답이 만들어질 것이다. 어떤 관점에서 바라본다면 next()에 의해서 원하는 값을 가지고 왔는지 체크한다는 점에서 데이터 체크가 맞을 수 있고, 혹은 그 값이 올바른 값이다 라는 것은 아닐 수 있다. 때문에 추가적인 확인 작업을 거쳐야 한다면 rs.next()를 통해서 얻어낸 결과값에 cursor를 위치시킨 다음 세부사항을 확인하는 작업이 이루어진다. 그렇다면 여러 개의 result라면 어떨까?

 

 여러개의 row로 테이블을 이루게 될 것이고, 그 테이블을 순회하는 방법이 쓰일 수 있다. (만약 특정한 값만을 찾고 싶다면) 이때 while문을 이용해서 while(rs.next()) 가 true일 경우에만 즉 row가 존재할 경우에만 while문에 대한 특정 작업이 진행될 수 있다. 위에서는 값을 찾는다면 이라고 했으니 특정한 값을 만족하는 것을 while문에서 찾고자 한다면 이렇게 사용될 수 있다. 그럼 이러한 동작은 while문에 의해 rs.next()는 지정된 위치에서 다음 row를 계속해서 가리키게 될 것이다. 그럼 그 row들의 값을 얻기 위해 ResultSet에서 제공해 주는 getter 메서드들을 이용하게 되면 특정한 작업 수행이 이루어질 수 있다. 

 

 위와 같은 동작방식을 통해서 나는 값을 체크하기 위해서 next()를 사용할 수 있다는 것을 알 수 있었다. 또한 그 동작 방식은 ResultSet에 cursor를 통해 row에 접근할 수 있다는 사실을 이용해 무작정 next()를 사용하는 것이 아닌, 왜 next()를 써서 데이터 체크를 하는지 이해할 수 있었다.