SW 개발/Spring

Kotlin, Spring Boot 2.3.x로 버전 업그레이드하기

지단로보트 2020. 9. 23. 02:44

개요

  • Spring Boot 2.3.x는 이전 버전 대비 Docker 이미지 자체 빌드와 같은 굵직한 신규 기능이 여럿 추가되었다. 24시간 운영 중인 Kotlin, Spring Boot 프로젝트에 새로운 버전을 적용하기까지 유독 이전과 다르게 많은 시간을 할애하며 다양한 트러블슈팅을 해야 했는데, 잊지 않고자 관련 내용을 정리하고자 한다.

버전 업그레이드 작업

  • Spring Boot 2.3.x 버전 업그레이드를 위한 첫 작업이다. 프로젝트 루트의 /build.gradle 파일의 아래 내용을 수정한다.
buildscript {
    ext {
        kotlinVersion = '1.4.10'
        springBootVersion = '2.3.5.RELEASE'
        querydslVersion = '4.4.0'
    }
    ...
}

test {
    useJUnitPlatform()
}

dependencies {
    ...
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: "org.junit.vintage", module: "junit-vintage-engine"
    }
    ...
}
  • 프로젝트에서 JSR 380을 이용한 유효성 검사를 사용할 경우, Spring Boot 2.3.x는 이전 버전과 달리 spring-boot-starter-validation 아티팩트 추가를 요구한다. 이전 버전을 쓰던 관성대로 생략할 경우 빌드시 오류가 발생한다.

Gradle 버전 6.5.1 사용

  • Spring Boot 2.3.xGradle 6.3 이상의 버전을 요구한다. 하지만 Kotlin에서는 최신 버전을 사용할 경우 빌드시 오류가 발생한다. 다운그레이드하여 Gradle 6.5.1을 사용해야 한다. 프로젝트 루트의 /gradle/wrapper/gradle-wrapper.properties 파일에서 distributionUrl 옵션 값을 아래와 같이 수정하면 된다.
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip

그레이스풀 셧다운 오류 수정

  • Spring Boot 2.3.x부터 공식적으로 Apache Tomcat, Jetty에 한해 완전한 Graceful Shutdown 기능을 지원한다. 하지만 기본 내장 WASApache Tomcat에서 간헐적으로 아래와 같은 오류와 함께 애플리케이션의 프로세스가 죽지 않고 살아있는 현상이 발생하는데, 내장 WASJetty로 변경하면 해결된다.
2020-09-28 00:40:21,425 11353771 [SpringContextShutdownHook] INFO  o.s.c.s.DefaultLifecycleProcessor - Failed to shut down 1 bean with phase value 2147483647 within timeout of 30000ms: [webServerGracefulShutdown]
2020-09-28 00:40:21,436 11353782 [SpringContextShutdownHook] WARN  o.s.c.s.DefaultLifecycleProcessor - Failed to stop bean 'webServerStartStop'
java.lang.NoClassDefFoundError: org/apache/catalina/Lifecycle$SingleUse
        at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:269)
        at org.apache.catalina.startup.Tomcat.stop(Tomcat.java:496)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.stopTomcat(TomcatWebServer.java:273)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.stop(TomcatWebServer.java:331)
        at org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.stop(WebServerStartStopLifecycle.java:51)
        at org.springframework.context.SmartLifecycle.stop(SmartLifecycle.java:117)
        at org.springframework.context.support.DefaultLifecycleProcessor.doStop(DefaultLifecycleProcessor.java:238)
        at org.springframework.context.support.DefaultLifecycleProcessor.access$300(DefaultLifecycleProcessor.java:53)
        at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.stop(DefaultLifecycleProcessor.java:377)
        at org.springframework.context.support.DefaultLifecycleProcessor.stopBeans(DefaultLifecycleProcessor.java:210)
        at org.springframework.context.support.DefaultLifecycleProcessor.onClose(DefaultLifecycleProcessor.java:128)
        at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1022)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.doClose(ServletWebServerApplicationContext.java:170)
        at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:949)
2020-09-28 00:40:51,436 11383782 [SpringContextShutdownHook] INFO  o.s.c.s.DefaultLifecycleProcessor - Failed to shut down 1 bean with phase value 2147483646 within timeout of 30000ms: [webServerStartStop]
  • Jetty로 변경하는 방법은 프로젝트 루트의 /build.gradle 파일의 아래 내용을 수정한다.
configurations {
    compile.exclude module: "spring-boot-starter-tomcat"
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jetty'
    ...

JPA 멀티 데이터 소스 사용

  • Spring Boot 2.3.xJPA 멀티 데이터 소스 환경 설정이 이전 버전과 달라 그대로 유지할 경우 애플리케이션 기동시 오류가 발생한다. 아래와 같이 수정한다.
// @Primary
@Bean
fun dataSource(...) {

    ...
}

@Bean
fun entityManagerFactoryBuilder(): EntityManagerFactoryBuilder {

    return EntityManagerFactoryBuilder(HibernateJpaVendorAdapter(), HashMap<String, Any?>(), null)
}

// @Primary
@Bean
fun entityManagerFactory(

    @Qualifier("entityManagerFactoryBuilder") builder: EntityManagerFactoryBuilder,
    @Qualifier("dataSource") dataSource: DataSource,

): LocalContainerEntityManagerFactoryBean {

    return builder
            .dataSource(dataSource)
            .packages("com.jsonobject.entity")
            .persistenceUnit("some")
            .build()
}
  • 가장 먼저, dataSource, entityManagerFactory 빈에 대한 @Primary 어노테이션을 제거해야 한다. 이전 버전 그대로 유지할 경우 순환 참조 오류가 발생한다.
  • 두번째로는, 이전 버전과 달리 entityManagerFactoryBuilder 빈을 직접 생성하고 entityManagerFactory 빈 생성 시점에 @Qualifier("entityManagerFactoryBuilder")로 참조해야 한다. [참고 링크]

참고 글