결론으로 바로 가기
먼저 이 문제는 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스'라는 책을 실습하는 과정에서 마주친 상황이다.
책은 몇 년 전에 지어진 기준이라 자바나 스프링부트의 버전이 낮게 되어있다. 그렇지만 그 당시 버전보다는 지금 상황에서 안정된 최신 버전으로 변경사항은 고치며 해보고 싶어 최신버전으로 진행했다.
- Spring Boot : 3.1.2
- Java : 17
- Gradle : 8.2.1 (Gradle은 신경 못 썼지만 이거 때문에 문제가 있었다..)
문제 상황
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
처음에는 저렇게 롬복 의존성 라이브러리를 추가해 주었다.
@Getter나 @Setter나 롬복에서 지원하는 어노테이션들은 잘 사용도 되고 자동완성도 됐다.
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
private final String name;
private final int amount;
}
책에서 나온 대로 실습하니 우선 노란 줄로 "Class can be a record"라며 JDK16에서 정식 스펙으로 포함된 record를 사용하라고 권장했다. 처음에는 넘어갔다.
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class HelloResponseDtoTest {
@Test
public void 롬복_기능_테스트() {
//given
String name = "test";
int amount = 1000;
//when
HelloResponseDto dto = new HelloResponseDto(name, amount);
//then
assertThat(dto.getName()).isEqualTo(name);
assertThat(dto.getAmount()).isEqualTo(amount);
}
}
그런데 테스트 코드를 돌려보니 오류가 났다. error는 "variable name not initialized in the default constructor" 그래서 record를 사용하면 될까 하고 바로 사용해 봤다. (record는 코틀린의 data class와 비슷한 느낌을 받았다.)
public record HelloResponseDto(String name, int amount) {
}
record를 사용하면 생성자를 작성하지 않아도 되고 toString, equals, hashCode 메서드에 대한 구현을 자동으로 제공한다고 하여 롬복 어노테이션들은 지워줬다.
@Test
public void 롬복_기능_테스트() {
//given
String name = "test";
int amount = 1000;
//when
HelloResponseDto dto = new HelloResponseDto(name, amount);
//then
assertThat(dto.name()).isEqualTo(name);
assertThat(dto.amount()).isEqualTo(amount);
}
dto가 변하면서 테스트 로직도 조금 변경됐는데 `getName()`이 아닌 `name()`으로 롬복을 사용하지 않은 모습이다.
이렇게 해서 테스트는 성공했지만 지금 보면 근본적으로 해결한 것은 아니었다. 결국 두 번째 문제가 발생한다.
문제 상황 2
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@Entity
public class Posts {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 500, nullable = false)
private String title;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
private String author;
@Builder
public Posts(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
}
이렇게 생성자 주입을 할 때 롬복의 builder 패턴을 사용해 보는 걸 권장하셨다.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@SpringBootTest
class PostsRepositoryTest {
@Autowired
PostsRepository postsRepository;
@AfterEach
public void cleanup() {
postsRepository.deleteAll();
}
@Test
public void 게시글저장_불러오기() {
//given
String title = "테스트 게시글";
String content = "테스트 본문";
String author = "test";
postsRepository.save(Posts.builder()
.title(title)
.content(content)
.author(author)
.build());
//when
List<Posts> postsList = postsRepository.findAll();
//then
Posts posts = postsList.get(0);
assertThat(posts.getTitle()).isEqualTo(title);
assertThat(posts.getContent()).isEqualTo(content);
assertThat(posts.getAuthor()).isEqualTo(author);
}
}
그런데 계속 테스트를 하는 과정에서
error: cannot find symbol
postsRepository.save(Posts.builder()
^
symbol: method builder()
location: class Posts
이런 에러가 발생했다. 계속 롬복을 인식을 못하는 것이었다. 그래서 스프링부트버전과 자바버전도 다운시켜 보고 다 해봤는데 해결을 하지 못했다. 결국 문제는 Gradle의 버전이었다..
결론
Gradle의 버전이 올라가면서 Lombok 의존성을 추가하는 방법이 바뀌었다는 것이다!
Gradle 5.x 미만
dependencies {
implementation 'org.projectlombok:lombok'
}
Gradle 5.x 이상
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
참고
책은 JUnit4를 사용하고 있는데 JUnit5로 넘어오면서 @RunWith는 @ExtendWith로 변환하게 되었다.
@RunWith(SpringRunner.class) ➜ @ExtendWith(SpringExtension.class)
언제나 잘못된 설명이나 부족한 부분에 대한 피드백은 환영입니다🤍