spring-boot 3.1 to 3.2 업그레이드 마이그레이션

spring-boot 3.1.10 -> 3.2.5 로 마이그레이션 하는 시나리오입니다.

출처: https://spring.io/projects/spring-boot#support

보시다시피 spring-boot 3.1.x는 오픈소스 EoL이 2024-05-18입니다.
(글을 발행한 시점이 2024-04-20 이어서 오늘 날짜로 빗금이 있네요)
spring-boot 오픈소스 버전 EoL 주기가 짧기 때문에 미리 미리 버전업을 해야할 것 같습니다. 만약 취약점이 발견되는 경우 치명적인 문제가 생길 수 있기 때문입니다.

공식 업그레이드 가이드

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes#upgrading-from-spring-boot-31

다들 공식 업그레이드 가이드를 정독하시길 바랍니다.

Gradle

build.gradle.kts

plugins {
    java
    groovy
    idea
    checkstyle
    jacoco
    id("org.springframework.boot") version "3.2.5"           // Modified
    id("io.spring.dependency-management") version "1.1.4"    // Modified
    id("com.google.cloud.tools.jib") version "3.4.1"
    id("org.sonarqube") version "4.4.1.3373"
}
...
// Properties
extra["springCloudVersion"] = "2023.0.1"                    // Modifyed
Kotlin

Compile Error

1. ErrorAttributes.ERROR_ATTRIBUTE 이 존재하지 않음

    @Nullable
    @Override
    public Throwable getError(WebRequest webRequest) {
        Throwable exception = getAttribute(webRequest, ERROR_INTERNAL_ATTRIBUTE);
        if (exception == null) {
            exception = getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION);
        }
        if (exception == null) {
            return null;
        }
        // webRequest.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, exception, RequestAttributes.SCOPE_REQUEST);    // 컴파일 오류가 발생해서 제거: 필요하지 않음.
        return exception;
    }
Kotlin

관련해서 spring-boot 에 등록된 이슈를 살펴보면 기존 레거시 코드 찌꺼기여서 제거 요망.

Ref: https://github.com/spring-projects/spring-boot/issues/38907

Deprecated

1. MethodArgumentNotValidException#resolveErrorMessages

	/**
	 * Resolve global and field errors to messages with the given
	 * {@link MessageSource} and {@link Locale}.
	 * @return a Map with errors as keys and resolved messages as values
	 * @since 6.0.3
	 * @deprecated in favor of using {@link BindErrorUtils} and
	 * {@link #getAllErrors()}, to be removed in 6.2
	 */
	@Deprecated(since = "6.1", forRemoval = true)
	public Map<ObjectError, String> resolveErrorMessages(MessageSource messageSource, Locale locale) {
		return BindErrorUtils.resolve(getAllErrors(), messageSource, locale);
	}
Java

BindErrorUtilsgetAllErrors() 가 spirng-web-mvc 6.2 에서 제거될 예정이라고 함.
보통 Deprecated 에는 대안을 지정해주는데, 여기에서는 그런 것도 없음.

private List<FieldErrorView> getFieldErrorViews(MethodArgumentNotValidException ex, Locale locale) {
    Map<ObjectError, String> messageMap = 
            // ex.resolveErrorMessages(messageSource, locale); AS-IS
            BindErrorUtils.resolve(ex.getAllErrors(), messageSource, locale); // TO-BE
    return ex.getFieldErrors().stream()
             .map(f -> new FieldErrorView(f, messageMap.get(f)))
             .toList();
}
Java

우선 위처럼 변경했는데, 효과가 있을지….

Spring Initialization Error

1. java.lang.IllegalStateException: @TransactionalEventListener method must not be annotated with @Transactional unless when declared as REQUIRES_NEW or NOT_SUPPORTED

이것을 Spring Initialization 시 검증해주는 것을 좋다고 봄.
@TransactionalEventListener 를 사용하는 경우 phaseAFTER_COMMIT 인 경우에는 새로운 트랜잭션이 필요하기 때문에 꼭 REQUIRES_NEW 로 트랜잭션이 생성되게 선언해주어야함.
반대로 phaseBEFORE_COMMIT 인 경우에는 어짜피 트랜잭션 범위 안에 있으니 @Transactional 을 제거하도록 하고 있음.(굳이 유지해도 될 것 같은데, 제거하게 예외가 발생됨.)

    @TransactionalEventListener(value = OpenChattingProfileSaved.class, phase = TransactionPhase.BEFORE_COMMIT)
    // @Transactional 제거 요망: BEFORE_COMMIT 이기 때문에 존재하는 트랜잭션 내에서 실행하는 불필요한 @Transactional 애노테이션이 있어서 예외가 발생하고 있었음.
    public void subscribeOpenChattingProfileSaved(OpenChattingProfileSaved event) {
        KakaoId kakaoId = event.getKakaoId();
        agree(Kind.OPEN_CHATTING_PERSONAL_INFO, kakaoId);
    }
Java

Ref: https://bin-repository.tistory.com/167

Test

1. 400 BadRequest 기본 메시지 변경

  • AS-IS: “title: '공백일 수 없습니다'
  • TO-BE: “공백일 수 없습니다.”
 .andExpect(status().isBadRequest())

 .andExpect(jsonPath("$.err").value("요청 본문이 유효하지 않습니다. errors를 참고하세요."))
 .andExpect(jsonPath("$.errors.length()").value(6))
 .andExpect(jsonPath("$.errors[?(@.field == 'title')].message")
              // 변경 전
              //.value("title: '공백일 수 없습니다'"))
              // 변경 후
              .value("title: '공백일 수 없습니다'"))
Java

Leave a Comment