[ Spring / MyBatis ] H2 연동해 테스트 수행 중, selectKey 태그 내에서 LAST_INSERT_ID() 구문 오류 발생 시

TenaLee·2021년 7월 31일
1

(부제 : H2 연동해 테스트 수행 시, SQL 호환 모드 설정 - 특정 벤더 기준으로 작성한 SQL 구문을 H2 에서도 실행 가능하도록)


에러 내용 :

Resolved [org.springframework.jdbc.BadSqlGrammarException: Error selecting key or setting result to parameter object. Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: Function "LAST_INSERT_ID" not found; SQL statement:


원인 : 구문 오류 / H2 에서 구문 호환

  • H2 구문에는 MySQL 의 LAST_INSERT_ID() 라는 함수가 없다.

  • 따라서 H2 연동 후, SELECT LAST_INSERT_ID() 질의 시 JdbcSQLSyntaxErrorException / BadSqlGrammarException 를 뱉는다.

  • 테스트 케이스 수행 시 H2 를 연동하면 가볍고 편하다. ORM 방식의 JPA 를 사용하는 경우는 자동적으로 벤더에 맞게 쿼리를 수행하기 때문에 별다른 문제없이 편하다. 하지만 SQL Mapper 방식의 MyBatis 의 경우 H2 로 가볍게 띄워서 테스트를 수행할 때, 구문 오류가 발생한다.
    Mapper 내 작성된 특정 벤더 기준의 SQL을 H2에서 기본적으로 인식할 수 없기 때문에 마주하는 이슈이다.

  • 그럼 H2 연동 시 MySQL, Oracle, MS-SQL 등 특정 벤더에서 사용하는 SQL 구문과 호환되게 하려면 어떻게?


해결 방법

👉 결론은 방법 2 를 활용한다.


  • 방법 1 👉 그냥 참고로 알아둘 것, 벤더별 대체 함수 사용

    • 사용 중인 벤더에 해당하는 쿼리로 교체한다.
    • SELECT LAST_INSERT_ID() : MySQL
    • SELECT SCOPE_IDENTITY() : MS-SQL, H2 (둘 다 해당)
    • CALL SCOPE_IDENTITY() : H2
    • MySQL 의 LAST_INSERT_ID() 과 같은 동작을 수행하는 각 벤더별 함수를 사용하도록 쿼리 변경
    <insert id="...">
      <selectKey keyProperty="id" resultType="int" order="AFTER">
        SELECT SCOPE_IDENTITY()
      </selectKey>
      ...
    </insert>

    [방법 1] 은 SQL 벤더별 함수를 알아두는 의미에서 참고삼아 정리한 것이고, 실질적으론 [방법 2] 를 사용한다. (테스트 시 가볍게 인메모리 H2 돌리려는 상황에서, 방법 1과 같이 Mapper 내의 구문을 변경하는 건 번거롭고, 실제로 사용할 SQL 또한 아니므로 테스트 자체도 모순적)


  • 방법 2 👉 결론

    • application-test.yml 의 데이터소스 접속 URL에 Mode 옵션 추가
      • spring.datasource.url;MODE=MySQL 옵션을 추가한다.
    • (파일 하나에 프로필 별로 분리해 사용한다면 test 프로필 항목에 추가)
    spring:
      datasource:
        url: jdbc:h2:mem:testdb;MODE=MySQL
        driver-class-name: org.h2.Driver

    예컨데 MyBatis Mapper 내의 쿼리가 MySQL 구문으로 작성되어 있다면 위와 같이 ;MODE=MySQL 을 추가하면 된다.

    • MODE : SQL 구문상 호환되기 원하는 벤더명 추가
      • H2 연동 시, 특정 벤더 (MySQL, Oracle, MS-SQL, ...) 의 구문과 호환되도록 설정해주는 옵션이다.
      • 가령 Mapper 내 작성된 SQL 에 ANSI SQL 이 아닌, 특정 벤더에 특화된 SQL이 포함되어 있을 것이다. 만약 테스트를 작성하고 수행하는 상황에서 가볍게 인메모리 H2 를 띠우려고 할 때, Mode 옵션을 활용하면 좋다.
      • 또한 H2 콘솔 프롬프트에서 Mode 로 지정한 벤더 고유의 함수를 실행할 수 있다. (당연히 모든 함수를 지원하진 못할 것임 그렇게 무거울 필요도 없고)

  • 방법 3 👉 selectKey 태그 대신 useGeneratedKeys 속성 사용

    • 애초에 Mapper 내에 selectKey 태그를 사용하지 말자.
    • 대신에 Insert 시 자동 생성키 가져오는 방식을 useGeneratedKeys 속성을 사용하는 방식으로 변경

    Ex)

    <insert id="writeArticle" useGeneratedKeys="true" keyProperty="id">
      ...
    </insert>
profile
Tenacity

0개의 댓글