블로그

고승조

spring 과 springBoot의 차이점

Spring과 Spring Boot는 모두 스프링 프레임워크를 기반으로 한 자바 웹 개발 프레임워크입니다. 둘 간에는 몇 가지 차이점이 있습니다.Spring은 스프링 프레임워크의 핵심 모듈을 모아서 만든 프레임워크입니다. Spring에서는 개발자가 직접 설정 파일을 작성하여 스프링 컨테이너를 구성하고, 필요한 빈 객체를 등록하고, 빈 객체 간의 의존성을 설정해야 합니다. Spring은 특정한 구성을 위해 추가적인 라이브러리와 설정이 필요합니다.반면, Spring Boot는 스프링 프레임워크를 보다 쉽게 사용할 수 있도록 만든 프레임워크입니다. Spring Boot에서는 개발자가 설정 파일을 작성할 필요 없이, 프로젝트의 설정과 라이브러리 의존성을 자동으로 처리해주는 기능을 제공합니다. 또한, Spring Boot는 실행 가능한 JAR 파일을 만들 수 있습니다.Spring Boot는 Spring에서 제공하는 여러 기능들을 자동으로 설정하여 개발자가 보다 쉽게 사용할 수 있도록 해줍니다. 예를 들어, Spring Boot는 스프링 MVC, 스프링 Data JPA, 스프링 Security 등의 기능을 자동으로 설정하며, 개발자가 별도로 설정 파일을 작성하지 않아도 사용할 수 있습니다. 또한, Spring Boot는 Actuator라는 모니터링과 관리를 위한 기능을 제공하여, 애플리케이션의 상태를 모니터링하고, 필요한 조치를 취할 수 있도록 해줍니다.이러한 차이점들은 Spring과 Spring Boot의 사용 목적과 방식을 크게 달리 합니다. Spring은 개발자가 직접 설정 파일을 작성하고, 빈 객체를 등록하고, 빈 객체 간의 의존성을 설정하는 것을 요구합니다. 반면, Spring Boot는 개발자가 보다 쉽게 스프링을 사용할 수 있도록 설정과 의존성 처리 등을 자동으로 처리합니다. Spring은 스프링 프레임워크를 보다 세밀하게 제어하고자 하는 경우에, Spring Boot는 빠르고 간단하게 스프링 애플리케이션을 개발하고자 하는 경우에 사용됩니다.간단 요약 : 스프링(Spring)은 프레임워크이며, 스프링 부트(Spring Boot)는 스프링 프레임워크를 기반으로 한 도구입니다. 스프링은 설정 파일을 작성해야 하지만, 스프링 부트는 자동 설정을 제공하여 간편하게 개발할 수 있습니다. 또한, 스프링 부트는 내장 서버를 제공하여 쉽게 웹 애플리케이션을 실행할 수 있습니다. Spring은 스프링 프레임워크를 보다 세밀하게 제어하고자 하는 경우에, Spring Boot는 빠르고 간단하게 스프링 애플리케이션을 개발하고자 하는 경우에 사용됩니다.

스프링

왜 자바 백엔드 실무에선 스프링 부트가 중요할까?

한국은 물론, 세계적으로도 가장 인기 있는 서버 개발 스택은 자바(Java) 언어 기반의 스프링(Spring) 프레임워크를 이용한 백엔드 기술입니다. 스프링은 불필요하거나 반복적인 코드를 줄임으로써 코드의 복잡성을 낮추고, 개발자가 핵심 비즈니스 로직에 집중할 수 있도록 돕는 역할을 합니다.하지만 스프링을 사용하려면 초기 환경을 일일이 설정해야 하는 등 번거롭고 어려운 면이 있었는데요. 이런 스프링의 복잡한 부분을 개선하고 보다 손쉬운 웹 애플리케이션 개발을 가능하게 한 게 바로 스프링 부트(Spring Boot)입니다. 스프링 부트를 통해 XML 구성을 할 필요도 없고, Tomcat 등의 기본 HTTP 서버가 내장되어 있어 편의성은 높으면서도 더 빠른 개발이 가능하게 되었죠.이러한 스프링 부트를 통해 자바/스프링 개발자들은 초기 설정처럼 핵심적인 부분은 아니지만 빼놓을 수 없는 공정의 부담을 덜어내고, 프로그램 및 시스템 운용이라는 관점에 집중하여 개발할 수 있게 된 셈입니다.•••베테랑 시니어 개발자들이 알려주는 스프링 부트 노하우가 궁금하신가요?지금 인프런 프리즘 [스프링 부트 로드맵]을 통해 학습해보세요. https://www.inflearn.com/roadmaps/649•••인프런 프리즘 브랜드 스토리 읽어보기 >>

백엔드SpringSpringBootJava스프링스프링부트백엔드Back-End인프런프리즘InflearnPrism

요즘 백엔드 취업 시장에서 코프링이 핫하다던데?

코틀린(Kotlin)은 젯브레인즈(JetBrains)에서 개발한 크로스 플랫폼 범용 프로그래밍 언어입니다. JVM 기반의 언어이면서 자바(Java)와 100% 호환되도록 설계되었습니다. 구글은 2019년부터 코틀린을 안드로이드 개발 공식 언어로 지정했어요. 간결한 문법, 안정성, 다양한 기능이 있다는 장점과 함께 전 세계적으로 사랑받고 있는 언어입니다.그동안 백엔드에선 자바 언어와 스프링 프레임워크의 조합이 가장 압도적인 점유율을 차지하고 있었는데요. 최근엔 코틀린을 도입하거나 자바를 코틀린으로 대체하려는 기업이 늘면서 코틀린 언어와 스프링 프레임워크의 조합, 일명 '코프링'이 주목받기 시작했습니다. 실제로 현재 취업 시장을 살펴보면 코틀린 언어를 다루는 능력을 자격이나 우대 사항으로 기재해 두는 기업을 어렵지 않게 찾아볼 수 있어요. 하지만 비교적 최근에 주목받고 있는 만큼 백엔드 현업에서의 코틀린 혹은 코프링 관련 사례나 자료를 찾는 건 쉽지 않죠.앞으로 사용이 더 늘어날 것으로 전망되는 코틀린, 코틀린과 코프링의 세계에 발 빠르게 뛰어들고 싶다면 지금 시도해 보는 건 어떨까요?•••Java 개발자를 위한실무밀착형 코프링을 배우고 싶다면?지금 인프런 프리즘 [자바 개발자를 위한 실전 코프링 입문 (Kotlin + Spring)]을 통해 학습해보세요.https://www.inflearn.com/roadmaps/703•••인프런 프리즘 브랜드 스토리 읽어보기 >>

백엔드코틀린Kotlin스프링SpringSpringBoot스프링부트코프링백엔드인프런프리즘InflearnPrism

저니

스프링 가이드 목록 2022

* page : https://spring.io/guides     클릭하시면 해당 페이지로 넘어갑니다. 🍃 Building a RESTful Web Service Scheduling Tasks Consuming a RESTful Web Service Building Java Projects with Gradle Building Java Projects with Maven Accessing Relational Data using JDBC with Spring Uploading Files Authenticating a User with LDAP Messaging with Redis Messaging with RabbitMQ Accessing Data with Neo4j Validating Form Input Building a RESTful Web Service with Spring Boot Actuator Messaging with JMS Creating a Batch Service Securing a Web Application Building a Hypermedia-Driven RESTful Web Service Accessing Data in Pivotal GemFire Integrating Data Caching Data with Pivotal GemFire Managing Transactions Accessing Data with JPA Accessing Data with MongoDB Serving Web Content with Spring MVC Converting a Spring Boot JAR Application to a WAR Creating Asynchronous Methods Handling Form Submission Building an Application with Spring Boot Using WebSocket to build an interactive web application Working a Getting Started guide with STS Consuming a RESTful Web Service with AngularJS Consuming a RESTful Web Service with jQuery Enabling Cross Origin Requests for a RESTful Web Service Consuming a SOAP web service Accessing JPA Data with REST Accessing Neo4j Data with REST Accessing MongoDB Data with REST Accessing Data in Pivotal GemFire with REST Producing a SOAP web service Caching Data with Spring Deploying to Cloud Foundry from STS Spring Boot with Docker Working a Getting Started guide with IntelliJ IDEA Creating CRUD UI with Vaadin Service Registration and Discovery Centralized Configuration Testing the Web Layer Accessing data with MySQL Creating a Multi Module Project Creating API Documentation with Restdocs Messaging with Google Cloud Pub/Sub Building a Reactive RESTful Web Service Consumer Driven Contracts Accessing Vault Vault Configuration Accessing Data Reactively with Redis Deploying a Spring Boot app to Azure Building a Gateway Client-Side Load-Balancing with Spring Cloud LoadBalancer Spring Cloud Stream Spring Cloud Data Flow Spring Cloud Task Spring Boot Kubernetes Accessing data with R2DBC Spring Cloud Circuit Breaker Guide Observability with Spring Building a Guide with VS Code Accessing Data with Cassandra Spring Security Architecture Spring Boot Docker Spring on Kubernetes Building REST services with Spring Spring Security and Angular React.js and Spring Data REST Spring Boot and OAuth2 Building web applications with Spring Boot and Kotlin Spring Boot with Kotlin Coroutines and RSocket Metrics and Tracing with Spring     var list = Array.from(document.getElementsByClassName('guide-link')); list.forEach((e) => { console.log('* [' + e.innerHTML + '](http://spring.io' + e.getAttribute('href') + ')'); }); 출처 : https://okky.kr/article/1209730

백엔드스프링프레임워크가이드springframeworkbackendguides

스프링 AOP

AOP(Aspect-Oriented Programming)는 객체 지향 프로그래밍의 한계를 보완하기 위해 등장한 프로그래밍 패러다임입니다. 스프링 AOP는 스프링 프레임워크에서 AOP를 구현한 것으로, 코드의 횡단 관심사(cross-cutting concerns)를 분리하여 모듈화하고, 공통 로직을 재사용하는 기능을 제공합니다.AOP에서 횡단 관심사란, 애플리케이션의 여러 부분에서 공통으로 사용되는 로직입니다. 예를 들어, 로깅, 보안, 트랜잭션 관리 등이 있습니다. 이러한 로직은 애플리케이션 전반에 걸쳐 중복되어 사용될 수 있습니다. 이때 AOP를 이용하면 공통 로직을 모듈화하여 각 부분에서 코드의 재사용성과 가독성을 높일 수 있습니다.스프링 AOP는 프록시 패턴과 런타임 코드 생성을 이용하여, 메서드 호출 시점에 공통 로직을 삽입하는 방식으로 동작합니다. 메서드 호출을 가로채어 공통 로직을 수행하고, 호출한 메서드의 결과를 반환하는 방식입니다. 이를 위해 스프링은 Join Point, Pointcut, Advice, Aspect 등의 개념을 제공합니다.Join Point는 공통 로직이 삽입될 수 있는 지점으로, 스프링은 메서드 호출을 기준으로 Join Point를 정의합니다. Pointcut은 Join Point 중에서 실제로 공통 로직이 적용될 메서드를 선택하는 기능을 수행합니다. Advice는 실제로 삽입될 공통 로직을 의미합니다. Aspect는 Pointcut과 Advice의 조합으로, 특정한 기능을 담당하는 AOP 모듈을 의미합니다.스프링 AOP는 프로그램 전반에서 중복되는 로직을 간편하게 모듈화할 수 있는 기능을 제공하여, 코드의 가독성과 재사용성을 높입니다. 또한, 스프링의 다양한 기능과 연동하여, 보안, 로깅, 트랜잭션 처리 등 다양한 영역에서 활용됩니다.예시@Aspect public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("Before executing " + methodName + " method"); } @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("After executing " + methodName + " method"); } }위 코드는 LoggingAspect라는 이름의 클래스를 정의하고, @Aspect 어노테이션을 사용하여 스프링 AOP에서 Aspect로 등록합니다. @Before, @AfterReturning 등의 어노테이션을 이용하여 메서드 실행 전/후에 로그를 출력하는 Advice를 정의합니다.@Before 어노테이션은 execution 패턴을 이용하여, com.example.service 패키지의 모든 메서드가 실행되기 전에 공통 로직을 수행하도록 설정합니다. JoinPoint 인터페이스를 이용하여 현재 실행되는 메서드의 정보를 추출하고, 메서드 이름을 출력합니다.@AfterReturning 어노테이션은 실행된 메서드가 정상적으로 반환될 때 공통 로직을 수행하도록 설정합니다. returning 속성을 이용하여 반환된 값을 추출하고, 메서드 이름을 출력합니다.위와 같이 스프링 AOP를 이용하면 메서드 실행 전/후에 공통 로직을 간단하게 삽입할 수 있습니다.

스프링

유원준

[인프런 워밍업 클럽(백엔드, 0기)] - 개별 정리 : 자바 어노테이션

스프링으로 프로젝트를 하면서 자바 어노테이션을 항상 사용해 왔지만 아직 잘 모르는 부분이 많은 것 같아서 1일 차에 대한 과제 제출 기간은 지났지만, 추가적으로 인프런 블로그를 통해 다시 한 번 정리해 보고자 한다. (1) https://twojun-space.tistory.com/178본인 블로그에 내용을 별도로 정리했지만 한 번 더 리마인드해 보고자 한다.   1. 자바 어노테이션(Java Annotation)1-1. 정의(1) 어노테이션은 사전적 정의로는 "주석"이라는 의미를 가지고 있지만 자바에서의 어노테이션은 소스 코드에 추가할 수 있는 일종의 메타 데이터라고 볼 수 있다.(2) 애플리케이션 레벨에서 처리되어야 하는 대상이 아닌, 컴파일, 런타임 시점에 코드를 어떻게 처리해야 할지 알려주기 위한 정보로 볼 수 있겠다.  1-2. 장점(1) 코드 가독성, 컴파일 시점에서의 오류 체크코드 레벨에서 동일하게 작성되기 때문에 코드의 가독성이 좋고, 일부 어노테이션의 경우 컴파일 시점에 문법 에러(아래에서 설명할 @Override, @FunctionalInterface)를 잡아주기도 한다. (2) 중복 코드 제거중복되는 어노테이션의 경우 공통화시킬 수 있고 재사용이 가능하기 때문에 코드의 중복을 줄이고 효율적인 코드 작성이 가능하다. (3) 커스텀 어노테이션 (사용자 정의 어노테이션) 사용 가능직접 용도에 맞게 커스텀 어노테이션을 작성할 수 있다. 프로젝트를 진행함에 따라 각각 필요한 제약사항들을 별도로 정리해서 커스텀 어노테이션 구성이 가능하다.  1-3. 단점(1) 런타임 시 발생할 수 있는 오버헤드만약 런타임 시점에 자바의 리플렉션(Reflection) 등을 사용해서 처리되는 어노테이션이라면 이 부분을 처리하기 위한 별도의 오버헤드가 발생할 수 있다 (성능 상 문제)    2. 어노테이션의 종류살펴볼 어노테이션의 종류로 총 2가지가 있다.(1) 표준 어노테이션(빌트 인 어노테이션)(2) 메타 어노테이션    3. 표준 어노테이션(1) 표준 어노테이션의 경우 자바에서 기본적으로 제공하고 있는 어노테이션이다. 대표적으로 아래와 같이 3가지가 있다. 3-1. @Override(1) 현재 메서드가 부모 타입 클래스 또는 인터페이스의 메서드를 오버라이딩했음을 컴파일러에게 명시하는 역할을 수행한다. 만약 형식에 맞지 않게 오버라이딩되었다면, 컴파일러가 이를 인지하고 오류를 발생시킨다.  3-2. @Deprecated(1) 현재 메서드를 사용하지 않도록 유도한다. 만약 해당 어노테이션이 붙은 메서드를 사용하면 컴파일러가 오류를 발생시킨다.  3-3. @FunctionalInterface(1) 해당 인터페이스가 함수형 인터페이스임을 명시한다. 함수형 인터페이스의 경우 추상 메서드가 반드시 1개 존재해야 한다. 추상 메서드가 존재하지 않거나 2개 이상이라면 컴파일러가 오류를 발생시킨다.    4. 메타 어노테이션(Meta Annotation)(1) 메타 어노테이션이란 다른 어노테이션에서 사용될 수 있는 어노테이션을 의미하며 아래에서 작성할 커스텀 어노테이션(사용자 정의 어노테이션)을 생성할 때 주로 사용되는 어노테이션이다.   4-1. @Target(1) 어노테이션을 적용할 위치를 알려주는 어노테이션이다. (2) 예를 들어 @Target(ElementType.TYPE)의 경우 해당 어노테이션을 다른 어노테이션의 대상으로 사용할 수 있다는 의미이다. (3) 메타 어노테이션을 선언해 줄 때 사용되는 일반적인 방법 중 하나다.@Target({ ElementType.PACKAGE, // 패키지 선언 ElementType.TYPE, // 타입 선언 ElementType.CONSTRUCTOR, // 생성자 선언 ElementType.FIELD, // 멤버 변수 선언 ElementType.METHOD, // 메소드 선언 ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언 ElementType.LOCAL_VARIABLE, // 지역 변수 선언 ElementType.PARAMETER, // 매개 변수 선언 ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언 ElementType.TYPE_USE // 타입 사용 })  4-2. @Retention(1) @Retention의 경우 어노테이션이 적용되고 유지되는 범위를 설정하기 위해 사용되는 메타 어노테이션이다.@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해서 참조가 가능하다. @Retention(RetentionPolicy.CLASS) // 컴파일러가 클래스를 참조할 때까지 유효하다. @Retention(RetentionPolicy.SOURCE) // 어노테이션 정보는 컴파일 이후 없어진다.  4-3. @Inherited(1) 해당 어노테이션이 적용된 경우 자식 클래스가 해당 어노테이션을 상속받을 수 있게 된다.(2) 따라서 부모 클래스에 선언된 @Inherited 어노테이션은 하위 클래스에서 자동으로 상속받는다.  4-4. @Repeatable(1) 반복 가능한 어노테이션을 정의할 때 사용될 수 있는 어노테이션이다.    5. 커스텀 어노테이션(Custom Annotation, 사용자 정의 어노테이션) 5-1. 정의, 사용 방법public @interface SimpleAnnotation { }(1) 자바에서는 위와 같이 @interface 키워드를 통해 커스텀 어노테이션을 정의할 수 있다.   5-2. 실제로 커스텀 어노테이션 적용해 보기@Retention(RetentionPolicy.RUNTIME) @Inherited @Target(ElementType.TYPE) @RestController @RequestMapping("/new") public @interface CustomMyAnnotation { String name() default "MemberController"; String value(); } @CustomMyAnnotation(name = "MemberController", value = "MemberController") @RequiredArgsConstructor public class MemberController { private final MemberService memberService; @GetMapping("/list") public List<MemberListResponseDto> getAllMemberList() { List<Member> allMemberList = memberService.findAllMembersList(); return allMemberList.stream() .map(member -> new MemberListResponseDto(member)) .collect(Collectors.toList()); } }  5-3. 자바의 리플렉션(Reflection)(1) 현재 어노테이션을 사용해서 코드의 가독성이 좋아짐은 물론 어노테이션 자체가 되게 많은 일을 대신 해주고 있는 것을 확인해 볼 수 있다. 이 부분은 자바의 리플렉션 기술들이 해결해 주고 있는데 추후에 리플렉션에 관한 내용을 블로그에 다시 한 번 정리해 볼 예정이다.  마지막으로 부족하지만 글을 읽어주신 분들께 감사드립니다!!  

백엔드백엔드인프런워밍업자바어노테이션스프링

wisehero

<인프런 워밍업 스터디 클럽 0기> - BE 발자국 1주차

사실 강의 수강은 워밍업 클럽이 열리기 전부터 듣고 있었어요. 이미 JPA로 전환하는 부분까지 다 넘어갔었습니다. 사실 이것때문에 30% 할인 쿠폰을 미리 받지 못한 것에 대해서는 조금 아쉽긴 했습니다 ㅋㅋㅋ. 강의를 수강하게 된 이유는 저는 국비교육을 수료한 이후로 다른 정량적 스펙이나 코딩테스트, 자기소개서에 시간을 너무 많이 쓰게 되었고 중간에 인턴이나 현재 재직중인 직장에서는 스프링 부트나 스프링 5이상 버전, JDK 11~21 버전을 사용하지 않는 환경에 있었어요. 그래서 최근 개발 트렌드나 버전에 따른 변화에 뒤쳐지지 않기 위해 강의를 수강하기 시작했습니다. 1주차 동안은 주로 강의를 다시 듣는 것은 아니었고 이미 들었던 강의, 강의를 들으면서 작성했던 코드들을 다시 한번 보게 되었어요. 이 과정에서 JPA를 주로 사용하는 바람에 JdbcTemplate을 잘 사용해본 적이 없어서 이에 다시 적응하는 시간이 되어서 좋았습니다. 그리고 정말 레이어드 아키텍처에 따른 모든 코드들을 오랜만에 작성하면서 예전에 국비 교육때 열심히 했던 시간들을 다시 한번 되새길 수 있었고, 취업 준비를 하면서 많이 꺾였던 마음을 다시 세울 이유와 동기를 얻는 과정이어서 좋았습니다. 아쉬웠던 점은 시간이 그렇게 많지 않아, 과제 수행 중에 발견한 문제점이나 궁금했던 점에 대해 따로 깊이 파볼 시간이 조금 부족했던 것이 있습니다. 아무래도 2월부터 회사에 다니게 되었고 이 루틴이 익숙치 않아 오는 문제점인것 같은데 어떻게든 해결책을 마련해서 극복해야겠습니다. 워밍업 클럽을 진행하면서, 혹은 강의를 수강하면서 스스로에게 그나마 좋았다고 말해줄 수 있는 점은 질문을 적극적으로 하는 자세였습니다. 아마도 태현님 강의에서 질문을 제일 많이 한 것 같아요. 그리고 그 질문들중 좋은 질문이라고 반응해주셔서 행복했고, 태현님이 제 질문에 답변을 주신 것을 다른 분들의 질문에 대한 답변으로 대신하시는 것을 보고 '내가 의미있는 질문을 했구나'하는 생각을 했습니다. 세상에 멍청한 질문은 없다지만, 그 질문들 가운데서도 핵심을 짚는, 가치가 높은 질문들은 있다고 생각을 하는데 그런 질문을 하는 사람이 되어간다는 느낌을 받았습니다. 앞으로도 그런 질문을 계속 던질 수 있는 개발자가 되어야겠다고 다짐했던 좋은 경험이었습니다. 미션 수행과 관련해서...미션 수행은 저 말고도 다른 분들도 크게 어렵지 않게 해결하셨을거 같아요. 다만 저의 경우엔 몇 가지 아쉬운점이 있었어요. 어노테이션 관련해서 딥다이브를 하는 과정에서 과연 '딥'하게 들어갔는지에 대해는 의문이었어요. 다른 분들은 어노테이션이 '마법'을 일으키는 과정을 따라들어가보면 '리플렉션'이라는 개념이 등장하는데 제가 이 리플렉션 코드를 직접 짜보거나 하지는 않았거든요. 반성해야할 지점이었습니다. 단순히 개념적인 것, 글만 읽고 끝내는 공부를 또 반복하게 된듯한 느낌이었거든요.  나머지 미션들이 크게.. 특별히 어려웠던 점은 없었는데 코치님께서 남겨주신 4일차 과제 피드백을 듣고 다음에 비슷한 동작을 수행하는 코드를 작성할 때 더 좋은 코드를 작성할 수 있는 법을 배웠어요. 코치님이 남기신 피드백 내용은 다음과 같습니다.제가 이 피드백에서 교훈을 얻은 이유는 코치님이 언급하신, 데이터베이스에서 데이터를 전달해주고, 서버에서 연산 작업을 처리하게 되어 네트워크 대역폭 증가와 서버 자원 사용량의 증가라는 효과를 불러일으키는 방식으로 코드를 작성했기 때문이에요. 그렇다면 왜 그런 코드를 작성했을까요?사실 그냥 이 과제가 SQL 문제로 주어졌다면, 저는 아무런 고민없이 데이터베이스에서 바로 연산을 하는 SQL문을 바로 짤 수 있었을 거에요. 하지만 서버 애플리케이션 프로그래밍을 배우면서 이런 얘기를 들었어요. '데이터베이스에서 비즈니스 로직을 처리하게 되면 DB 종속적으로 프로그래밍을 하게 되어 서버 애플리케이션의 존재 의미가 흐릿해진다.'실제로도 현재 근무하고 있는 회사에서는 비즈니스 로직이 오라클 데이터베이스의 프로시져에 몽땅 때려박혀있는 구조이고 저는 이것을 지금 개선하고 있기 때문에 DB라는 것에서 어떤 처리를 최소한으로 하려고 하는 습관이 생겼어요. 그리고 여기에 더해 강의를 통해 자바 스트림을 적극적으로 사용하는 것을 보고, 스트림 처리를 적극적으로 사용하는 것이 간결하고 명확하며 멋져보여서 이를 적극적으로 사용하는 것이 머리 속을 지배했습니다.하지만 저는 과제의 요구사항이 '통계성 데이터를 반환하는 것'이라는 것을 잊고 있었고 이에 따른 트레이드 오프를 고민하는 자세를 갖지 못하고 바로 서버에서 스트림으로 연산을 처리해야겠다는 사고에 지배를 당해 아래와 같이 코드를 작성했습니다.@Transactional(readOnly = true) public List<Long> fruitStat(String name) { fruitRepository.findFruitsByName(name); List<Fruit> findFruits = fruitRepository.findFruitsByName(name); if (findFruits.size() == 0) { throw new IllegalArgumentException("해당 이름을 갖고 있는 과일이 없습니다."); } Long salesAmount = findFruits.stream().filter(Fruit::getSold).mapToLong(Fruit::getPrice).sum(); Long notSalesAmount = findFruits.stream().filter(fruit -> !fruit.getSold()).mapToLong(Fruit::getPrice).sum(); return List.of(salesAmount, notSalesAmount); } 이름 하나를 넘겨받고 그 이름과 동일한 이름을 가진 과일을 모두 가져오고, 팔린 물건과 그렇지 않은 물건을 따로따로 계산해주고 있습니다. 하지만 저 코드는 Fruit 테이블에 엄청나게 많은 데이터가 있었다면, 합을 구하는데 오랜 시간이 걸릴 수 있음이 분명했습니다. 통계성 데이터 처리는 그냥 한꺼번에 디비에서 해서 넘겨주는 것이 더 컴퓨팅 자원을 덜 소모할 수 있는 방법이라는 것을 분명 공부했지만 하나의 문제를 해결할 수 있는 방법을 여러개 놓고 그 중에 고른다기보다 저는 기존에 배웠던 것을 새로 배운 것으로 덮어쓰기 해버리는 바람에 트레이드 오프를 고려하는 습관을 유지하지 못한 부끄러움이 있었습니다. 그래서 우선 오늘은 자고 내일 개선해보자라고 생각했으나 마침 6일차 과제에 JPA가 아닌 JdbcTemplate을 사용할 것을 가정하고 나온 과제 내용을 다시 보고 개선할 수 있는 기회를 얻었습니다. 그래서 작성한 코드는 아래와 같습니다.동일한 작업을 모두 SQL로 작성하고 이를 DB에서 처리하게 했습니다. 이렇게 하고 단순히 응답을 맵으로 감싸서 넘기는 방식을 취하고 있죠. 만약 통계 결과를 얻기 위한 데이터가 엄청 많다면 이러한 방식이 더 효율적일 것 같습니다. 다음엔 좀 더 많은 임의의 데이터를 넣고 코드를 시험해봐야겠습니다. 감사합니다.

백엔드워밍업클럽백엔드최태현스프링

유원준

[ 인프런 워밍업 클럽(백엔드, 0기)] - 0일 차 오리엔테이션 회고!

안녕하세요! 이번 인프런 워밍업 클럽 백엔드 스터디 0기에 참여하게 되었습니다.기존 Java, Spring, JPA로 백엔드를 공부했고, 그동안 배웠던 내용을 체화시켜야 할 기회가 필요했습니다.학습만 하고 체화시키지 않는다면 절대 본인의 지식이 될 수 없다고 생각하고 있기 때문입니다. 만약 학습한 내용을 바로 머릿속에 오래 남겨둘 수만 있다면 큰 행운인 것 같습니다. 하지만 저도 사람이기에 단순히 눈으로 보기만 한 내용은 기억에 오래 남지 못하는 것 같습니다. 저랑 비슷하신 분이 계실진 모르겠지만 배운 내용, 실습한 내용들을 본인의 GitHub나 개인 블로그에 잘 정리해 놓으면 상당히 기억에 오래 남고, 필요한 부분을 다시 찾아볼 수 있다는 점 때문에 저는 기록하는 것을 항상 좋아합니다! 제 루틴대로 배웠던 학습 내용들을 정리하고, 과제, 프로젝트 수행 부분 또한 별도로 블로그, GitHub에 정리할 예정입니다. 열정 가득한 코치님, 참가자분들과 함께 공부할 수 있게 되어 기쁜 마음입니다!백엔드 0기로 합류하시게 된 분들 모두 같이 파이팅했으면 좋겠습니다 😀 감사합니다 😀 

백엔드인프런워밍업백엔드스프링발자국0기화이팅!

TDD

TDD는 Test-Driven Development(테스트 주도 개발)의 약어로, 소프트웨어 개발 방법론 중 하나입니다. TDD는 개발자가 코드를 작성하기 전에 테스트 케이스를 먼저 작성하고, 이를 통해 개발된 코드의 품질을 보장하고자 합니다.TDD의 핵심 개념은 "Red-Green-Refactor"입니다. 개발자는 먼저 테스트를 작성하여 실패하는 테스트 케이스를 만듭니다. 이후 코드를 작성하여 테스트를 통과시킵니다. 마지막으로, 코드를 리팩토링하여 개선합니다.TDD를 통해 코드 품질을 향상시키는 이유는 다양합니다. 첫째로, 테스트 케이스를 먼저 작성함으로써 개발자는 코드의 기능을 명확하게 이해하고 설계할 수 있습니다. 둘째로, 테스트 케이스를 통해 코드의 동작을 검증함으로써 버그를 발견하고 수정할 수 있습니다. 셋째로, TDD는 코드의 유지보수를 용이하게 합니다. 테스트 케이스를 작성함으로써 코드 변경에 따른 부작용을 쉽게 파악할 수 있기 때문입니다.TDD는 또한 지속적인 통합과 배포에도 매우 유용합니다. TDD를 사용하면 변경 사항이 발생했을 때 자동화된 테스트 스위트를 실행하여 코드의 안정성을 검증할 수 있습니다. 이를 통해 빠른 시간 내에 안정적인 소프트웨어를 배포할 수 있습니다.하지만 TDD는 모든 상황에서 적용하기에는 적합하지 않을 수 있습니다. 특히, 복잡한 시스템에서는 TDD를 적용하기 어렵거나 시간이 많이 걸릴 수 있습니다. 따라서 TDD는 개발 방법론 중 하나일 뿐, 상황에 따라 적절히 선택하여 사용해야 합니다.간단 요약 : TDD는 코드 작성 전 테스트 케이스를 작성하여 개발자가 코드 품질을 보장하는 소프트웨어 개발 방법론이다. TDD를 사용하면 코드의 기능을 명확하게 이해하고 설계할 수 있으며, 버그를 발견하고 수정할 수 있다. 또한 지속적인 통합과 배포를 용이하게 한다.

스프링

REST API

REST(Representational State Transfer)는 분산 시스템에서 서버와 클라이언트 사이의 통신 방식을 규정한 아키텍처 스타일입니다. REST API는 이러한 아키텍처 스타일을 따르는 API입니다.REST API는 HTTP 프로토콜의 메서드(GET, POST, PUT, DELETE 등)를 이용하여 데이터를 주고받습니다. URI(Uniform Resource Identifier)를 이용하여 요청하는 리소스를 표시하고, HTTP 메시지의 헤더와 바디에 포함된 정보를 이용하여 요청 처리 결과를 반환합니다. REST API의 특징으로는, 각 리소스는 고유한 URI를 가지며, URI에 대한 요청은 의도를 명확하게 나타내야 한다는 것이 있습니다.REST API를 사용하면, 다양한 클라이언트에서 플랫폼에 독립적으로 데이터를 주고받을 수 있으며, API의 확장성과 유연성이 높아집니다. 또한, REST API를 사용하면 캐싱 등 다양한 웹 기술을 활용하여 성능을 최적화할 수 있습니다.스프링 프레임워크에서는 Spring MVC를 이용하여 REST API를 개발할 수 있으며, Jackson 라이브러리를 이용하여 JSON 데이터를 처리하는 등 다양한 기능을 제공합니다. 또한, Spring Boot를 이용하여 REST API를 쉽게 개발하고 배포할 수 있습니다.GET 메서드: 리소스의 정보를 요청할 때 사용합니다. 웹 브라우저에서 주소 창에 URL을 입력하여 웹 페이지를 요청할 때 사용되는 메서드입니다.POST 메서드: 서버에 데이터를 제출할 때 사용합니다. 대표적으로 회원가입, 로그인 등에서 사용됩니다.PUT 메서드: 서버에 데이터를 업데이트할 때 사용합니다. 대표적으로 게시물 수정, 파일 업로드 등에서 사용됩니다.DELETE 메서드: 서버에 있는 데이터를 삭제할 때 사용합니다. 대표적으로 게시물 삭제, 파일 삭제 등에서 사용됩니다.PATCH 메서드: PUT 메서드와 유사하게 데이터를 업데이트할 때 사용합니다. 하지만 PUT 메서드는 전체 데이터를 업데이트하고, PATCH 메서드는 일부 데이터만 업데이트합니다.

스프링

스프링 트랜잭션 처리

스프링에서 트랜잭션 관리란, 여러 개의 데이터베이스 작업을 한 덩어리로 묶어서 실행하고, 실행 결과가 모두 정상이면 데이터베이스 상태를 일관성 있게 유지하는 것입니다. 예를 들어, 계좌 이체를 처리하는 과정에서 첫 번째 계좌에서 출금을 하고, 두 번째 계좌로 입금을 하는데, 이 과정에서 한 번이라도 오류가 발생하면 둘 다 처리되지 않도록 하는 것입니다.스프링에서는 트랜잭션 관리를 위한 기능을 제공하며, 이를 사용하면 프로그래머가 직접 코드를 작성하지 않아도 데이터베이스 작업을 트랜잭션으로 묶어줄 수 있습니다. 이를 Declarative Transaction Management라고 부르며, 메서드에 @Transactional 어노테이션을 추가하면 해당 메서드에서 실행되는 데이터베이스 작업이 모두 트랜잭션으로 처리됩니다.이 방법을 사용하면, 데이터베이스 작업을 실행하는 코드에 트랜잭션 처리 코드를 추가하지 않아도 되므로 코드가 간결해집니다. 또한, 중복 코드를 제거하여 유지보수성을 높일 수 있습니다.예시@Service public class MyService { @Autowired private MyMapper myMapper; @Transactional public void transferMoney(String fromAccount, String toAccount, int amount) { // 출금 myMapper.withdraw(fromAccount, amount); // 입금 myMapper.deposit(toAccount, amount); } }위 코드에서 @Service 어노테이션을 사용하여 MyService 클래스를 스프링 빈으로 등록합니다. @Autowired 어노테이션을 이용하여 MyMapper 인터페이스를 구현한 클래스를 주입받습니다.@Transactional 어노테이션을 사용하여 transferMoney() 메서드에서 실행되는 데이터베이스 작업이 트랜잭션으로 처리되도록 설정합니다. 출금과 입금 작업이 모두 성공해야만 트랜잭션이 커밋되며, 그렇지 않으면 롤백됩니다. 이렇게 하면, 중간에 예외가 발생하더라도 출금과 입금 작업이 모두 취소되므로 데이터베이스 상태가 일관성 있게 유지됩니다.간단 요약 : 스프링에서 제공하는 트랜잭션 관리 기능을 이용하면, 여러 개의 데이터베이스 작업을 한 덩어리로 묶어서 실행하고, 실행 결과가 모두 정상이면 데이터베이스 상태를 일관성 있게 유지할 수 있습니다. @Transactional 어노테이션을 이용하여 메서드 단위로 트랜잭션을 설정하고, 이를 Declarative Transaction Management라고 부릅니다. 이 방법을 사용하면 코드가 간결해지고, 중복 코드를 제거할 수 있습니다.

스프링

spring IoC/DI

IoC(Inversion of Control)는 제어의 역전을 의미하며, 객체 생성과 생명주기 관리 등을 개발자가 아닌 프레임워크에 위임하여 의존성 관리를 단순화하고 유지보수성을 높입니다. 기존에는 객체 생성과 의존성 주입 등을 직접 구현하여 개발해야 했습니다. 하지만, IoC는 객체의 생성 및 관리를 프레임워크에 위임하고, 객체 간의 의존성 주입을 프레임워크가 자동으로 처리하므로 개발자는 코드 구현에 집중할 수 있습니다.DI(Dependency Injection)는 의존성 주입을 의미하며, 객체를 생성하고 이용하는 시점에서 필요한 의존 객체를 주입하는 방식입니다. 이를 통해 객체 간의 결합도를 낮추고 유지보수성을 높일 수 있습니다. 예를 들어, 객체 A가 객체 B에 의존하는 경우, 일반적으로는 객체 A가 객체 B를 직접 생성하고 의존성을 주입합니다. 하지만, DI를 이용하면 객체 A가 필요한 객체 B를 외부에서 주입받아 사용할 수 있으므로, 객체 간의 결합도를 낮출 수 있습니다.IoC와 DI를 함께 사용하여, 객체 생성 및 의존성 주입 등을 자동화하여 개발자의 부담을 줄이고 애플리케이션의 유연성과 확장성을 높일 수 있습니다. 스프링 프레임워크는 IoC와 DI를 중심으로 구성되어 있으며, BeanFactory와 ApplicationContext를 제공하여 객체의 라이프사이클을 관리하고, 객체 간의 의존성을 주입하는 기능을 제공합니다. 이를 통해 스프링은 유지보수성이 높은 애플리케이션을 쉽게 개발할 수 있습니다.간단 요약 : IoC(Inversion of Control)는 객체 생성 및 생명주기 관리 등을 프레임워크에 위임하여 의존성 관리를 단순화합니다. DI(Dependency Injection)는 객체 간의 의존성을 주입하는 방식으로, 객체 간의 결합도를 낮추고 유지보수성을 높입니다. 스프링 프레임워크는 IoC와 DI를 중심으로 구성되어 있으며, 객체의 라이프사이클을 관리하고, 객체 간의 의존성을 주입하는 기능을 제공합니다.

스프링

유원준

[인프런 워밍업 클럽 0기 백엔드] 3주차(최종) 발자국 (내용 정리)

인프런 워밍업 스터디의 마지막 주차가 마무리되어 가고 있습니다.3주차에 학습했던 내용들을 전체적으로 정리해 보고자 합니다.미니 프로젝트 수행은 하단의 깃허브 주소를 남겨두도록 하겠습니다.이전과 동일하게 학습한 내용, 미니 프로젝트 관련 내용들을 개인 블로그, 깃허브에 모두 정리하고 있습니다.하단은 학습 내용을 정리한 제 블로그 주소입니다.https://twojun-space.tistory.com/category/%EA%B8%B0%EB%A1%9D%2C%20%ED%9A%8C%EA%B3%A0/InFlearn%20Warming-up%200%EA%B8%B0%20BE (1) GitHub (Mini project)https://github.com/twojun/inflearn_warmingup_be_project   1. [11일 차] - 배포(Deployment), H2 DB를 통한 Profile 적용, Git & GitHub, AWS EC2(1) 11일 차 관련 학습 내용 개인 블로그 정리https://twojun-space.tistory.com/194 1-1. 배포의 뜻, 스프링 Profile(1) 애플리케이션에서의 배포의 뜻, 배포의 특징에 대해 알아보았다.(2) 사용자가 최종적으로 서비스를 이용할 수 있게 진행하는 일련의 작업으로 보면 된다.(3) 그리고 profile에 대해서도 알아보았는데 우리는 개발을 하면서 자연스럽게 profile 기능을 사용하고 있었다.(4) 이처럼 profile의 경우 똑같은 서버 코드를 실행시키지만 실행환경과 장소에 따라 각 다른 프로그램과 자원을 사용할 수 있도록 하는 것을 의미하게 된다.  1-2. Profile 적용(1) 간단하게 인메모리 DB인 H2 Database에 대해 알아보고, application.properties 또는 application.yml에서 DB profile을 설정하기 위한 옵션 적용이 가능함을 학습했다. (2) Run/Debug Configurations 메뉴에서 Active profiles를 yml 또는 properties에서 설정한 값으로 채워두고 서버를 실행시킨다.  1-3. Git & GitHub(1) Git, GitHub의 특징과 차이점을 알아보고 GitHub를 왜 많은 개발자들이 사용하고 있는지 그 장점과 특징에 대해 중점적으로 알아보았다. (2) git init, git remote, git status 등 Git의 기본적인 명령어 학습  1-4. AWS EC2(Amazon Web Service Elastic Computer Cloud)(1) 아마존에서 웹 서비스의 배포와 운영을 위해 제공하는 클라우드 서비스인 AWS EC2와 특징에 대해 학습했다.(2) AWS EC2를 통해 개발한 서비스의 배포와 운영을 위해 가상 서버 인스턴스를 생성할 수 있으며 애플리케이션의 트래픽, 규모 등을 고려하여 생성한 서버 인스턴스의 리소스를 확장할 수도 있다.  1-5. 11일 차 학습 내용 개인 회고(1) 개발만큼 배포와 운영 단계도 백엔드 개발에서 가장 중요한 부분이다. 이 부분에 대해 학습하고 실제 프로젝트에도 적용할 수 있는 능력을 기르고자 한다.   2. [12일 차] - AWS EC2 접속, 기본적인 Linux command, AWS Computing 환경에서 서버 배포를 위한 환경 구성, & 배포, 종료되지 않는 실행(foreground & background)(1) 12일 차 관련 학습 내용 개인 블로그 정리https://twojun-space.tistory.com/195  2-1. AWS EC2의 두 가지 접속 방법(1) key pair를 통한 접속 (Mac os의 경우 Iterm을 통해 접속 가능)(2) AWS Console을 통한 접속  2-2. 기본적인 리눅스 커맨드 학습(1) 디렉토리 생성 및 삭제, 경로 이동 등 기초적인 리눅스 명령에 대해 학습  2-3. AWS Linux에서 서버 배포 준비(1) 우선 Console에서 Git을 별도로 설치한다. (2) 서버 코드의 실행을 위한 JDK를 설치한다. (3) DBMS 설치(RDB : MySQL)  2-4. EC2 환경에서도 동일하게 데이터베이스 구성, 빌드와 배포(1) EC2 환경에서도 동일한 테이블 구조를 세팅한다. (2) Remote repository에서 Git Clone으로 기존 서버 코드를 모두 가져온다. (3) AWS EC2 Free tier의 경우 성능이 좋지 않다. 요즘은 가벼운 애플리케이션이더라도 서버 리소스를 많이 잡아먹기 때문에 Swap Setting을 통해 저사양의 인스턴스를 대상으로 메모리와 함께 추가적으로 스토리지도 함께 사용할 수 있도록 설정한다. (4) gradlew를 통해 빌드를 진행한다. 무료 서버를 사용하는만큼 테스트는 돌리지 않는 것이 성능상 유리하다. (5) 빌드 성공 시 빌드 디렉토리가 생성되고 내부로 이동하면 .jar 파일이 생성되어 있다. 이제 빌드된 .jar 파일을 통해 서버 실행이 가능한 상태이다. java -jar 명령으로 서버를 실행한다.  2-5. 종료되지 않는 실행(1) foreground & background의 차이에 대해 알아보았다. (2) nohup 명령어를 알아보고, 파일 내부를 확인할 수 있는 cat, tail 등 명령어에 대해 추가 학습했다.  2-6. 12일 차 학습 내용 개인 회고(1) 배포에 대한 전반적인 내용을 빠르게 학습해볼 수 있는 파트였던 것 같다. 서버 애플리케이션의 경우 대부분 리눅스 환경에서 다루어지기 때문에 리눅스에 대한 이해와 커맨드를 빠르게 체화시키는 부분이 중요할 것 같다.      3. [13일 차] - build.gradle, Spring & Spring Boot(1) 13일 차 관련 학습 내용 개인 블로그 정리https://twojun-space.tistory.com/196  3-1. build.gradle(1) build.gradle이 무엇인지 빌드 도구인 gradle이 무엇인지 알아보고 프로젝트에 필요한 의존성을 관리할 수 있는 도구임을 학습했다. (2) build.gradle을 이루는 plug-in, repositories, dependencies, dependencies에 대해 학습했다.  3-2. Spring & Spring Boot(1) Spring, Spring Boot를 시기별로 출시된 버전, 버전이 갖는 의미에 대해 학습했다. (2) 기존 스프링과 스프링 부트의 차이에 대해 학습했다.스프링, 스프링 부트와의 차이점 : (1) 간편한 설정 제공스프링, 스프링 부트와의 차이점 : (2) 간단한 의존성(라이브러리, 프레임워크) 설정 관리스프링, 스프링 부트와의 차이점 : (3) 강력한 확장성, MSA(Micro-Service Architecture)에 적합한 모니터링 기준 제공 등  3-3. application.yml(properties), Lombok library(1) application.yml, application.properties 모두 스프링 프로젝트의 전반적인 설정 정보를 정의하기 위한 파일로 볼 수 있다. (2) yml은 계층 구조를 갖고 properties는 동일한 key, value 타입이지만 계층 구조가 존재하지 않는다. (3) Lombok 라이브러리는 개발자에게 많은 편리성을 제공해주는 라이브러리이다. Getter, Setter와 같이 반복되는 Boiler plate code를 제거할 수 있게 도와준다.  3-4. 13일 차 학습 내용 개인 회고(1) 스프링으로 개발을 진행하며 의존성을 관리하는 도구인 build.gradle과 스프링, 스프링 부트의 차이점, 설정 정보를 관리하는 application.yml에 대해 학습할 수 있었다. 무심하게 사용해오던 도구들이지만 의미를 다시 한 번 더 정리하고 제대로 알고 사용할 수 있으면 좋겠다는 생각을 하게 되었다.   4. 14일 차 마지막 마무리 영상, 스터디 마지막 최종 회고(1) 마무리 섹션에서는 코치님께서 백엔드 개발에 있어 전체적인 학습 방향성을 개인적으로 조언해 주시는 시간과, AWS 호스팅 서비스의 과금 계산, 강의에서 소개되지 않았던 SQLMapper인 MyBatis, ORM 기반 기술인 JPA의 비교, 클라이언트 사이드 렌더링, 서버 사이드 렌더링에 대해 간단하게 정리해 주셨다. (2) 약 3주 간 온라인 강의와 세션 외에도 수강생들의 끊임없는 질문에 항상 웃으며 친절하게 대답해 주셨던 열정이 가득한 최태현 코치님, 열심히 배우고 성장하기 위해 끊임없이 달리는 러너분들이 있어서 스터디를 잘 마무리할 수 있었던 것 같습니다. 짧았다면 짧고, 길었다면 긴 시간이었지만 스터디를 완주할 수 있게 되어서 기쁘고 이번 스터디 수료를 통해 많은 성장은 아니지만 개인적으로 어느 정도 성장하는 데 도움이 되었다고 생각합니다. 스터디를 참여하면서 많은 분들을 만났고, 꾸준히 배움을 멈추지 않는 수강생분들을 보며 저 자신도 많이 반성하게 되었던 기간이었던 것 같습니다 다시 한 번 코치님, 러너분들, 이 스터디 기회를 만들어주신 인프런 관계자 여러분들께 감사하다는 말씀을 드리고 싶습니다. 코치님, 0기 백엔드 러너분들, 인프런 관계자분들을 항상 응원하겠습니다!모두 화이팅입니다. 😁  

백엔드인프런워밍업백엔드스프링발자국회고

영후이

[인프런 워밍업 클럽 0기 BE] - 세 번째 발걸음

미니 프로젝트 Step 02구현 내용* ①출근 기능* 등록된 직원은 출근을 할 수 있어야 한다. 출근의 경우 이름은 동명이인이 있을 수 있으므로, DB에 등록된 ID를 기준으로 처리된다.<br>* ②퇴근 기능* 출근한 직원은 퇴근을 할 수 있어야 한다. 퇴근 역시 DB에 등록된 ID르ㅜㄹ 기준으로 처리된다.<br>* ③특정 직원의 날짜별 근무시간을 조회하는 기능* 특정 직원 id와 2024-01과 같이 연/월을 받으면, 날짜별 근무 시간과 총 합을 반환해야 한다. 이때 근무 시간은 분단위로 계산된다.* 예를 들어, 1번 id를 갖는 직원에 대해 2024-01을 기준으로 조회하면, 다음과 같은 응답이 반환되어야 한다.{ "detail": [ { "date": "2024-01-01", "workingMinutes": 480 }, { "date": "2024-01-02", "workingMinutes": 490 }, ... // 2024년 1월 31일까지 존재할 수 있다. ] "sum": 10560 } 📌 edge-case> - 등록되지 않은 직원이 출근 하려는 경우> - 출근한 직원이 또 다시 출근하려는 경우> - 퇴근하려는 직원이 출근하지 않았던 경우> - 그 날, 출근했다 퇴근한 직원이 다시 출근하려는 경우  --- 과정* Table> 💡 고민 1.테이블을 어떻게 짤까?CREATE TABLE commute ( id bigint auto_increment, start_of_work datetime, end_of_work datetime, attendance tinyInt, member_id bigint, primary key (id) ); > 📌 JPA Auditing저번 피드백에서 @EntityListeners(AuditingEntityListener.class) 어노테이션을 사용하는BaseEntity 추상 클래스를 만들어 봤는데, BaseEntity의 CreatedAt, UpdatedAt 필드를 상속받고 <br> 출/퇴근시간을 자동으로 기록하는게 개인적인 목표입니다!<br>*Controller @RestController @RequiredArgsConstructor public class CommuteController { private final CommuteService commuteService; @PostMapping("/start-of-work") public void startOfWork(@Valid @RequestBody startOfWorkRequest request) { commuteService.startOfWork(request); }@PostMapping("/end-of-work") public void endOfWork(@Valid @RequestBody endOfWorkRequest request) { commuteService.endOfWork(request); }@GetMapping("/commute") public ResponseEntity<GetCommuteRecordResponse> GetCommuteRecord(@Valid GetCommuteRecordRequest request){ GetCommuteRecordResponse getCommuteRecordResponse = commuteService.GetCommuteRecord(request); return ResponseEntity.ok().body(getCommuteRecordResponse); } } > 📌 클래스를 매개변수로 사용하는 경우클래스 형태의 객체를 매개변수로 받는 컨트롤러 메소드에서 별도의 어노테이션을 사용하지 않는 경우,스프링은 기본적으로 쿼리 파라미터를 클래스의 프로퍼티와 매핑한다.@RequestParam 어노테이션을 사용하면 매개변수가 쿼리 파라미터로 넘어오는 것이 아니라, 매개변수 자체가 요청의 특정 파라미터와 매핑되도록 기대한다.따라서 클래스 타입의 객체를 @RequestParam으로 직접 받는다면 쿼리 파라미터 매핑이 자동으로 이뤄지지 않는다.<br> DTO(Request)public record endOfWorkRequest(@NotNull long id) {public record GetCommuteRecordRequest(@NotNull long id, @DateTimeFormat(pattern = "yyyy-MM") YearMonth yearMonth) { }public record startOfWorkRequest(@NotNull long id) { public Commute toEntity(Member member){ return Commute.builder() .member(member) .build(); }💡 저번 피드백에서 record 사용법을 배워서 record로 생성하였습니다.<br>* Domain@Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @AttributeOverrides({ @AttributeOverride(name= "createdAt", column = @Column(name= "start_of_work")), @AttributeOverride(name= "updatedAt", column = @Column(name= "end_of_work")) }) public class Commute extends BaseEntity { //BaseEntity를 상속받음 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private boolean attendance = true; // 출근 상태 @ManyToOne(fetch= FetchType.LAZY) private Member member; public void endOfWork(){ this.attendance = false; } @Builder public Commute(Member member){ this.member = member; } }  💡 저번 피드백에서 @EntityListeners(AuditingEntityListener.class) 어노테이션을 사용하는추상 클래스 생성을 권유 받았는데, 만들고 나니 써먹고 싶어져서 추상 클래스를 상속받고, attendance값의 변경을 통해 출/퇴근을 구현해보려고 합니다.<br> 💡 처음에는 Member 도메인과 1 : N 양방향 연관 관계로 설계를 해뒀었는데,블로그를 작성하면서 확인해보니, Member에 맺어둔 @OneToMany를 전혀 사용을 안했다는걸 깨닫고, Commute의 @ManyToOne 단방향 연관관계만 살려두었습니다.<br>* Service@Service @Slf4j @RequiredArgsConstructor public class CommuteService { private final CommuteRepository commuteRepository; private final MemberRepository memberRepository;@Transactional public void startOfWork(startOfWorkRequest request) { Member member = findMemberById(request.id()); Commute latestCommute = findLatestCommuteByMember(member); if (latestCommute.isAttendance()) throw new AbsentCheckOutException(); //이전 기록 퇴근확인 boolean isAlreadyAttendance = LocalDate.now().equals(LocalDate.from(latestCommute.getCreatedAt())); if (isAlreadyAttendance) throw new AlreadyAttendanceException(); //당일 출근기록 확인 commuteRepository.save(request.toEntity(member)); }@Transactional public void endOfWork(@RequestBody endOfWorkRequest request) { Member member = findMemberById(request.id()); Commute latestCommute = findLatestCommuteByMember(member); if (!latestCommute.isAttendance()) throw new AlreadyDepartureException(); latestCommute.endOfWork(); //변경감지 자동저장 }@Transactional public GetCommuteRecordResponse GetCommuteRecord(GetCommuteRecordRequest request) { findMemberById(request.id()); List<GetCommuteDetail> commuteDetailList = findCommuteListByMemberIdAndStartOfWork(request); Long sum = commuteDetailList.stream() .map(GetCommuteDetail::workingMinutes) .reduce(0L, Long::sum); //commuteDetailList에서 workingMinutes를 조회, reduce로 합을 반환 return new GetCommuteRecordResponse(commuteDetailList, sum); } private Member findMemberById(long id) { return memberRepository.findById(id).orElseThrow(MemberNotFoundException::new); } private Commute findLatestCommuteByMember(Member member) { return commuteRepository.findLatestCommuteByMemberId(member.getId()) .orElseThrow(CommuteNotFoundException::new); } private List<GetCommuteDetail> findCommuteListByMemberIdAndStartOfWork(GetCommuteRecordRequest request) { List<Commute> commuteList = commuteRepository.findCommuteListByMemberIdAndStartOfWork(request.id(), request.yearMonth().getYear(), request.yearMonth().getMonth().getValue()); if (commuteList.isEmpty()) throw new CommuteNotFoundException(); //해당범위에 통근기록 존재 X return commuteList.stream().map(GetCommuteDetail::from).toList(); //CommuteDetail으로 변환 } } 💡**고민 2.**현재 startOfWork의 isAlreadyAttendance(전 기록 퇴근처리 확인) 기능이 과연 필요한가 고민중입니다.야근 후, 12시가 넘어서 퇴근을 찍지않고 출근을 찍을 경우를 대비해서 넣어뒀는데,퇴근을 찍지않으면 그 날의 CreatedAt(출근시간)과 UpdatedAt(퇴근시간) 이 동일하여근무시간이 0으로 찍히기 떄문에 본인이 알아서 인사팀에 찾아가지 않을까요? 🤔<br>* DTO(Reponse)@Builder public record GetCommuteDetail(LocalDate date, long workingMinutes) { public static GetCommuteDetail from(Commute commute){ Duration duration = Duration.between(commute.getCreatedAt(), commute.getUpdatedAt()); return GetCommuteDetail.builder() .date(commute.getCreatedAt().toLocalDate()) .workingMinutes(duration.toMinutes()) .build(); } }public record GetCommuteRecordResponse(List<GetCommuteDetail> detail, long sum) { }💡 DTO 반환 과정에서 Duration을 활용해 생성시간과 수정시간의 차이를 분으로 바꿔줬습니다.<br>* Repositorypublic interface CommuteRepository extends JpaRepository<Commute, Long> { @Query("SELECT latestcommute FROM Commute latestcommute WHERE latestcommute.member.id = :memberId AND latestcommute.createdAt = (SELECT MAX(commute.createdAt) FROM Commute commute WHERE commute.member.id = :memberId)") Optional<Commute> findLatestCommuteByMemberId(Long memberId); @Query("SELECT commute FROM Commute commute WHERE commute.member.id= :memberId AND FUNCTION('YEAR', commute.createdAt)= :year AND FUNCTION('MONTH', commute.createdAt)= :month") List<Commute> findCommuteListByMemberIdAndStartOfWork(Long memberId, int year, int month); } 💡 다른분들께 배운 JPQL 활용해보기* findLatestCommuteByMemberId = SELECT MAX를 통해 가장 최근의 통근 기록을 조회한다.* findCommuteListByMemberIdAndStartOfWork = GetCommuteRecordRequest 의①`MemberId`, ②`year(request.yearMonth().getYear())`, ③`month(request.getMonth().getValue())`값을 만족하는 모든 Commute를 조회한다. # 구현 결과 ①출근 기능 등록된 직원은 출근을 할 수 있어야 한다. 출근의 경우 이름은 동명이인이 있을 수 있으므로, DB에 등록된 ID를 기준으로 처리된다.![](https://velog.velcdn.com/images/vosxja1/post/b9cc82f7-5bc9-4fa0-815f-11130f5d5a05/image.png)![](https://velog.velcdn.com/images/vosxja1/post/85ac2b6b-4e68-42c2-997b-81021840c187/image.png)<br> ②퇴근 기능 출근한 직원은 퇴근을 할 수 있어야 한다. 퇴근 역시 DB에 등록된 ID를 기준으로 처리된다.![](https://velog.velcdn.com/images/vosxja1/post/3dd809d4-5a5a-4942-be1e-af6d74c8c3a4/image.png)![](https://velog.velcdn.com/images/vosxja1/post/a9cf93c1-93ee-4e3b-bd17-70dc86d99f7e/image.png)<br> ③특정 직원의 날짜별 근무시간을 조회하는 기능 특정 직원 id와 2024-01과 같이 연/월을 받으면, 날짜별 근무 시간과 총 합을 반환해야 한다. 이때 근무 시간은 분단위로 계산된다. 예를 들어, 1번 id를 갖는 직원에 대해 2024-01을 기준으로 조회하면, 다음과 같은 응답이 반환되어야 한다.![](https://velog.velcdn.com/images/vosxja1/post/01d2ef12-b8f1-42bf-b206-50e738bf0a29/image.png)![](https://velog.velcdn.com/images/vosxja1/post/8c8c536e-2f3f-4ebd-a869-19956daa1780/image.png) ④edge-case 등록되지 않은 직원이 출근 하려는 경우![](https://velog.velcdn.com/images/vosxja1/post/b47982b1-bfb6-4f34-981d-46e816e2ffe1/image.png)  출근한 직원이 또 다시 출근하려는 경우![](https://velog.velcdn.com/images/vosxja1/post/16e45880-37e3-4d5d-bc03-97a3e2b69365/image.png) 퇴근하려는 직원이 출근하지 않았던 경우![](https://velog.velcdn.com/images/vosxja1/post/808de891-11e2-41dc-ab8c-c3747d2e3d59/image.png) 그 날, 출근했다 퇴근한 직원이 다시 출근하려는 경우![](https://velog.velcdn.com/images/vosxja1/post/2ba0fb2b-3b3b-4a55-9ad7-7168c6f811aa/image.png)  코드 리뷰 Step 02피드백 1.@Query("SELECT latestcommute FROM Commute latestcommute WHERE latestcommute.member.id = :memberId AND latestcommute.createdAt = (SELECT MAX(commute.createdAt) FROM Commute commute WHERE commute.member.id = :memberId)") ![](https://velog.velcdn.com/images/vosxja1/post/f204010b-c67e-45be-8702-48f3939136de/image.png) 📌 서브 쿼리를 이용해 가장 최근 기록을 가져오셨군요! 생각하지 못한 방법 또 하나 배우고 갑니다! 😊추가로 질문을 드리자면 저 같은 경우는 서브쿼리는 성능이 걱정되어 불가피한 상황이 아닌 경우에는 지양하는 편인데 영훈님은 어떻게 생각하시는지 궁금합니다...!<br> 해결 과정 1. 1. JPQL을 활용해 Join ORDER BY LIMIT 으로 변경 우선 쿼리문을 수정했습니다.@Query("SELECT latestcommute FROM Commute latestcommute JOIN latestcommute.member member WHERE member.id = :memberId ORDER BY latestcommute.createdAt DESC") 하지만 LIMIT를 활용하려 하니, JPQL 자체적으론 LIMIT를 지원하지 않는다는 사실을 알게 되었습니다.@Query("SELECT latestcommute FROM Commute latestcommute JOIN latestcommute.member member WHERE member.id = :memberId ORDER BY latestcommute.createdAt DESC") List<Commute> findFirstByMemberId(long MemberId);# 실제 쿼리 조회 Hibernate: select c1_0.id, c1_0.attendance, c1_0.start_of_work, c1_0.member_id, c1_0.end_of_work from commute c1_0 join member m1_0 on m1_0.id=c1_0.member_id where m1_0.id=? order by c1_0.start_of_work desc 받아온 List를 Service 단에서 처리하려다, 모든 List를 받아오는것이 마음에 들지 않아서 JPA에 대해 조금 더 찾아보았습니다.2. JPA 쿼리 메서드 활용 JPQL에서 LIMIT을 지원하지 않는데, 굳이 JPQL을 사용할 이유가 없었습니다.Optional<Commute> findFirstByMemberIdOrderByCreatedAtDesc(Long memberId); 해당 쿼리 메서드 조회시 실제 전송되는 쿼리문Hibernate: select c1_0.id, c1_0.attendance, c1_0.start_of_work, c1_0.member_id, c1_0.end_of_work from commute c1_0 left join member m1_0 on m1_0.id=c1_0.member_id where m1_0.id=? order by c1_0.start_of_work desc limit ?💡 서브쿼리를 사용하지 않고, Left Join을 통해 최근순으로 정렬하고, LIMIT을 통해 제일 처음(가장 최근)`Commute`을 반환합니다.피드백 2.public record startOfWorkRequest(@NotNull long id) { public Commute toEntity(Member member){ return Commute.builder() .member(member) .build(); } }![](https://velog.velcdn.com/images/vosxja1/post/35fca9b4-8685-49ca-905e-58ddfdb26abd/image.png)📌 요청값 검증 처리를 위해 @NotNull 등 어노테이션을 사용하고 계신데,요청값에 대해 예외가 발생하면 어떤식으로 응답이 나가는지 알고 계신가요?몇몇 커스텀 예외에 대해 핸들링 해서 일관된 응답 형식으로 응답이 나가고 있는데,요청값 검증 예외는 다른 형식으로 응답이 나갈것 같습니다.<br>해결 과정 2.Edge-Case의 Exception을 우선적으로 설정하느라 @Valid 로 값 검증을 하고 있으면서도ExceptionHandler 에서 정작 ValidException 처리하는 메서드를 만들어 놓지 않았다는걸 깨달았습니다.따로 ValidException 처리를 하지 않았기때문에, 요청값 검증 예외가 발생한다면,전부 500 Internal-server-error로 처리 되었을 것입니다. @ExceptionHandler(MethodArgumentNotValidException.class) protected ResponseEntity<ErrorResponse> handle(MethodArgumentNotValidException e){ e.getStackTrace(); log.error("MethodArgumentNotValidException", e); return createErrorResponse(ErrorCode.INVALID_INPUT_VALUE); }![](https://velog.velcdn.com/images/vosxja1/post/b44cfa4c-1c57-450f-9afa-50fc8de0eb9c/image.png)💡 MethodArgumentNotValidException 예외가 발생한다면, 400 Bad Request와 올바르지 않은 입력값이라는 메세지가 출력되도록 했습니다.미니 프로젝트 Step 03구현 내용연차 신청 이제부터 직원은 연차를 신청할 수 있습니다.연차는 무조건 하루 단위로만 사용이 가능합니다.올해 입사한 직원은 11개의 연차를, 그 외 직원은 15개의 연차를 사용할 수 있습니다.연차를 사용하기 위해서는 연차 사용일을 기준으로 며칠전 연차 등록을 해야 합니다.연차를 등록하기만 하면, 매니저의 허가 없이 연차가 바로 적용됩니다.단, 며칠 전에 연차를 등록해야 하는지는 팀 마다 다르게 적용됩니다.예를 들어 A팀은 하루 전에만 등록하면 연차를 사용할 수 있지만, B팀은 7일 전에 등록해야 연차를 사용할 수 있습니다.연차 조회 각 직원은 id를 이용해 올해 사용하지 않고 남은 연차를 확인할 수 있습니다.특정 직원의 날짜별 근무시간을 조회하는 기능 Version02연차를 신청할 수 있게되며, project_Step02 에서 개발했던 기능도 조금 변경되어야 합니다.만약 연차를 사용했다면, UsingDayOff : true가 반환되어야 합니다. { "detail": [ { "date": "2024-01-01", "workingMinutes": 480, "usingDayOff": false // 연차를 사용하지 않았으니, false가 반환 }, { "date": "2024-01-02", "workingMinutes": 0, "usingDayOff": true // 연차를 사용한 날은 true가 반환 }, ... // 2024년 1월 31일까지 존재할 수 있다. ] "sum": 10560 } > 📌 edge-case연차를 사용한 직원이 출근하려는 경우 각 팀별 설정 연차 등록일 이전에 연차를 사용하려하는 경우해당일에 이미 연차를 등록한 경우 과거로 연차를 사용하려 하는 경우올해의 연차를 모두 사용한 경우과정💡 고민 1.연차 신청과 연차 조회는 쉽게 만들 수 있을거 같은데..특정 직원의 날짜별 근무시간을 조회하는 기능은 어떻게 처리할지, 만약 그 방법으로 처리한다면필요한 Column은 무엇인지, 비즈니스 로직은 어디서 어떻게 처리할지가 고민이었습니다.<br>TableCREATE TABLE annual ( id bigint auto_increment, annual_date_leave datetime, member_id bigint, primary key (id) ); 📌 어떻게 구현할지 생각해보기 Step02에서 만들었던 특정 직원의 날짜별 근무시간을 조회하는 기능을 처리할 때,해당 연월에 연차를 사용했는지 체크하고, 연차 사용기록이 존재한다면 연차를 사용한 요일 : {date}, 일한 시간 : 0, usingDayOff : true 로 반환해주려 합니다. 올해 사용하지 않고 남은 연차 조회 기능은 간단합니다. LocalDate.now() 로 구한 현재 년도와 MemberId 로 사용한 연차의 갯수를 구하고, ChronoUnit.Years.between을 활용해 입사년도와 LocalDate.now() 의 차이가 1보다 크거나 같다면 15, 그렇지 않다면 11 에서 위에서 구한 사용한 연차의 갯수를 빼주면 됩니다.만약 연차의 개수가 0보다 작거나 같다면, CustomException으로 예외를 던져주겠습니다.CREATE TABLE annual(id bigint auto_increment,annual_date_leave datetime,member_id bigint,primary key (id)); 📌 기존 team 테이블 수정팀별 연차 등록일을 설정해주기 위해 team 테이블을 수정하였습니다<br>Controller@RestController @RequiredArgsConstructor public class AnnualLeaveController { private final AnnualLeaveService annualLeaveService; @PostMapping("/annual") public ResponseEntity<Void> registerAnnualLeave(@RequestBody @Valid RegisterAnnualLeaveRequest request) { annualLeaveService.registerAnnualLeave(request); return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping("/annual") public ResponseEntity<GetRemainAnnualLeavesResponse> getRemainAnnualLeaves(@Valid GetRemainAnnualLeavesRequest request) { long remainAnnualLeaves = annualLeaveService.getRemainAnnualLeaves(request); GetRemainAnnualLeavesResponse response = new GetRemainAnnualLeavesResponse(remainAnnualLeaves); return ResponseEntity.ok(response); } }@RestController @RequiredArgsConstructor public class TeamController { private final TeamService teamService; @PostMapping("/team") public ResponseEntity<Void> createTeam(@RequestBody CreateTeamRequest request) { teamService.createTeam(request); return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping("/team") public ResponseEntity<List<GetAllTeamsResponse>> getAllTeams() { List<GetAllTeamsResponse> allTeamsList = teamService.getAllTeams(); return ResponseEntity.ok().body(allTeamsList); } @PutMapping("/team/day-before-annual") public void updateDayBeforeAnnual(@RequestBody @Valid UpdateDayBeforeAnnualRequest request){ teamService.updateDayBeforeAnnual(request); } } 💡 각 팀별 연차 등록일 설정을 위해 TeamController 에 updateDayBeforeAnnual 메서드를추가하였습니다.<br>DTOAnnualLeavepublic record GetRemainAnnualLeavesRequest(@NotNull long id) { }public record RegisterAnnualLeaveRequest(@NotNull long id, @Future LocalDate date) { public AnnualLeave toEntity(Member member){ return AnnualLeave.builder() .annualDateLeave(date) .member(member) .build(); } }public record GetRemainAnnualLeavesResponse(long remainAnnualLeaves) { }💡 @Future 어노테이션을 사용하여 연차를 과거로 떠나려는 시도를 막았습니다. 😊<br> Commutepublic record GetCommuteRecordRequest(@NotNull long id, @DateTimeFormat(pattern = "yyyy-MM") YearMonth yearMonth) { public int getYear(){ return this.yearMonth.getYear();} public int getMonth(){ return this.yearMonth.getMonth().getValue(); } }@Builder public record GetCommuteDetail(LocalDate date, long workingMinutes, boolean usingDayOff) { public static GetCommuteDetail from(Commute commute){ Duration duration = Duration.between(commute.getCreatedAt(), commute.getUpdatedAt()); return GetCommuteDetail.builder() .date(commute.getCreatedAt().toLocalDate()) .workingMinutes(duration.toMinutes()) .usingDayOff(false) .build(); } public static GetCommuteDetail from(AnnualLeave annualLeave){ return GetCommuteDetail.builder() .date(annualLeave.getAnnualDateLeave()) .workingMinutes(0) .usingDayOff(true) .build(); } } 💡 특정 직원의 날짜별 근무시간 조회시, 범위 내 연차 사용기록이 있으면 CommuteResponseDTO로변환하기위해 GetCommuteDetail를 수정하였고,좀더 쉽게 연도와 월 값을 구하기 위해 requestDTO에서 요청하는 날의 값을 가져올 수 있게GetCommuteRecordRequest를 수정하였습니다.<br>Domain@Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class AnnualLeave { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private LocalDate annualDateLeave; @ManyToOne(fetch = FetchType.LAZY) private Member member; @Builder public AnnualLeave(LocalDate annualDateLeave, Member member) { this.annualDateLeave = annualDateLeave; this.member = member; } }<br>Service@Service @Slf4j @RequiredArgsConstructor public class AnnualLeaveService { private final AnnualLeaveRepository annualLeaveRepository; private final MemberService memberService; @Transactional public void registerAnnualLeave(RegisterAnnualLeaveRequest request){ Member member = memberService.findMemberById(request.id()); if(isAcceptTeamPolicy(member, request)) throw new AcceptTeamPolicyException(); if(isAlreadyUsingAnnualLeaves(member, request.date())) throw new AlreadyRegisteredException(); if(isRemainAnnualLeaves(member)) throw new RemainAnnualLeavesException(); annualLeaveRepository.save(request.toEntity(member)); } @Transactional(readOnly = true) public long getRemainAnnualLeaves(GetRemainAnnualLeavesRequest request){ Member member = memberService.findMemberById(request.id()); return remainAnnualLeaves(member); } private boolean isAcceptTeamPolicy(Member member, RegisterAnnualLeaveRequest request){ return ChronoUnit.DAYS.between(LocalDate.now(), request.date()) < member.getTeam().getDayBeforeAnnual(); } private long remainAnnualLeaves(Member member){ // 남은 연차 계산 & 연차 조회시 반환 long maxAnnualLeave = ChronoUnit.YEARS.between(member.getCreatedAt(), LocalDateTime.now()) >= 1 ? 15L : 11L; long usedThisYear = annualLeaveRepository.countByMemberId(member.getId(), YearMonth.now().getYear()); return maxAnnualLeave - usedThisYear; } private boolean isRemainAnnualLeaves(Member member){ return remainAnnualLeaves(member) <= 0; } public boolean isAlreadyUsingAnnualLeaves(Member member, LocalDate date){ return annualLeaveRepository.existsByMemberIdAndAnnualDateLeaveEquals(member.getId(), date); } public List<AnnualLeave> findAnnualLeavesByMemberIdAndYearMonth(long memberId, YearMonth request){ int year = request.getYear(); int month = request.getMonth().getValue(); // return annualLeaveRepository.findAllAnnualLeavesByMemberIdAndYearMonth(memberId, year, month); } }@Service @Slf4j @RequiredArgsConstructor public class CommuteService { // @@생략 @Transactional(readOnly = true) public GetCommuteRecordResponse GetCommuteRecord(GetCommuteRecordRequest request) { memberService.findMemberById(request.id()); List<GetCommuteDetail> commuteDetailList = findCommuteListByMemberIdAndStartOfWork(request); long sum = commuteDetailList.stream() .mapToLong(GetCommuteDetail::workingMinutes) .sum(); //commuteDetailList에서 workingMinutes를 조회, reduce로 합을 반환 return new GetCommuteRecordResponse(commuteDetailList, sum); } private List<GetCommuteDetail> findCommuteListByMemberIdAndStartOfWork(GetCommuteRecordRequest request) { List<Commute> commuteList = commuteRepository .findCommuteListByMemberIdAndStartOfWork(request.id(), request.getYear(), request.getMonth()); if (commuteList.isEmpty()) throw new CommuteNotFoundException(); //해당범위에 통근기록 존재 X? -> 통근기록없음 예외처리 List<GetCommuteDetail> commuteDetailList = commuteList.stream() .map(GetCommuteDetail::from) .collect(Collectors.toList()); //통근기록을 CommuteDetail으로 변환 List<AnnualLeave> annualLeaveLeavesList = annualLeaveService .findAnnualLeavesByMemberIdAndYearMonth(request.id(), request.yearMonth()); // 연차기록찾기 (오늘보다 미래의 연차기록은 가져오지않음) mergeAndSort(commuteDetailList, annualLeaveLeavesList); //Merge하고 sort함 return commuteDetailList; } private void mergeAndSort(List<GetCommuteDetail> commuteDetailList, List<AnnualLeave> annualLeaveLeavesList) { if (annualLeaveLeavesList != null) { //해당범위 연차기록이 있으면 Merge List<GetCommuteDetail> annualLeavesToDetails = annualLeaveLeavesList.stream() .map(GetCommuteDetail::from) .toList(); commuteDetailList.addAll(annualLeavesToDetails); } commuteDetailList.sort(Comparator.comparing(GetCommuteDetail::date)); //있던없던 sort는 함 } } 💡 특정 직원의 날짜별 근무시간 조회시 연차목록을 조회하기 위해 CommuteService에서 AnnualLeaveList를 참조하도록 하였습니다.<br> Repositorypublic interface AnnualLeaveRepository extends JpaRepository<AnnualLeave, Long> { boolean existsByMemberIdAndAnnualDateLeaveEquals(long memberId, LocalDate annualDate); @Query("SELECT COUNT(*) FROM AnnualLeave annual " + "WHERE annual.member.id = :memberId " + "AND FUNCTION('YEAR', annual.annualDateLeave) = :year") long countByMemberId(long memberId, int year); @Query("SELECT annual FROM AnnualLeave annual " + "WHERE annual.member.id = :memberId " + "AND FUNCTION('YEAR', annual.annualDateLeave) = :year " + "AND FUNCTION('MONTH', annual.annualDateLeave) = :month " + "AND annual.annualDateLeave <= CURRENT_DATE()") List<AnnualLeave> findAllAnnualLeavesByMemberIdAndYearMonth(long memberId, int year, int month); } countByMemberId는 남은 연차를 계산할때 사용합니다. 기본적으로 memberId와 현재년도로조회하여, 올해 사용한 연차의 갯수를 반환합니다. findAllAnnualLeavesByMemberIdAndYearMonth는 memberId, 요청년도, 요청월,로 조회하며,현재 날짜 이전의 연차 사용기록 리스트를 반환합니다.현재 날짜 이전으로 설정하지 않는다면, 03월 08일에 2024-03월 근무기록 조회시,03월 15일에 신청한 연차 기록이 날짜별 근무시간 조회로 반환될 것입니다.뭔가 굉장히 어색하고 만약 내가 서비스 이용자였다면, 굉장히 유저 경험이 좋지 않았을듯 하여 수정하였습니다. 구현 결과연차 신청![](https://velog.velcdn.com/images/vosxja1/post/70105182-d948-4f79-be14-7982f027b647/image.png) 📌 MemberId : 2 인 Member의 2024-12-12연차 신청![](https://velog.velcdn.com/images/vosxja1/post/0e2366b3-f3fc-46ec-9be5-e827736d1e04/image.png) 📌 2024-12-12에 연차등록 완료<br> 연차 조회![](https://velog.velcdn.com/images/vosxja1/post/ecde2bdb-201d-4ec2-b26c-1c21978f72a5/image.png) 📌 Id : 6인 member의 남은 연차 조회Hibernate: select m1_0.id, m1_0.birthday, m1_0.work_start_date, m1_0.name, m1_0.role, m1_0.team_id from member m1_0 where m1_0.id=? Hibernate: select count(*) from annual_leave al1_0 where al1_0.member_id=? and year(al1_0.annual_date_leave)=?📌 전송되는 쿼리문![](https://velog.velcdn.com/images/vosxja1/post/9d9f29cb-41c5-4d8a-bbc0-6d106f643c16/image.png)![](https://velog.velcdn.com/images/vosxja1/post/c16e3777-97e7-4eaa-a7d1-9fee82f77695/image.png) 📌 사용한 연차 수가 5개이지만,입사한지 1년이 지나지 않았기 때문에 남은 연차 : 6 이 반환된걸 확인할 수 있습니다. 특정 직원의 날짜별 근무시간을 조회하는 기능 Version02![](https://velog.velcdn.com/images/vosxja1/post/51f59cb9-021f-443f-81f5-fe004242a5d7/image.png) 📌 2024-03월 근무기록 조회Hibernate: #멤버 검색 select m1_0.id, m1_0.birthday, m1_0.work_start_date, m1_0.name, m1_0.role, m1_0.team_id from member m1_0 where m1_0.id=? Hibernate: #요청 년,월 근무기록 조회 select c1_0.id, c1_0.attendance, c1_0.start_of_work, c1_0.member_id, c1_0.end_of_work from commute c1_0 where c1_0.member_id=? and year(c1_0.start_of_work)=? and month(c1_0.start_of_work)=? Hibernate: #요청 년, 월 연차기록 조회(미래의 연차기록은 조회X) select al1_0.id, al1_0.annual_date_leave, al1_0.member_id from annual_leave al1_0 where al1_0.member_id=? and year(al1_0.annual_date_leave)=? and month(al1_0.annual_date_leave)=? and al1_0.annual_date_leave<=current_date 📌 전송되는 쿼리문{ "detail": [ { "date": "2024-03-01", "workingMinutes": 867, "usingDayOff": false }, { "date": "2024-03-02", "workingMinutes": 0, "usingDayOff": true }, { "date": "2024-03-03", "workingMinutes": 0, "usingDayOff": true }, { "date": "2024-03-04", "workingMinutes": 0, "usingDayOff": true }, { "date": "2024-03-05", "workingMinutes": 0, "usingDayOff": true }, { "date": "2024-03-06", "workingMinutes": 619, "usingDayOff": false }, { "date": "2024-03-07", "workingMinutes": 685, "usingDayOff": false }, { "date": "2024-03-08", "workingMinutes": 0, "usingDayOff": true } ], "sum": 2171 }📌 연차를 사용했을 경우, usingDayOff : true 반환📌 edge-case 연차를 사용한 직원이 출근하려는 경우![](https://velog.velcdn.com/images/vosxja1/post/686b18de-60a0-4cbe-8ea5-eccc4a07cd6f/image.png) <br>각 팀별 설정 연차 등록일 이전에 연차를 사용하려하는 경우![](https://velog.velcdn.com/images/vosxja1/post/08615a7e-58fc-415a-afa8-16d0690c9212/image.png) <br>해당일에 이미 연차를 등록한 경우![](https://velog.velcdn.com/images/vosxja1/post/268a6a7e-4395-443a-a256-c11577184bfc/image.png) <br>과거로 연차를 사용하려 하는 경우![](https://velog.velcdn.com/images/vosxja1/post/2114d5ea-9c0a-4f9c-a1be-09b0f60d7238/image.png) <br>#### 올해의 연차를 모두 사용한 경우![](https://velog.velcdn.com/images/vosxja1/post/4781e9db-70f5-4356-8b7e-7aff44fc1511/image.png)코드 리뷰 Step 03피드백 1.ChronoUnit.DAYS.between(LocalDate.now(), request.date()) < member.getTeam().getDayBeforeAnnual(); } private long remainAnnualLeaves(Member member){ // 남은 연차 계산 & 연차 조회시 반환 long maxAnnualLeave = ChronoUnit.YEARS.between(member.getCreatedAt(), LocalDateTime.now()) >= 1 ? 15L : 11L;![](https://velog.velcdn.com/images/vosxja1/post/1861c4b0-1b0f-47b0-aabc-7318230fff52/image.png) 📌 매직 넘버를 상수로 처리하면 가독성이 더 좋아질 것 같은데, 어떻게 생각하시나요?<br>나의 답변 1.![](https://velog.velcdn.com/images/vosxja1/post/864ad1d5-caa6-4462-969d-9ab836256c33/image.png)<br>해결 과정 1.long maxAnnualLeave = ChronoUnit.YEARS.between(member.getCreatedAt(), LocalDateTime.now()) >= 1 ? 15L : 11L;안그래도 이 부분의 15L : 11L 가 조금 명확하지 않다고 생각하여 (15L, 11L이 무슨 숫자인지 알 수가 없다.)enum처리를 하려 합니다.@RequiredArgsConstructor public enum JoinDate { OVER_ONE_YEAR(15L), UNDER_ONE_YEAR(11L); private final long maxAnnualLeaves; public long getAnnualLeaves(){return maxAnnualLeaves;} }long maxAnnualLeave = ChronoUnit.YEARS .between(member.getCreatedAt(), LocalDateTime.now()) >= 1 ? JoinDate.OVER_ONE_YEAR.getAnnualLeaves() : JoinDate.UNDER_ONE_YEAR.getAnnualLeaves(); 💡 JoinDate 를 enum으로 생성하고, maxAnuualLeave를 enum으로 처리하였습니다. 피드백 2.//해당범위에 통근기록 존재 X? -> 통근기록없음 예외처리 List<GetCommuteDetail> commuteDetailList = commuteList.stream() .map(GetCommuteDetail::from) .collect(Collectors.toList()); //통근기록을 CommuteDetail으로 변환![](https://velog.velcdn.com/images/vosxja1/post/2bce0c18-9dea-4306-a906-266e281e72e3/image.png)📌`Collectors.toList()`와 Stream.toList()의 차이를 아시나요? Collectors.toList()를 사용한 이유가 궁금합니다!!<br>나의 답변 2.![](https://velog.velcdn.com/images/vosxja1/post/4bd8049d-dd4e-4f01-a0cb-6be2b8038e30/image.png)<br>해결 과정 2.안그래도 변환 가능한 리스트를 반환하는게 조금 신경 쓰였는데,MergeAndSort 종료 후, Merge가 완료된 CommuteList를 Collections.unmodifiableList()를 통해 불변 List로감싸서 반환하도록 처리하겠습니다.private List<GetCommuteDetail> findCommuteListByMemberIdAndStartOfWork(GetCommuteRecordRequest request) { List<Commute> commuteList = commuteRepository .findCommuteListByMemberIdAndStartOfWork(request.id(), request.getYear(), request.getMonth()); if (commuteList.isEmpty()) throw new CommuteNotFoundException(); //해당범위에 통근기록 존재 X? -> 통근기록없음 예외처리 List<GetCommuteDetail> commuteDetailList = commuteList.stream() .map(GetCommuteDetail::from) .collect(Collectors.toList()); //통근기록을 CommuteDetail으로 변환 List<AnnualLeave> annualLeaveLeavesList = annualLeaveService // 연차기록찾기 (오늘보다 미래의 연차기록은 가져오지않음) .findAnnualLeavesByMemberIdAndYearMonth(request.id(), request.yearMonth()); mergeAndSort(commuteDetailList, annualLeaveLeavesList); // .addAll()을 통한 merge return Collections.unmodifiableList(commuteDetailList); // 불변리스트로 변환 후 반환 💡 return시 commuteDetailList를 Collections.unmodifiableList()로 감싸서 불변List로 만들어 주었습니다.GitHub 

스프링백엔드

영후이

[인프런 워밍업 클럽 0기 BE] - 두 번째 발걸음

강의 수강  일주일 동안 학습했던 내용을 요약해주세요.2주차(6일차 ~ 14일차)의 학습 내용.  스프링 컨테이너, JPA 사용, 그리고 AWS EC2 서버를 이용한 배포까지 진도를 나갔다. 일주일 간의 학습 내용에 대한 간단한 회고를 작성해 주세요.-> 강의를 들으며 작성한 강의노트를 바탕으로 작성    19~22강. 역할의 분리와 스프링 컨테이너Spring Container (클래스 저장소)-> 데이터소스/JDBCTemplate/환경...-> 관계를 파악하고 자동으로 의존성 설정을 해준다. Spring Bean-> 스프링 컨테이너 안으로 들어간 클래스를 스프링 빈이라고 한다.-> JDBCTemplate도 스프링 빈으로 등록되어있다.-> @Service / @Repository 어노테이션으로 Service와 Repository도 스프링 빈으로 등록할 수 있다. @PrimaryBookMeomeoryRepository 와 BookMySqlRepository 둘 다 @Repository 선언이 있을 경우,스프링 컨테이너도 어떤 레포지토리를 선택해야할지 모른다.그럴때 @Primary 어노테이션 사용하여 우선권을 정해줄 수 있다. @Configuration클래스에 붙이는 어노테이션.@Bean 을 사용할 때 함께 사용해 주어야 한다. @Bean메소드에 붙이는 어노테이션.메소드에서 반환되는 객체를 스프링 빈에 등록한다. @Component주어진 클래스들을 '컴포넌트'로 간주한다.이 클래스들은 스프링 서버가 시작될 때, 자동으로 감지된다.@Repository, @Service, @RestController 등등에도 @Component 처리가 되어있다. -> @Component , 언제 사용할까?1) @Controller, @Service, @Repository 모두 아니고,2) 개발자가 직접 작성한 클래스를 Spring Bean으로 등록할 때사용되기도 한다. 정리• 우리가 개발과정에서 만들게 되는 class 들은 @Service나 @Repository를 사용하는것이 좋다.( @Configuration 과 @Bean을 통해 구현할 순 있다.)• Spring Bean 을 주입받는 방식에는 3가지가 있는데, 1) 생성자를 통한 주입방식 -> 가장 권장 됨2) @Setter 와 @AutoWired를 통한 주입. -> 누군가가 setter를 사용하면 오작동할 수 있다.3) 필드에 직접 @Autowired 사용 -> 테스트를 어렵게 만드는 요인. 23~29강. Spring Data JPA를 사용한 데이터베이스 조작SQL을 직접 작성하는 것의 단점1) 문자열을 작성하기 때문에 실수할 수 있고, 실수를 인지하는 시점이 느리다.-> 자바 문법이 아니라 그저 문자열일 뿐이기 때문에 컴파일 시점에 에러가 발견되는 것이 아니라, 런타임 시점에 알게된다.2) 특정 데이터베이스에 종속적이게 된다.3) 반복 작업이 많아진다. 테이블을 하나 만들때마다 CRUD 쿼리가 항상 필요함.4) 데이터베이스의 테이블과 개ㅑ객체는 패러다임이 다르다. Java Persistence API (JPA)- 자바 진영의 ORM ?Object : 자바의 객체Realational : 관계형 DB의 TableMapping : 둘을 짝찟는다.-> 객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java진영의 규칙. HIBERNATE 객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙을 구현한 구현체. (내부적으로 JDBCTemplate 사용) @Entity스프링이 해당 객체와 테이블을 같은 것으로 인식한다.@Entity는 반드시 기본 생성자가 필요하다. (public or protected) @Id해당 필드값을 데이터베이스 테이블의 PrimaryKey 로 인식함. @Columnnull이 들어갈 수 있는지 여부, 길이 제한, DB 컬럼 이름과의 매핑 등등을 설정할 수 있다.만약 @Column 어노테이션이 달려있는 변수의 이름이 데이터베이스의 컬럼명과 동일하다면, name 속성은 생략 가능하다. Spring Data JPASpring Data JPA는 복잡한 JPA 코드를 쉽게 사용할 수 있도록 바꿔준다.Spring DATA JPA -> Hibernate(JPA를 구현) -> JdbcTemplate 이런식으로 접근하여 JDBC를 사용하게 된다. 트랜잭션(Transaction)쪼갤 수 없는 업무의 최소단위.ex) 쇼핑몰에서 물건을 주문시, 1) 주문 기록을 저장, 2) 포인트 저장, 3) 결제 기록 저장-> 하나라도 실패하면 전부다 실패처리 영속성 컨텍스트스프링에선 트랜잭션이 시작되면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다. 영속성 컨텍스트의 특수능력 4가지[1] 변경감지 : 영속성 컨텍스트 안에서 불러와진 Entity는 명시적으로 save하지 않아도, 변경을 감지해 자동으로 저장된다.[2] 쓰기지연 : DB의 INSERT/UPDATE/DELETE Sql을 바로 날리는 것이 아니라, 트랜잭션이 commit될 때 모아서 한번만 날린다.[3] 1차캐싱 : ID를 기준으로 Entity를 기억해뒀다가, 다시 해당 Entity를 조회해야할 경우, 캐싱된 객체를 반환하고추가적인 Sql문의 생성을 막는다.[4] 지연로딩 : 연결되어 있는 객체를 꼭 필요한 순간에만 가져온다. (불필요한 sql문의 전송을 막는다.) 30~ 36강. 트랜잭션과 영속성 컨텍스트를 이용한 요구사항 구현@ManyToOne/ @OneToMany(N : 1의 연관관계 )ManyToOne (다수) / OneToMany(1)ManyToOne은 단방향으로 쓸 수 있다. (반대쪽에 OneToMany를 붙여주지않아도 괜찮다.) 연관관계의 주인연관관계의 주인 = 데이터베이스의 Table을 보았을 때, 누가 관계의 주도권을 가지고 있는가?-> 다수(ManyToOne)이 One의 Primary_Key를 가지고 있으므로, Many가 주인이다.연관관계의 주인의 값이 설정되어야지만 진정한 데이터가 저장된다. @ManyToMany(N : N의 연관관계)구조가 복잡하고, 테이블이 직관적으로 매핑되지 않아 사용하지 않는 것을 추천한다. (1 : N으로 나눠버리는것 이 편하다.) cascade 한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 흘러 연결되어 있는 객체도 함께 저장되거나 삭제되는 기능.cascade 옵션을 활용하면 저장이나 삭제를 할 때 연관관계에 놓인 테이블까지 함께 저장 또는 삭제가 이루어진다. orphanRemoval연관관계가 끊어진 데이터를 자동으로 제거해준다. 정리연관관계의 장점1) 각자의 역할에 집중하게 된다. (= 응집성) - 서비스 계층의 역할 : 꼭 필요한 경우 서로 다른 도메인끼리 협업을 하게 도와준다. 트랜잭션을 관리한다. 외부 의존성(Spring Bean)등을 관리한다. - 도메인의 역할 : 도메인 객체가 표현하고 이쓴ㄴ 관심사에 대한 로직을 처리한다. 2) 새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다.3) 테스트 코드 작성이 쉬워진다.  연관관계의 단점1) 지나치게 사용하면 성능상의 문제가 생길 수도 있고 도메인 간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수 있다.2) 너무 얽혀 있으면 A를 수정했을 경우 B C D 까지 영향이 퍼지게 된다. 37~ 42강. 배포 준비배포최종 사용자에게 SW를 전달하는 과정 / 전용 컴퓨터에 우리의 서버를 옮겨 실행하는 것. Profile배포용 서버는 보통 Linux를 사용하게 된다. (운영환경이 달라짐)-> 똑같은 서버 코드를 실행시키지만, 실행될 때 설정을 다르게 하고 싶다..! H2 DB경량 데이터베이스로 , 개발 단계에서 많이 사용되며, 디스크가 아닌 메모리에 데이터를 저장한다.개발 단계에서는 테이블이 자주 변경되므로, 테이블을 신경쓰지 않고 코드에만 집중할 수 있다. Git코드를 쉽게 관리할 수 있게 해주는 버전관리 프로그램 GitHubgit으로 관리되는 프로젝트의 코드가 저장되는 저장소. git으로 관리되는 프로젝트를 gitHub에 올릴 수 있다.-> GitHub에 저장하는 이유1) 컴퓨터의 코드는 모종의 이유로 소실 될 수 있다.2) 배포를 할 때 활용할 수 있다.3) 내 컴퓨터에서 배포용 컴퓨터로 코드를 옮기는데 깃허브를 사용할 수 있다. 정리1) 배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야 하는지 알아 본다.2) 스프링 서버를 실행할 때 DB와 같은 설정들을 코드 변경 없이 제어할 수 있는 방법을 알아본다.3) Git과 GitHub의 차이를 이해하고, Git에 대한 기초적인 사용법을 알아본다.4) AWS의 EC2가 무엇인지 이해하고, AWS를 통해 클라우드 컴퓨터를 빌려본다.  43~48강. AWS와 EC2 배포정리1) EC2에 접속하는 방법을 알아보고, EC2에 접속해 리눅스 명령어를 다뤄보았다.2) 개발한 서버의 배포를 위해 환경 세팅을 리눅스에 진행하였다.3) Foreground와 Background의 차이를 이해하고, Background 서버를 제어한다.4) 도메인 이름을 사용해 사용자가 IP대신 이름으로 접속 할 수 있도록 한다. 49강~ 마무리. Spring Boot 설정 / 버전업 이해하기스프링과 스프링부트의 차이점 1. 간편한 설정 2. 간단한 의존성 관리 3. 강력한 확장성 4. MSA에 적합한 모니터링 MSA란 ? 하나의 거대한 서버를 이용하는 대신 관심사에 맞는 작은 서버들을 잘게 쪼개서 각 관심사에 맞는 부분들만 관리하는 것. 정리1) Build.gradle 의 정의와 플러그인, dependencies에 대해 학습하였다.2) 스프링과 스프링부트의 차이점에 대해 학습하였다.3) application.yml 파일을 분석하며 YAML 문법에 대해 학습하였다.4) Spring Boot 2.7 버전에서 Spring Boot 3.0 버전으로 마이그레이션을 진행해 보았다.  미션 미션을 해결하는 과정을 요약해 주세요. [6일차 과제]스프링 컨테이너에 대하여 학습하고, 기존에 작성했던 Controller 코드를 3단으로 분리를 하는 과제였지만, 이미 분리를 하여 과제를 수행하였기에, 반대로 다시 합쳐보는 과정을 통해 계층 간 분리의 필요성과 클린코드에 대해 다시 한 번 느낄 수 있는 과제였다. [7일차 과제] 6일차 과제에서 만들었던 Fruit 기능들을 JPA를 이용하여 구현하는 과제였다. 분명 강의를 들었을 땐 이해했다고 느꼈던 개념들이, 막상 직접 구현하려하니 생각보다 어려웠고, JPA에 대한 숙련도가 충분하지 못하다는걸 깨닫게 되었다..또한, DataBase의 Column명과 객체의 필드명이 일치하지 않는 문제로 고생을 했었는데, 객체의 필드명이 만약 soldOut 이런식이면 데이터베이스에 매핑될 때는 sold_out이런식으로 언더바가 들어가게 된다는것을 처음알게 되었다.  [미니 프로젝트 - 1일차] 팀 등록 기능 / 직원 등록 기능 / 팀 조회 기능 / 직원 조회 기능을 구현하는 과제였다. 사용된 기술 스택은 자바 17 버전Spring Boot 3.x.x버전JPAMySql이다. [코드 리뷰 - 1일차]리뷰를 통해 내가 미처 생각하지 못했던 더 좋은 프로그래밍 방법이나, 앞으로 무엇을 더 학습하면 좋을지 알게 되었다.나름대로 고민했던 부분들도 다른 분들의 피드백을 통해 더 나은 방향으로 나아갈 수 있었다. 양질의 피드백을 남겨주시는 그룹원들이 정말 너무 고맙다 🙇  GitHubVelog 

백엔드스프링

유원준

[인프런 워밍업 클럽 0기 백엔드] 2주차 발자국 (내용 정리 + 과제 + 미니 프로젝트)

인프런 워밍업 스터디의 2주차가 마무리되어 가고 있는 한 주입니다.2주차 동안 학습했던 내용들과 과제 수행 관련된 내용까지 한 번에 정리하고자 합니다.추가로 2주차 동안 학습한 내용, 과제 수행 부분은 개인 블로그에 항상 정리하고 있습니다.참고하실 분들은 참고해 주시면 좋을 것 같습니다 :)제 블로그 주소 링크 남겨드리겠습니다.(1~10일차 학습 및 과제 수행 내용)https://twojun-space.tistory.com/category/%ED%9A%8C%EA%B3%A0/InFlearn%20Warming-up%200%EA%B8%B0%20BE추가적으로 과제가 마무리 된 후 미니 프로젝트가 시작됩니다.미니 프로젝트는 제 GitHub에 소스 코드, 기능 설명을 남겨두고자 합니다.하단은 제 GitHub 주소입니다.https://github.com/twojun/inflearn_warmingup_be_project   1. [6일 차] - Spring Bean & Spring Container, Spring Container를 다루는 방법, Spring Bean 주입 받기, 스프링 빈의 우선 순위 설정1-1. Spring Bean?(1) 스프링 빈에 대한 정의, 스프링 빈이 사용되는 이유스프링 컨테이너와 연관지어 설명, 스프링 빈의 라이프 사이클 의존성 확인 및 주입을 담당하는 스프링 컨테이너 내부에 스프링 빈이 존재한다. 1-2. Spring Container(1) 스프링 컨테이너가 사용되는 이유(2) 스프링 컨테이너의 역할(3) 스프링 컨테이너를 사용할 때와 사용하지 않을 때의 Controller-Service-Repository, 사용 전 후로 달라지는 점1-3. 의존성 주입(Dependency Injection), 제어의 역전(Inversion of Control, IoC)스프링 프레임워크의 핵심 기능인 의존성 주입, 제어의 역전이 무엇인지 코드를 통해 직접 알아보며 스프링 프레임워크가 DI, IoC를 제공해 줌으로써 가져다 주는 이점에 대해 정리 생성자 주입, Setter 주입 등 스프링 컨테이너가 제공해주는 다양한 기능 정리  1-4. @Controller, @Service, @Repository를 계층형 아키텍처에 직접 적용(1) 관련 객체들이 스프링 빈으로 등록되어 컨테이너 내부에서 서로 간의 의존성, 라이프 사이클이 관리됨을 이해한다.  1-5. 스프링 컨테이너를 다루는 방법, @Configuration, @Bean, @Component, 스프링 빈을 주입받는 방법@Configuration, @Bean이 직접적으로 사용되는 시기에 대해 학습@Component의 효과스프링 빈 주입 시 Constructor injection 사용(불변 타입의 객체와 함께 사용)Constructor injection 사용 시 테스트 용이  1-6. @Qualifier, @Primary(1) 동일한 타입의 빈에 대해서 어떠한 빈을 주입 대상으로 설정할지 우선권을 부여하는 어노테이션직접 타겟을 텍스트로 지정하는 @Qualifier가 @Primary보다 우선권이 높다.  1-7. 6일 차 개인 회고(1) 스프링 빈, 스프링 컨테이너, DI(Dependency Injection), IoC(Inversion of Control) 등 스프링을 이루고 있는 주요 핵심 기능에 대해 학습할 수 있었다. (2) 스프링을 이루고 있는 핵심 기술인만큼 확실하게 이에 대해 인지하고 있어야 할 필요성을 느끼게 되었다.   2. [6일 차] - 과제 수행 : 계층 구조인 Controller - Service - Repository로 분리하기(1) 과제 수행 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0 (2) 6일차 과제수행 리뷰 : (개인 블로그)https://twojun-space.tistory.com/188 2-1. 문제 1번 : Fruit 서비스를 각각의 계층구조로 분리하기(1) 문제 접근 및 해결 방법기존에 스파게티처럼 얽혀 있던 코드를 각각의 역할에 맞게 수행할 수 있도록 코드를 분리했다.사용자의 요청을 받아 응답을 반환하는 Controller, 비즈니스 로직을 처리하는 Service, 데이터베이스와의 통신을 담당하는 Repository로 분리했다.기존 코드도 그렇고, 분리 과정에서 각 계층마다 역할이 뚜렷했기 때문에 분리하는 데 큰 어려움은 없었던 것 같다.  2-2. 정상 동작 확인(1) 기존 코드를 계층적으로만 분리해서 조금 더 유지보수성을 높였기 때문에 이전과 동일하게 API 반환, 데이터베이스에 데이터가 적재되는 등 정상적으로 동작하는 부분을 확인했다.  2-3. 문제 2번 : FruitRepository 코드를 FruitMysqlRepository, FruitMemoryRepository로 구분하고 리포지토리를 변경해가며 코드 실행하기(1) 문제 접근 및 해결 방법우선 동일한 타입을 갖는 객체에 대해 FruitMysqlRepository, FruitMemoryRepository 두 개 중, FruitMysqlRepository 클래스에 @Primary 어노테이션을 적용하여 코드를 실행해보니 정상적으로 코드가 동작하는 것을 확인할 수 있었다. 기능은 동일하고 어떤 빈을 주입받을지만 선택하면 되는 부분이었기에 큰 어려움 없이 해결할 수 있는 과제였다.  2-4. 6일차 과제 개인 회고(1) 기존의 코드를 Controller - Service - Repository로 분리하며 각 계층이 가져야 하는 역할, 책임에 대해 생각해 볼 수 있었다. (2) 조금 더 좋은 코드를 위해 지켜야 되는 부분은 무엇인지, 공부해야 되는 부분은 무엇인지 찾아볼 수 있었다.     3. [7일 차] - 지금까지 코드를 작성하며 Repository에 대해 개선할 점, JPA 도입, 추상화 레벨을 높인 Spring Data JPA 3-1. Repository 영역에서 지금까지 작성된 코드의 아쉬운 점(1) 문자열 기반으로 직접 쿼리를 작성하고 있다.(2) 이 부분에 대한 심각한 문제 : 컴파일 타임이 아닌 런타임 시점에 오류가 발생한 것을 인지하게 된다.(3) 특정 DB에 대한 종속적 쿼리 발생(4) 기본적인 CRUD에 대한 반복 작업 등등...  3-2. 객체와 DB 테이블의 패러다임 불일치 문제(1) 객체와 데이터베이스 테이블은 서로 간의 패러다임이 완전히 불일치한다.(대표적으로 연관 관계 문제, 상속구조를 표현할 수 없는 문제, 서로 참조할 수 있는 매커니즘이 아예 다른 문제 등) (2) 대부분의 애플리케이션이 객체지향적 설계를 지향한다. 객체와 관계형 데이터베이스를 같이 사용하기 위해서는 이 부분을 어떻게 해결할까? -> JPA 도입   3-2. JPA(Java Persistence API)(1) JPA의 정의, ORM 기술이 어떠한 기술인지 학습(2) JPA의 구현체인 하이버네이트의 등장  3-4. 객체와 테이블을 매핑하기 위한 여러 가지 어노테이션, 기타 옵션(1) @Id, @GeneratedValue(2) @Column 등 ...(3) JPA에 대한 여러 가지 설정 및 옵션 설정 : application.yml(4) spring.jpa.hibernate.ddl-auto 옵션 : create, create-drop, validate, update, none 존재(5) spring.jpa.properties.hibernate.show_sql(6) spring.jpa.properties.hibernate.format_sql(3) spring.jpa.properties.hibernate.dialect   3-5. Spring Data JPA를 통한 CRUD 작업 공통화, Repository 작성(1) Spring Data JPA의 대한 개념, 정의 학습(2) Spring Data JPA는 높은 추상화 레벨을 가진 라이브러리로 스프링에서 데이터베이스에 대한 접근을 단순화하고 효율적으로 처리하며 반복적인 CRUD 작업 또한 추상화하여 개발자가 직접 코드를 작성하지 않고 간단한 CRUD 기능을 수행할 수 있도록 설계되어 있다.(3) 회원 도메인에 직접 Spring Data JPA 적용해 보기 (4) 인터페이스를 사용하는 데 구현체를 생성하지 않았음에도 불구하고 해당 인터페이스를 사용할 수 있는 이유 학습(5) 스프링 데이터 JPA 쿼리 메서드 작성 방법, 여러 가지 옵션 확인   3-6. 7일 차 학습 내용 개인 회고(1) Spring Data JPA가 갖는 편리함 덕분에 개발 생산성이 높아졌지만 내부적으로 동작하는 매커니즘을 알기 위해선 JPA에 대한 기초적인 이해가 선행되어야 한다고 생각했다. (2) JPA를 이루는 기초적인 개념을 다시 한 번 정리할 수 있었고 헷갈렸던 부분에 대해 제대로 학습해 볼 수 있었다.    4. [7일 차] : 과제 수행 - Spring Data JPA 적용(1) 과제 수행 GitHub 주소 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0  (2) 7일 차 과제 정리 블로그 주소 : https://twojun-space.tistory.com/190  4-1. 과제 수행 : 문제 1번(1) 기존 Fruit 서비스가 Spring Data JPA Repository를 사용할 수 있도록 FruitRepository 인터페이스를 생성한다. (2) 이후 서비스 계층에서 새롭게 생성된 FruitRepository (Interface)를 의존할 수 있도록 설정한다.(3) 접근법은 매우 간단하다. 인터페이스를 생성하고 서비스 계층에서 해당 인터페이스를 의존할 수 있도록 의존 관계를 변경해 주기만 하면 된다.   4-2. 과제 수행 : 문제 2번(1) 문제 요구사항 : 특정 과일을 기준으로 판매된(거쳐간) 과일의 개수를 출력하는 API 설계 (2) 접근 방법 및 문제 해결 과정서비스 계층에 로직이 모두 몰리는 것을 막기 위해 과일 상태 변경에 관한 구현 로직을 도메인 계층에서 구현한다.판매 전 과일 판매 상태는 false, 판매 이후 true로 변경한다.// 판매 상태 변경 public void changeSalesStatus(boolean salesStatus) { this.salesStatus = salesStatus; }또한 Setter를 사용하는 것은 좋지 않다. 의미 있는 비즈니스 메서드(changeSalesStatus) 등을 생성하는 것이 좋다.@Repository public interface FruitRepository extends JpaRepository<Fruit, Long> { // select * from fruit where name = ? and salesStatus = true; long countByNameAndSalesStatusIsTrue(String name); }Repository 영역에 쿼리 메서드를 통해 판매된 과일의 개수를 계산하는 코드를 작성한다.(3) 기능 테스트본인은 매번 데이터 로우를 넣는 부분이 번거로워서 별도의 InitDbService를 만들어서 서버가 재시동될 때마다 해당 데이터들을 추가하도록 설정했다.@Component @Transactional @RequiredArgsConstructor static class InitDbService { private final EntityManager em; public void initFruitDb() { Fruit fruit1 = createFruit("사과", 4000L, LocalDate.of(2024, 02, 01)); Fruit fruit2 = createFruit("바나나", 2000L, LocalDate.of(2024, 02, 01)); Fruit fruit3 = createFruit("사과", 6500L, LocalDate.of(2024, 02, 01)); Fruit fruit4 = createFruit("사과", 7000L, LocalDate.of(2024, 02, 01)); Fruit fruit5 = createFruit("사과", 3000L, LocalDate.of(2024, 02, 01)); Fruit fruit6 = createFruit("포도", 12000L, LocalDate.of(2024, 02, 01)); Fruit fruit7 = createFruit("사과", 2500, LocalDate.of(2024, 02, 01)); Fruit fruit8 = createFruit("사과", 5000L, LocalDate.of(2024, 02, 01)); em.persist(fruit1); em.persist(fruit2); em.persist(fruit3); em.persist(fruit4); em.persist(fruit5); em.persist(fruit6); em.persist(fruit7); em.persist(fruit8); } private static Fruit createFruit(String name, long price, LocalDate warehousingDate) { return new Fruit(name, price, warehousingDate); } }테스트 진행 시 판매된 과일의 개수가 정상적으로 출력됨을 확인했다.4-3. 과제 수행 : 문제 3번(1) 문제 요구사항아직 판매되지 않은 특정 금액 이상, 금액 이하의 과일 리스트를 출력하는 API 설계 (2) 문제 접근 및 해결 방법 / 테스트Controller에 판매되지 않은 특정 금액, 이상, 이하 과일들, 결과 반환 개수까지 반환하는 메서드를 만든다.응답 DTO에 판매되지 않은 과일 개수를 나타내기 위해 별도의 resultSize 필드를 추가한다.이후에 과일의 판매 상태 컬럼을 활용해 판매되지 않은 과일의 리스트를 반환하는 로직을 각각 짜고 결과를 테스트 해 봤을 때 정상적으로 API 반환값이 확인된다.4-4. 과제 개인 회고(1) 높은 추상화 덕분에 우리의 개발 생산성이 올라갔지만, 그 뒤에는 우리가 편리한 기술을 사용하기 위해서 뒷받침되는 기술들이 많다는 것을 다시 한 번 느껴볼 수 있었다.(2) 지금 작성한 코드들도 문제없이 작동하지만 코드를 더 좋은 방향으로 짤 수 있지 않을까라는 생각이 계속 들었다.5. [8일 차] - 트랜잭션의 정의, 커밋과 롤백(Commit & Rollback), JPA의 Persistence Context, Persistence Context의 여러 가지 기능 살펴보기(1) 관련 내용 정리 (개인 블로그) : https://twojun-space.tistory.com/191  5-1. 트랜잭션의 정의와 트랜잭션의 커밋과 롤백(1) 예시와 함께 트랜잭션의 정의, 성공적인 트랜잭션 후 데이터베이스와의 동기화를 진행하는 커밋 옵션, 트랜잭션 실패 시 작업 이전으로 되돌아가는 롤백 옵션에 대해 확인해 보았다.5-2. 실제 코드에 선언적 트랜잭션(Declarative transaction) 적용하기(1) 코드에 @Transaction 어노테이션(선언적 트랜잭션 방법)을 적용하여 서비스 계층에서 메서드가 실행될 때 트랜잭션이 적용될 수 있도록 하고 작업 성공 시 커밋, 실패 시(예외 발생 시) 롤백하게 된다.5-3. JPA의 Persistence Context, Persistence Context의 여러 가지 기능(1) Persistence context(영속성 컨텍스트)의 정의에 대해 학습했다.(2) 첫 번째 기능 : 변경 감지트랜잭션 커밋 시점에 엔티티에 대한 변경사항을 감지하여 이에 대한 쿼리가 실제 데이터베이스로 전송되는 것을 말함(3) 두 번째 기능 : 쓰기 지연 저장소엔티티를 관리하며 발생한 관련 쿼리들을 쓰기 지연 저장소에 두고 트랜잭션 커밋 시점에 모든 쿼리를 데이터베이스로 전송한다.(4) 세 번째 기능 : 1차 캐시 기능 지원엔티티의 Identifier를 기준으로 영속성 컨텍스트에 영속화된 엔티티는 1차 캐시에 스냅샷(영속화 당시 엔티티의 정보)이 저장되어 있고 추후 해당 엔티티를 조회하는 경우라면 데이터베이스까지 조회 쿼리를 날리지 않고 1차 캐시에서 조회함에 따라 성능상 조금의 이점이 생긴다.5-4. 8일차 학습 회고(1) JPA의 기능들을 무심하게 사용해 오다가 JPA의 내부 매커니즘을 이해할 수 있는 영속성 컨텍스트에 대해 다시 한 번 리마인드 할 수 있었다. 관련 기반 기술의 이해가 선행되어야 해당 기술을 문제없이 잘 사용할 수 있다고 생각하기 때문에 이 부분에 대해 다시 한 번 정리해볼 생각이다.6. [9일 차]- 추가 기능에 대한 API 개발학습 내용 관련 블로그 정리 : https://twojun-space.tistory.com/1926-1. 도서 등록, 대출, 반납 요구사항 추가에 대한 API 개발, 개인 회고(1) 이전과 동일하게 요구사항을 확인하고 요청/응답 DTO의 스펙, Controller, Service, Repository를 개발했다.(2) 이를 통해 회원 관리, 도서 등록과 대출 반납에 대한 기능 구현이 모두 완료되었지만 한 가지 의문점이 남는다. 서비스 계층을 중심으로 모든 로직이 돌아가고 절차지향적인 코드라는 느낌이 많이 든다. 각 생성된 엔티티 간의 협력을 통해 개발할 수 없을까? 라는 의문점이 들고 JPA에서 제공하는 연관관계 기능을 사용해서 엔티티의 역할과 협력을 추가적으로 사용해서 조금 더 객체지향적으로 설계해 보는 것이 좋다고 생각했다.7. [10일 차] - JPA 연관관계와 지연 로딩(Lazy Loading), 객체지향적 설계, 도서 대출/반납 기능 Refactoring(1) 관련 학습 내용 블로그 정리 : https://twojun-space.tistory.com/1937-1. 연관관계란 무엇인가?(1) 연관관계의 정의에 대해 알아봤다. 데이터베이스 테이블과 객체 간 서로를 참조하는 방식에 대한 패러다임이 불일치하기 때문에 JPA의 연관관계를 통해 객체와 테이블을 정확하게 매핑하는 것이 중요하다.7-2. 연관관계의 주인(1) 일대일의 경우 핵심 비즈니스 로직이 존재하는 엔티티를 연관관계의 주인으로 둔다.(2) 다대일의 경우 외래 키가 존재하는 엔티티를 연관관계의 주인으로 설정한다.(3) 연관 관계의 주인으로 설정된 엔티티는 객체가 서로 연결될 수 있는 기준이 된다.(4) 일대일 관계에서는 @OneToOne 어노테이션을 사용한다.(5) 다대일 관계에서는 @ManyToOne, @OneToMany를 사용한다.(6) 양방향 연관관계를 가진다면, 한 쪽 엔티티에 대한 변경이 다른 쪽 엔티티에도 반영될 수 있도록 도와주는 메서드인 연관관계 편의 메서드를 만든다.(7) 양방향 연관관계를 사용하게 된다면, 신중하게 선택해야 한다.7-3. 실무에서 다대다 연관관계는 사용하지 않는다.따라서 중간에 별도의 테이블을 생성하여 처리하는 방법으로 매핑한다.7-4. @ManyToOne은 단방향으로만 사용한다,1쪽에서 @OneToMany로 참조하는 부분을 없애고 @ManyToOne 단방향으로만 남겨놓는다.  7-5. 연관관계의 주인이 사용할 수 있는 어노테이션@JoinColumn을 통해 연관관계의 주인이라는 부분을 명시한다.  7-6. 영속성 전이(Cascade)옵션과 orphanRemoval(1) cascade 옵션의 경우 연관관계와 관련은 없다.(2) 부모 엔티티가 자식 엔티티를 관리해야 할 때 사용한다.(3) orphanRemoval은 고아 객체를 제거할 때 사용하는 옵션으로 부모 엔티티와 자식 엔티티 간 연관관계가 제거된 경우 해당하는 자식 엔티티를 삭제하게 된다.  7-7. 도서 대출/반납 기능 리팩토링(1) 지금까지 학습한 내용을 바탕으로 각 엔티티가 협력할 수 있도록 모든 코드를 수정한다.(2) 엔티티에 도서 대출, 도서 반납 등의 핵심 로직을 설계한다.(3) 이를 통해 도메인 간 협업이 가능하도록 설계하여 서비스 계층의 로직이 단순해지고 특정 기능이 필요한 경우 엔티티의 기능을 직접 호출하도록 설계한 것을 확인할 수 있었다.7-8. 9일차에서 소개되지 않은 영속성 컨텍스트의 기능 : 지연 로딩(Lazy Loading)(1) 지연 로딩은 두 엔티티를 대상으로 특정 엔티티를 조회함에 있어서 연관된 엔티티는 바로 가져오지 않고, 해당 연관된 엔티티를 필요로 할 때(사용될 때) 로딩하는 기법을 의미한다.(2) 지연 로딩의 핵심 매커니즘은 프록시 기술과 연관되어 있다.7-9. 연관관계를 사용해서 도메인 중심으로 설계 시 얻는 장점?(1) 각자의 역할에 집중하게 된다 :계층별로 응집성이 강해진다. 도메인끼리의 협업 관계를 통해 서비스 계층에서는 외부 트랜잭션 관리, 의존성 관리 등의 역할만 맡게됨으로써 각자의 역할에 더 집중하는 코드가 완성된다.(2) 협업 시 코드 리딩이 쉬워진다.서비스 계층에 너무 많은 로직이 몰려있다 보면 코드를 리딩하는 입장에서 번거로울 수 있다. 하지만 도메인 중심으로 코드를 설계하면 계층이 어느 정도 분리됨을 가져가면서 도메인 계층이 각각 어떤 역할을 수행하는지 파악하기 수월해진다.(3) 테스트 코드 작성 시 용이하다.(4) 결론원론적인 이야기이지만 항상 연관관계를 사용하는 것이 정답은 아니기에 요구사항, 도메인 아키텍처 등 여러 가지 사항을 충분히 고민해서 연관관계 사용을 선택해야 한다.7-10. 학습 내용 개인 회고(1) 개인적으로는 이번 섹션이 가장 중요한 내용인 것 같다.(2) 영속성 컨텍스트(JPA 내부 동작 방식 이해)를 이해하는 것과 객체와 테이블을 매핑시킬 때 가장 중요한 연관관계에 대한 내용이라 더욱 그렇게 느꼈던 것 같다.(3) 연관관계를 조금 더 정확히 이해하고 항상 주의깊게 코드를 설계하도록 노력해야 할 것 같다.8. 2주 차 회고(1) 벌써 스터디에 참여한지 2주차가 마무리되는 시점이다.(2) 스터디 완주까지 최선을 다하고 싶고, 기회가 된다면 우수 러너도 도전해 보고 싶다. 많은 것을 경험하고 얻어갈 수 있는 시간이 되었으면 좋겠다.(3) 0기 스터디원분들 모두 화이팅입니다 :)

백엔드인프런워밍업스프링백엔드발자국

bananachacha

[인프런 워밍업 스터디 클럽 0기] 두 번째 발자국

강의 출처https://inf.run/XKQg  2주차 학습 내용 요약 Day 6: 스프링 컨테이너의 의미와 사용 방법스프링 컨테이너를 통해 애플리케이션의 객체 라이프사이클과 설정을 관리하며, 개발자가 직접 객체를 생성하지 않고 의존성 주입을 요청할 수 있어 코드 간의 결합도를 낮추고 유지보수성을 향상할 수 있습니다.또한, @Primary와 @Qualifier 어노테이션을 사용하면, 같은 타입의 빈이 여러 개 있을 경우 어떤 빈을 주입받을 것인지를 선택할 수 있습니다.  Day 7: Spring Data JPA를 사용한 데이터베이스 조작Spring Data JPA는 개발자가 JPA를 더 쉽고 편리하게 사용할 수 있도록 도와주는 도구입니다.JPA는 Java Persistence API의 약자로, 자바 ORM 기술에 대한 API 표준 명세입니다. ORM(Object-Relational Mapping)은 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 말합니다.SQL을 직접 사용하는 경우, 개발자가 직접 문자열을 작성하기 때문에 실수를 할 수 있고, 실수를 인지하는 시점이 늦어져 서버를 실행할 때 오류를 발견하게 됩니다.또한, SQL은 DB에 종속적이므로, MySQL이 아닌 다른 DB를 사용한다면 모든 SQL문을 수정해야 합니다. 이러한 문제를 해결하기 위해 JPA가 탄생하게 되었습니다.JPA는 API이기 때문에 말 그대로 규칙일 뿐이며, 이 규칙에 따라 코드를 작성해야 합니다.이를 돕기 위해 Hibernate와 같은 JPA 구현체를 사용합니다.Hibernate는 JPA의 표준을 준수하면서, 데이터베이스와의 작업을 단순화하고 자동화해주는 역할을 합니다.Spring Data JPA는 이를 한 단계 더 추상화하여, 개발자가 JPA를 더 쉽고 편리하게 사용할 수 있게 도와주는 도구입니다.이를 통해, 데이터베이스의 Create, Read, Update, Delete(CRUD) 연산을 Repository 인터페이스에 정의된 메소드로 간단히 수행할 수 있습니다.또한, 메소드 이름만으로도 쿼리를 생성하고 실행하는 기능을 제공합니다.  Day 8: 트랜잭션과 영속성 컨텍스트트랜잭션은 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한 번에 수행되어야 할 일련의 연산들을 의미합니다.트랜잭션은 데이터의 일관성을 보장하며, 작업이 완전히 수행되거나(Commit), 아니면 전혀 수행되지 않아야(Rollback) 함을 보장하는 '원자성'을 가집니다.영속성 컨텍스트는 엔티티를 영구 저장하는 환경을 의미합니다.JPA에서는 엔티티 매니저를 통해 엔티티를 영속화하며, 이 때 엔티티 매니저는 영속성 컨텍스트에서 엔티티를 관리합니다.트랜잭션과 영속성 컨텍스트는 밀접한 관계를 가지고 있습니다.영속성 컨텍스트는 트랜잭션을 시작할 때 생성되며, 트랜잭션이 끝날 때까지 엔티티를 관리합니다.트랜잭션이 커밋되면, 영속성 컨텍스트에 저장된(영속화된) 엔티티들이 데이터베이스에 반영됩니다.만약 중간에 오류가 발생하면 트랜잭션이 롤백되면서 데이터베이스에는 아무런 변화가 없게 됩니다.  Day 9: 조금 더 복잡한 기능을 API로 구성하기스프링빈, Spring Data JPA, 트랜잭션 등의 개념을 활용하여 도서관 API를 개발하는 과정에 대해 배웠습니다.  Day 10: 객체지향과 JPA 연관관계객체지향 프로그래밍에서는 객체 간의 관계를 표현하기 위해 연관관계를 사용합니다.이는 객체가 다른 객체를 참조하는 것을 의미하며, 이를 통해 객체 간의 상호작용을 표현할 수 있습니다.JPA에서는 이러한 객체지향적 연관관계를 데이터베이스의 테이블 관계에 매핑하는 방법을 제공합니다.@ManyToOne, @OneToMany, @OneToOne, @ManyToMany 등의 어노테이션을 사용하여 엔티티 간의 연관관계를 설정할 수 있습니다.  2주차 미션 이번 2주차 미션을 통해, JPA를 활용하여 개발 과정을 보다 효율적으로 진행할 수 있음을 배웠습니다.Repository를 인터페이스로 구현하는 방식을 적용해봄으로써 다양한 구현 클래스를 활용한 유연한 설계 방법을 배울 수 있었습니다.@Primary와 @Qualifier 어노테이션을 활용하여 두 개의 Repository가 바뀌어 동작하는 과정을 관찰하며, 이를 통해 코드의 확장성과 유연성을 높일 수 있었습니다.Spring Data JPA를 활용하여 기존 코드를 리팩토링하는 작업을 수행하였습니다.여기서 Entity와 JpaRepository를 이용하니 데이터베이스 연산을 보다 간편하게 처리할 수 있었고, 이를 통해 코드의 효율성과 가독성이 크게 향상되는 것을 체감할 수 있었습니다.특히, 인터페이스 내에서 메소드 시그니처만을 작성하면 스프링이 알아서 해당 인터페이스에 대한 구현체를 생성해주는 점이 매우 인상적이었습니다.이를 통해 JPA를 활용하면 얼마나 코드를 효율적이고 깔끔하게 작성할 수 있는지를 실감할 수 있었습니다.   

웹 개발인프런워밍업스터디클럽0기발자국스프링

[인프런 워밍업 클럽 BE]4-5일차 강의 정리 + 1주차 과제 정리 및 짧은 회고

이 문서는 아래 강의 정리글입니다.https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EC%98%AC%EC%9D%B8%EC%9B%90/dashboard 벌써 스터디를 시작한 지 일주일이 지나간다...! 과제는 나름대로 열심히 하고 강의도 진도표에 맞게 잘 들어왔지만... 바보 같게도 스케줄 관리 실패로 5일차의 중간 점검 라이브 참석을 못해버렸다... 최근의 일 중 가장 멍청했던 일 아닐까 싶다. 완주 혜택은 말끔하게 사라져버렸지만, 일단 끝까지 완수해보려고 한다. 꾸준히 공부하는 것 만으로도 얻는 것이 클테니까. 사실 슬슬 이론보다는 코드의 양이 늘어가기에, 이걸 어떻게 정리해야 하나, 고민이 든다. 개인 노션 페이지에는 마음껏 코드를 올리고 있지만, 이곳에서는 최대한 이론만 들고오려 했기에. 그래도 나름대로 필요하다고 생각하는 내용 위주로 정리해보겠다. 4일차 14강.업데이트HTTP 메서드 - PUTHTTP path - /userHTTP body - JSON결과 반환X변경은 이름만 변경할 예정이다.-> 식별자인 id와 변경하고 싶은 이름을 가져와 업데이트하기 때문에, request에는 id와 name이 필요 @PutMapping - PUT API를 위한 어노테이션String sql = ""; jdbcTemplate.update(sql, ...); 삭제HTTP 메서드 - DELETEHTTP path - /userHTTP 쿼리 - 문자열 name결과 반환X->이름을 기준으로 삭제한다.@DeleteMapping - Delete API를 위한 어노테이션 현재 개발 API의 문제사실 현재 API에는 큰 문제에 있다. 직접 페이지에서 실행했을 때는 문제가 없어 보이지만 포스트맨 같은 곳에서 존재하지 않는 사람에 대해 변경 혹은 삭제를 시도할 경우, 오류가 아닌 200ok를 내고있다.-> 예외 처리가 필요! 15강.예외 처리예외 처리를 어떻게 할까? 방식 자체는 간단하다. 자바 기초 문법을 배울 때부터 자주 접하는throw new IllegalArgumentException(); //500 내부 서버 오류를 던져준으로 해결된다. 근데 아무 곳에서나 예외 처리를 할 수는 없다! 이 데이터의 존재 여부를 확인 후, 예외 처리를 해야한다. 즉, 《조회 쿼리로 데이터가 있는 지 확인 => 예외 처리》 순서로 간다.String sql = "특정 조건에 대한 조회 쿼리"; boolean isNotExist = jdbcTemplate.query(sql, (rs,rowNum) -> 0, request.getId()).isEmpty();조회 결과가 있을 경우 0이 담긴 리스트가, 없다면 빈 리스트가 나온다는 것을 이용해, isEmpty() 함수로 boolean으로 반환되도록 한다.만약 isNotExist 가 true인 경우는, 오류를 던지고 그 외에는 정상 처리를 한다. 5일차. 17강.Clean CodeCode는 요구사항을 표현하는 언어→ Clean Code가 왜 필요한가?→ 실무에 가면 코드를 읽는 것을 피할 수 없다! 그러니 누가봐도 이해하기 쉬운 코드를 쓰자!스파게티 코드가 되는 것을 막아야한다!⇒그렇다면 어떻게 클린 코드를?⇒클래스는 작아야 한다, 그리고 하나의 책임(단일 책임 원칙)만을 갖게 한다!+ 객체 지향 설계의 5대 원칙 (SOLID) 에 대해 추가적으로 알아보자.https://www.nextree.co.kr/p6960/링크를 건 사이트 외에도 다양한 곳에서 정보를 얻을 수 있다! 여기까지만 말해도 느껴진다. 이전에 짠 예제 코드는 컨트롤러 내부에서 너무 많은 처리를 하고 있다…! DB와 연결하고, 쿼리를 실행하고, 필요하면 추가적인 처리를 하고, 결과를 변환해서 반환하고…⇒ 답은 간단하다! 역할을 분리하여 쪼개기! 18강.ControllerAPI의 진입 지점 / HTTP body를 객체로 변환사용자의 요청을 받고, 처리 결과를 반환Service애플리케이션의 비즈니스 로직요청 이행을 위한 도메인 객체들이 협력 수행하도록 조정예외 처리 역시 서비스에서!Repository외부와의 통신 담당 public class UserRepository { private final JdbcTemplate jdbcTemplate; public UserRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }public class UserService { private final UserRepository userRepository; public UserService(JdbcTemplate jdbcTemplate) { userRepository = new UserRepository(jdbcTemplate); } }public class UserController { private final UserService userService; public UserController(JdbcTemplate jdbcTemplate) { this.userService = new UserService(jdbcTemplate); } } }그 외 메서드 분리는 강의를 직접 들으며 공부하자! 과제1-5일차 과제는 개인 노션에 별도 정리했기 때문에 과제 링크를 남긴다!1일차 - https://www.notion.so/1-35973c9bdd624337b2aedfc1605245262일차 - https://www.notion.so/2-644f4434de87438d8c282edd2e9d39bd3일차 - https://www.notion.so/3-31c88c0c9fed403281340735b1b01b454일차 - https://www.notion.so/4-722e922685c04b6c96675bc0931999985일차 - https://www.notion.so/5-7013168ec49f4069a051e05f92f8b82c마치며나는 사실 스터디 시작 전에도 해당 강좌를 절반 정도 이미 수강했었다. 하지만, 조금 더 기초부터 착실하게 시작할까 하면서 다른 강의를 수강하다 자격증을 준비하다... 꽤나 끝을 보지 못하고 난잡한 목표의 삶을 살고 있었다.그런데 강의를 들으면서 새로운 과제도 하고, 질문 답변을 하며 열정을 보이는 다른 사람들을 보며 조금씩 동기부여가 되는 기분이었다. 그것과는 별개로 많이 부족한 자신을 뼈저리게 맞닥뜨리기도 했다. 과제는 제출하고 의문이 든 것에 관해 내가 한 질문 혹은 다른 분들이 질문한 것에 대한 코치님의 답변을 받을 때마다 역시 최선의 답이 아니었구나, 싶은 후회의 연속이었다. 특히 리서치 위주의 조사는 시간 부족으로 너무 겉만 핥고 지나간 것들도 많은 것 같아 아쉬웠다. 그래도 좋았던 점을 말하자면 과제를 하다 스스로 두루뭉술하게 알고 있던 키워드는 직접 조사하며 개념을 다잡을 수 있었다. 무엇보다 좋은 점은 다른 수강생분들과 같이 사이드 프로젝트를 할 기회를 얻었다는 것! 또 다른 소소한 장점이라면, 강의를 정리하면서 마크 다운 문서를 나름대로 빠르게 작성해나갈 수 있게 되었다. 

백엔드스프링스터디

유원준

[인프런 워밍업 클럽 0기 백엔드] 1주차 발자국 (내용 정리 + 미션 수행 정리)

인프런 워밍업 스터디의 1주차가 마무리되어 가고 있는 한 주입니다.1주차 동안 학습했던 내용들과 미션 수행 관련된 내용까지 한 번에 정리하고자 합니다.추가로 1주차 동안 학습한 내용, 과제 수행은 개인 블로그에 별도로 상세하게 정리해 둔 부분이 있습니다.참고하실 분들은 참고해 주시면 좋을 것 같습니다😁제 블로그 주소 링크 남겨드리겠습니다.😁 (1~7일 차 학습 내용, 과제 수행)https://twojun-space.tistory.com/category/%ED%9A%8C%EA%B3%A0/InFlearn%20Warming-up%200%EA%B8%B0%20BE  1. [0일 차] - 인프런 워밍업 스터디 0기 Orientation1-1. 스터디 진행 방식, 일정, 완주 러너와 우수 러너 기준 정리 등(1) 스터디 진행에 있어 필요한 부분들을 코치님께서 모두 정리해 주셨습니다.(2) 또한 디스코드에 발자국 작성법, 완주, 우수러너의 기준 선정 등 다양한 내용들이 디스코드에 나와 있어서 과제 수행에 별 어려움은 없었습니다.  1-2. 코치님의 추가 강의 : 자바의 역사(1) 코치님께서 Orientation 이후 자바의 역사에 대해 간단하게 정리해 주셨는데, 이 부분에 대해 정리해 보겠습니다.  1-3. Java 7(1) 프로그래밍 언어 자바의 암흑기가 찾아온 버전으로, 다른 언어들보다 오래 되었고, 기능이 한참 부족하다는 의견들이 다수 존재한 상황 (2) 당시 사람들의 생각 : 데이터 타입, 조건문, 반복문, OOP, Generic 정도만 다루면 잘 다루는 거 아니야? 라는 생각들이 당시 대다수 사람들의 생각  1-4. Java 8 (2014년)(1) 자바 8을 기점으로 언어의 대격변이 발생했던 시기 (2) Functional Programming(FP), Lamba Expression, Stream API, Optional operation, 날짜, 시간을 다루는 방법 등 (LocalDateTime) 등 현재 시점에서도 가장 많이 사용되는 자바의 핵심 문법들이 등장하기 시작함  1-5. Java 9 (2017년)(1) 자바 9부터 자바의 버전업 주기가 대부분 6개월로 변경  1-6. Java 11(1) 2024년 현재 기준으로 서비스 기반 IT 기업들의 주력 프로그래밍 언어로 선정된 자바  1-7. Java 21 (2023년)(1) 자바의 현 시점 가장 최근 버전  2. 0주 차 개인 회고(1) 이번 스터디를 통해 지금까지 학습해 왔던 스프링, JPA와 관련된 내용을 적용해 보고 싶었다.(2) 자바 8에 등장하게 된 핵심 문법들은 지금도 많이 사용되고 있고 이 부분에 대해 따로 한 번 정리해야겠다는 생각을 하게 되었다.(3) 우수 러너로 선정되어 개인적인 성장에 한 걸음 더 다가가고 싶다는 생각을 할 수 있었다.(4) 파이팅😊    2. [1일 차] - 스프링 프로젝트 메타 데이터, 서버와 네트워크, HTTP, URL 등 기본 지식 등 학습 내용 정리2-1. 프로젝트 메타데이터(1) 스프링 부트 프로젝트 생성 시 메타데이터인 Group, Artifact, Description, Dependencies 추가 등 프로젝트를 구성함에 있어 설정할 수 있는 메타데이터들, 의존성을 추가하는 방법을 알아봤다.  2-2. 서버와 네트워크(1) 서버와 네트워크의 정의와 특징 HTTP 통신의 핵심이 되는 네트워크 지식, IP, DNS, 등에 대해 빠르게 요약 및 정리할 수 있었다.  2-3. 웹 기반 서비스에서의 통신 규약, HTTP(HyperText Transfer Protocol)(1) HTTP의 정의, 요청과 응답에 각각 사용되는 HTTP Request Message, HTTP Response Body, REST API를 정의할 때 자원과 행위를 명시하는 데, 이때 행위를 명시할 때 사용되는 HTTP Method(GET, POST...등)에 대해 학습했다.  2-4. API(Application Programming Interface)(1) API는 프론트엔드와 백엔드 간의 통신을 위한 규약, 규칙 수단을 의미, 추가적으로 REST API, RESTful API에 대해 개인적으로 따로 정리해 보았다.  2-5.API를 개발하기 위한 다양한 Annotation(1) API 명세의 중요성, @RestControler, @GetMapping, @PostMapping, @RequestParam 등 다양한 어노테이션에 대해 정리할 수 있었다.  2-6. 1주차 학습에 대한 회고(1) 백엔드를 공부하면서 지금까지 다양한 용어를 공부했었지만 무의식적으로 용어를 계속 사용하다보니 정의와 아예 어긋나게 알고 있었던 개념들이 조금 있었다. (2) 이번 학습을 통해 잘못 이해하고 있던 부분을 바로 잡긴 했지만 조금 더 빨리 알았으면 좋았을 것 같다는 생각을 해보며 과거에 공부했던 부분을 다시 정리해 보고 잘못 알고 있는 부분은 바로 잡을 필요가 있다고 생각했다.   3. [1일 차] - 과제 수행 : 자바의 어노테이션(1) 인프런 블로그에 별도로 정리한 내용 : https://www.inflearn.com/blogs/6610 3-1. 자바의 어노테이션이란?, 어노테이션을 활용하면서 생기는 장점(1) 코드 가독성 향상, 반복되는 어노테이션의 공통화(2) 커스텀 어노테이션 생성 가능(3) 컴파일 시점에서의 오류 체크 가능 등어노테이션을 활용하면 다양한 장점이 존재한다. 3-2. 어노테이션의 종류(1) 표준 어노테이션(빌트 인 어노테이션), 메타 어노테이션(2) 표준 어노테이션 : @Override, @FunctionalInterface, @Deprecated 등...(3) 메타 어노테이션 : @Target, @Retention, @Inherited, @Repeatable  3-3. 커스텀 어노테이션 정의 방법public @interface SimpleAnnotation { }(1) 별도의 커스텀 어노테이션 생성 가능 @Retention(RetentionPolicy.RUNTIME) @Inherited @Target(ElementType.TYPE) @RestController @RequestMapping("/new") public @interface CustomMyAnnotation { String name() default "MemberController"; String value(); }@CustomMyAnnotation(name = "MemberController", value = "MemberController") @RequiredArgsConstructor public class MemberController { private final MemberService memberService; @GetMapping("/list") public List<MemberListResponseDto> getAllMemberList() { List<Member> allMemberList = memberService.findAllMembersList(); return allMemberList.stream() .map(member -> new MemberListResponseDto(member)) .collect(Collectors.toList()); } }  3-4. 과제 접근 방법, 회고(1) 접근 방법코드 공통화 등 여러 이점을 챙기기 위해 커스텀 어노테이션을 사용해야 하는데, 이 부분에 대해서는 메타 어노테이션에 대한 이해가 필요하다고 생각하여 우선 커스텀 어노테이션을 정의하기 위한 메타 어노테이션을 별도로 공부했다. 이후 위의 코드에서 공통으로 묶을 수 있는 어노테이션을 확인하고 이 부분에 대해 별도로 메타 어노테이션까지 적용해 봄으로써 커스텀 어노테이션을 생성해 볼 수 있었다. (2) 과제 회고개발을 해 보면서 별도로 커스텀 어노테이션을 사용해 본 적이 없어서 이 부분이 조금 낯설게 느껴졌지만 계속 반복해서 학습하다 보니 커스텀 어노테이션의 필요성, 언제 사용되는지 등에 대해 조금 인사이트가 생긴 것 같다.     4. [2일 차] - JSON, HTTP API 코드 작성, 배운 내용을 기반으로 도메인(회원) Entity 개발, 데이터베이스에 대한 기본 개념 4-1. POST 방식에서 데이터를 주고받는 방법, JSON의 구조(1) POST 방식에서 요청 데이터를 서로 주고받는 데 사용되는 데이터 형식 : JSON(2) 자바스크립트 객체 문법으로 구조화된 데이터들을 담을 수 있는 구조 : JSON(3) Key=value의 타입 구성  4-2. HTTP API 코드 작성, @RequestBody(1) 실제 코드를 작성해 보고 Postman을 이용해 API 응답 결과가 반환되는 것을 확인(2) @RequestBody : POST, PUT 방식에서 요청 바디에 적재한 데이터를 자바의 컨트롤러 메서드의 파라미터 객체와 매핑하는 어노테이션  4-3. 회원 도메인 개발(1) @RestController 어노테이션의 경우 응답 결과의 형식이 JSON으로 반환되도록 설계되어 있다.  4-4. Section 1 정리(1) 스프링 프로젝트의 구조, 실행 방법(2) 네트워크, IP 주소, 도메인 네임, 포트 번호, HTTP Request/Response 구조, 클라이언트-서버 구조, 기본적인 API 개념들 학습 (3) Spring Boot 기반으로 기본적인 GET, POST API 설계 방법4-5. 지금까지 설계한 API의 문제점(1) 데이터베이스의 부재 - 영속성을 관리하기 위한 수단이 존재하지 않는다.4-6. 데이터베이스의 개념(1) RDBMS(Relational Database Management System)의 개념과 정의, SQL(Structured Query Language)4-7. 2일 차 학습 내용 개인 회고(1) 위의 회원 API를 개발할 때 데이터베이스를 전혀 사용하지 않고 메모리에서 동작하도록 자바 컬렉션을 사용해서 데이터를 저장하고 조회했는데 평소 데이터베이스에서 CRUD를 하다 보니 자바 컬렉션에서 처리하는 코드들이 조금 낯설게 느껴진 것 같다.5. [2일 차] - 과제 수행 : GET, POST API 설계소스 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0  5-1. 문제 1번(1) 두 수를 입력하면 다음과 같은 결과가 반환되는 GET API를 설계한다.(2) URL Path : /api/v1/calc(3) Query parameter : num1, num25-2. 접근 및 해결 방법(1) 쿼리 파라미터가 사용되므로 @RequestParam을 통해 매핑시킬 수 있도록 한다.(2) DTO를 제외한 순수 엔티티 계층에는 @Setter를 열지 않는다.(3) 응답을 위한 별도의 DTO를 만들어서 클라이언트 측으로 반환한다.5-3. 문제 2번(1) 날짜를 입력하면 무슨 요일인지 알려주는 GET 조회 API를 설계한다.(2) URL Path, Query parameter의 경우 임의로 설계해도 상관없다.(3) 본인은 문제에서 기재된 URL과 쿼리 파라미터를 동일하게 설계했다.5-4. 접근 및 해결 방법(1) 날짜를 원하는 형식으로 포맷팅하기 위해, getDayOfWeek(), getDisplayName(TextStyle.SHORT, Locale.ENGLISH등의 날짜와 관련된 메서드를 별도로 찾아보고 문제에 적용시켰다.5-5. 문제 3번(1) 요청 메시지 바디에 리스트 형태로 여러 수를 입력하고 해당 수들의 모든 합을 계산하는  POST 조회 API를 설계한다.(2) API에서 받게 되는 요청 바디의 예시는 아래와 같다.5-6. 접근 및 해결 방법(1) 클라이언트에서 리스트 형태로 바디를 전송하기 때문에 이를 받아서 객체로 파싱하기 위해 위와 같이 List 컬렉션을 내부에 선언해 주었다.(2) 해당 과정에서 자바의 기본 생성자가 필요하다.(2) 반환 결과에서 기본 생성자가 추가적으로 필요한데 이유는 다음과 같다.위의 예외는 스프링의 Jackson 라이브러리의InvalidDefinitionException 예외이며, jackson 라이브러리가 JSON 내부에 객체 형태로 넘어온 값을 실제 객체로 변환하기 위해 역직렬화 과정을 거치는데, 이 과정에서 기본 생성자가 없어서 역직렬화가 실패했기 때문에 위와 같은 예외가 발생하는 것. 따라서 관련 클래스에 기본 생성자를 추가해 줘야 한다.5-7. 과제 수행 회고 (1) 과제를 수행하면서 날짜 정보를 나타낼 수 있는 LocalDate, 날짜와 시간 정보를 모두 나타내는 LocalDateTime에 대해 한 번씩 정리해 볼 수 있었다. 날짜 + 시간 정보가 함께 필요한 환경에서 LocalDateTime은 거의 필수로 사용되는 것 같다. (2) 또한 API 반환 관련 부분을 많이 찾아보면서 Jackson 라이브러리의 역직렬화에 대해 조금 더 자세히 찾아볼 수 있었는데,  JSON ↔ 객체 사이의 데이터를 변환해야 하는 일이 있을 때 주체 클래스에서 기본 생성자가 변환 과정에서 반드시 필요하다는 것을 인지했고 이런 부분을 설계할 때는 기본 생성자를 조금 더 신경쓰도록 주의해야 할 것 같다.6. [3일 차] - 데이터베이스 기본적인 DDL, DML, MySQL 데이터 타입, 데이터베이스 도입에 따른 도메인 코드 수정6-1. 데이터베이스 DDL(Database Definition Language) (1) 기본적인 데이터베이스 및 테이블 생성, 및 목록 확인  6-2. MySQL의 대표적 데이터 타입(1) 숫자형 타입(정수형, 실수형 타입)(2) 문자열 타입(3) 날짜, 시간 타입  6-3. 데이터베이스 CRUD(DML : Database Manipulation Language)(1) INSERT INTO~ VALUES(2) SELECT ~ FROM ~ WHERE(3) BETWEEN ~ AND ~(4) IN, NOT IN,(5) UPDATE ~ SET ~ WHERE (조건절 명시 유의)(6) DELETE FROM ~ WHERE ~ (조건절 명시 유의)  6-4. 회원 도메인 Controller 코드 수정(1) 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0 (2) jdbcTemplate.update() : 쿼리를 통해 데이터베이스에 대한 갱신(update, delete insert) 발생 시 사용 가능한 메서드로 파라미터로 넘어온 값들을 실제 SQL의 ?(와일드 카드) 부분과 매핑시키는 역할을 수행한다. (3) jdbcTemplate.query(sql, new RowMapper<UserListDto>()RowMapper의 경우 쿼리의 결과를 받아서 원하는 결과 객체를 반환하는 역할을 수행한다.이후 결과 반환을 위해 UserListDto가 필요하므로 생성자에 원하는 결과값을 파라미터로 넣어서 해당 객체를 생성 후 반환한다.(4) 또한 람다식을 이용해 코드를 좀 더 간결하게 작성할 수 있었다.  6-5. 3일 차 학습 내용 개인 회고(1) 스프링에서 Repository를 다룰 때 거의 하이버네이트와 같은 ORM 기반 기술만 쓰다 보니 직접적으로 JdbcTemplate을 통해 데이터베이스에 접근하는 것은 코드가 손에 익숙하지 않았던 것 같다. 이 부분이 조금 아쉬웠다. (2) 람다와 스트림에 대한 추가 정리가 필요하다고 생각했다.    7. [3일 차] - 함수형 프로그래밍, 람다식, Stream API, Method Reference (1) 과제 수행 GitHub : https://github.com/twojun/java8_core_study  7-1. 익명 클래스의 정의, 특징 정리(1) 익명 클래스의 의미, 익명 클래스가 갖는 주요 특징과 함께 코드를 작성하면서 익명 클래스를 실제로 사용하는 이유(함수형 인터페이스와 람다식과 연계)  7-2. 함수형 프로그래밍(Functional Programming) & 람다 표현식(Lambda expression) 정리(1) 함수형 프로그래밍의 뜻, FP가 자바에서 가지게 되는 의미(2) 람다 표현식 정의와 특징, 일급 객체(3) 함수형 인터페이스의 뜻과 사용될 수 있는 데이터 타입(4) 람다 표현식 작성 방법과 람다식을 더 줄일 수 있는 메서드 참조(Method Reference)(5) Method Reference : 실행하려는 람다식의 메서드를 참조해서 파라미터의 정보, 반환 타입을 추론한 뒤 람다식에서 선언이 불필요한 부분을 제거  7-3. 자바에서 기본적으로 제공하는 함수형 인터페이스Predicate<T> , Consumer<T>, Function<T, R>, Supplier<T>  7-4. Stream API(1) 스트림의 정의와 주로 사용되는 곳(2) 스트림 파이프라인 : 중간 연산과 최종 연산 정리(3) 중간 연산과 최종 연산의 주요 메서드들, 주의사항   7-5. 과제 수행 회고(1) 하이버네이트와 같은 ORM 기술을 사용하면서 람다와 스트림이 자주 사용되고, 코드의 가독성을 올려주는 데 효과적이라고 생각했었으며 이번 계기로 이 부분을 다시 한 번 정리해 볼 수 있었다. (2) 람다식, 스트림의 코드 스타일을 외우기 보다는 많이 사용해보면서 체화시키는 부분이 중요하다고 생각했다.    8. [4일 차] - 회원 도메인 수정, 삭제 API (1) 4일차 관련 내용은 도메인에 대한 코드 수정 위주이다.(2) 관련 내용 수행 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0  8-1. 회원 수정, 삭제 API를 직접 작성해 보면서 생각해 보아야 할 것?(1) 현재 회원 도메인만 봐도 컨트롤러 레벨에서 문제점이 하나 있다.(2) 하나의 컨트롤러가 너무 많은 책임과 역할을 갖고 있다.조회, 예외 처리(비즈니스 로직), 데이터베이스 통신 등 많은 역할을 수행하고 있다.지금은 요구사항이 예제 수준으로 작지만 요구사항이 10가지만 되어도 컨트롤러에서 모든 코드를 감당하기 어려울 것이다.따라서 이 부분을 해결해야 한다.  8-2. 학습 내용 개인 회고(1) 좋은 컨트롤러는 들어온 요청에 대해 적절한 처리를 다른 계층에서 수행해 주고 응답만을 돌려받아 다시 클라이언트측으로 반환하는 역할을 수행해야 하는데 좋은 컨트롤러란 무엇인가? 라는 부분에 대해 다시 한 번 생각해 봤다. (2) 너무 많은 기능을 담당하는 코드가 컨트롤러에 존재하면 가독성은 물론 유지보수가 매우 어려워진다. 이 부분을 빠르게 개선해야 할 것 같다고 생각했다.   9. [4일 차] - 과제 수행 : API 개발9-1. 문제 1번(1) 과일 가게에 입고된 과일 정보를 저장하는 API를 설계하자.(2) HTTP Method : POST(3) HTTP Path : /api/v1/fruit 9-2. 문제 접근 및 해결 방법(1) 이후 문제의 요구사항에 맞추기 위해 미리 테이블 컬럼에 판매 여부를 저장하는 is_sale 컬럼을 생성한다.(2) 이후 코드는 과일 정보를 저장하기 위한 SQL 문자열을 별도로 작성하고 jdbcTemplate.udpate() 메서드를 사용해서 실제 과일 상품의 정보를 저장하도록 한다. (3) 이후 정상적으로 테이블에 로우가 적재된 것을 확인 (4) int보다 long 타입을 사용해 보도록 하자.본인이 생각하고 있는 이유가 정확하진 않을 수도 있지만, 수의 표현 범위에서의 차이이지 않을까 싶다. int의 경우 약 21억 정도의 크기를 갖는 수를 저장할 수 있는데 해당 자료형이 담는 수도 매우 커보이지만, 이 부분이 실무를 넘어가면 21억보다 큰 수를 받아야 하는 상황이 종종 생길 수도 있기 때문에 long 타입을 사용하는 것으로 알고 있다  9-3. 문제 2번(1) 과일이 판매되면 판매된 과일 정보를 기록하는 API를 설계하자.(2) HTTP Method : PUT(3) HTTP Path : /api/v1/fruit9-4. 문제 접근 방식 및 해결 방법(1) 입고된 과일의 컬럼 정보 is_sale을 1로 변경해야 입고된 과일로 간주한다.(2) 입고되지 않은 과일의 정보를 바꾸는 것 자체가 모순이기에 이 부분에 대해선 IllegalStateException 예외를 던지도록 한다.(3) 코드를 작성해서 정상 결과 반환을 확인했다.9-5. 문제 3번(1) 특정한 과일을 대상으로 판매된 금액, 판매되지 않은 금액의 총합을 계산해보는 API를 설계하자.(2) HTTP Method : GET(3) HTTP Path : /api/v1/fruit/stat(4) HTTP Query parameter : name  9-6. 문제 접근 방법 및 해결 방법(1) 위에서 정의한 판매 상태를 기준으로 각각의 총합 금액을 계산하는 쿼리를 작성한다.(2) 이후 각각 조회된 결과를 받고 별도의 결과 반환 DTO를 만들어서 해당 값들을 DTO로 반환할 수 있도록 코드를 작성했다.(3) 정상적으로 문제에서 요구된 특정 과일에 대한 판매, 미판매 금액이 조회되는 것을 확인할 수 있었다.  9-7. 과제 수행 개인 회고(1)  저번 과제에 이어서 API를 추가적으로 설계해 보면서 REST API 설계에 대해 익숙해 질 수 있었다.(2) 이번 스터디를 계기로 더 많은 API를 반환하는 것을 연습해 보면 더 좋을 것 같다고 생각했다.   10. [5일 차] - 클린 코드, Controller-Service-Repository로 계층 분리하기(계층형 아키텍처) (1) 관련 내용 수행 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0 10-1. 클린 코드(1) 클린 코드가 무엇인지 이를 통해 얻을 수 있는 장점(2) 안 좋은 코드가 쌓여가면 프로덕트의 생산성이 낮아지는 점을 인지할 수 있었다.  10-2. 계층형 아키텍처(1) Controller-Service-Repository 형태와 같이 각 계층이 서로에 맞게 분리되어 있는 설계 구조를 의미(2) 각 계층에서 수행되는 역할 정리  10-3. 회원 도메인에 대해서 계층형 구조로 개선 (1) 단순 사용자 정보 수정만이 아닌, 생성, 조회, 삭제 등 모든 비즈니스 포인트를 Controller-Service-Repository 계층 구조로 분리하는 작업 진행  10-4. 5일 차 학습 내용 개인 회고(1) 이번 학습을 통해 클린 코드의 중요성, 모든 역할을 하나의 계층에서 정의하는 것이 아닌 각각의 역할에 따라 계층을 나누고 코드를 분리하는 것이 가독성도 좋고 협업하기 좋은 코드라고 생각할 수 있었다. (2) 앞으로도 코드를 작성하면서 더 좋은 계층구조를 갖는 코드가 무엇인지 생각하고 코드를 작성할 필요성을 느끼며 반성할 수 있는 기회였다.   11. [5일 차] - 클린 코드 적용하기11-1. 주사위 게임 코드 리팩토링(1) 과제 수행 코드 : https://github.com/twojun/java8_core_study(2) 기존 주사위 게임 코드를 클린 코드로 변경해보는 과제였다.  11-2. 문제 접근 및 해결 방법 & 개인 회고(1) 아래의 코드가 만약 주사위의 눈의 수가 30 또는 100까지 늘어난다면? 요구사항 수정에 의한 코드 변경 파급력이 급격하게 커지게 된다. 변경에 의한 파급력이 최대한 작아지게, 또한 읽기 좋게 코드를 수정하려면 어떻게 해야 할까? 라는 생각을 가지고 코드를 작성했던 것 같다. 아래의 코드를 개선된 클린 코드로 리팩토링해 봐야겠다고 생각했다. (2) 본인은 Scanner보다는 BufferedReader를 사용하는 것이 더 익숙해서 코드에 BufferedReader를 적용시켰다. (3) 메서드, 클래스 단위로 나누어서 코드를 작성해도 좋지만 사용자로부터 최대 나올 수 있는 주사위 눈의 수, 게임 반복 횟수를 입력받고 관련 주사위 수가 몇 번 나왔는지 출력하면 되는 문제이기 때문에 별도의 클래스나 메서드로 나누지 않고 간단하게 자료구조(배열)을 확인해 문제를 풀었다. (4) 과제가 끝난 후 다른 분들 코드를 보면 메서드, 클래스 단위로 대부분 나누어 코딩하신 것 같아서 나만 너무 이상하게 코딩했나라는 생각을 했지만, 클린 코드의 의미를 되돌아 보면 읽기 쉬운 코드도 특징 중 하나이기에 최대한 간결하고 눈에 들어올 수 있도록 자료구조를 활용해 표현했지만 뭔가 부족한 느낌이 든다.    12. 1주차 총 회고(1) 스터디가 시작된지 1주일이 경과했다. 디스코드에서 보니 다들 열심히 하시는 분들이 많으신 것 같고 항상 열정적이신 코치님, 동료분들이 있어서 스터디하기 좋은 환경인 것 같다. (2) 열심히 참여해서 개인적인 성장에 한 걸음 더 다가서려고 한다.😄 

백엔드인프런워밍업발자국회고스프링

wisehero

[워밍업 클럽 BE-0기] 7일차 과제 - JPA로의 전환, 그리고 더 다양한 API 만들

7일차 과제는 기존의 프로젝트를 JPA로 전환하고, 더 다양한 API를 만드는 것입니다.저는 이미 과제를 처음부터 JPA로 진행하고 있었으므로, 해당 부분을 생략하고 문제를 바로 보겠습니다. 첫 번째 문제는 다음의 스펙에 맞추어 API를 만드는 것입니다.HTTP method : GETHTTP path : /api/v1/fruit/countHTTP query : name -> 과일 이름HTTP 응답 Body 예시{ "count" : long } 코드는 다음과 같습니다!FruitControllerFruitServiceFruitRepository이름을 파라미터로 넘겨주면 그 이름과 일치하는 과일 엔티티들의 갯수를 반환하도록 했고요. 아래와 같이 요청을 보냈습니다.그리고 현재 제가 만든 Fruit 테이블의 상태는 아래와 같습니다. 사과가 3개 있으니 3개를 가져와야할 것입니다.정상적으로 3개가 출력되는 것을 확인할 수 있었습니다. 다음 문제는 팔리지 않은 과일들 중에서 특정 금액 이상, 혹은 특정 금액 이하의 과일 목록을 받아오는 것입니다.스펙은 아래와 같습니다HTTP method : GETHTTP path : /api/v1/fruit/listHTTP queryoption : "GTE" 혹은 "LTE"라는 문자열이 들어온다.GTE는 크거나 같다는 의미LTE는 작거나 같다는 의미price : 기준이 되는 금액이 들어온다.응답 Body 예시[{ "name" : String, "price" : long, "warehousingDate" : LocalDate, }, ... ] 저는 아래와 같이 코드를 작성했습니다.FruitControllerFruitServiceFruitRepository그리고 요청을 보내보았습니다.정상 작동을 확인했습니다.다만 아쉬운 점이 서비스레이어에서 엔티티를 DTO로 변환을 하는 과정인데.. 이것을 그냥 레포지토리에서 DTO로 조회를 하는 방식이 더 나을 것 같다는 생각이 들었습니다. 하지만 이렇게 되면 쿼리 애노테이션을 달아주어 SQL을 작성해줘야하는데 레포지토리에 있는 코드들이 길어질 것 같았습니다. 어떤 방식이 더 선호되는 지는 공부를 해보고 개선해봐야겠습니다. 저 변환을 하는 작업이, 필드가 늘면 늘수록 중간 연산의 코드가 점점 더 길어지게 되는 단점이 있다고 생각이 들었거든요. 그럼 오늘도 봐주셔서 감사드리고 모든 워밍업 클러버들의 무운을 빕니다.

백엔드최태현스프링워밍업클럽백엔드

영후이

[인프런 워밍업 클럽 0기 BE] - 첫 번째 발걸음

강의 수강  일주일 동안 학습했던 내용을 요약해주세요.1주차(1 일차 ~ 5 일차)의 학습 내용.  서버 개발을 시작하기 위한 환경 설정부터 , 네트워크와 API, 데이터 연동에 대해 배웠고,  리팩토링을 통해 클린코드의 중요성에 대해 배웠다. 일주일 간의 학습 내용에 대한 간단한 회고를 작성해 주세요.-> 강의를 들으며 작성한 강의노트를 바탕으로 작성 1 일차 | - 서버 개발을 위한 환경 설정 및 네트워크 기초 Java를 공부하기 전에 알아두면 좋을 것들! #1(JVM, JDK - 유튜브)컴파일 : 컴퓨터는 생각보다 바보다. 그래서 우리가 작성하는 코드를 알아 먹지 못하기 때문에, 컴파일이라는 과정을 통해 바이너리코드(컴퓨터가 이해하는 0과 1)로 변환 해줘야한다.자바는 컴파일러를 거쳐가기 때문에 운영체제(윈도우, 리눅스)에 영향을 받지않는다.= 다른환경이라도 같은 결과 JVM , JRE, JDKJVM : 자바 가상머신 (운영체제별로 존재)JRE : 자바 실행 환경(JVM이 실행되기 위한 여러 라이브러리)JDK : 자바 개발 도구 (JRE+개발을 위한 도구) -> JDK는 버전이 있고 각 버전별로 새로운 기능이 추가되거나 기존 기능이 사라진다.-> LTS란 다른 버전은 시간이 오래 지나면 지원하지 않는데, 오랫동안 지원해주는 버전을 말한다. Java를 공부하기 전에 알아두면 좋을 것들! #2(빌드, 빌드툴 - 유튜브) 빌드 소스코드파일을 컴퓨터에서 실행할 수 있는 독립 SW가공물로 변환시키는 과정 (독립 SW가공물 = Artifact) 빌드의 세분화 1. 소스코드 컴파일2. 테스트코드 컴파일3. 테스트코드 실행4. 테스트코드 리포트작성5. 기타 추가설정 작업진행6. 패키징 수행 -> 패키징 수행 : 오픈API들과 나의 코드를 하나로 묶는다.7. 최종SW결과물(Artifact) 실행 내가 작성한 코드(테스트코드)를 컴파일을 거쳐 작동시켜 보는것 독립 SW가공물(Articfact)가 나올수도 있고, 나오지 않을수도 있다.인터프리터 언어(자바스크립트, 파이썬) 은 컴파일이 필요없다. 빌드 툴 1. 소스코드의 빌드 과정을 자동으로 처리해주는 프로그램2. 외부소스코드(외부라이브러리) 자동추가, 관리 정리1. 빌드란 단순히 실행하는것과는 다르다.2. 빌드 과정 자동화와 외부 라이브러리 관리를 위해 빌드툴이 사용된다. 1강. 스프링 프로젝트를 시작하는 두 번째 방법정리스프링 프로젝트를 설정해 시작하고 실행하는 방법과 서버란 무엇인지, 네트워크와 HTTP, API는 무엇인지JSON은 무엇인지 등등 서버 개발에 필요한 다양한 개념에 대해 배웠다. 2강. @SpringasBootApplication과 서버Annotation마법같은 일을 자동으로 해줌. 예를들어, @SpringBootApplication 실행에 필요한 모든 설정들을 자동으로 해준다. 서버 (기능을 제공하는 프로그램 or 그 프로그램을 실행시키고 있는 컴퓨터)서버란 어떠한 기능을 제공하는 '것'을 의미한다. ex) 회원가입 기능 / 정보 가져오기 기능/ 추천 기능사람대신 컴퓨터가 이런 기능을 수행한다.누군가의 요청이 있어야 기능을 수행한다.ex) 시리야 오늘의 날씨를 알려줘이 요청을 인터넷을 통해 하게된다. 3강. 네트워크란 무엇인가?!정리네트워크와 택배 시스템의 비교를 통해 IP와 도메인 , port에 대해 학습하였다. 4강. HTTP와 API란 무엇인가?정리택배를 보내는 과정과의 비교를 통해 데이터를 주고 받을 때 사용하는 표준(HTTP)과 API에 대해 학습하였다. HTTP(HyperText Transfer Protocol)내놓아라(운송장을 받는 사람에게 요청하는 행위) / 파란집(운송장이 가는 장소)/ 둘째야(운송장을 실제 받는 사람) /포션(운송장을 받는사람에게 원하는 자원)/ 빨강색 2개(자원의 세부조건)과 같이 현실세계에서 데이터를 주고 받는 표준을 의미한다. API(applicatino Programming Interface)정보를 주고 받기 위해서는 서로 정해진 약속을 해야하고, 이 약속을 통해 특정 기능을 수행하는것.ex) GET / portion?color=red&count=2 5강. GET API 개발하고 테스트하기정리덧셈 API를 만들어 보며 GET API를 만들어보며 DTO에 대해 학습하였다. DTO(Data Transfer object)HTTP 쿼리를 받았을때 적절한 객체가 있다면 스프링 부트가 알아서 그 값을 넣어서 전달하게 되는데, 이때 데이터를 전달하기 위한 적절한 객체를 DTO라고 한다. 6강. POST API 개발하고 테스트하기정리POST API를 만드는 과정에서 HTTP Body로 데이터를 받아보며 JSON에 대해 학습하였다. JSON객체 표기법, 즉 무언가를 표현하기 위한 형식 중괄호 안에 "Key": "value" 형식으로 표기한다ex) {"name": "김영훈", "age": 99} 7~9강. 유저 생성 API와 유저 조회 API 개발정리앞서 배웠던 GET API와 POST API를 활용하여 유저 생성 API와 유저 조회 API를 만들어 보았다.문제점현재 유저 정보가 메모리에서만 유지되고 있기 때문에, 서버를 재실행 하면 모든 데이터가 사라지는 문제점이 있다. 10~16강. 생에 최초 Database 조작하기정리1. 디스크와 메모리의 차이를 이해하고, Database의 필요성에 대해 학습하였다.2.MySQL Database를 SQL과 함께 조작하는 방법에 대해 학습하였다.3.스프링 서버를 이용해 Database에 접근하고 데이터를 저장, 조회 , 업데이트 , 삭제하는 방법에 대해 학습하였다.4. API의 예외 상황을 알아보고 예외를 처리해 보았다. 500 Internal server error함수 내에서 함수가 정상종료되지 않고, 그 안에서 예외가 발생하였기 때문에 500 Internal server error 반환 문제점현재 controller 에서 너무 많은 일을 하고있다.1. DB 접근2. create3. read4. update5. delete 17~18강. 역할의 분리정리controller를 리팩토링 해보며 클린코드의 중요성에 대해 학습하였다. 하나의 함수가 너무 많은 기능을 수행하면 안되는 이유1. 동시에 여러명이 수정할 수 없다.2.읽고 이해하기가 어렵다.3.한부분을 수정하더라도 함수 전체에 영향을 미칠 수 있다.4.테스트가 힘들다.5.유지보수성이 떨어진다. updateUser(변환 전)//변환 전의 userUpdate @PutMapping("/user") public void userUpdate(@RequestBody UserUpdateRequest request){ String readsql = "select * from USER WHERE id=?"; boolean userCheck = jdbcTemplate.query(readsql,(rs, rowNum) -> 0, request.getId()).isEmpty(); if(userCheck){ throw new IllegalArgumentException(); } String sql = "UPDATE USER SET name=? WHERE id=?"; jdbcTemplate.update(sql, request.getName(), request.getId()); } updateUser(변환 후)1.api 진입 지점으로 HTTP Body를 객체로 변환한다. (controller)2.유저가 있는지 없는지 예외처리한다. (service)3.sql을 사용해 실제 database와의 통신을 담당한다. (repository)@PutMapping("/user") public void updateUser(@RequestBody UserUpdateRequest request){ service.updateUser(request); }//service public void updateUser(UserUpdateRequest request) { if (repository.isUserNotExist(request.getId())) throw new IllegalArgumentException(); repository.updateUser(request); }//repository public void updateUser(UserUpdateRequest request) { String sql = "UPDATE USER SET name=? WHERE id=?"; jdbcTemplate.update(sql, request.getName(), request.getId()); } public boolean isUserNotExist(long id) { String sql = "SELECT * FROM user WHERE id=?"; return jdbcTemplate.query(sql, (rs, rowNum) -> 0, id).isEmpty(); }  미션 미션을 해결하는 과정을 요약해 주세요.   [1일차 과제](https://velog.io/@vosxja1/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-BE-1%EC%9D%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C) 어노테이션을 사용하는 이유를 정리하고 나만의 어노테이션을 만드는 방법을 고민하는 과제였다.무엇을 만들어 볼까 고민하다 어노테이션을 통해 인자를 전달받고, 간단한 연산을 수행하는 계산기를 만들었는데 과제의 의도와 약간 미스매치였지 않나 하는 후회가 남는다. 다른 러너분들중에 @validated 어노테이션을 커스텀 어노테이션을 사용하여 맛깔나게 쓰신분이 계시던데 아마 그것이 과제의 의도와 좀 더 부합되는 활용법이 아닌가 싶었다.조만간 validated 어노테이션과 커스텀 어노테이션에 대해 학습하고 정리해보아야겠다. [2일차 과제](https://velog.io/@vosxja1/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-BE-2%EC%9D%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C)GET API와 POST API에 대해 학습하고 , 약간의 응용이 필요했던 과제이다.@GetMapping("/api/day-of-the-week") //@DateTimeFormat으로 yyyy-MM-dd 형태로 변환해서 받는다 public ResponseEntity<DayOfWeekResponse> dateResponse(@RequestParam("date") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate localDate) { DayOfWeek dayOfWeek = apiService.dayOfWeekService(localDate); DayOfWeekResponse dayOfWeekResponse = new DayOfWeekResponse(dayOfWeek); return ResponseEntity.ok() .body(dayOfWeekResponse); }이 당시, @RequestParam을 통해 넘어오는 date를 @DateTimeFormat를 통해 패턴화 시켜 받았는데, 질의응답시간에 springBoot 버전에따라 @DateTimeFormat을 사용하지 않아도 받을 수 있다는 것을 알게 되었다.  [3일차 과제](https://velog.io/@vosxja1/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-BE-3%EC%9D%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C)자바의 람다식이 왜 등장하였고 람다식과 익명 클래스가 어떤 관계가 있을지 정리하는 과제였다.람다식을 사용하면서도 람다식이 어째서 등장했는지, 익명 클래스는 무엇이고 함수형 인터페이스가 무엇인지도모르면서 사용하고 있었다는걸 깨달았다 ㅠ   [4일차 과제](https://velog.io/@vosxja1/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-BE-4%EC%9D%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C) 2일차 과제와 유사한 API 생성 문제였다. AWS에서 database의 대소문자 구분 때문에 고생했던날이었다.  [5일차 과제](https://velog.io/@vosxja1/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-BE-5%EC%9D%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C) 클린하지 않은 코드를 클린하게 리팩토링해보는 과제였다. 이 정도면 나쁘지 않은거 같은데 싶었지만, 다른 러너분들을 보니 아직도 많이 부족하구나.. 싶었다.다음주 금요일에 태현님이 해당 코드 리팩토링을 진행하신다던데 벌써 기대가 된다. 

백엔드스프링

wisehero

[워밍업 클럽 BE-0기] 6일차 과제 - 레이어 나누기와 @Primary 사용해보기

오늘 과제는 Controller 코드를 3 계층으로 나누는 것과 FruitMemoryReposiotry와 FruitMySqlRepository라는 두 개의 구현체 중에 하나만을 주입받아 사용하도록 하는 것이었습니다. 하지만 저는 이전의 과제에서부터 계층을 나누어서 코드를 작성했기 때문에 그 부분은 생략하고 Repository쪽의 코드를 확인해보겠습니다. 처음부터 JPA를 사용했으므로, 새로운 코드가 필요했습니다. 따라서 아래와 같이 작성했습니다. FruitRepositorypublic interface FruitRepository { void save(FruitCreateRequest request); void updateStatus(Long id); Map<String, Long> statForFruit(String name); } FruitMySqlRepository@Repository @Primary public class FruitMysqlRepository implements FruitRepository { private final JdbcTemplate jdbcTemplate; public FruitMysqlRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void save(FruitCreateRequest request) { String sql = "insert into fruit (name, warehousing_date, price) values(?, ?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice()); } @Override public void updateStatus(Long id) { String sql = "update fruit set is_sold = CASE WHEN is_sold = 0 THEN 1 ELSE 0 END WHERE id = ?"; jdbcTemplate.update(sql, id); } @Override public Map<String, Long> statForFruit(String name) { String sql = "SELECT \n" + " SUM(CASE WHEN is_sold = 1 THEN price ELSE 0 END) AS salesAmount,\n" + " SUM(CASE WHEN is_sold = 0 THEN price ELSE 0 END) AS notSalesAmount\n" + "FROM\n" + " fruit\n" + "WHERE \n" + " name = ?;"; Map<String, Object> result = jdbcTemplate.queryForMap(sql, name); Map<String, Long> stats = new HashMap<>(); stats.put("salesAmount", ((Number)result.get("salesAmount")).longValue()); stats.put("notSalesAmount", ((Number)result.get("notSalesAmount")).longValue()); return stats; } } FruitMemoryRepository@Repository public class FruitMemoryRepository implements FruitRepository { private List<Fruit> fruits = new ArrayList<>(); private Long id = 1L; @Override public void save(FruitCreateRequest request) { Fruit fruit = new Fruit(id++, request.getName(), request.getWarehousingDate(), request.getPrice()); fruits.add(fruit); System.out.println(fruits.size()); } @Override public void updateStatus(Long id) { Fruit fruit = fruits.stream().filter(f -> Objects.equals(f.getId(), id)) .findFirst().orElseThrow(IllegalArgumentException::new); fruit.changeStatus(); } @Override public Map<String, Long> statForFruit(String name) { List<Fruit> fruitsByName = fruits.stream() .filter(f -> f.getName().equals(name)).collect(Collectors.toList()); Long salesAmount = fruitsByName.stream().filter(Fruit::getSold) .mapToLong(Fruit::getPrice).sum(); Long notSalesAmount = fruitsByName.stream().filter(f -> !f.getSold()) .mapToLong(Fruit::getPrice).sum(); HashMap<String, Long> result = new HashMap<>(); result.put("salesAmount", salesAmount); result.put("notSalesAmount", notSalesAmount); return result; } }저는 현재 FruitMySqlRepository에 @Primary 어노테이션을 달아주었습니다. 그러면 과연, 스프링 컨테이너가 띄워졌을 때, FruitMySqlRepository가 구현체로 등록되었는 지 확인해보겠습니다. 이를 위해 아래와 같은 코드를 사용했습니다.@SpringBootApplication public class LibraryAppApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(LibraryAppApplication.class, args); FruitRepository bean = applicationContext.getBean(FruitRepository.class); System.out.println("주입된 FruitRepository의 구현체: " + bean.getClass().getSimpleName()); } }스프링 부트 애플리케이션을 실행하고 난 뒤 FruitRepository에 주입되어있는 빈의 이름을 가져오게 했는데요. 실행 결과는 아래와 같습니다.FruitMySqlRepository가 잘 주입된 것을 확인할 수 있습니다. 다시 FruitMemoryRepository에 @Primary 를 달아주면!역시 잘 확인할 수 있었네요. 그런데... 저 뒤에 EnhancerBySpringCGLIB~~ 라는 놈은 무엇일까요?더 깊게 알아보아야할 것을 얻은 것 같습니다. 6일차 과제 이상입니다.

백엔드백엔드최태현워밍업클럽스프링