문제 발생

레디스 사용도 끝내고 기분 좋게 깃헙에 PR을 날리는 중 발생한 에러이다.

PR을 올리면 깃헙액션이 동작하여 빌드 테스트를 하도록 설정해두었다.

yml 파일 변경이 있어서 그것도 업데이트 해주었고 빌드는 문제없이 진행될거라고 생각했다.

로컬에서는 빌드와 테스트 모두 잘 됐기 때문에 문제될 부분은 없다고 생각했다.

하지만 아래와 같은 에러가 발생했다.


문제 해결

결론부터 말하자면 깃헙 액션에서 빌드하는 우분투 환경에 레디스가 설치되지 않아서 생긴 문제이다.

에러 메시지를 확인하면 아래와 같이 제법 명시적으로 어떤 문제인지 알려준다.
org.springframework.data.redis.RedisConnectionFailureException at AuthControllerTest.java:164

 

따라서 깃헙액션 워크플로우에 아래처럼 레디스 환경을 조성해주면 된다.

또한 레디스를 사용한다면 EC2에서도 레디스를 설치해둬야 쓸 수 있다.

name: Java CI with Gradle

on:
  pull_request:
    branches: [ "develop" ]

jobs:
  build:

    runs-on: ubuntu-latest

    # Github Actions 환경에서 레디스 연결
    services:
      redis:
        image: redis:latest
        ports:
          - 6379:6379

    ... 이후는 자바 빌드 확인 코드

 

참고자료

 

[OpenRoadmaps] Github Actions를 통한 CI/CD 구축하기

테스트 코드와 AWS를 통한 배포환경이 준비되었으니 이제 CI/CD를 구축할 수 있다. CI/CD의 간단한 정의는 다음과 같다. CI (Continuous Integration): 레포지트로의 코드가 변경될 때, 자동으로 빌드와 테스

devjaewoo.tistory.com

 

문제 발생

기존에 gradle-groovy 방식을 주로 썼는데 이번에 gradle-kotlin 방식을 써보려고 했다.

또한 스프링부트 3.0 이상을 쓰면서 QueryDSL을 써본적이 없었기에 겸사겸사 build.gradle.kts로 세팅하고 있었다.

의존성 추가 코드는 아래와 같다.

// build.gradle.kts
...
dependencies {
    // QueryDSL
    implementation("com.querydsl:querydsl-jpa")
    implementation ("com.querydsl:querydsl-core")
    implementation ("com.querydsl:querydsl-collections")
    annotationProcessor ("com.querydsl:querydsl-apt:5.0.0")
...
}

QueryDslConfig

또한 아래처럼 JPAQueryFactory의 생성자 파라미터로에 엔티티 매니저를 넣어줘야하는데

생성자에 엔티티 매니저를 넣으려 하니 컴파일 에러가 발생했다.


문제 해결

결론부터 말하자면 의존성 추가 부분을 아래처럼 버전을 명시해주면 해결된다.

본인은 기존 의존성 추가 코드를 안지우고 아래에 코드만 추가해서 의미없는 시간을 너무 허비했다.

꼭 기존 QueryDSL의존성 추가 코드는 지우고 쓰자.

// build.gradle.kts
...
val queryDslVersion = "5.0.0" // QueryDSL Version Setting

dependencies {
    // QueryDSL
    implementation("com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta")
    implementation ("com.querydsl:querydsl-core")
    implementation ("com.querydsl:querydsl-collections")
    annotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}:jakarta")
    annotationProcessor("jakarta.annotation:jakarta.annotation-api") // java.lang.NoClassDefFoundError (javax.annotation.Entity) 방지
    annotationProcessor("jakarta.persistence:jakarta.persistence-api") // java.lang.NoClassDefFoundError (javax.annotation.Entity) 방지
...
}

 

기존의 의존성을 추가 코드는 아래처럼 그림 JPAQueryFactory 구현체가 javax에서 엔티티 매니저를 탐색한다.

위처럼 세팅하고 나면 아래와 같이 엔티티 매니저를 jakarta에서 찾기 때문에 오류가 해결된다.

변경 전

변경 후

 

혹시 필요한 사람이 있을지 모르니 build.gradle.kts 전문은 아래와 같다.

// build.gradle.kts
plugins {
	java
	id("org.springframework.boot") version "3.2.4"
	id("io.spring.dependency-management") version "1.1.4"
}

group = "com.commerce"
version = "0.0.1-SNAPSHOT"
val queryDslVersion = "5.0.0" // QueryDSL Version Setting

java {
	sourceCompatibility = JavaVersion.VERSION_17
}

configurations {
	compileOnly {
		extendsFrom(configurations.annotationProcessor.get())
	}
}

repositories {
	mavenCentral()
	maven(url="https://jitpack.io")
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-actuator")
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	implementation("org.springframework.boot:spring-boot-starter-data-redis")
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("org.springframework.boot:spring-boot-starter-validation")

	// 아임포트
	implementation("com.github.iamport:iamport-rest-client-java:0.2.23")

	// 웹소켓
	implementation("org.springframework.boot:spring-boot-starter-websocket")

	// JWT
	implementation("io.jsonwebtoken:jjwt-api:0.12.1")
	runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.1")
	runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.1")
	implementation("org.springframework.security:spring-security-crypto")
	implementation("org.bouncycastle:bcprov-jdk15on:1.70")

	// QueryDSL
	implementation("com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta")
	implementation ("com.querydsl:querydsl-core")
	implementation ("com.querydsl:querydsl-collections")
	annotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}:jakarta")
	annotationProcessor("jakarta.annotation:jakarta.annotation-api")
	annotationProcessor("jakarta.persistence:jakarta.persistence-api")

	annotationProcessor("org.projectlombok:lombok")
	compileOnly("org.projectlombok:lombok")
	runtimeOnly("com.h2database:h2")
	runtimeOnly("com.mysql:mysql-connector-j")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
	useJUnitPlatform()
}

val querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile

sourceSets.getByName("main") {
	java.srcDir(querydslDir)
}

tasks.withType<JavaCompile> {
	options.generatedSourceOutputDirectory.set(file(querydslDir))
}

// clean 이후에 querydsl 폴더를 지움
tasks.named("clean") {
	doLast {
		file(querydslDir).delete()
	}
}

문제 발견

이전 글에서 @ConfigurationProperties를 써서 yml 파일에서 환경 변수 값을 불러왔다.

간만에 백엔드 프로젝트를 만들면서 적용을 하니 아래와 같은 에러 메시지와 함께 애플리케이션을 실행할 수 없었다.


문제 해결

해결을 위해 검색을 해본 결과 인프런의 영한님이 답변을 남겨주신게 있었다. 

문제는 본인이 주로 쓰던 빌드 방식인 인텔리제이 IDEA 빌드 방식에 있었다.

스프링을 처음 공부하면서 이렇게 하면 속도 측면에서 이득을 볼 수 있다고 해서 쭉 써오던 방식이었다.

 

전에 썼던 버전은 스프링부트 2점대 버전이라서 문제 없이 동작했었다.

하지만 스프링부트 3.2버전부터는 매개변수 이름 추론 방식이 바껴서 설정을 변경해야 했다.

링크를 확인하면 몇 가지 방법이 있지만 가장 간단한건 아래처럼 빌드 방식을 gradle로 바꾸는것이다.

 

이렇게 바꾸고 실행해주면 문제 없이 동작한다.

 

출처)

 

현재 SpringBoot 3.2.1 부터 @ConfigurationProperties 를 사용한 설정값 바인딩에 이슈가 있는것 같습니다. -

안녕하세요! 질문 내용은 제목과 같습니다. 스프링부트 3.2.1 부터 @ConfigurationProperties 가 동작하지 않는것 같습니다. 확실하지 않지만 스텍오버플로우에도 비슷한 이슈가 올라온 상태인것 같습니

www.inflearn.com

 

문제 발

이전 글에서 @ConfigurationProperties 과 @ConstructorBinding 를 이용하여 yml 파일에서 환경변수를 불러왔었다.

동일한 방식으로 적용하려 하니 아래와 같이 빨간줄이 뜨면서 문제가 발생했다.


문제 해결

스프링부트 3.0 이상 버전으로 오면서 바뀐 부분 때문에 발생한 에러였다.

저 글을 쓸 당시 불변성을 보장하기 위해 생성자로 바인딩하기 위해 해당 애노테이션을 사용했었다.

하지만 스프링부트 3.0 이상에서는 @ConfigurationProperties 애노테이션을 사용할 때

생성자로 바인딩하기 위해 @ConstructorBinding 을 표기할 필요가 없어졌다.

따라서 Configuration 클래스에 여러 생성자가 있는 경우가 아니면 제거해야한다.

 

위 처럼 @ConstructorBinding 을 제거하고 실행해도 문제 없이 동작한다.

 

출처)

 

[SpringBoot 3.0] SpringBoot 3.0 버전 업그레이드 방법

SpringBoot 3.0 1년이 넘는 기간동안 151명의 개발자들이 5700자 이상의 코드 커밋을 통해 마침내 스프링부트 3.0이 릴리즈 되었습니다. 스프링부트 2.0이 릴리즈 된 이후 4년 반만에 릴리즈 된 스프링

mein-figur.tistory.com

 

문제 발견

아래와 같이 로그인/회원가입 구현 중 JWT를 발급하기 위해 시크릿 키를 만들던 도중 발생하였다.

@RequiredArgsConstructor
@ConfigurationProperties(prefix = "midcon")
public class AppConfig {

    private final String jwtKey;

    public SecretKey getSecretKey() {
        byte[] byteJwtKey = Decoders.BASE64.decode(this.jwtKey);
        return Keys.hmacShaKeyFor(byteJwtKey);
    }
}

 

분명 이 글을 쓰던 시점에서는 잘 됐으나 시크릿 키를 만드는 로직의 위치만 옮겼을 뿐인데 오류가 발생하였다.


문제 해결

결론부터 말하자면 버전문제였다.

호돌맨님 인강을 보며 만들때는 문제가 없었지만 지금은 바뀐 조건이 많아서 뭐가 문제인지 확인하기 어렵다.

대충 생각 나는 원인은 아래 세 개 정도이다.

  • 로직 위치를 바꾼게 문제인지
  • 스프링부트 버전을 2.7대에서 3.2대로 올린게 문제인지
  • 자바 버전을 11에서 21로 올린게 문제인지

해결 방법은 아래처럼 0.12.1 버전으로 올리니 해결되었다.

stackoverflow 글참고하였다.

 

문제 발견

배포를 위해 AWS EC2 인스턴스를 생성하고 스프링부트를 띄우려고 로컬에서 EC2로 빌드 파일을 전송하려던 중

아래 사진과 같은 에러를 만났다.

사실 인텔리제이에서 애플리케이션을 실행할때도 봤던 문제긴 하지만 그때는 인텔리제이 설정으로 해결이 되었다.

하지만 이런 CLI에서는 인텔리제이 설정을 할 수 없어서 다른 방법을 찾아야했다.


문제 해결

에러 메시지를 읽어보면 자바 버전 문제라는것을 알 수 있었지만 해결 방법을 몰랐다.

결과부터 말하자면 시스템 환경 변수 설정에 classpath가 최신화 되지 않은 문제였다.

시도 해본 방법은 아래와 같다.

1. build.gradle 확인

에러 메시지에도 나오는 단어기도 하고 검색 해보면 sourceCompatibility를 확인 변경해주래서 확인해보았다.

하지만 build.gradle 파일에는 아래처럼 제대로 잘 적혀있었다.

2. 로컬의 자바 버전 확인

로컬에 깔린 자바 버전 문제일까 싶어서 확인해보니 자바 17버전으로 나왔다.

3. 시스템 환경 변수 확인

최종적으로  시스템 환경변수 설정 문제였다.

아득한 옛날 뭣도 모르고 인터넷 검색해서 자바 환경 변수 설정을 했던 적이 있었다.

당시엔 자바 11로 설치하고 환경 변수 설정을 했었다.

변경 전

변경 후

 

시스템 환경 변수 설정에서 JAVA_HOME을 원하는 자바 버전으로 변경한 후

CLI(본인은 Git Bash)를 다시 실행하고 빌드를 해보면 제대로 잘 동작한다.

문제 발견

호돌맨님의 강의 중 검증 파트에서 발생한 오류이다.

 

PostController

 

PostCreate

 

PostControllerTest

글 작성 요청 시 제목, 내용 모두 null 인 케이스 테스트이다.

 

 

이 테스트에서 실패하며 아래와 같은 로그가 출력된다.

 

 

응답 바디에도 아무것도 오지 않는다.

 

로그를 쫓아서 ErrorResponse 에 브레이크 포인트를 찍어보았다.

 

ErrorResponse

여기서 validation 이 null로 잡힌다.

 

 

PostControllerAdvice

처음 ExceptionHandler 에서 생성자에 validation 을 매개변수로 받지 않았을땐 오류가 발생하지 않았는데

validation 을 매개변수로 받고 나서 오류가 생겼다.

 

 

@Builder 를 이용한 생성자가 ErrorResponse 의 validation 초기화를 무시하고 null 값으로 넣어버리는 현상이라고 생각된다.

 

 

그래서 따로 빌더 안붙인 validation 을 받지 않는 생성자를 만드니 잘 돌아간다.

 

 


문제 해결

강의 따라서 코드 치는 사이에 잠깐 놓쳐서 발생했던 문제였다.

하지만 원인은 예상했던대로 @Builder 를 이용한 생성자가 validation 의 초기화를 무시하고 null 로 넣은것이었다.

기존 코드에서는 아래처럼 validation 을 초기화를 해줘도 빌더 생성 시 validation 이 null 이면 null 값이 들어가게된다.

변경 전

ErrorResponse

public class ErrorResponse {

    // ...
    private Map<String, String> validation = new HashMap<>();
}

 

따라서 아래처럼 validation 이 null 인지 확인하는 로직을 작성해야 한다.

 

변경 후

ErrorResponse

@Getter
public class ErrorResponse {

    private final String code;
    private final String message;
    private final Map<String, String> validation;

    @Builder
    public ErrorResponse(String code, String message, Map<String, String> validation) {
        this.code = code;
        this.message = message;
        this.validation = validation != null ? validation : new HashMap<>();
    }

    public void addValidation(String fieldName, String errorMessage) {
        this.validation.put(fieldName, errorMessage);
    }
}

 

위처럼 ErrorResponse 를 바꿔주면 정상적으로 동작한다.

문제 발견

기존 QueryDsl 없이 NativeQuery 로 작성한 리포지토리 메서드를 리팩토링 하다가 발생한 에러이다.

@SpringBootTest 는 스프링을 띄워서 의존성 주입을 다 받기 때문에 상관없지만

리포지토리 테스트는 대부분 @DataJpaTest 를 사용하기 때문에 QueryDsl 관련 빈을 주입받지 못해 발생한 에러였다.

 


문제 해결

아래 두가지를 순서대로 따라하니 문제가 해결되었다.

 

1. QueryDslConfig 작성

QueryDslConfig

@Configuration
public class QueryDslConfig {

    @PersistenceContext
    public EntityManager em;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(em);
    }
}

 

2. Test 에 @Import 추가

Test

@DataJpaTest
@Import(QueryDslConfig.class)
class ProductRepositoryTest {
...
}

 

 

참고자료)

 

No qualifying bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' available: expected at least 1 bean which qualifies as autowi

JPA Config 파일 추가test에 @Import 추가

velog.io

 

 

@DataJpaTest - 인프런 | 질문 & 답변

QueryDsl 강의 잘듣고 있습니다!QueryDsl을 사용하면 @DataJpaTest를 사용하지 못하나요?JPAQueryFactory에 대해서 NoSuchBeanDefinitionException이 발생하는데 따로 주입하는 방법을 모르겠습니다ㅠㅠ - 질문 & 답변

www.inflearn.com

 

+ Recent posts