Springboot 3 Migration (from 2.6)
■ Intro
springboot 3으로 마이그레이션한 과정과, 내 프로젝트에서 해결한 문제를 기록한다. 일반적으로 springboot 3 마이그레이션시 만나는 문제를 다양하게 만날 수 있어서 좋았다.
보안과 유지보수 측면에서 마이그레이션을 해야 한다. 마이그레이션 대상 프로젝트는 2.6.x를 사용중이었고 이걸 스부 3 이상으로 마이그레이션 하는 것이 목표다.
마이그레이션 순서를 간략히 정리하면, 자바 17로 버전업 후 springboot 2.7로 중간 버전업 해서 종속성 문제 해결 뒤 springboot 3.x으로 버전업해서 나머지 종속성을 해결하는 것이다. 2.7로 중간 버전업하는 거는 springboot 버전이 낮은 편이었다거나 하면 도움이 될 것이다. 참고로 나는 2.6을 쓰고 있었는데 2.7로 중간 버전업 했을 때 충돌이 안 났다.
↓ 특히 2.5 이하에서는 중간중간 거쳐서 버전업해주는 것이 좋다.
Upgrading instructions are always provided in our release notes. For example, if you are upgrading from Spring Boot 2.6 to Spring Boot 2.7, you can follow this section.
If you’re upgrading from Spring Boot 2.5 or earlier, we don’t recommend skipping releases. It’s often easier to upgrade in steps (e.g. 2.5 → 2.6 → 2.7) rather than trying to upgrade directly from 2.5 → 2.7.
■ Springboot 3 버전 정하기
어떤 3.x를 택할지는.. 프로젝트 특성에 따르면 될 듯 한데..
나는 유지관리 단계에 접어든 3.2를 선택했다. (3.3과 3.4는 개발이 활성된 상태)
↓ 참고
마이그레이션은 단계적으로 2.6.3 → 2.7.18 → 3.2.12 로 진행했다.
- springboot 3.0 마이그레이션 가이드를 보면 버전업하려는 프로젝트를 2.7로 단계적 버전업해서 종속성을 검토해본 뒤에 3.0으로 넘어가라고 안내한다.
- 2.7 릴리즈 노트를 보면 2.7.18을 마지막으로 springboot 3으로 넘어갔다.
■ 마이그레이션 과정
아래와 같이 순차적으로 작업했다.
크게 어려운 부분은 없었고, 종속성 해결을 위한 라이브러리 버전업을 잘 해주면 됐다. 또한 일반적인 springboot 3 마이그레이션시 만나는 종속성 문제를 해결할 수 있었다.
① Java 11 → 17
- build.gradle에서 자바 버전을 17로 지정
- Intellij: Project SDK를 17로 설정
- Intellij: settings에서 gradle 설정의 Gradle JVM 17로 지정
✔️ 완료
② 2.7.18 중간 버전업
- springboot 3.0 마이그레이션 가이드를 보면 버전업하려는 프로젝트를 2.7로 단계적 버전업해서 종속성을 검토해본 뒤에 3.0으로 넘어가라고 안내한다.
- build.gradle의 스프링부트 버전을 2.7.18로 지정
나는 큰 차이 없는 2.6에서 넘어가서 그런지 문제 없이 컴파일되었고, 종속성 문제 없이 Run 되었다.
유의점은 springboot 버전이랑 gradle 버전도 맞아야 한다. 나도 3으로 갈 때 gradle 버전을 올려야 했다 (아래 내용)
✔️ 완료
③ 3.2.12 최종 버전업
이제 부터는 몇가지 문제를 해결해야 했다.
일단 build.gradle에서 스부 버전을 3.2.12로 지정하고,
빌드해보면 문제가 하나씩 터질 건데, 하나씩 해결해갔다.
❗ GradleException:
Gradle 버전업하거나 lock 파일을 지워야 할 수도
스부 버전만 변경한채로 실행해보면 Gradle 오류가 뜰 수 있다.
java.util.concurrent.ExecutionException: org.gradle.api.GradleException: Failed to create Jar file {path}\.gradle\caches\jars-9\~~~~~~~.jar.
◆ 검토사항 첫번째, Gradle 버전 업
Gradle distribution 을 Wrapper로 지정한 상태라면 gradle 버전 변경을 위해 gradle/wrapper/gradle-wrapper.properties를 수정해주면 된다.
스택오버플로우 답변 내용을 보면 Gradle 7.6대 버전이 '멀티 릴리즈 jar 파일을 처리하지 못하는 버그' 라고 한다. 질문자는 Jackson 관련 jar를 만들 때 일어난 오류였는데, Jackson이 바로 여러가지 자바 버전으로 만들어진 멀티릴리즈 라이브러리라고 한다. 나는 spring-core-6.2.1.jar 관련해서 일어났는데 스부 3이니까 일단 자바 17을 포함할 것이다.
Gradle 8.4부터는 해결된 문제라고 해서 8.4로 바꿔줬다.
*참고: Java & Gradle 버전 호환도 검토 필요!
◆ 검토사항 두번째, Gradle 버전 문제를 없앴는데 또 뜬다면?
로그상 경로에서 (예) C:\Users\{user}\.gradle\caches\jars-9\jars-9.lock 을 삭제한다.
참고: 맨첨에 rm -rf C:\Users\{user}\.gradle\caches\ 했는데 새로 lock file이 생기는지 문제를 해결하지 못했음
이거 하고서야 빌드 성공했다.
✔️ 완료
❗패키지명의 javax를 jakarta로 변경
javax 패키지가 jakarta 패키지로 변경되었다. (Java EE가 Eclipse Foundation으로 이관되며 상표권 문제로)수동으로 변경해줘도 되고,
IntellJ 기능을 사용해도 편하다.
* Ultimate 사용하는 경우, Refactor > Migrate Packages and Classes > Java EE to Jakarta EE > Scope > 패키지 변경할거라고 하면 됨 > Run 누르면 Do Refactor 할 수 있도록 작업창이 활성화된다.
주의점은 패키지명의 javax를 모두 jakarta로 바꾸면 문제다. Java EE가 Jakarta EE로 넘어간거지, 기본 플랫폼인 Java SE는 javax 그대로 사용한다.
- Java SE 예시: java.lang, java.util, java.io, java.net, javax.sql, javax.crypto, javax.net.ssl 등
- Java EE 예시: jakarta.transaction, jakarta.persistence, jakarta.annotation, jakarta.servlet 등
✔️ 완료
❗ Spring cloud 버전업
spring cloud를 사용중이라면 검토가 필수다. 버전이 안 맞으면 아래 예외를 볼 수 있을 것임.
Caused by: java.lang.IllegalArgumentException: Log types cannot be injected, please use DeferredLogFactory
at org.springframework.boot.context.config.ConfigDataLocationResolvers.lambda$new$0(ConfigDataLocationResolvers.java:64)
at org.springframework.core.io.support.SpringFactoriesLoader$ArgumentResolver.lambda$ofSupplied$3(SpringFactoriesLoader.java:585)
springboot 3.2.x는 spring cloud 2023.0.x 을 사용한다.
2024.0.4가 24.11.26 업데이트라서 너무 최근인 것 같아
2024.0.3은 24.07.10 이길래 얘로 선택했다.
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
✔️ 완료
❗MyBatis 호환
@Mapper 붙은 Dao 가 빈으로 주입되지 않는 현상이 발생!
***************************
APPLICATION FAILED TO START
***************************
Description:
Field sttDao in com.xx.xx.aa.AaaAaaa required a bean of type 'com.xx.xx.xx.XxxDao' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.xx.xx.xx.XxxDao' in your configuration.
- 스캔 패키지를 명시하면 빈 등록이 잘 된다는 걸 보니까 @Mapper가 빈 대상인 것은 명확하다. (참고)
- 빈 등록을 제대로 못하는 이유는 springboot & MyBatis 버전 호환성이다.
기존: implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
변경: implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
참고: 내가 일단 임의로 3.0.0으로 바꿔봤다가 BeanDefinitionStoreException을 만났다.
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'XXxxDao' defined in file [~~~]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String
이 문제는 3.0.3 으로 하면 해결된다고 한다. (참고)
✔️ 완료
서버의 핵심 기능은 잘 돌아간다.
우선 이렇게 일단락된 걸로!
이제 할 작업은 단위테스트를 만들어두는 것이다.
원래도 필요성을 잔뜩 느끼고 있었는데,
마이그레이션 하고나니 지금 당장 전/후를 테스트 쫙 돌려볼 수 없다는 게 아쉽다.
테스트 공부좀 해야지이이