Skip to content

스프링 부트 2.7.x에서 3.2.x 버전으로 마이그레이션할 때 주의할 점 ​

레거시 프로젝트의 부트 버전이 2.7.5였다.

스프링 부트의 3.2에서 개발 입맛을 돋구는 많은 기능들이 추가되었다.

이번 기회에 3.2.x로 업데이트하며 발생한 이슈를 남기며 공유한다.

변경점 전부는 아니니 참고하자.


요약 ​

no내용변경 전변경 후
1application.properties의 spring 설정 경로 변경됨spring.redis, management.metrics.export.*spring.data.redis, management.*.metrics.export
2jakarta로 패키지명 변경됨import javax.*import jakarta.*
3@ConstructorBinding Target이 변경됨Type Level에 어노테이션 적용Constructor Level에 어노테이션 적용 혹은 제거
4RequestMapping 시 Trailing slash matching 기본값 변경RequestMapping("/name") -> /name/ OKRequestMapping("/name") -> /name/ NOT FOUND
5Spring security HttpSecurity 메서드 deprecatedHttpSecurity.cors()...HttpSecurity.cors(withDefault())...
6spring.jpa.properties.hibernate.timezone.default_storageDB 타임존을 지원하지 않는 컬럼에 저장시 그냥 저장함DB 타임존을 지원하지 않는 컬럼에 젖아시 UTC로 저장함
7version specific Database Dialect DeprecatedMySQL8Dialect, PostgreSQL95DialectMySQLDialect, PostgreSQLDialect
8JarLauncher 경로 변경됨org.springframework.boot.loader.JarLauncher org.springframework.boot.loader.launch.JarLauncher
9spring.mvc.throw-exception-if-no-handler-found DeprecatedHandler 없을 시 자동으로 404 응답NoResourceFoundException 예외가 발생해 GlobalExceptionHandler로 처리
10Database custom 함수를 위한 registerFunction DeprecatedCustomDialect.registerFunction으로 함수 설정FunctionContributor 를 사용해 커스텀 함수 설정
11Java 버전 최소 17Java 버전 최소 8Java 버전 최소 17

1. application.properties 설정 경로 변경 ​

Spring boot 3.0으로 올라가면서 몇 설정용 프로퍼티들에 변경이 있습니다.

제 경우에는 영향이 spring.redis와 management.metrics...에 해당되어 아래와 같이 변경했습니다.

  • spring.data.redis

  • management.prometheus.metrics...

스프링부트에서 제공해주는 Configuration Properties Migrator 를 활용해 migration 해보시는것을 추천 드립니다.

참고링크 ​

2. 패키지명 javax에서 jakarta로 변경 ​

Spring boot 는 Jakarta EE 명세를 따르고 있습니다.

이번 3.0 버전에서는 Jakarta EE 10 버전에 의해 javax 패키지 대신 jakarta를 사용합니다.

참고링크 ​

3. @ConstructorBinding 어노테이션 타겟 변경 ​

제 경우 application property 를 type 으로 선언해 사용하고 있습니다.

이때 data class를 애용해 활용해 @ConstructorBinding을 이용해 값을 불러오고 있었습니다.

이제 어노테이션 없이도 생성자를 통해 바인딩이 가능해 해당 어노테이션을 제거하거나 다른 Constructor에만 활용하면 됩니다.

참고링크 ​

4. RequestMapping 시 Trailing slash matching 기본값 변경 ​

기존에는 RequestMapping 작성시 Trailing comma를 명시하지 않아도 같이 매핑되었습니다.

이제는 명시하지 않으면 해당 경로로 조회시 메서드로 매핑되지 않으니 주의해야합니다.

예시) @GetMapping("/some/greeting", "/some/greeting/")

참고링크 ​

5. Spring security HttpSecurity 메서드 deprecated ​

Spring boot 3 에서는 Spring Security 6.0을 사용합니다.

Spring Security 5.8버전에서 설정 방식 등이 변경되었습니다.

Spring boot 2.7에서 업그레이드하는 경우 Spring Security 5.8 마이그레이션 문서를 참고해 설정을 변경해야합니다.

kotlin
@Bean
fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
    return httpSecurity.cors()
        .and()
        .csrf().disable()
        .headers().frameOptions().disable()
        .and()
        .exceptionHandling().authenticationEntryPoint(entryPoint)
        .and()
        .requestMatchers()
        .antMatchers("/api/**")
        .antMatchers("/app/**")
        .and()
        .build()
    }

@Bean
fun adminFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
    return httpSecurity.cors()
        .and()
        .csrf().disable()
        .headers().frameOptions().disable()
        .and()
        .exceptionHandling().authenticationEntryPoint(entryPoint)
        .and()
        .requestMatchers()
        .antMatchers("/admin/**")
        .antMatchers("/admin2")
        .and()
        .addFilterAfter(
            AdminAuthFilter(),
            ?::class.java
        )
        .build()
kotlin
@Bean
fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
    return httpSecurity
        .cors(withDefaults())
        .csrf { csrf -> csrf.disable() }
        .headers { headersCustomizer -> headersCustomizer.frameOptions {}.disable() }
        .exceptionHandling { exceptionHandlingCustomizer ->
            exceptionHandlingCustomizer.authenticationEntryPoint(entryPoint)
        }
        .securityMatchers { matchers ->
            matchers.requestMatchers(
                antMatcher("/api/**"),
                antMatcher("/app/**")
            )
        }
        .build()
}

@Bean
fun adminFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
    return httpSecurity
        .cors(withDefaults())
        .csrf { csrf -> csrf.disable() }
        .headers { headersCustomizer -> headersCustomizer.frameOptions {}.disable() }
        .exceptionHandling { exceptionHandlingCustomizer ->
            exceptionHandlingCustomizer.authenticationEntryPoint(authEntryPoint)
        }
        .securityMatchers { matchers ->
            matchers.requestMatchers(
                antMatcher("/admin/**"),
                antMatcher("/admin2")
            )
        }
        .addFilterAfter(
            AdminAuthFilter(),
            ?::class.java
        )
        .build()
}

참고링크 ​

6. spring.jpa.properties.hibernate.timezone.default_storage ​

Spring boot 3.1 부터는 hibernate 6.2를 사용합니다.

변경 내용 중 시간대에 대한 내용을 알고 있어야 데이터가 정상적으로 적재될 것이여서 참고해야합니다.

DB 컬럼이 Timezone을 저장할 수 있는 컬럼의 경우 Timezone 정보와 함께 저장됩니다.

DB 컬럼이 Timezone을 저장할 수 없는 컬럼의 경우 UTC로 변환 후 저장합니다.

참고링크 ​

7. version specific Database Dialect Deprecated ​

Hibernate 버전이 6.2로 업데이트 되었습니다.

관련해서 DB의 Dialect를 지정해서 사용하고 있다면 굳이 설정할 필요가 없어졌습니다.

추가로 Version을 명시하는 Dialect가 Deprecated가 되어 버전 명시 Dialect 대신 포괄적인 Dialect를 사용해야합니다.

참고 링크 ​

8. JarLauncher 경로 변경됨 ​

스프링 부트의 jarLauncher의 경로가 변경되었다.

변경전변경후
org.springframework.boot.loader.JarLauncherorg.springframework.boot.loader.launch.JarLauncher
org.springframework.boot.loader.PropertiesLauncherorg.springframework.boot.loader.launch.PropertiesLauncher
org.springframework.boot.loader.WarLauncherorg.springframework.boot.loader.launch.WarLauncher

따라서 위 방식으로 사용한다면 경로를 변경해주어야 합니다.

dockerfile
#...
# ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

기존방식대로 유지하기 ​

  • gradle
groovy
bootJar {
  loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC
}
  • maven
xml
<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>repackage</goal>
          </goals>
          <configuration>
            <loaderImplementation>CLASSIC</loaderImplementation>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

참고링크 ​

9. spring.mvc.throw-exception-if-no-handler-found Deprecated ​

Spring 3.2버전으로 업데이트되면서 Spring framework 6.2 버전으로 업데이트 되었다.

이때 throw-exception-if-no-handler-found 옵션이 기본값이 true로 변경되었다.

그리고 해당 프로퍼티는 deprecated 되었다.

따라서 Spring boot 3.2로 업그레이드 시 GlobalExceptionHandler 도입을 적극 검토하고 NoResourceFoundException를 추가해주어야한다.

참고링크 ​

10. Database custom 함수를 위한 registerFunction Deprecated ​

이번 hibernate 버전 업데이트로 registerFunction 함수가 사라져 그대로 사용할 수 없게되었습니다.

제가 사용한 방법은 아래와 같습니다.

  • querydsl의 stringTemplate, numberTemplate을 활용해 함수 사용
  • registerFunction대신FunctionContributor`를 사용
kotlin
import org.hibernate.dialect.PostgreSQL95Dialect
import org.hibernate.dialect.function.SQLFunctionTemplate
import org.hibernate.type.StandardBasicTypes

class LegacyCustomDBDialect : PostgreSQL95Dialect() {
    init {
        registerFunction(
            "string_agg",
            SQLFunctionTemplate(
                StandardBasicTypes.STRING,
                "string_agg(distinct ?1, ?2)"
            )
        )
        registerFunction(
            "dense_rank",
            SQLFunctionTemplate(
                StandardBasicTypes.LONG,
                "dense_rank() over (order by sum(?1) desc)"
            )
        )
    }
}
kotlin
package com.sample.example.path.config.db.CustomFunctionContributor

import org.hibernate.boot.model.FunctionContributions
import org.hibernate.boot.model.FunctionContributor
import org.hibernate.type.StandardBasicTypes

class CustomFunctionContributor : FunctionContributor {
    override fun contributeFunctions(functionContributions: FunctionContributions) {
        functionContributions.functionRegistry.registerPattern(
            "string_agg_distinct",
            "string_agg(distinct ?1, ?2)",
            functionContributions.typeConfiguration.basicTypeRegistry.resolve(StandardBasicTypes.STRING)
        )
    }
}

// src/resources/META-INF/services/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor
// 에 클래스 추가
kotlin
com.sample.example.path.config.db.CustomFunctionContributor
kotlin
queryFactory.select(
  Projections.constructor(
    ProjectionClass::class.java,
    entity.id,
    // QueryDSL 의 numberTemplate을 그대로 활용해 호출
    Expressions.numberTemplate(
        Long::class.java,
        "dense_rank() over (order by sum({0}) desc)",
        entity.score
    ).`as`("rank"),
    // Query DLS 의 stringTemplate을 활용해 CustomFunction 호출
    // PostgreSQL의 string_agg(distinct {0}, {1})가 정상적으로 호출되지 않아 아래와 같은 방식 사용
    // stringTemplate을 그대로 사용시 string_agg(distinct ({0}, {1}))로 쿼리가 작성됨.
    Expressions.stringTemplate(
        "string_agg_distinct({0}, {1})",
        entity.string_for_array, // {0}
        Expressions.constant(",") // {1}
    ).`as`("string_arrays")
  )
)

참고 링크 ​

11. Java 버전 최소 17 ​

Spring boot 3 으로 업데이트 되면서 Java 버전이 최소 17이 되어야한다.

참고로 Spring boot 3.2부터 Java 21의 가상 스레드를 지원하니 참고하면 더 좋다.

참고링크 ​

Email: echo.youn@kakao.com