JdbcTemplate

재우·2024년 10월 1일
0
post-custom-banner

커넥션 조회, 커넥션 동기화, 쿼리실행 등 JDBC의 반복 문제를 해결하기위해, JdbcTemplate을 사용하기 앞서 깊게 공부해보고 싶었다.

추상메서드 : 메서드의 선언만 있고, 구현은 없는 메서드
함수형 인터페이스 : 하나의 추상메서드만 가지는 인터페이스
제네릭 타입 : 특정한 타입을 지정하지않고, 실제 사용할때 원하는 타입을 넣어준다.


먼저, 아래와 같은 예시 코드를 보자.

@Override
    public Member findById(String memberId) {
        String sql = "select * from member where member_id = ?";
        return template.queryForObject(sql, memberRowMapper(), memberId);
    }

    private RowMapper<Member> memberRowMapper(){
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setMemberId(rs.getString("member_id"));
            member.setMoney(rs.getInt("money"));
            return member;
        };
    }

RowMapper<Member> 를 들어가보면, RowMapper<T> 는 함수형 인터페이스이고,
T mapRow(ResultSet rs, int rowNum) throws SQLException 이라는 추상메서드를 가지고 있다.

  • 함수형 인터페이스를 구현해서 구현객체를 만들때는 추상메서드를 람다식으로 구현해서 구현객체를 만든다.
  • interface MyFunctionalInterface {
        void doSomething();
    }
    `
    예를 들어, 위와 같은 함수형 인터페이스가 있을때, 
    MyFunctionalInterface instance = 
    	() -> System.out.println("Doing something!"); 이런식으로 람다식으로 구현객체를 만든다.
  • RowMapper<Member> memberRowMapper = 
                (rs, rowNum) -> {
                            Member member = new Member();
                            member.setMemberId(rs.getString("member_id"));
                            member.setMoney(rs.getInt("money"));
                            return member;
                };
    `
    RowMapper<T>는 mapRow라는 하나의 추상 메서드만을 가진 함수형 인터페이스이다. 그래서 이 인터페이스를 구현할 때 람다식을 사용할 수 있다.
  • 여기서 memberRowMapper의 구현객체는 RowMapper<Member> 타입의 객체이다.
    함수형인터페이스 RowMapper<Member>의 구현객체는 함수형인터페이스의 추상메서드를 람다식으로 구현해서 RowMapper<Member>타입의 구현객체를 생성한다.

❗참고로, 이렇게 람다식으로 구현객체를 생성하는것이 mapRow()를 호출하는게 아니다.
template.queryForObject(sql, memberRowMapper(), memberId); 에서
JdbcTemplate은 매개값 sql과 매개값 memberId를 통해 SQL을 처리하고, SQL 쿼리 결과인 ResultSet을 내부적으로 생성한다. 이 ResultSet을 객체로 변환하기위해 두번째 매개값인 RowMapper 타입의 구현객체를 넣어줘야한다.

memberRowMapper()가 호출되면 RowMapper<Member>객체를 생성해서 리턴하는데, 이 객체는 mapRow()메서드를 구현해서 생성한 객체이고, 이때 mapRow()가 실행되는것이아니다.
memberRowMapper()가 호출될 때 mapRow()가 실행되지 않는 이유는, memberRowMapper()는 단순히 RowMapper<Member> 객체를 생성해서 반환하는 메서드이기 때문이다. 이때 반환되는 RowMapper<Member> 객체는 mapRow() 메서드를 구현해서 생성한 객체일 뿐이고, 이 순간에는 mapRow() 메서드가 실제로 실행되지는 않는다. memberRowMapper()를 실행하면, RowMapper<Member> 객체가 생성되고, 이 객체는 mapRow() 메서드를 어떻게 실행할지를 정의한 객체이다. 하지만 이 시점에서는 mapRow()가 호출되지 않고, 단지 객체가 만들어져 반환될 뿐이다.
mapRow()는 JdbcTemplate가 SQL을 처리하고, 내부적으로 자동으로 ResultSet을 객체로 변환하기위해 RowMapper<Member>객체안에있는 mapRow()를 실행한다.

mapRow()를 실행한다는것은,

(rs, rowNum) -> {
          Member member = new Member();
          member.setMemberId(rs.getString("member_id"));
          member.setMoney(rs.getInt("money"));
          return member;
};

이 부분을 실행하는것이다. mapRow()를 실행해서 객체로 변환한다.


❓그렇다면, RowMapper<Member>객체안에있는 mapRow()를 실행할때, SQL 쿼리 결과인 ResultSet과 rowNum 매개값을 mapRow()의 매개값에 넣어주는데, 이건 어떻게 넣어주는것일까?
🙆‍♂️JdbcTemplateSQL을 처리하고, 내부적으로 자동으로 ResultSet과 rowNum을 생성해서 매개값으로 넣어준다. ResultSet에는 여러데이터(여러 행)가 있을수도 있고 한개의 데이터(한개의 행)가 있을수도 있다. rowNum은 각각의 행의 인덱스를 나타내며, 첫번째 행은 0, 두번째행은 1처럼 증가한다.
ResultSet 내부에서 JdbcTemplate이 각 행을 처리하는 방식은 rs.next()를 사용하여 순차적으로 각 행을 읽어간다. 처음에는 자동으로 ResultSet내부에 있는 커서가 첫번째행을 가리키는 상태인 ResultSet과 rowNum은 0, 두번째는 자동으로 ResultSet내부에 있는 커서가 두번째행을 가리키는 상태인 ResultSet과 rowNum은1 이런식으로 매개값으로 넣어준다. 이런식으로 현재 몇 번째 행을 가리키는지에 대한 상태는 ResultSet 내부에 저장된다.


✅이때, 한개의데이터(한개의행)를 객체로 변환할땐 queryForObject()를 사용하고, 여러데이터(여러행)을 객체로 변환할땐 query()를 사용한다.
  • 내부적으로 queryForObject()는 mapRow()를 한번만 호출한다. 따라서 한 행이 mapRow()메서드를 통해 처리되고, Member객체로 변환된다.
  • 내부적으로 query()는 rs.next()를 사용하여 ResultSet의 각 행에 대해 mapRow()가 반복적으로 호출된다. 현재 가리키는 각 행에 대해 mapRow()가 호출된다. 따라서 모든 행이 mapRow() 메서드를 통해 하나씩 처리되고, Member 객체로 변환된다. 그래서 변환된 객체들을 List에 추가한다.(List<Member>)

❓ template.queryForObject()의 리턴 타입이 Member인 이유는 메서드의 매개변수로 전달된 RowMapper<Member>객체의 제네릭타입 T가 Member로 지정되었기 때문이다.
즉, template.queryForObject()의 리턴 타입은 RowMapper<T>의 제네릭타입이 무엇인지에 따라 달라진다.

post-custom-banner

0개의 댓글