sqlExceptionHelper is null 에러 해결 / Github Actions 데이터베이스 연결

Jake·2024년 7월 7일
0

안녕하세요. 이번시간에는 sqlExceptionHelper is null 에러를 왜 만났고 어떻게 해결하게 되었는지에 대해 알아보겠습니다.


개요

market 프로젝트에서 develop 브랜치로 Pull Request 요청을 할 경우 테스트 결과를 알려주기 위해 Github Actions를 사용하여 Test Results를 Pull Request 에 해두고 싶었습니다. 그래서 스크립트를 통해 환경구성을 하게 되었습니다.


히스토리

이전에 Github Actions 를 통해 환경구성한 스크립트가 존재하여 그걸 그대로 가져다 썼지만 저번 테스트에서는 Mock을 사용하였고 이번 테스트에서는 SpringBootTest를 사용하여 Mock이 아닌 실제 Bean들을 사용하게 되는 환경이었습니다. 그래서 저번 스크립트로는 테스트진행이 불가하며 Github Actions에 MySQL을 설치해야하는 요구사항이 발생했습니다.


sqlExceptionHelper is null

데이터베이스 구성은 mirromutth/mysql-action@v1 를 사용하였습니다. 스크립트는 아래와 같습니다.

ci.yml 스크립트 중 일부

      - name: MySQL 설치
        uses: mirromutth/mysql-action@v1
        with:
          mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL
          mysql database: market_db # Optional, default value is "test". The specified database which will be create
          mysql user: rivkode # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Of course you can use secrets, too
          mysql password: ${{ secrets.DB_PASSWORD }}

위와 같이 구성한 뒤 Github Action을 실행시켰지만 sqlExceptionHelper is null 에러가 발생하였습니다.


에러 발생 및 해결 과정

1. sqlExceptionHelper is null이 왜 나오게 되었는지 10초간 생각해본다.

이전에 에러 해결을 하는 과정에서 아무 생각없이 구글링부터 하게 되면 정보를 얻는 과정에서 중요한 포인트를 잡지 못하게 되는 경우가 많이 생겼습니다.

그래서 그것을 경험한 뒤로는 항상 구글링 하기 전 10초간 내가 어떤것을 원하는지, 왜 구글링을 하는지에 대해 고민해본 후 구글링을 하기 시작합니다.


  • sqlExceptionHelper 가 null 이라는 것은 생성될때 정상적인 값이 들어가지 않았기 때문에 null이 발생하였다고 생각
  • sqlExceptionHelper가 무엇인지 찾아봐야겠다는 생각
  • 어느시점에서 에러가 발생하였는지 Log를 확인

을 하게 되었습니다.


2. Log에서 얻은 정보

스프링 부트에서 DataBase와의 Connection을 HikariPool 에서 관리하게 됩니다. 위 로그를 보면 HikariPool이 Starting 되고난 직후에 Could not obtain connection to query metadata 에러가 발생하게 됩니다.

그리고 그 밑에 sqlExceptionHelper is null 가 발생하게 됩니다.

이것을 통해 데이터베이스를 연결하는 과정에서 에러가 발생하였다고 짐작해볼 수 있었습니다.


3. sqlExceptionHelper is null을 구글링 한다.

sqlExceptionHelper() is null 에러 글을 통해 스프링과 데이터베이스를 연결하는 과정에서 컴퓨터 마다 세팅이 다르므로 발생할 수 있다고 하였고 이 분은 접속 허용 문제였지만 저의 경우 해당하지 않았습니다. 하지만 세팅이 다르다는 내용을 통해 설정값 (username, password) 이 달라서 발생한 것일 수 있음을 짐작할 수 있었습니다.


4. 데이터베이스와 관련된 내용들을 모두 Verify하는 스크립트를 넣어 확인한다.

데이터베이스가 실제로 설치되었는지, 정상 동작을 하는지를 직접 확인해보고싶었습니다. Local이라면 ping 테스트나 접속하여 알 수 있었겠지만 Github Actions 환경에서는 그럴 수 없었습니다. 그래서 따로 스크립트를 작성하여 확인하였습니다.

아래는 데이터베이스를 Verify 하는 스크립트입니다.


      ...
      
      
      - name: Wait for MySQL
        run: |
          while ! mysqladmin ping --host=127.0.0.1 --password=$DB_PASSWORD --silent; do
            sleep 1
          done
      
      - name: Verify application.properties
        run: cat ./src/main/resources/application.properties
        shell: bash
        
      - name: Verify MySQL is running
        run: sudo netstat -tlnp | grep 3306
      
      
      ...
      

이를 통해 Build 과정 중 추가하여 데이터베이스를 설치하고 난 뒤 아래와 같이 확인할 수 있었습니다.


하지만 동일하게 데이터베이스를 연결하는 과정에서 실패하였습니다.


5. Github Secrets

저는 데이터베이스와 정보와 application.properties 값들을 Github Secrets를 통해 관리하고 있었습니다. 여기서 설정값은 올바르게 하였나 ? 라는 의문을 가졌고, 여러번 이전에 확인하였지만 이번에는 메모장에 설정정보를 입력한 뒤 정보를 서로 맞춰가며 입력해보았고 성공하게 되었습니다.


이 과정을 통해 설정 정보를 입력하는 과정은 머리로 기억하기 보다는 메모장에 적어놓고 보는 것이 중요함을 알게되었습니다.


6. Redisson Client

그렇게 실행하니 MySQL이 성공적으로 연결되면서 Table들이 정상적으로 생성된 것을 확인하였습니다. 하지만 이번에는 Redisson Client가 없어서 실패하에 되었습니다.


orderFacade 가 빈 생성에 실패하였고 그 이유를 따라가다보니

  • redisson 이름의 빈을 생성하는데 에러가 발생하였고
  • Redis 에 연결할 수 없었기 때문에 실패한 것을 알 수 있었습니다.

Factory method 'redisson' threw exception with message: java.util.concurrent.ExecutionException: org.redisson.client.RedisConnectionException: Unable to connect to Redis server: localhost/127.0.0.1:6379


저는 이전에 redis를 사용함에도 이를 설치해주지 않았었습니다. 따라서 아래 스크립트를 통해 redis 설치를 진행해주었습니다.

    services:
      redis:
        image: redis:latest
        ports:
          - 6379:6379

7. 성공

Redis까지 설치해주니 정상적으로 모든 job 들이 실행되었고 Test Results결과도 정상적으로 확인되었습니다.


최종 스크립트는 아래와 같이 구성하였습니다.


name: develop CI check

on:
  push:
    branches: [ "develop" ]
  pull_request:
    branches: [ "develop" ]

permissions:
  checks: write
  pull-requests: write

jobs:
  build:

    runs-on: ubuntu-latest

    services:
      redis:
        image: redis:latest
        ports:
          - 6379:6379

    steps:

      - name: 레포지토리 체크아웃
        uses: actions/checkout@v4

      - name: 기본 MySQL 종료 (SUDO 필요)
        run: sudo service mysql stop # 기본 MySQL 종료

      - name: JDK 17 설치
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'

      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: MySQL 설치
        uses: mirromutth/mysql-action@v1
        with:
          mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL
          mysql database: market_db # Optional, default value is "test". The specified database which will be create
          mysql user: rivkode # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Of course you can use secrets, too
          mysql password: ${{ secrets.DB_PASSWORD }}

      - name: Wait for MySQL
        run: |
          while ! mysqladmin ping --host=127.0.0.1 --password=$DB_PASSWORD --silent; do
            sleep 1
          done

      - name: properties 파일 생성
        run: |
          cd ./src/main
          mkdir resources
          cd ./resources
          touch ./application.properties
          echo "${{ secrets.APPLICATION_PROPERTIES }}" > ./application.properties

      - name: Verify application.properties
        run: cat ./src/main/resources/application.properties
        shell: bash

      - name: Verify MySQL is running
        run: sudo netstat -tlnp | grep 3306

      # gradle 실행 허가
      - name: Run chmod to make gradlew executable
        run: chmod +x ./gradlew

      - name: 빌드 진행
        run: ./gradlew build -x test

      - name: 테스트 코드 실행
        run: ./gradlew --info test

      - name: 테스트 결과 PR에 코멘트 작성
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()  # 테스트가 실패했을때만 or 테스트가 성공했을때만 알려주기(여기선 둘다!)
        with:
          files: |
            **/build/test-results/**/*.xml

      # Files changed에서 어디에서 잘못되었는지 가르쳐준다.
      - name: Publish Test Report
        uses: mikepenz/action-junit-report@v3
        if: success() || failure() # always run even if the previous step fails
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'

참고 자료

0개의 댓글

관련 채용 정보