개인과제 -TDD

ChoRong0824·2025년 2월 25일
0

Web

목록 보기
34/51
A problem occurred configuring root project 'expert'.
> Could not resolve all files for configuration ':classpath'.
   > Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.3.3.
     Required by:
         project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.3.3
      > No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.3.3 was found. The consumer was configured to find a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.4' but:
          - Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.3.3 declares a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares an API of a component and the consumer needed a runtime of a component
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.4')
          - Variant 'javadocElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.3.3 declares a runtime of a component, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 17)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.4')
          - Variant 'modernGradleRuntimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.3.3 declares a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, as well as attribute 'org.gradle.plugin.api-version' with value '8.7' and the consumer needed a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.4'
          - Variant 'runtimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.3.3 declares a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.5' and the consumer needed a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.4'
          - Variant 'sourcesElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.3.3 declares a runtime of a component, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 17)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.4')

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

깃 클론하여, 기술 과제를 하려고 하였더니 해당 에러가 발생하였다.
자바 17버전으로 바꿧으나 해당 에러가 발생한 것이다.
이는, Gradle 버전이 낮음을 의미한다.
즉, spring-boot-gradle-plugin이 Java 17을 지원하지만, 현재 프로젝트에서는 Gradle 7.4 환경에서 플러그인을 해석하려다 충돌이 발생했다는 것입니다.
따라서, Spring Boot 3.3.3은 Gradle 8.3 이상을 요구하니, 이에 맞춰 Gradle 버전 업그레이드 해줍니다.

일단, gradle가 설치되었는지부터 확인해봅시다.
gradle -v 명령어로 확인.
설치되지 않았다면, 아래와 같이 진행하면 됩니다.

  1. Homebrew 최신 버전 업데이트
    brew update
  2. gradle 설치
    brew install gradle
  3. 설치 확인
    gradle -v

만약 정상적으로 설치되었다면, 다음과 같이 버전 정보가 나옵니다.

이제 gradle을 설치 했으니, 프로젝트에서 gradlew가 없거나 손상된 경우 gralde Wrapper도 설정해야 합니다.

  • Spring Boot 3.3.3이 Gradle 8.3 이상을 요구하므로, 8.4를 사용하는 것이 안전합니다.
  1. Gradle Wrapper 생성 (프로젝트 폴더에서 실행)

    • gradle wrapper --gradle-version 8.4
  2. Wrapper 실행 권한 부여

    • Mac에서는 ./gradlew 실행 파일이 기본적으로 실행 권한이 없을 수 있으므로, 명령어 실행
      chmod +x ./gradlew
  3. Gradle Wrapper 버전 확인

  • ./gradlew --version
    - 정상적으로 실행되었는지도 이때 확인하면 됌.


이제 추가적으로 자바 버전만 확인하면 됩니다.
java -version 명령어로 확인.
-> 만약 23.0.2 뭐 이런 식으로 되어있으면, brew 등 그냥 강제로 바꿀 수도 있고, 아니면 편하게 인텔리제이에서 수정하면 됩니다.
저는 인텔리제이에서 자바 버전을 17로 픽스했습니다.

그러면 이렇게 정상적으로 바뀌고 정상 실행되는 것을 확인할 수 있습니다.


/Users/mun/Library/Java/JavaVirtualMachines/corretto-17.0.14/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=61428:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/mun/Desktop/2025/TestCodeAssignment/build/classes/java/main:/Users/mun/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.34/ec547ef414ab1d2c040118fb9c1c265ada63af14/lombok-1.18.34.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-api/0.11.5/f742940045619d06383e7df37b21ac422b476cf1/jjwt-api-0.11.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-validation/3.3.3/9a585e4c45168f6c37812ab47df3abf869c9c289/spring-boot-starter-validation-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-data-jpa/3.3.3/7eeb176d6130f7aa83405fe2985fe7cadc11ebc5/spring-boot-starter-data-jpa-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/3.3.3/f19325fefd33dc920c0f68547a798ed731a8c70c/spring-boot-starter-web-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/at.favre.lib/bcrypt/0.10.2/430be75a265cb3b5998807f88f1c40fc750bc63c/bcrypt-0.10.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/3.3.3/174d6cb1c59036a01756d6bf4d8945325f94155f/spring-boot-starter-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/10.1.28/3220f687c5ffc8650559f08ecac5b149838627b3/tomcat-embed-el-10.1.28.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.hibernate.validator/hibernate-validator/8.0.1.Final/e49e116b3d3928060599b176b3538bb848718e95/hibernate-validator-8.0.1.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-aop/3.3.3/f64c22484c1ab36ae70abc752509ec4c7b1abcfc/spring-boot-starter-aop-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-jdbc/3.3.3/c97a56c12c79d1ce419a306ddee4ccb89b7d3728/spring-boot-starter-jdbc-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.hibernate.orm/hibernate-core/6.5.2.Final/e9e0cc47f6cd2b2553968aee66bd9e55e7485221/hibernate-core-6.5.2.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-jpa/3.3.3/9b89dbd19eee24c4dc4df12564efc603c614aa91/spring-data-jpa-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aspects/6.1.12/b72435b5813755a894531e2cf1565eff1b313574/spring-aspects-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-json/3.3.3/3dde3b6089f772a77763020cc97a219307a8f39f/spring-boot-starter-json-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-tomcat/3.3.3/8ca660b0ecba2f0ff8ce27603fe89953dc4ec18d/spring-boot-starter-tomcat-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webmvc/6.1.12/86bb0c203d68adb4b2675765f5a372f4c5254b2d/spring-webmvc-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/6.1.12/b60e69b3ec87f8e001537a0fa7e3e2732dfafa7e/spring-web-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/at.favre.lib/bytes/1.5.0/9617977854566948d767e6da8ec343b1fa107c48/bytes-1.5.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/3.3.3/4445f0bbf838c1e3aeb5a83bf2367fbd1013237f/spring-boot-autoconfigure-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/3.3.3/f34dba93611823e521800a47043267715184911b/spring-boot-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/3.3.3/43d07b2e6bbb628c7fc5df103d35ee2eb091522/spring-boot-starter-logging-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/2.1.1/48b9bda22b091b1f48b13af03fe36db3be6e1ae3/jakarta.annotation-api-2.1.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/6.1.12/5f4162fb645c4f72b57b21d721fce9f26edb9bb9/spring-core-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/2.2/3af797a25458550a16bf89acc8e4ab2b7f2bfce0/snakeyaml-2.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.validation/jakarta.validation-api/3.0.2/92b6631659ba35ca09e44874d3eb936edfeee532/jakarta.validation-api-3.0.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.jboss.logging/jboss-logging/3.5.3.Final/c88fc1d8a96d4c3491f55d4317458ccad53ca663/jboss-logging-3.5.3.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml/classmate/1.7.0/e98374da1f2143ac8e6e0a95036994bb19137a3/classmate-1.7.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/6.1.12/5fce5ff02455fad179fc0ea05435dfb79bbeaa66/spring-aop-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.aspectj/aspectjweaver/1.9.22.1/bca243d0af0db4758fbae45c5f4995cb5dabb612/aspectjweaver-1.9.22.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jdbc/6.1.12/feb177a07e109f633c3fb1892d22a2867d0b2284/spring-jdbc-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.zaxxer/HikariCP/5.1.0/8c96e36c14461fc436bb02b264b96ef3ca5dca8c/HikariCP-5.1.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.persistence/jakarta.persistence-api/3.1.0/66901fa1c373c6aff65c13791cc11da72060a8d6/jakarta.persistence-api-3.1.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.transaction/jakarta.transaction-api/2.0.1/51a520e3fae406abb84e2e1148e6746ce3f80a1a/jakarta.transaction-api-2.0.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/6.1.12/e69d476d1334935529a61f403749c6b8593e3272/spring-context-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-orm/6.1.12/9df98d9ebdfcf5359dbda945de515d8af2cabe89/spring-orm-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-commons/3.3.3/89ab77494e1e56b058001354265e381a0a2868e6/spring-data-commons-3.3.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-tx/6.1.12/c47885e494845584e08407b611031ea2bcef0ef7/spring-tx-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/6.1.12/f0344fa934f114a15f02c2b1a199189ec12b7eb5/spring-beans-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.antlr/antlr4-runtime/4.13.0/5a02e48521624faaf5ff4d99afc88b01686af655/antlr4-runtime-4.13.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.16/172931663a09a1fa515567af5fbef00897d3c04/slf4j-api-2.0.16.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.17.2/267b85e9ba2892a37be6d80aa9ca1438a0d8c210/jackson-datatype-jsr310-2.17.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.17.2/d27b9f95ccce98984c1ba58d61c5a9c072b1ad95/jackson-module-parameter-names-2.17.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.17.2/efd3dd0e1d0db8bc72abbe71c15e697bb83b4b45/jackson-datatype-jdk8-2.17.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.17.2/e6deb029e5901e027c129341fac39e515066b68c/jackson-databind-2.17.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.28/1a5ee88106795215e8a7b6cfea46e57cdfc96430/tomcat-embed-websocket-10.1.28.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/10.1.28/624f53789b7ba96bc3c8e871fb5eed4cab5a7c89/tomcat-embed-core-10.1.28.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/6.1.12/644bbc2e65eb40feb7284b8570d7a7eb8b1fe8df/spring-expression-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.micrometer/micrometer-observation/1.13.3/99840eb14ecf95d349f39123c867c2ca33808d/micrometer-observation-1.13.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.5.7/ad4ff8ca015737ade8e8a9de07de565c423e5b7a/logback-classic-1.5.7.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.23.1/425ad1eb8a39904d2830e907a324e956fb456520/log4j-to-slf4j-2.23.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/2.0.16/6d57da3e961daac65bcca0dd3def6cd11e48a24a/jul-to-slf4j-2.0.16.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/6.1.12/f46d6016fd1dd3801a0e6fcd64789ac468254944/spring-jcl-6.1.12.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.17.2/147b7b9412ffff24339f8aba080b292448e08698/jackson-annotations-2.17.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.17.2/969a35cb35c86512acbadcdbbbfb044c877db814/jackson-core-2.17.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.micrometer/micrometer-commons/1.13.3/47b6b093cd6ba80c9da8d973ea79c7ef69a878d5/micrometer-commons-1.13.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.5.7/ba0d0cbac3f21abd28faed3b4931d5b49cc5887e/logback-core-1.5.7.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.23.1/9c15c29c526d9c6783049c0a77722693c66706e1/log4j-api-2.23.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.h2database/h2/2.2.224/7bdade27d8cd197d9b5ce9dc251f41d2edc5f7ad/h2-2.2.224.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.mysql/mysql-connector-j/8.3.0/1cc7fa5d61f4bbc113531a4ba6d85d41cf3d57e1/mysql-connector-j-8.3.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-impl/0.11.5/40a599f0e8a8e4e0701596fbb15e67bfda64fdf0/jjwt-impl-0.11.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-jackson/0.11.5/3b83a06809e98a69402d7333dcf67df6f6ea4579/jjwt-jackson-0.11.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.hibernate.common/hibernate-commons-annotations/6.0.6.Final/77a5f94b56d49508e0ee334751db5b78e5ccd50c/hibernate-commons-annotations-6.0.6.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.smallrye/jandex/3.1.2/a6c1c89925c7df06242b03dddb353116ceb9584c/jandex-3.1.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy/1.14.19/4c0c637b8f47dc08f89240e6f59900011752c97b/byte-buddy-1.14.19.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/4.0.5/ca84c2a7169b5293e232b9d00d1e4e36d4c3914a/jaxb-runtime-4.0.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2/6cd5a999b834b63238005b7144136379dc36cad2/jakarta.xml.bind-api-4.0.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.inject/jakarta.inject-api/2.0.1/4c28afe1991a941d7702fe1362c365f0a8641d1e/jakarta.inject-api-2.0.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-core/4.0.5/7b4b11ea5542eea4ad55e1080b23be436795b3/jaxb-core-4.0.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.activation/jakarta.activation-api/2.1.3/fa165bd70cda600368eee31555222776a46b881f/jakarta.activation-api-2.1.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.eclipse.angus/angus-activation/2.0.2/41f1e0ddd157c856926ed149ab837d110955a9fc/angus-activation-2.0.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/txw2/4.0.5/f36a4ef12120a9bb06d766d6a0e54b144fd7ed98/txw2-4.0.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.sun.istack/istack-commons-runtime/4.1.2/18ec117c85f3ba0ac65409136afa8e42bc74e739/istack-commons-runtime-4.1.2.jar org.example.expert.ExpertApplication
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.3)

2025-02-25T11:41:13.915+09:00  INFO 99343 --- [           main] org.example.expert.ExpertApplication     : Starting ExpertApplication using Java 17.0.14 with PID 99343 (/Users/mun/Desktop/2025/TestCodeAssignment/build/classes/java/main started by mun in /Users/mun/Desktop/2025/TestCodeAssignment)
2025-02-25T11:41:13.919+09:00  INFO 99343 --- [           main] org.example.expert.ExpertApplication     : No active profile set, falling back to 1 default profile: "default"
2025-02-25T11:41:14.268+09:00  INFO 99343 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-02-25T11:41:14.310+09:00  INFO 99343 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 39 ms. Found 4 JPA repository interfaces.
2025-02-25T11:41:14.678+09:00  INFO 99343 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-02-25T11:41:14.688+09:00  INFO 99343 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-02-25T11:41:14.688+09:00  INFO 99343 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.28]
2025-02-25T11:41:14.716+09:00  INFO 99343 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-02-25T11:41:14.717+09:00  INFO 99343 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 761 ms
2025-02-25T11:41:14.721+09:00 ERROR 99343 --- [           main] o.s.b.web.embedded.tomcat.TomcatStarter  : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'filterConfig' defined in file [/Users/mun/Desktop/2025/TestCodeAssignment/build/classes/java/main/org/example/expert/config/FilterConfig.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jwtUtil': Injection of autowired dependencies failed
2025-02-25T11:41:14.731+09:00  INFO 99343 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2025-02-25T11:41:14.738+09:00  WARN 99343 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
2025-02-25T11:41:14.744+09:00  INFO 99343 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-02-25T11:41:14.751+09:00 ERROR 99343 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Unable to start web server
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:165) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:619) ~[spring-context-6.1.12.jar:6.1.12]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.example.expert.ExpertApplication.main(ExpertApplication.java:14) ~[main/:na]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
	at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:147) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:516) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:222) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:188) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162) ~[spring-boot-3.3.3.jar:3.3.3]
	... 8 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'filterConfig' defined in file [/Users/mun/Desktop/2025/TestCodeAssignment/build/classes/java/main/org/example/expert/config/FilterConfig.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jwtUtil': Injection of autowired dependencies failed
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:795) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:237) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1375) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1212) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:409) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1355) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1185) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:211) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:202) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:97) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:86) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:266) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:240) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52) ~[spring-boot-3.3.3.jar:3.3.3]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4414) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
	at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[na:na]
	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
	at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[na:na]
	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.StandardService.startInternal(StandardService.java:415) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:874) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.startup.Tomcat.start(Tomcat.java:437) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128) ~[spring-boot-3.3.3.jar:3.3.3]
	... 13 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtUtil': Injection of autowired dependencies failed
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:515) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1439) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:782) ~[spring-beans-6.1.12.jar:6.1.12]
	... 62 common frames omitted
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'jwt.secret.key' in value "${jwt.secret.key}"
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-6.1.12.jar:6.1.12]
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-6.1.12.jar:6.1.12]
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) ~[spring-core-6.1.12.jar:6.1.12]
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-6.1.12.jar:6.1.12]
	at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:200) ~[spring-context-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:964) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1374) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:785) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:768) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.1.12.jar:6.1.12]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509) ~[spring-beans-6.1.12.jar:6.1.12]
	... 74 common frames omitted


Process finished with exit code 1

1. UnsatisfiedDependencyException - jwtUtil 빈 생성 실패

Error creating bean with name 'filterConfig' defined in file [...] Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jwtUtil': Injection of autowired dependencies failed
  • jwtUtil이라는 빈을 생성하려고 하는데, 필요한 의존성을 주입할 수 없는 상황이 발생함.
  • 이 문제는 다음과 같은 이유로 인해 발생할 가능성이 높음a
  1. jwtUtil 클래스에서 환경 변수 값(@Value("${jwt.secret.key}"))을 가져올 때 실패함.
  2. application.properties 또는 application.yml 파일에서 jwt.secret.key가 정의되지 않음
spring.application.name=TestCodeAssignment

spring.datasource.url=jdbc:mysql://localhost:3306/expert
spring.datasource.username=root
spring.datasource.password=85947ads
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect


jwt.secret=2r81SMs1eJjT9fK2zLskd8f7eU7k3F6B9nN4vP0qC3rX8sY1aD4vG6pJ7lM9nR2x
jwt.expirationMs=86400000

jwtUtil이 @Component 또는 @Service로 등록되어 있는지 확인

  • 해당 클래스에 @Component, @Service, @Bean 등이 선언되어 있어야 Spring에서 빈으로 등록 가능함.

2. FilterConfig 클래스에서 빈 생성 오류

Error creating bean with name 'filterConfig' defined in file [...]
  • FilterConfig가 생성될 때 필요한 의존성 주입 실패
  • jwtUtil 관련된 문제일 수 있으니, 확인.

해결 방법

  • 필요한 의존성이 자동 주입되는지 확인
@Configuration
public class FilterConfig {
    private final JwtUtil jwtUtil;

    @Autowired
    public FilterConfig(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }
}

만약 JwtUtil이 정상적으로 빈으로 등록되지 않는다면, @Component 어노테이션을 추가해야 함.

3. Spring Data JPA 관련 문제

Bootstrapping Spring Data JPA repositories in DEFAULT mode.
Finished Spring Data repository scanning in 39 ms. Found 4 JPA repository interfaces.

원인

  • Spring Data JPA 리포지토리 인터페이스를 찾았으나, 올바르게 빈을 생성하지 못한 경우 발생.
  • 보통 다음과 같은 원인일 가능성이 있어서 확인.
  1. @Entity 또는 @Table 어노테이션이 잘못 사용되었거나 누락됨.
  2. @Repository가 잘못 정의됨.
  3. JPA 설정(application.properties)이 누락되었거나 올바르게 설정되지 않음.

JwtUtil 클래스에서 @Value("${jwt.secret}")가 아니라 @Value("${jwt.secret.key}")로 사용되고 있으므로, properties 수정.
jwt.secret.key=2r81SMs1eJjT9fK2zLskd8f7eU7k3F6B9nN4vP0qC3rX8sY1aD4vG6pJ7lM9nR2x

그리고 properties에
spring.jpa.hibernate.ddl-auto=create로 되어있는데,

  • create 옵션을 사용하면 애플리케이션 실행 시 기존 DB 스키마가 삭제되고 새로 생성됩니다.
  • 데이터가 삭제되지 않도록 유지하려면 update를 사용하는 것이 좋음.

그리고 지금 최신 버전이 아닌 MySQLDialect 최신 버전으로 변경해줍니다.
현재 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect 설정은 Deprecated 경고가 뜰 수 있으므로, 최신 Hibernate에서는 MySQL8Dialect를 사용하는 것이 좋음으로 수정해줍니다.
그냥 말 그대로 8만 사이에 넣어주면 되는 것입니다.
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

그럼 이제 이렇게 정상적으로 동작함을 확인할 수 있습니다.
이제 과제를 시작하겠습니다.


1. 코드 개선 퀴즈 - Early Return

조건에 맞지 않는 경우 즉시 리턴하여, 불필요한 로직의 실행을 방지하고 성능을 향상시킵니다

if (userRepository.existsByEmail(signupRequest.getEmail())) {
    throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}

해당 에러가 발생하는 상황일 때, passwordEncoder의 encode() 동작이 불필요하게 일어나지 않게 코드를 개선


퀴즈를 풀기 전에 해당 내용에 대해 빠삭하게 알고 해결하는 것이 좋다고 생각되어 필요한 개념을 학습하고 진행하도록 하겠습니다.

Early Return

함수에서 조건문을 만족할 때 일찍 반환하는 것을 말함.
근데 과연 Early Return은 좋은가?

Early Return의 장점

Early Return은 들여쓰기를 줄여 코드를 간결하고 읽기 쉽게 만들어 줍니다.

if someCondition {
    if someOtherCondition {
        if someOtherOtherCondition {
            // countlessly nested if statements
        }
    }
}

또한 유효하지 않은 경우를 먼저 처리하여(bouncer pattern) 함수의 "진짜" 본문에 집중할 수 있게 합니다.

func someFunc() error {
    if err := someValidation(); err != nil {
        return fmt.Errorf("some validation error: %s", err)
    }

    // do something
    return nil
}

Early Return의 단점

반면에, Early Return은 함수의 반환이 여러 곳으로 흩어지게 되어 함수의 복잡도를 높이고, 오히려 가독성을 떨어뜨릴 수 있습니다. 예를 들어 200줄이 넘는 함수에 여러 return문이 무작위로 존재한다면 읽기 쉬운 코드라고 할 수 없습니다.

if someCondition {
    return
}
// do something
if someOtherCondition {
    return
}
// do something
if someOtherOtherCondition {
    return
}
// do something
if someOtherOtherOtherCondition {
    return
}
// ...

Early Return은 들여쓰기를 줄여 코드를 간결하고 읽기 쉽게 만드는 좋은 방법입니다. 그러나 이 방법이 매번 적용될 수 있다는 의미는 아닙니다.

때때로 Early Return은 함수의 복잡도를 증가시키고 코드를 더 복잡하게 만들 수 있습니다. 그리고 리뷰어의 의견에서처럼, 간결한 코드에서는 오히려 if-else문이 더 명확할 수 있습니다.

따라서, 읽기 쉬운 코드를 작성하기 위해서는 상황에 맞게 적절한 패턴을 선택하는 것이 중요.

출처


패키지 package org.example.expert.domain.user.service;UserService 클래스에 있는 changePassword() 중 아래 코드 부분을 해당 API의 요청 DTO에서 처리할 수 있게 개선해주세요.

if (userChangePasswordRequest.getNewPassword().length() < 8 ||
        !userChangePasswordRequest.getNewPassword().matches(".*\\d.*") ||
        !userChangePasswordRequest.getNewPassword().matches(".*[A-Z].*")) {
    throw new InvalidRequestException("새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다.");
}

📌 Tip!
'org.springframework.boot:spring-boot-starter-validation' 라이브러리를 활용해주세요!

Validation 라이브러리 활용해서, 리팩토링 한다면?
그 전에 Validation에 대해 학습해야 합니다.
참고 1, 2, 3, 4, 5
이렇게 참고 자료를 쭉 읽으면 해당 파트의 리팩토링은 문제없이 할 것으로 예상됩니다. 저도 간만에 복습하니 Valid에 대해 더 도움이 된 것 같습니다.


@NotBlank
    @Size(min = 8, message = "새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다.")
    @Pattern(regexp = ".*\\\\d.*", message = "새 비밀번호는 숫자를 포함해야합니다.")
    @Pattern(regexp = ".*[A-Z].*", message = "새 비밀번호는 대문자를 포함해야합니다.")
    private String newPassword;

이렇게 DTO에 제약 조건을 설정해두면, 컨트롤러에서 해당 DTO를 받을 때 @Valid 애노테이션을 사용하여 스프링 부트가 자동으로 유효성 검사를 수행하니까, UserController도 수정해줍니다.

@PutMapping("/users")
    public void changePassword(@Auth AuthUser authUser, @RequestBody @Valid UserChangePasswordRequest userChangePasswordRequest) {
        userService.changePassword(authUser.getId(), userChangePasswordRequest);
    }

@Valid 어노테이션만 추가했습니다.


Lv 2. N+1 문제 필수

  • TodoControllerTodoService를 통해 Todo 관련 데이터를 처리합니다.
  • 여기서 N+1 문제가 발생할 수 있는 시나리오는 getTodos 메서드에서 모든 Todo를 조회할 때, 각 Todo와 연관된 데이터를 개별적으로 가져오는 경우입니다.
  • 요구사항:
    • JPQL fetch join을 사용하여 N+1 문제를 해결하고 있는 TodoRepository가 있습니다. 이를 동일한 동작을 하는 @EntityGraph 기반의 구현으로 수정해주세요.

@EntityGraph 기반의 구현으로 수정하라고 했는데, @EntityGraph 기반이 무엇인지 아는가 ?

@EntityGraph는
JPA에서 제공하는 기능으로, 엔티티의 특정 연관관계(attribute graph)를 미리 정의하여 조회 시 함께 로딩하도록 하는 방법입니다. 이 기능은 JPQL의 fetch join과 유사한 역할을 하지만, 주로 어노테이션을 통해 선언적으로 사용할 수 있습니다.

@EntityGraph(attributePaths = {"user", "category"})
List<Todo> findAll();

위와 같이 리포지토리 메서드에 @EntityGraph를 사용하면, Todo와 연관된 user와 category 엔티티를 한 번의 쿼리로 가져올 수 있어 N+1 문제를 예방할 수 있습니다.

따라서, JPQL의 fetch join을 사용한 기존 구현을 @EntityGraph 기반으로 수정하는 것은 동일한 동작(연관 엔티티를 함께 조회)을 선언적으로 달성할 수 있는 방법

예를 들어, N+1 문제는 Todo를 조회할 때 각 Todo에 연결된 연관 엔티티(예: User, Category 등)를 별도의 쿼리로 반복 조회할 때 발생합니다. 이를 해결하기 위해 @EntityGraph를 사용하면, 해당 연관 엔티티들을 한 번의 쿼리로 fetch join하여 로딩할 수 있습니다.

참고 1, 2, 3, 4


그리고

수정해야 하는 이유에 대해 생각해봤습니다.

  1. N+1 문제 해결
    기존에는 fetch join을 JPQL로 명시하여 Todo를 조회할 때 연관된 user 엔티티도 함께 로딩했습니다.
    @EntityGraph를 사용하면 동일하게, 해당 연관관계(user)를 한 번의 쿼리로 함께 가져와서 N+1 문제를 예방할 수 있습니다.

  2. 선언적(fetch plan) 관리
    @EntityGraph는 메서드 선언부에 fetch할 연관관계를 배열 형식으로 지정합니다.
    이 방식은 쿼리 문자열에 fetch join 로직을 섞지 않고, 구현과 fetching 전략을 분리할 수 있어 유지보수가 용이합니다.

  3. JPQL 쿼리와의 분리
    JPQL 쿼리에서 JOIN FETCH 구문을 제거하고, 단순한 JPQL 쿼리를 유지하면서 동시에 연관 엔티티를 로딩하도록 할 수 있습니다.
    또한, 메서드 이름에서 "withUser"와 같은 키워드를 사용해 발생할 수 있는 문제(예: "No property 'withUser' found" 오류)를 방지할 수 있습니다.

  4. EntityGraph의 타입 설정
    EntityGraph.EntityGraphType.LOAD 옵션은, 엔티티에 지정된 fetch 전략을 따르면서도 지정된 연관관계를 추가로 EAGER하게 로딩합니다.
    이는 JPQL의 fetch join과 동일한 효과를 내지만, 선언적 방식으로 관리됩니다.


profile
백엔드를 지향하며, 컴퓨터공학과를 졸업한 취준생입니다. 많이 부족하지만 열심히 노력해서 실력을 갈고 닦겠습니다. 부족하고 틀린 부분이 있을 수도 있지만 이쁘게 봐주시면 감사하겠습니다. 틀린 부분은 댓글 남겨주시면 제가 따로 학습 및 자료를 찾아봐서 제 것으로 만들도록 하겠습니다. 귀중한 시간 방문해주셔서 감사합니다.

0개의 댓글