keyholder는 데이터베이스마다 다른 값을 반환한다 - 초록 스터디 2주차

김성재·2024년 3월 9일
0

초록스터디 2주차는 mvc 패턴을 학습하며 jdbctemplate을 사용해봤다. bcsd 비기너 과정때 drivermanager를 사용해서 connection객체를 일일이 생성해 완전 생(?) jdbc를 사용하는 고생도 해봤지만 후에 이미 jpa의 맛을 본 나는 jdbctemplate 사용도 그닥 즐겁지는 않았던 것 같다ㅋㅋㅋ🥲 쿼리문 짜는 것도 너무 귀찮고 데이터베이스에서 자동생성된 id도 keyholder로 일일이 가져오면서 새삼 jpa사용의 위대함을 느꼈다...

public Long insertWithKeyHolder(Customer customer) {
   String sql = "insert into customers (first_name, last_name) values (?, ?)";
   KeyHolder keyHolder = new GeneratedKeyHolder();

   jdbcTemplate.update(connection -> {
       PreparedStatement ps = connection.prepareStatement(
               "insert into customers (first_name, last_name) values (?, ?)",
               new String[]{"id"});
       ps.setString(1, customer.getFirstName());
       ps.setString(2, customer.getLastName());
       return ps;
   }, keyHolder);

   Long id = keyHolder.getKey().longValue();

   return keyHolder.getKey().longValue();
}

과제와 미션을 수행하며 다른 것들은 다 이해가 가는데 keyholder를 사용하면서 이해가 가지 않는 부분이 있었다. 다른 jdbctemplate메서드는 다 내가 지정한 파라미터만 넣는데 난데 없이 ps문 안에 new String[]{"id"});의 정체가 너무너무 궁금해졌었다. 난데없이 여기에 꽂혀서 엄청나게 알아봤다. 정리한 바는 다음과 같다

new String[]{"id"}의 정체

일반적인 상황에서 JdbcTemplate은 자동생성된 키를 얻기 위해서 원래 new String[]{"id"}의 자리에 Statement.RETRUN_GENERATED_KEYS를 사용한다고 한다.

사용 예 :

PreparedStatement ps = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS);

문제는 이렇게 사용하며 Oracle을 데이터베이스로 사용시 Oracle의 JDBC 드라이버가 해당 옵션에 id 값을 반환하지 않고 ‘행의 내부 포인터’를 반환한다고 한다. id 값를 반환해야 명시적으로 쿼리 할 수 있는데 행의 내부 포인터를 반환시 대부분의 사용 사례에 적합하지 않으며 오류가 빈번히 발생한다.

행의 내부 포인터를 반환해서 나오는 에러 :

HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataRetrievalFailureException: The generated key is not of a supported numeric type. Unable to cast [oracle.sql.ROWID] to [java.lang.Number]

이를 해결하기 위해 JDBC는 생성된 열을 얻기 위한 추가 방법을 제공하는데 이때 갖고 싶은 열의 이름을 명시적으로 지정하는 것이고 이러면 행의 내부 포인터를 반환하는 Oracle의 driver대신 열 값을 검색해서 반환해준다. 이게 바로 new String[]{“id”}이다.

오라클을 사용해본 적이 없어서 잘은 모르지만 오라클의 dirver가 행의 내부 포인터를 반환하는 이유는 얼마전까지 identity(mySql에서 autoincrement)가 없어서, id를 자동생성 하는 것이 없어서 사용자가 직접 커스텀 하는 수 밖에 없었다고 한다.

한마디로 오라클이 rdbms 1등이니까 Statement.RETURN_GENERATED_KEYS대신 모두가 관습적으로 이렇게 쓰고 있는 것이 아닐까 추측해본다..
(h2 데이터 베이스를 과제에서 사용한 우리는 Statement.RETURN_GENERATED_KEYS을 사용해도 아주 잘 돌아간다! 👍)

  • 추가로 PostgreSQL와 Firebird 같은 데이터베이스의 driver들은 모든 열을 반환하는데 이때 테이블 상에서 id가 첫번째가 아니면 동일한 에러가 발생한다고 한다!

찾아보고 나니까 별로 쓸데 없는 것을 알게 된 것 같기도...

출처 : https://stackoverflow.com/questions/66940164/why-do-we-add-new-stringid-when-fetching-keyholder-value-in-namedparameter

0개의 댓글