블로그

[인프런워밍업스터디_BE_0기] 2주차 정리!

섹션 3. 역할의 분리와 스프링 컨테이너[섹션 목표]1. 좋은 코드가 왜 중요한지 이해하고, 원래 있던 Controller 코드를 보다 좋은 코드로 리팩토링한다.2. 스프링 컨테이너와 스프링 빈이 무엇인지 이해한다.3. 스프링 컨테이너가 왜 필요한지, 좋은 코드와 어떻게 연관이 있는지 이해한다.4. 스프링 빈을 다루는 여러 방법을 이해한다.스프링 컨테이너와 스프링 빈은 무엇일까?지난주 우리는 @SpringBootApplication 어노테이션이 다양한 설정을 자동으로 해준다고 배웠는데 이 중 하나가 바로 스프링 컨테이너를 만드는 것으로 이 안에는 클래스들이 들어가게 된다. 그리고 스프링 컨테이너 안에 들어간 클래스를 스프링 빈이라고 부른다. 스프링은 이렇게 컨테이너 안에 있는 클래스를 자동으로 관리해준다.  스프링 컨테이너는 왜 필요할까?기존에 우리가 구현한 코드에서 JdbcTemplate을 활용한 Repository를 다른 Repository로 변경하고자 한다면 기존의 코드를 모두 찾아서 다른 Repository로 변경해야한다! 하지만 스프링 컨테이너가 Repository 중 하나를 선택해 자동으로 적용해준다면 코드를 일일이 찾아서 바꿔줄 필요가 없게 된다! 이러한 개념을 제어의 역전 (Inversion of Control)이라고 한다. 그리고 이렇게 Repository 중 하나를 선택에 적용하는 과정을 의존성 주입 (Dependency Injection) 이라고 한다.스프링 빈을 다뤄보자!이전까지 우리는 @RestController, @Service, @Repository 등을 활용하여 클래스를 스프링 빈으로 등록하였다. 이러한 어노테이션은 클래스에 직접 사용하였는데, 외부에서 빈을 등록하는 방법이 있다.@Configuration public class UserConfiguration { @Bean public UserRepository userRepository(JdbcTemplate jdbcTemplate) { return new UserRepository(jdbcTemplate); } }이와 같이 외부에 설정 클래스를 만들어 빈을 등록할 수도 있다. 그럼 이런 방법은 언제 사용할까?선생님께서는 정답은 없지만 일반적으로 개발자가 직접 만든 클래스는 @Service, @Repository 등을 사용하고, 외부 라이브러리나 프레임워크에 만들어져 있는 클래스를 사용할 때는 @Configuration 과 @Bean 의 조합을 사용하는 편이라고 한다. 섹션 4. 생애 최초 JPA 사용하기[섹션 목표]1. 문자열 SQL을 직접 사용하는 것의 한계를 이해하고, 해결책인 JPA, Hibernate, SpringData JPA가 무엇인지 이해한다.2. Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제할 수 있다.3. 트랜잭션이 왜 필요한지 이해하고, 스프링에서 트랜잭션을 제어하는 방법을 익힌다.4. 영속성 컨텍스트와 트랜잭션의 관계를 이해하고, 영속성 컨텍스트의 특징을 알아본다.문자열 SQL 사용의 한계점문자열을 직접 작성하기 때문에 실수가 나올 수 있는데 이러한 실수가 있을 경우에도 컴파일 시에는 발견하지 못하고 정상 실행되어 런타임중에야 알 수 있다.SQL도 데이터베이스의 종류마다 문법이 다른데, 문자열로 작성하면 특정 데이터베이스에 의존하게 된다.간단한 CRUD 작업을 할 때마다 SQL을 작성해줘야하기 때문에 반복작업이 많다.데이터베이스의 테이블과 객체는 패러다임이 다르다. JPA, Hibernate, Spring Data JPAJPA(Java Persistence API) 로 자바 진영의 ORM(Object-Relational Mapping) 기술 표준을 의미한다. 조금 어려운데 선생님께서는 다음과 같이 쉽게 요약해주셨다.JPA : 객체와 관계형 데이터베이스의 테이블을 매핑해 데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진 규칙그리고 JPA는 API이기 때문에 말 그대로 '규칙'일 뿐이고, 그 규칙을 실제 코드로 작성한 가장 유명한 프레임워크가 Hibernate이다. 그리고 이들을 활용해서 만들어진 Spring Data JPA를 활용하면 복잡한 JPA 코드를 직접 사용하는 것이 아니라, 추상화된 기능으로써 사용할 수 있게 되는 것이다. Spring Data JPA로 CRUD 작업을 해보자!// UserServiceV2 클래스.. public void saveUser(UserCreateRequest request) { userRepository.save(new User(request.getName(), request.getAge())); } public List<UserResponse> getUsers() { return userRepository.findAll().stream() .map(UserResponse::new) .collect(Collectors.toList()); } public void updateUser(UserUpdateRequest request) { User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); userRepository.save(user); } public void deleteUser(String name) { User user = userRepository.findByName(name) .orElseThrow(IllegalArgumentException::new); userRepository.delete(user); } 기존 JdbcTemplate을 사용했던 코드를 위와 같이 변경하였다!트랜잭션은 왜 필요하고, 스프링에서 어떻게 사용할까?트랜잭션이란 쪼갤 수 없는 업무의 최소 단위로 여러 SQL을 사용해야할 때 한 번에 성공시키거나, 하나라도 실패하면 모두 실패시키는 기능이다. 선생님께서는 다음과 같은 예시를 통해 트랜잭션의 필요성을 말씀해주셨다.인터넷 쇼핑물 사이트에서 결제가 완료되면 1. 주문 기록을 남긴다. 2. 포인트를 증가시킨다. 3. 구매 기록을 저장한다.이렇게 세 가지 일을 처리한다. 이 중 하나라도 실패한다면 주문 기록, 포인트, 구매 기록에 있는 데이터가 서로 맞지 않게 되고, 구매 기록이 없어 사용자가 물건을 받지 못하는 등 다양한 문제가 발생할 것이다.트랜잭션을 적용하면 위와 같은 경우에 하나라도 실패하면 rollback 되기 때문에 앞서 말한 문제가 발생하지는 않을 것이다! 그럼 위 코드에 트랜잭션을 적용해보자!// UserServiceV2 클래스.. @Transactional public void saveUser(UserCreateRequest request) { userRepository.save(new User(request.getName(), request.getAge())); } @Transactional(readOnly = true) public List<UserResponse> getUsers() { return userRepository.findAll().stream() .map(UserResponse::new) .collect(Collectors.toList()); } @Transactional public void updateUser(UserUpdateRequest request) { User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); userRepository.save(user); } @Transactional public void deleteUser(String name) { User user = userRepository.findByName(name) .orElseThrow(IllegalArgumentException::new); userRepository.delete(user); }스프링에서는 이렇게 간단히 트랜잭션을 관리할 수 있도록 @Transactinal 어노테이션을 제공해준다. 이 때 Read 작업만 하는 경우에는 readOnly = true 속성을 줄 수 있다. 영속성 컨텍스트란 무엇이고, 트랜잭션과 어떤 관계가 있을까?영속성 컨텍스트란 테이블과 매핑된 Entity 객체를 관리/보관하는 역할을 수행한다고 한다. 이러한 영속성 컨텍스트는 4가지의 특별한 능력을 가지고 있다. 이번 시간에는 다음 세가지만 배웠다!변경 감지영속성 컨텍스트 안에서 불러와진 Entity는 다음과 같이 명시적으로 save 해주지 않아도 변경을 감지하여 저장할 수 있게 해준다. @Transactional public void updateUser(UserUpdateRequest request) { User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); // userRepository.save(user); 생략해도 자동으로 저장이 된다! } 쓰기 지연@Transactional public void saveUsers() { userRepository.save(new User("A", 10)); userRepository.save(new User("B", 20)); userRepository.save(new User("C", 30)); }위와 같은 코드가 있을 때 한 줄, 한 줄 DB에 SQL을 날리는 것이 아니라 모아서 한 번에 날리게 된다. 1차 캐싱@Transactional(readOnly = true) public void saveUsers() { userRepository.findById(1L); userRepository.findById(1L); userRepository.findById(1L); }위 코드 역시 DB를 3번 조회해 올 것 같지만, 최초 1번만 DB를 조회하고 나머지 두 번은 영속성 컨텍스트가 보관하고 있는 데이터를 바로 가져와 사용한다! 그리고 이러한 영속성 컨텍스트는 스프링에서 트랜잭션을 사용하면 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다. 섹션 5. 책 요구사항 구현하기[섹션 목표]1. 책 생성, 대출, 반납 API를 온전히 개발하며 지금까지 다루었던 모든 개념을 실습해본다.2. 객체지향적으로 설계하기 위한 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 이해한다.3. JPA에서 연관관계를 매핑하는 방법을 이해하고, 연관관계를 사용해 개발할 때와 사용하지 않고 개발할 때의 차이점을 이해한다.책 생성, 대출, 반납 API를 만들어보자!지금까지 배운 내용을 토대로 책 생성, 대출, 반납 API를 만들어보았다. 하지만 우리가 작성한 코드를 조금 더 객체지향적으로 만들 수 있다고 한다! 다음 주에는 기존의 코드를 조금 더 객체지향적으로 만들 수 있는 방법을 알아본다! 2주차 과제 정리각 과제를 블로그로 정리해두어 아래에 링크를 남긴다.Day 6 : https://www.inflearn.com/blogs/6874Day 7 : https://www.inflearn.com/blogs/6899 2주차를 마치며아쉬웠던 점이번주 역시 역시 가장 아쉬운건 일과 학습의 병행이었다. 주말에 미리 이번주 분량의 강의를 모두 들어두었지만 역시 시간이 부족하다...ㅠㅠㅠ 칭찬할 점잠을 줄여가며 과제를 완성하고, 커뮤니티에서 만난 사람들과 함께 과제에 대한 코드 리뷰도 도전해보는 등 최선을 다한 점은 역시 칭찬할 점이다! 일에 지장을 전혀 주지 않았다고는 할 수 없지만... 업무도 크게 무리 없이 진행하였던 나에게 따봉 하나를 선사한다...ㅎㅎ 보완할 점현실적으로 시간이 부족하지 않도록 최선을 다하기란 힘들다는 것을 느꼈다... 마지막 한 주는 그보다는 주어진 시간을 조금 더 알차게 활용하는 것을 목표로 해봐야겠다!

김 동현

2주차 강의 정리 [인프런 워밍업 클럽 0기 BE]

2주차의 주 내용은 Jpa를 사용해서 데이터베이스에 접근하는 개발방법이었다.이전에는 일일이 쿼리를 만들고 쿼리를 실행해서 받아온 결과를 응답객체에맵핑해서 사용했다. Jpa를 사용하면서 데이터베이스에 접근하는 객체가 명확하게 구분되고 간단한 조회나 저장 업데이트는 트랜잭션과 영속성컨텍스트를 통해서 자동으로 되도록 처리할수있었다.  1.ORM(Object Relation Mapping)객체 와 DB의 테이블을 매핑 시켜서 RDB테이블을 객체지향적으로 사용하게 해주는 기술이다. 자바 진영에는 대표적으로 JPA와 MyBatis가 있는데 MyBatis는 java의 클래스코드와 작성한 SQL코드를 매핑시켜주어야하는 불편함이있었다. 반면에 JPA는 객체가 db에 연결되어서 SQL을 작성하지않고 표준 인터페이스를 기반으로 처리해준다는 차이가있다. feat. 이전 si에서 3달정도 일해본적이있었는데 지금 돌아와서 생각해보면 거기서는 Mybatis를 썼던것같다. 에러코드에서 본듯한 기억이.. 그때는 프론트엔드과정을 마치고 입사해서 자바에대한 기초도없고 스프링도모르고 사용해서 뭐가뭔지 몰랐는데. 지금보니 그게 이거였다는 생각이.. 비즈니스 로직이다보니 쿼리가 길면 몇십줄에서 거의 백줄까지 가는경우도 있었다.안정성 때문이라그랬나? Jpa를 썻다면 쿼리는 짧게쓰고 객체에서 처리할수도있지않을까 싶은 쿼리문들이 몇개 생각이난다..  2.트랜잭션트랜잭은 db에서 한 로직이 실행되어야하는 단위를 의미한다. 예를들어 은행에서 한쪽에서는 돈이빠지고 한쪽 에서는 돈이 들어가는걸 각각의 트랜잭션으로 돌린다면한쪽에서 돈이 빠졌는데 다른쪽에서는 에러가 발생해서 돈이 들어가지않을수도 있다. 이런 경우는 비즈니스로직상 대형 사고이기때문에 이런 한번에 일어나야하는 일련의 작업들을 트랜잭션으로 묶는다.두개를 트랜잭션으로 묶게되면 한쪽에서 돈이빠지고 중간에 에러가발생해서 뒤의작업이 실행되지않더라도 DB가 롤백되면서 작업을 취소시켜버린다. 만약 트랜잭션안의 쿼리가 모두 실행되었다면 커밋을 하게되고 그렇게되면 db에 반영이 되게되는것이다.  3.영속성컨텍스트JPA를 사용하고 service로직에 @Transactional 어노테이션을 달아주게되면 영속성컨텍스트가 적용된다.영속성 컨텍스트가 적용되면 해당 로직내에서 entity객체가 수정되면 따로 업데이트를 해주지않아도 영속성 컨텍스트가 변화를 저장해두었다가 로직이끝날때 변화를 모두 업데이트해주는 작업을 자동으로 해준다. Jpa를 사용하면서 굉장히 편해졌지만 아직 익숙하지않아서 사용하는게 어색하고 찾아보면서하느라 시간이 조금 걸리는것같다. 익숙해진다면 많은 시간을 단축시킬수있을것같다. 2주차 후반부터는 미니프로젝트 실습을 하고있는데 이전처럼DB의 테이블에 어떤테이블에 어떤필드가 들어가야할지 다 나와있지는 않아서 테이블을 구성하는것도 필요한 기능을보고 고민하면서 하게되는것같다. erd도 작성해보고 이후에는 명세서나 필요한 레퍼런스도 노션에 간단하게나마 작성해볼 생각이다. 

백엔드워밍업백엔드jpa

wisehero

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

2주차는 좀 많이 아쉬운 주였습니다. 몸이 아파서 학습에 온전히 시간을 쓰지 못했고, 연휴에도 골골대면서 누워있던 시간이 많았습니다. 그래도 중간중간 기운이 좀 나거나 상태가 괜찮아지면 다시 책상에 앉아 코드를 작성했는데요. 미니 프로젝트는 Java 21, 스프링부트 3버전을 사용하고 있습니다. Java 버전을 21로 선택한 이유는 추후 태현님께서 Java 21과 관련된 강의를 출시할 것이라고 예고를 해주셨고 후에 Java 21을 적용할 수 있는 부분을 찾아 연습을 해보기 위해 선택했습니다. 종종 인텔리제이로 코딩을 하면서, Java 최신 기능을 적용할 수 있는 부분은 ToolTip으로 알려주었던 것 같은데, 아직까진 이러한 부분을 찾질 못했습니다. 이번 미니 프로젝트를 진행하면서 새롭게 시도한 부분은 record를 좀 더 적극적으로 사용하는 것이었습니다. 레코드를 사용하게 되면 기존의 DTO 클래스를 작성하는 것보다 좀 더 간결하게 DTO의 목적 자체에 맞는 코드를 작성할 수 있고 롬복과 관련된 애노테이션들을 사용할 필요가 없다는 점이 큰 장점으로 다가왔습니다. 프로젝트는 현재 1단계는 다 완성이 되었고 2단계를 수행하고 있습니다. 한 걸음 더!에 나와있는 edge-case들을 다루는 내용을 해결하는 과정에서 큰 재미를 느끼고 있습니다. 실제 개발에서도 이러한 부분을 사전에 얼마나 잡아내느냐에 따라 제품의 퀄리티와도 직결될 것 같은데, 이런 케이스들을 사전에 고안해내고 핸들링하는 부분에 있어서 경쟁력을 가지고 싶다고 생각이 들었습니다. 현재는 코드리뷰어를 구하지 못해서 GPT에게 코드 리뷰를 대신 부탁해주고 있는 실정인데, 초기에 매우 지저분했던 서비스 레이어가 그래도 나름 깔끔해졌습니다. 물론 여전히 레포지토리에 선언한 쿼리메서드의 메서드명이 좀 긴 것은 고쳐야할 부분입니다. isAttendToday의 경우는 그래도 나름 빨리 적절한 메서드명을 떠올렸는데 퇴근 시간을 기록하는 메서드의 경우는 잘 떠오르지 않아 냅둔 상태네요. 현재 이 부분에서 이미 같은 날의 출근과 퇴근을 모두 마친 직원이 다시 출근을 하려고하는 경우를 핸들링 해야하는데 좋은 방법이 없을 지 생각 중입니다. 미니 프로젝트에서 남은 과제들을 보면, 변경에 유연하게 대처할 수 있는 코드를 작성할 수있고, 설계를 할 수 있는지에 대한 역량을 시험할 수 있는 것 같습니다. 그래서 엔티티에 필드하나를 추가하거나, 코드 한 줄, 한 줄에도 근거를 담아보려고 노력하고 있습니다. 3단계와 4단계가 정말 그 시험대가 될 것 같아요. 부디 다음 주 한주는 최상의 컨디션으로 과제와 강의 모두 잘 병행할 수 있기를 바랍니다.  

백엔드최태현백엔드워밍업클럽

[인프런 워밍업 클럽 BE]9-10일차 강의 이론 위주 정리 + 2주차 회고 및 과제정리

이 문서는자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] 강의 - 인프런 (inflearn.com)강의와 연관 스터디 내용을 정리하였습니다.  시작하며 벌써 2주차 스터디가 끝나가는 일요일 저녁이다. 9-10일차의 강의는 본격적으로 대출, 반납 기능을 개발했다. 11일차부터는 기본적인 배포에 관해 배우는데 굉장히 설렌다. 수요일부터 미니 프로젝트와 코드 리뷰도 시작했는데 3월 초에 sqld 자격 시험도 있어서 일정이 꼬이지 않고 잘 완성시킬 수 있을 시 걱정이 앞선다. 😂😂 참고로 9-10일차 강의는 기능 개발이 중점이라 대부분이 리팩토링, 기능 개발에 관한 것이라 이론 분량이 이전보다는 적다! 그래서 이전 글처럼 n 일차, n강 식의 형식으로 작성하지 않고 이론을 간단한 리스트 형식으로만 정리하려고 한다. - 다른 기능에서 사용할 DTO가 완전히 동일하다면?대출/반납 등, 다른 기능에서 사용할 dto의 형식이 완전히 동일하다면 재활용 vs 새 DTO 생성?-> 유연한 대처를 위해 새 DTO 생성이 권장! - 연관 관계연관 관계의 주인관계의 주도권을 가진 테이블 = 상대 테이블을 참조하고 있는 테이블(상대 테이블의 기본키를 가지고 잇는 테이블)객체 연결의 기준관계의 주인이 아닌 쪽에 mappedBy 옵션을 단다.1:1 관계ex)사람과 실거주 주소의 관계@OneToOne 사용1:N@OneToMany와 @ManyToOne 사용@ManyToOne은 단방향 사용 가능@JoinColoum - 연관 관계 주인이 사용하는 어노테이션N:M@ManyToMany 사용구조적 복잡함비직관적사용하지 않는다. 일반적으로 N:1 1:M으로 풀어서 사용CascadeCascadeType.ALL - 아래 모든 작업 전파CascadeType.PERSIST - 영속성 컨텍스트에 새로운 엔티티 추가 시 해당 엔티티와 연관된 엔티티도 함께 영속화. 새로운 부모 엔티티 저장 시 연관 엔티티도 함께 저장CascadeType.MERGE - 엔티티 상태 변경 시 연관된 엔티티 상태도 함께 변경. 부모 엔티티 업데이트 시 자식 엔티티도 업데이트CascadeType.REMOVE - 부모 엔티티 삭제 시 자식 엔티티도 삭제. CascadeType.DETACH - 부모 엔티티 분리 시 자식 엔티티 분리CascadeType.REFRESH - 부모 엔티티 새로고침 시 자식 엔티티 새로고침OrphanRemoval객체간(연관 관계의 주인와 종속된 객체)의 관계가 끊어진 데이터를 자동으로 제거하는 옵션지연 로딩영속성 컨텍스트의 4번째 속성@OneToMany의 fetch 옵션에서 설정. 기본값 lazy필요한 순간 그 때 그 때 로딩연관 관계 사용의 장점각자 역할에 집중(=응집성)타인이 코드를 읽을 시 이해하기 쉬워짐테스트 코드 작성 용이연관 관계 사용의 단점지나친 연관 관계는 성능에 문제 발생도메인 간 복잡한 연결로 인한 시스템 파악 어려움⇒ 비즈니스 요구사항, 기술적 요구사항, 도메인 아키텍처 등 다방면 고민 후 결정  과제6일차. https://www.notion.so/6-6e9b71c98126401097211cbeb7f5b21c7일차. https://www.notion.so/7-95f01dca3af244439d75e0855322e6b2 간단한 회고2주가 순식간에 지나가 버렸다. 이전보다 성장한 것이 맞는지, 여러 의문이 들지만... 시간을 되돌릴 수는 없다.😭미니 프로젝트를 진행하며 다른 분들과 코드 리뷰를 진행 중이지만 보면 볼수록 내 부족함이 체감이 된다...!강의에는 특별히 다루지는 않지만, ExceptionHandler, logback 설정 등 하나하나 찾아보지 않고도 직접 어느 정도 구현할 정도의 성장이 필요할 것 같다! 코드 리뷰를 하면서 절실하게 느낀 점 중 하나는, 코드 컨벤션을 너무 안 지키고 있다는 것이다. 항상 혼자서만 진행하다 보니 타인이 내 코드를 보고 어려워할 수 있다는 것 자체를 인지하지 못한 것 같다. 

백엔드스터디

Isidore

[인프런 워밍업 클럽] 2주차 발자국

2주차엔 스프링 동작 원리와 JPA관련 기능들을 배웠다.스프링 어플리케이션은 컨테이너를 통해 클래스간 의존성을 관리한다. 어플리케이션이 시작하면, 관리할 대상인 빈들을 등록한다. 그리고 필요한 의존성이 있으면 설정해 준다. 이렇게 관리를 스프링이 하도록 만드는 개념을 제어의 역전이라고 한다. 또한 의존성하는 객체를 전달해주는 방식을 의존성 주입이라고한다. 이렇게 하면, 사용하는 쪽이 아니라, 제공하는 쪽에서 자유롭게 수정할 수 있는 장점이 있다.JPA는 데이터를 저장하기 위해 자바에서 사용하는 규칙이라 볼 수 있다. SQL을 직접 다루는 일은 컴파일러의 지원을 받을 수 없고 런타임에서야 알 수 있다. 또한 사용하는 SQL 제품에 의존하게 되고, 반복작업이 많다. 그리고 객체지향에서 객체는 양방향으로 참조하고, 상속이 있으나 관계형 데이터베이스는 이런 지원이 없어 제한적이다. JPA는 ORM의 일종으로 객체를 관계형 데이터베이스와 이어줘 저장해 이런 문제를 극복하도록 도와준다.2주차에선 비교적 적절한 속도로 진도를 진행할 수 있었다. 과제의 경우 추가적으로 욕심을 내 Query DSL을 적용할 수 있었으나 문제의 특성에 의존해 이를 사용하지 않고 과제를 수행했다. 과제를 제출할때는 별 생각이 없었으나 이 또한 배울 기회였다고 생각하니 다시 아쉽게 느껴졌었다. 

웹 개발

영후이

[인프런 워밍업 클럽 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 

백엔드스프링

dmstjd0214

[인프런 워밍업 스터디 클럽 0기_BE] 2주차 회고록 정리

요일 별 내용 정리Day6 강의스프링 컨테이너란?개발자 대신 객체의 생명 주기를 관리해주는 곳입니다.스프링 빈이란?스프링 컨테이너 안에 인스턴스를 넣어주는 것이 스프링 빈이다. 따라서 빈으로 등록된 메서드들은 스프링 컨테이너가생명 주기를 관리합니다.빈 등록 방법에는 @Bean을 붙혀줘야하며, 항상 클래스에 @Configuration을 선언해줘야 가능합니다.여기서 @Configuration 의 사용은 외부 라이브러리, 프레임워크에서 만든 클래스를 등록할때 사용됩니다.@Component를 사용하여 빈 등록이 가능합니다.주어진 클래스를 컴포넌트로 확인하며 해당 클래스는 서버가 런타임 시점에 자동으로 감지됩니다.강의를 공부하며 배웠던 @Repository,@Service,@RestController에 대한 어노테이션 내부에 @Component어노테이션이 달려있습니다. 주로 사용할 때는 개발자가 직접 작성한 클래스에 사용되며 @Bean어노테이션이 생략됩니다.여기서 더 나아가 @Configuration vs @Component다양한 빈 주입방식주입 방식 중에 자동으로 주입을 받는 어노테이션이 있는데 그건 바로 @Autowired입니다.자동으로 스프링에 의존성을 주입하는 역할을 수행합니다.생성자를 이용해 주입 받는 방식  private final BookJpaRepository bookJpaRepository; private final UserLoanHistoryRepository userLoanHistoryRepository; private final UserRepository userRepository; @Autowired public BookService(BookJpaRepository bookJpaRepository, UserLoanHistoryRepository userLoanHistoryRepository, UserRepository userRepository) { this.bookJpaRepository = bookJpaRepository; this.userLoanHistoryRepository = userLoanHistoryRepository; this.userRepository = userRepository; }생성자를 통해 주입 받는 방식으로 가장 권장하는 방법입니다. @RequiredArgsConstructor를 사용하여 생성자를 생략할 수 있습니다. 여기서 @RequiredArgsConstructor은 롬복이 정의해놓은 어노테이션으로 final이나 notnull로 되어있는 생성자에 대한 내용을 자동으로 생성해주는 어노테이션입니다.Setter를 이용한 주입 방식private JdbcTemplate jdbcTemplate; @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate){ this.jdbcTemplate = jdbcTemplate }Setter에 의존성 주입을 하는 방법이지만 사용하지 않습니다.필드에 직접적인 주입 방식@Autowired private JdbcTemplate jdbcTemplate;필드에 직접적으로 의존성 주입을 직접거는 방식이지만, Field injection is not recommended이라는 프레임워크 메세지와 함께 사용되지않는 방법입니다.강의에 대한 일부 내용으로 인터페이스를 생성하여 다형성을 활용해 메서드를 나눠놓았지만, 문제점은 어떤 곳에 대한 클래스를 사용해야할지 모르는 상황이 올 수 있는데 두가지 방법이 있습니다. @Primary클래스 위에 어노테이션을 사용하면 스프링 컨테이너에 있는 빈에 대한 우선권을 주는 어노테이션입니다.@Qualifier많은 후보군 중에서 어떤 것을 스프링 컨테이너에 넣어주는 역할을 하는 어노테이션입니다.클래스에서 소문자로 줄여서 사용합니다. appleService 또한 각 클래스내에 @Qualifier(”지정한 이름”)를 사용하면 양쪽 모두 사용하면 @Qualifier끼리 연결이 된다.@Primary, @Qualifier 우선 순위@Qualifier를 직접 작성한 클래스에 대해 우선 순위를 가진다.스프링은 사용자가 직접 명시한것이 우선순위가 높다. 과제노션 : https://silvercastle.notion.site/6-92b90a65eb4a4cfaa7241162fd8e3fa2?pvs=4깃허브 : https://github.com/backgom1/Inflearn_BE0_Study/tree/main/ens-library-app/src/main/java/com/group/enslibraryapp/daliy/daysix  Day7강의JPA란?JPA(Java Persistence API)는 자바 진영의 ORM의 표준 기술ORM이란?객체와 관계형 데이터베이스를 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java진형 규칙Hibernate란?자바 진영의 다양한 ORM 프레임워크 중 가장 많이 사용되는 프레임워크 Hibernate로 만들어진 ORM 표준 기술은 JPAJPA를 사용하기 위한 설정 jpa: hibernate: ddl-auto: none properties: hibernate: format_sql: show_sql: dialect: org.hibernate.dialect.MySQL8Dialectddl-auto : 스프링이 시작할때 DB 테이블 동작을 진행합니다.create : 컴파일 시작할때 기존 테이블이 있어도 삭제 후 새로생성create-drop : 런타임 종료후 dropupdate : 객체와 테이블이 다른 부분만 변경validate : 객체와 테이블이 동일한지 확인none : 아무 동작xcreate,update는 개발서버에서 사용하며, 운영서버에서는 사용하지 않고 none과 validate는 운영서버 및 개발서버에서 자주 사용할꺼같다 생각이 들었습니다.show_sql: JPA에 대한 쿼리를 보여주는 설정format_sql: 쿼리에 대한 동작에 정렬 설정dialect: 데이터베이스에 따른 방언을 맞추기 위한 설정  JPA에 대한 다양한 어노테이션 @Entity : 테이블과 객체가 1:1로 대응하는 데이터를 이야기합니다.왜 기본 생성자가 없는것을 entity에 작성할까? JPA는 Reflection API를 사용하여 객체를 생성하기 때문에 기본 생성자가 필요 private은 지연로딩 설정시 Proxy 오류가 발생합니다.Reflection API : 리플렉션은 힙 영역에 로드된 Class 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있도록 지원하고, 인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 API@Column@Column(nullable = false , length = 20, name = "name")null확인, 길이, 이름을 지정합니다. 테이블의 컬럼을 지정하는 어노테이션입니다.name이 동일하다면 name은 생략하다. Column 어노테이션도 생략이 가능하다.  JPA의 쿼리 작성 방법public interface UserRepository extends JpaRepository<User,Long> { }레포지토리 인터페이스를 생성하여 JpaRepository<엔티티 객체 ,엔티티 객체의 ID>를 입력해주면 Spring Data JPA 사용이 완료 됩니다. private final UserRepository userRepository; public void saveUser(UserCreateRequestDto request) { userRepository.save(new User(request.getName(), request.getAge())); }생성자를 이용한 의존성 주입을 진행한 후 쿼리문을 날려 사용합니다. save는 이때 엔티티의 있는 필드명을 입력을 해줘야합니다.기본적으로 제공하는 Spring JPA 메서드save: 사용자를 생성,수정을 하는 메서드findById: 아이디를 통해 가져오는 메서드 Optional<타입>, 타입findAll: 전체 조회 반환은 Listexist : 쿼리 결과가 존재하는지 booleancount : SQL의 개수를 센다.Optional<타입>을 활용하여, null값에 대한 처리를 진행하여 코드에 대한 가독성 및 null처리를 유연하게 처리할 수 있습니다.과제해당 과제를 진행하면서 Stream문에 대한 사용방법과 도메인 객체를 만들면서 아 이땐 이렇게 했구나라는 대답을 얻을 수 있었습니다. 또한 왜 사용해야지 하면서 깊게깊게 공부하다보니 이런식으로 동작하는 구나에 대해 많이 배웠습니다.또한 클래스와 엔드포인트가 동일한 문제를 겪으면서 프로젝트에 대한 패키지에 대해 다시 되돌아보는 시간이였습니다.코치님이 말씀해주신 enum을 통해 분기처리를 작업하는 방향성에 대해 생각하지 못했는데 알게 되어 기뻤습니다.노션 : https://silvercastle.notion.site/7-28ba1cb6c81a4972b6ec38a4cd69ef96?pvs=4깃허브 : https://github.com/backgom1/Inflearn_BE0_Study/tree/main/ens-library-app/src/main/java/com/group/enslibraryapp/daliy/dayseven  Day 8강의트랜잭션이란?데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위 , 쪼갤 수 없는 업무의 최소 단위강의 내용 중에 쇼핑몰 사이트에 주문을 하기 위해 주문작업에 대한 내용을 쪼개보면, 주문기록저장,포인트저장,결제기록 저장을 진행해야하는데 주문기록저장,포인트저장 작업이 정상적으로 수행되어도 결제기록 저장에 예외가 발생하면 다같이 처리를 해주지 않아야하는데 주문기록저장,포인트저장은 저장이 되는 상황을 막기위해 쿼리동작을 한번에 처리하는 것을 의미하기도합니다.SQL에서 사용하는 트랜잭션 사용방법start transaction; : SQL에서 트랜잭션 시작commit : 트랜잭션 실행rollback: 트랜잭션 실행 실패만약 다른 접속 사용자들이 insert문을 작업을 진행하였고, 또 다른 접속 사용자가 select를 해도 commit이 되어있지 않기 때문에 다른 접속자의 저장된 내용을 확인 할 수 없다. 강의에서 트랜잭션 적용해보기//어노테이션을 달면 메서드 시작 아래에서 트랜잭션 시작 //함수가 예외없이 잘끝나면 commit //아니면 rollback; @Transactional public void saveUser(UserCreateRequestDto request) { userRepository.save(new User(request.getName(), request.getAge())); throw new IllegalArgumentException("틀렸습니다."); }트랜잭션 어노테이션을 통해 saveUser라는 곳에 트랜잭션이 시작되고, save를 진행하여 커밋을 대기하는 상황에 예외가 발생하여 rollback이 되어 데이터베이스상에 저장이 되지 않는 모습을 볼 수 있습니다. @Transactional(readOnly = true) public List<UserResponseDto> getUsers() { List<User> users = userRepository.findAll(); return users.stream() .map(user -> new UserResponseDto(user.getId(), user.getName(), user.getAge())) .collect(Collectors.toList()); }select절은 @Transactional의 속성 중 readonly = true는 생성,수정,삭제에 대한 처리를 진행하지않고 조회만 해 성능 향상에 도움이 됩니다!!여기서 알아야할점은 checkedException은 예외가 발생하여도 트랜잭션 작업에 롤백 상황이 있어도 커밋이 됩니다.하지만 구글링을 통해 확인 해보았는데! try-catch및 개발자가 직접적으로 예외를 던져준다면, checkedException도 롤백을 진행할 수 있습니다. 꼭 checkedException은 트랜잭션 작업에 롤백이 안된다는 무조건적인것이 아니였습니다!!영속성 컨텍스트란?테이블과 매핑된 Entity객체를 관리/보관하는 역할트랜잭션을 사용하면 영속성컨텍스트가 생성되고 , 트랜잭션을 종료하면 영속성컨텍스트가 종료된다.Entity를 저장하거나 업데이트를 진행 하는 변경을 감지해 자동으로 저장된다. → 변경 감지쓰기지연 : INSERT/UPDATE/DELETE SQL 을 바로 날리는것이 아니라 commit이 진행되면 한번에 날려서 보낸다. 왜 한번만 날릴까? 한번에 묶어서 하면 성능이 올라간다1차캐싱 : id를 기준으로 entity를 기본적으로 사용할 수 있도록함Day 9강의책 생성 API 개발예전 강의에서 배운대로 클래스를 작성했고, 레이어드 아키텍쳐를 통해 Controller,Service,Repository에 패키지 구조를 나눴습니다.엔티티객체를 생성했고, DTO를 구간을 나눠 작성하는 방법과 Spring Data JPA를 통해 저장하는 방법을 복습했습니다.책 대출 기능 개발하기user_loan_history라는 테이블을 생성하며, id값을 참조하고있고, tinyint를통해 boolean을 알아 성능에 대해 줄일수 있다는 점을 배웠습니다.기존과 동일하게 DTO , 계층과 JPA를 사용하여 기능을 개발했습니다.반납 기능 개발하기반납기능 강의중에 DTO를 재활용하는 것과 새로 만드는것에 대해 이야기가 나왔는데두 기능에 한 기능에 변화가 생기면 유연하고 사이드 이펙트에 대해 대처를 할 수 없다 생각했습니다.회사코드 및 개인 프로젝트에서는 하나로 묶은 만능 DTO를 만들었는데 각각 나눠서 관리하는게 더 낫다 생각하여 이후 작업에 진행할 예정입니다.저는 지금도 좋은 코드를 작성한 줄 알았고, 지금도 그런 방식으로 코드를 구현해왔지만 더 좋은 방법이 있다. 라는 내용과 함께 많이 설레기도 했습니다. 정말 강의를 들으면서 기본기와 심화적인 디테일내용을 잡을 수 있어 너무너무 좋았던 것 같습니다. Day10강의코드를 객체지향 적으로 짤 수 없는 강의를 들으며, 예전 코드 및 지금 코드에서 쿼리 저장할때 각각 저장하는 방식을 사용했는데, 도메인 계층을 하나의 관심사로 묶어 처리하는 과정을 배웠습니다. 예전에 코치님께 여쭤본 쿼리를 나눠서 보내는 과정에 대해 아! 이때 이런식으로 쿼리를 만들면 되겠구나. 라는 것을 깨달았습니다(아닐 수도 있습니다 하하..)또한 다양한 연관관계에 대해 배웠는데.  @ManyToOne private User user;JPA는 연관관계에 대해 사용하는 방법으로 @ManyToOne으로 묶어 사용했으며, 반대 방향에서는@OneToMany(mappedBy = "user") private List<UserLoanHistory> userLoanHistoryList = new ArrayList<>();를 사용하여 양방향 연관관계에 대해 배웠습니다. 연관관계에 대한 주인은 주도권이 가지고있는 연결된 필드를 가진 사람이 주인이며mappedBy는 주인이 아닌 곳에 달아줘야하는것을 알았습니다. 또한 @ManyToOne를 가진 쪽이 주인인것으로 배웠으며, 연관관계에 많이 헷갈렸고 지금도 많이 헷갈렸지만, 조금씩 알아갔습니다.또한 주인의 대한 setter를 사용해야 연관되어있는 엔티티에 값이 저장되며, 주인이 아닌곳에 저장시, 주인 엔티티가 저장이 되지 않는 것을 배웠습니다. 다양한 연관관계 종류 @JoinColumn : 연관관계 주인이 활용할 수 있는 어노테이션필드의 이름이나 null여부 , 유일성 여부 , 업데이트 여부등을 지정@OneToOne : 1대1로 연관관계를 맺는 방식입니다. 하나의 사용자는 하나의 집 주소를 가지고 있습니다!@ManyToMany : 구조가 복잡하여 사용하지 않은것을 추천합니다. 중간 테이블을 만들어 관리를 진행해봤고 이것이 더 좋은 방법이라 생각이 됩니다!cascade : 함께 연결되어 있는 객체도 함께 저장되거나 삭제 되는 기능!만약 user와 대출기록이 묶여있다면 데이터베이스에 user와 대출기록이 삭제된다.orphanRemoval : 대출 기록이 삭제되어도 user는 삭제가 되지않는다. 객체간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션  public void loanBook(String bookName) { this.userLoanHistoryList.add(new UserLoanHistory(this, bookName)); } public void returnBook(String bookName){ UserLoanHistory targetHistory = this.userLoanHistoryList.stream() .filter(history -> history.getBookName().equals(bookName)) .findFirst() .orElseThrow(IllegalArgumentException::new); targetHistory.doReturn(); }도메인 객체에 대한 관심사를 묶어 협력하여 도메인 계층에 비지니스로직이 들어간 코드를 보고 와.. 저렇게 할수도 있구나 이러면 기능적으로 명확해지겠구나라는 생각을 했고 연관관계에 대해 조금더 깊게 생각이 들었습니다.그렇지만 연관관계에 대해 많이 묶여버린다면 사이드 이펙트에대한 고려와 성능상의 문제가 생길 수 있어 여러 아키텍쳐 공부 및 비지니스 요구사항에 대해 공부를 해야한다 느꼈으며, 아 아직 정말 많이 멀었다 생각했습니다. 그렇지만 해야할 일을 하나씩 진행하며 성장하고 있다는 기분을 받았고, 그것이 원동력을 만들어주었던 것 같습니다. 2주차에는 급하게 작성한 부분들때문에 부족해보이고 모자랐다 생각이 들었고, 다음 주차에서는 좀 더 신경을 많이 써서 진행을 해야겠다 생각했습니다.또한 미니 프로젝트를 팀 별로 구성하여 작업을 진행해보며, 내가 미쳐 생각하지 못했던 부분, 내가 더 나은부분에 대해 토론을 하며 배워갈 예정입니다.

김A

[인프런 워밍업 클럽 BE 0기] 2주차 발자국

Day 7 : 스프링 컨테이너의 의미와 사용 방법스프링 컨테이너가 생성되는 과정과 스프링 빈을 등록해서 사용하면 어떠한 장점이 있는지 알아보았다.스프링 컨테이너는 우리가 어떠한 객체를 사용할지 대신 결정해준다. 이를 IoC(제어의 역전)이라고 한다.빈을 만들고 객체간의 의존성을 연결해준 다음 프로그램에게 그 객체를 제공한다DI(의존성 주입)에서 "의존"은 A클래스가 B클래스의 메서드를 실행할 때 A클래스가 B클래스에 의존한다는 뜻으로 해석할 수 있다.만약 회원가입 기능을 MemberService 클래스에서 구현한다 가정했을 때 이 기능은 정확히 MemberRepository의 save 기능을 이용하는 것이다. 그러면 MemberService 클래스는 MemberRepository 클래스에 의존한다고 볼 수 있다. 미션 Fruit 기능들을 JPA를 이용해 변경특정 과일 기준으로 지금까지 가게를 거쳤갔던 과일 개수 세는 기능 개발아직 판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록 조회 기능 개제출과제7 Day 8 : Spring Data JPA를 사용한 데이터베이스 조작JPA를 이용하여 DB를 조작하였다.User 테이블에 대응되는 Enttiy 클래스인 User을 만들었다.UserRepository 인터페이스를 생성한 뒤 JpaRepository 클래스를 상속받아 JPA를 활용하여문자열 SQL 보다 편하게 DB를 조작하였다.public void saveUser(UserCreateRequest request){ User u = userRepository.save(new User(request.getName(), request.getAge())); } userRepository 클래스의 save 메소드는 이미 JpaRepository 클래스에 구현되어 있는 메소드이다.이 메소드를 활용하여 User 정보를 저장하였다. Day 9 : 트랜잭션과 영속성 컨텍스트트랜잭션은 쪼갤 수 없는 업무의 단위이다. SQL문을 실행했을 때 중간에 오류가 발생해서 중단되면 데이터 오류가 발생하기 때문에 모든 SQL을 성공시키거나 하나라도 실패하면 모두 실패시키기 위해 존재한다.영속성 컨텍스트는 테이블과 매핑된 Entity 객체를 관리/보관하는 역할이다.영속성 컨텍스트 기능으로는 변경감지/쓰기지연/1차 캐싱이 있다.@Transactional public void updateUser(UserUpdateRequest request){ User user = userRepository.findById(request.getId()) .orElseThrow(IllegalAccessError::new); user.updateName(request.getName()); userRepository.save(user); //여기서 변경 감지. 이게 없어도 업데이트됨 }@Transactional은 함수 시작 때 트랜잭션이 시작되고 끝나면 트랜잭션을 commit하는 어노테이션이다. Day 10 : 조금 더 복잡한 기능을 API로 구성하기책 관련 API를 개발하였다.Entity 클래스인 Book, JpaRepository를 상속받은 BookRepository, dto, service, controller 클래스를 생성하여 개발하였다. 현재 미니프로젝트 개발을 진행하고 있다. 스스로 개발하는 것 이다보니 어렵다. 최선을 다해서 3단계까지 개발을 할 수 있도록 열심히 하겠다 

윤대

[인프런 워밍업 0기] JPA findById() vs getReferenceById()

다음은 JPA에서 연관관계 매핑이 되어있는 객체를 save하는 상황이다. @Transactional public void recordArrivalTime(Long memberId, LocalDate attendanceDate, LocalTime startTime) { Member member = memberRepository.findById(memberId); workTimeRecordService.recordArrivalTime(member, attendanceDate, startTime); }WorkTimeRecord를 save하기 위해서는 Member를 알아야만 했고,Member를 찾기 위해서 기존에는 Id값으로 Member를 찾아왔지만, 문득 이는 굉장히 '비효율적'이라는 생각이 들었다.WorkTimeRecord를 저장하기 위해 필요한 것은 Member의 Id 값이지, Member의 모든 정보를 찾아올 필요는 없었기 때문에 불필요한 select문이 발생하는 셈이다.그래서, 이리저리 알아보던 차, Proxy객체라는 존재와 getReferenceById()라는 JpaRepository의 메소드를 알게 되었다.Proxy는 여러 곳에서 쓰이는 용어이지만, JPA를 구현하고 있는 hibernate에서는 Proxy객체는 실제 Entity(여기서는 Member)를 상속 받은 껍데기 객체이다.Entity를 상속받았기 때문에 Entity의 모든 역할을 대신 수행할 수 있으나, 내부의 값은 모두 비어있는 상태가 된다.만일, 상태가 필요한 순간이 되면 그때 JPA의 영속성 컨텍스트를 통하여 실제 Entity를 조회하여 값을 찾아오게 된다.위와 같은 기술을 Lazy라고 한다.즉, 나의 처음의 고민은 이를 통해 해결할 수 있게 되었다. @Transactional public void recordArrivalTime(Long memberId, LocalDate attendanceDate, LocalTime startTime) { Member member = memberRepository.getReferneceById(memberId); workTimeRecordService.recordArrivalTime(member, attendanceDate, startTime); }이렇게, Proxy객체로 Member를 찾게 되면, 실제로 select문은 발생하지 않고 정상적으로 WorkTimeRecord에 Member값을 할당할 수 있게 된다. 

백엔드JPA인프런워밍업MVC

인프런 워밍업 클럽 - 백엔드 스터디 [2주차]

이번주 발자국은 수업 시간에 다루지 않았거나, 놓친 사항들 정리하고자 한다. MySQL에서 column name 룰MySQL은 Column name 이 camel case 로 작성되어 있으면, “_” 로 인식한다.예컨대, warehousingDate 라고 컬럼명을 작성하였는데, 인텔리제이에서 컴파일을 하면 계속warehousing_date 필드가 없다고 오류가 발생하였다.찾아봤더니, camel case로 작성될 경우 제대로 인식하지 못하는 문제가 있었다.인텔리제이 무료 버전을 사용하고 있어서, MySQL은 workbench를 다운받아 사용하고 있는데,거기서는 컬럼명이 정상적으로 나왔다. 그리고, 쿼리도 잘 실행이 되었었는데 JPA를 통해서 하려니 문제가 발생하였다.이런 오류를 방지하기 위해서는 MySQL column 명을 지정할때는 camel case가 아닌 _ 로 만들면 된다.JAVA : warehousingDateMySQL : warehousing_date MySQL DATE 변수 형태 이건 무엇이 잘못되었는지 모르겠다. MySQL 에서 날짜를 지정하면 2023-03-03 이렇게 정상적으로 입력이되는데 이번 주에 과제를 하면서, 20230303 이런 포맷으로 입력된 적이 있었다.JAVA 에서는 2023-03-03 이렇게 SQL로 입력하려고 하고, SQL은 20230303 총 8 글자 자리만 있다보니 Data truncated ~~~~~ at row 1 이런 오류가 계속 발생하였다. 테이블을 삭제하고 YYYY-MM-DD 타입으로 입력해 주자 그 다음부터는 정상적으로 입력을 받았다. 이번엔 DATE타입에서 문제가 발생했었지만, column 내용 길이를 초과하는 경우도 이와 같은 문제가 발생 할 것 같다.비슷한 오류가 발생할때를 대비하여 정리하였다. Intelli J 무료 버전에서 profile 설정하는 방법강의에서는 유료 버전을 사용해서, run configuration 에서 profile 설정을 할 수 있었다.하지만 무료버전에서는 같은 기능을 사용할 수 없어서 다른 방법으로 설정해야한다.똑같이 Edit Configurations 에 들어간다. Add Run Options 에 가서, Add VM options 을 선택한다.-Dspring.profiles.active=dev 새로 생긴 옵션 입력 필드에 위와 같이 입력한다. dev 위치에는 Local, dev, product 등 설정한 profile 명을 입력하면 된다.       

진영준

[인프런 워밍업 클럽 스터디 0기 발자국] 2주차 회고

안녕하세요. 주니어 백엔드 개발자를 꿈꾸는 12hugs 실명은 진영준입니다. 😉지난주 월요일부터 인프런 워밍업 클럽 스터디 0기를 시작했으며,2주차에는 무엇을 느꼈고 무엇을 공부했으며 어떤 사실을 깨달았는 지에 대해공유를 해볼까합니다. 그럼 시작합니다!내가 알던 JPA는 더욱 넓은 세계였다.제가 알던 JPA는 단순히 백엔드 개발자가 SQL언어에 대한 공부를 따로 해야하기도 하고,좀 귀찮기때문에 나온 개념이라고 생각했습니다.그래서 별 거 아닌 존재라고 생각하기도 했고, 간과한 부분이 없지 않아있었습니다.그렇지만, 강의를 들으면서 JPA의 기능에는 놀라운 기능들이 있다는 것을 알게되었습니다.existBy, countBy 등등 많은 기능을 지원하기도 하고, 직접 @Query 어노테이션을 통해JPQL언어로 DB에 SQL을 날릴 수 있는 것도 확인하였습니다. 그리고 하이버네이트란 무엇이고, JPA와 ORM은 무엇인지에 대한 개념도 잡히게 되었습니다.아무래도 너무 야생형으로 공부한 입장으로 해당 기능과 관련된 지식이 없으면구글링조차 할 수도 없기때문에 해당 단어들과 기능들은 사막의 오아시스와도 같았습니다. 객체지향 모두 지향하지만, 사실 지양하고 있다.객체지향이 중요하다는 점은 모르는 사람이 없을 것입니다.사실 이런 개념때문에 객체지향언어들이 각광을 받기도 합니다.하지만, 저는 전혀 객체지향을 하고 있지 않았습니다. 제가 짠 로직들은 전부 절차지향이었으며,class가 주인이 되는 코드를 짜왔습니다. 그렇지만, 이번 2주차를 통해 객체지향에 대한 개념이 조금 잡히기 시작했습니다.이건 미니프로젝트를 진행하며, 느낀 부분이었습니다.저는 원래 평소와 같이 서비스에 모든 비즈니스 로직을 쑤셔넣었습니다.하지만, 제가 쓴 코드인데도 불구하고, 읽기가 힘들고, 이해하기가 힘들었습니다.다시 말하자면, 코드싸개였고, 전혀 객체지향적이지도 못했습니다.출처 : https://namu.wiki/w/%EC%BD%94%EB%8D%94 그렇지만, 객체지향을 공부하기 시작하면서 객체의 불변성을 유지하기 위해서기존 테이블 @Entity 객체의 @Setter을 빼고, 정말 객체끼리 상호작용 및 의사소통을 하며,비즈니스 로직의 간소화를 진행할 수도 있었고, 훨씬 깔끔해진 코드와 함께 가독성 또한 좋아지게 되었습니다. 그래서 2주차를 통해 얻게 된 것은?프로젝트를 진행하기 전에 요구사항과 설계가 중요했다는 점을 강조하고 싶습니다.요구사항 스펙은 사람들이 원하는 기능을 함축해놓은 스펙입니다.즉, 클라이언트가 원하는 기능이 없다면, 개발자는 필요가 없어지게 되겠죠. 그리고 설계가 굉장히 중요합니다.설계를 잘 해놓고 시작해야 변수사항에 대해 잘 대처를 할 수 있으며,보다 유연하고, 객체지향적인 코드를 짤 수 있다는 생각을 했습니다. 그래서 저는 처음에 설계를 안하고 미니프로젝트를 시작했기 때문에며칠뒤 코드를 처음부터 다시 짜는 불상사를 일으켰습니다. 더욱 유연하고 뛰어난 개발자가 되고 싶다면,공부도 물론 중요하겠지만, 설계와 요구사항을 충분히 이해하고객체지향적으로 코드를 짤 줄 아는 개발자여야 한다는 걸 깨닫게 된 2주차였습니다.

인프런객체지향절차지향설계워밍업클럽스터디0기

chldlsgh76

[인프런 워밍업 스터디 클럽 0기-BE] 2주차 발자국

Day 619 ~ 22강스프링 컨테어니러를 통해 다양한 설정들을 모두 자동으로 해주며, 클래스가 들어갈 때는 빈을 식별할 수 있게 이름 및 타입과 함께 다양한 정보를 저장하며 자동으로 의존성을 주입해준다.빈 등록 방법에는 클래스에 맞는 빈 어노테이션을 붙이고 @Qualifer와 @Primary 사용법을 배웠다.과제과제6 Day 723 ~ 26강지금까지 sql쿼리문을 짜서 하는것은 너무 힘들다. 문자열을 작성하기 때문에 실수를 인지하는 시점도 느리며, 특정 데이터베이스에 종속적이다. 반복 잔업이 많아지며 테이블과 객체 간에 패러다임이 다르다는 문제가 있었다.그래서 나온게 JPA다. 데이터를 영국적으로 보관하기 위해 Java 진영에서 정해진 규칙이다.프로젝트를 함계하면서 사용법을 익혔다. 과제 과제7 Day 8 27 ~ 29강트랜잭션이란 쪼갤 수 없는 업무의 최소 단위 의미이다.쉽게 말하자면 여러 SQL을 사용시 한 번에 성공시키거나 , 하나라도 실패하면 모두 실패하는 기능이다.트랜잭션을 왜 사용하는지 배웠다. 학교나 자격증때 공부했지만 다시 복습하니 왜 트랜잭션이 중요하지 다시 알게된 계기가 되었다.과제과제는 미니프로젝트인데 아직 구현을 하지못했다 이번주에 구현할예정이다. 정리벌써 2주차가 지나갓다.과제도 미니프로젝트 하나만하면 끝나고 많은걸배울수있엇다.spring boot에대해서 더욱자세히 알수있엇고 been 과 jpa사용 등등 새로운 기술들을배우면서재미를 느낄수있는 시간이었다.

crispin

[인프런 워밍업 스터디 클럽 0기_BE] 2주차 회고록 정리

2주차 회고시간이 엄청 빠르게 지나간다.하루에 최소 4시간씩 짬짬이 공부를하고 있는데, 강의보고 미션 해결하고 4시간이 부족할때가 많다.마침 TDD 스터디를 병행하고 있어, 미니프로젝트를 TDD 로 구현을 해봤는데 매우매우 어렵다.2주차 미션Day06강의19 ~ 22강스프링 컨테이너에 대해 학습했다.미션4일차 미션에서 작성한 코드를 Controller - Service - Repository 로 분리를 진행했다.분리를 진행하면서, 새로운 Repository 가 만들어졌는데, 어떤 Repository 를 빈으로 등록하여 사용할지 지정할 수 있는@Primary 어노테이션과 @Qualifier 어노테이션을 학습하고 적용시켜봤다.6일차 미션 정리Day07강의23 ~ 26강Spring Data JPA 활용 법에 대해 학습했다.미션6일차 과제에 JPA 를 적용시켜 보자.JDBC 를 활용하여, DB 에 저장하고 조회하는 작업을 JPA 를 적용시켜 작업하도록 변경하였는데 매우매우 편리했다.JPA 는 확실히 배워야하는게 매우 많지만, 사용하는데 있어서는 매우 편리한것 같다.사용이 편리하다고 깊게 공부하지않고 막 사용하다보면 문제가 팡팡 터질수 있기 때문에 JPA 관련해서는 꼭 깊게 공부해봐야겠다.7일차 미션 정리  Day08 ~ 10강의23 ~ 32강JPA 에 대해 좀 더 깊은 학습을 진행했다.미션드디어 미니프로젝트 미션에 들어갔다.1단계 미션을 수행하는데 생각보다 시간이 너무 많이 들어갔다. 원래 목표는 2단계 미션은 끝내려고 했는데 1단계 미션밖에 끝내지 못했다.구현 내용이 그리 어렵지 않아, TDD 를 활용하여 개발을 진행하고 있는데 기존에 개발하던 방식과는 너무 많은 점 이 달라 오래걸리는것 같다. TDD 는 진짜 생각보다 도 더 어렵다. 개발을 하다보면 나도모르게 기존 방식대로 개발을 하고 있다.미니프로젝트 1단계 미션 정리정리이번주 예상했던 만큼 진도가 나가지는 않았지만, 배우고 새로운걸 적용시켜 볼 수 있어 너무 좋았다.좀 더 집중하고, 계획된 만큼 프로젝트를 진행하는 습관을 만들어가야겠다.

백엔드워밍업스터디0기백엔드회고발자국

킹재

[인프런 워밍업 클럽 0기] 2주차 발자국

이번 주에 배운 내용스프링 컨테이너의 역할스프링 컨테이너가 시작되면 기본으로 등록되는 스프링 빈들이 있다. (JdbcTemplate)이어서 개발자가 직접 만든 클래스가 스프링 빈으로 등록된다. 이때 스프링 컨테이너가 필요한 의존성을 자동으로 주입해준다. (UserRepository를 빈으로 등록할 때 JdbcTemplate이 자동으로 DI)애플리케이션에 필요한 설정은 이제 컨테이너가 처리한다. 이를 제어의 역전이라 부른다. 개발자는 이제 비즈니스 로직에만 집중하면 된다.스프링 빈 등록 방법@Configuration + @Bean 조합@Component, @Controller, @Service, @Repository@Primary나 @Qualifier를 이용하면 원하는 빈을 우선 적용할 수 있다.JPA 사용하기 1 - 이론SQL을 직접 작성하면 실수가 발생할 확률도 높고, 특정 DB에 종속되는 단점도 있다. 결정적으로 DB 테이블과 Java의 객체는 패러다임이 다르다. 이런 문제를 해결하기 위해 등장한 데이터 접근 기술이 JPA (Java Persistence API)이다. JPA는 객체와 관계형 데이터베이스의 테이블을 맵핑하여, Java 데이터를 영구적으로 저장할 수 있도록 정해 놓은 규칙이다.JPA는 인터페이스이고, 이를 구현한 대표 기술로 Hibernate가 있다. Hibernamte는 내부적으로 JDBC를 사용한다.JPA 사용하기 2 - 실전@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; @Column(nullable = false, length = 20, name = "name") private String name; private Integer age; protected User() { } // 생략 }public interface UserRepository extends JpaRepository<User, Long> { }@Service public class UserServiceV2 { private final UserRepository userRepository; public UserServiceV2(UserRepository userRepository) { this.userRepository = userRepository; } public void saveUser(UserCreateRequest request) { User user = userRepository.save(new User(request.getName(), request.getAge())); System.out.println("id: " + user.getId()); } // 생략 }스프링 트랜잭션스프링의 @Transactional은 트랜잭션 관리를 지원한다. 주의할 점으로는 org.springframework.transaction.annotation.Transactional을 붙여야 한다는 것이다. 다른 패키지의 @Transactional을 붙이면 정상 동작하지 않을 수 있다.데이터의 변경이 없고, 조회 기능만 있을 때는 readOnly 속성을 설정할 수 있다.@Service public class UserServiceV2 { private final UserRepository userRepository; public UserServiceV2(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional public void saveUser(UserCreateRequest request) { User user = userRepository.save(new User(request.getName(), request.getAge())); System.out.println("id: " + user.getId()); } @Transactional(readOnly = true) public List<UserResponse> getUsers() { return userRepository.findAll().stream() .map(user -> new UserResponse(user.getId(), user.getName(), user.getAge())) .collect(Collectors.toList()); } // 생략 } 미션[6일차 - 어노테이션]링크: https://www.inflearn.com/blogs/6805레포지토리를 메모리 DB 버전과 MySQL 버전으로 나누고 교체해가며 빈으로 등록할 수 있도록 @Primary 어노테이션 선언하기@Repository public class FruitMemoryRepository implements FruitRepository { // 생략 }@Repository @Primary public class FruitMySqlRepository implements FruitRepository { // 생략 } 회고그간 MyBatis 같은 SQL Mapper만 써오다가 JPA를 이용해 애플리케이션을 개발했습니다. 기능을 추가할 때마다 SQL문도 일일이 만드는 과정이 번거로운데 JPA는 그럴 필요가 없으니 확실히 개발 생산성을 높일 수 있었습니다. 다만 새로운 유형의 기술이고 난이도도 있는 편이라 이에 적응하는 데는 시간이 걸릴 것 같습니다.  오늘도 함께 스터디 중인 여러분들을 존중하고 존경하며 여기서 마무리하도록 하겠습니다.

백엔드워밍업클럽

[인프런 워밍업 클럽] BE - 2주차 회고록

[2주차 학습 내용]진도표에는 32강까지 수강하라고 안내 되어있지만, 36강까지 듣고 섹션 1, 2 강의를 다시 들으면서 복습하는 시간을 가졌습니다.강의를 다시 돌려보면서 놓쳤던 부분이 뭐였는지를 체크하고, 강사님께서 제공해주시는 강의 자료에 추가 필기를 하며 내용 복기를 했습니다.[미션 수행]6번째 과제 : https://yestruly.tistory.com/967번째 과제 : https://yestruly.tistory.com/97지난 4번째 과제에서 연장되어 강의 내용을 적용해보는 과제였습니다.controller-service-repository로 분리하고 JPA를 적용하면서 "기존에는 이렇게 작성되어있는 걸 이런 식으로 작성하고 진화하게 되었구나!"를 직접 느껴볼 수 있었어요.6, 7번째 과제를 수행하면서 점점 코드 가독성도 좋아지는게 보이더라구요.아직 스트림을 사용하는 것이 익숙하지 않아서 스트림 이용하는 것 외에는 어려운 점은 없었습니다![마무리]다음주부터는 매일 수행해야 하는 과제는 없고 미니 프로젝트를 만들어보는 과제를 수행해야 합니다.현재 1단계는 수행했고, 내일 블로그 포스팅 + 깃 연결을 할 예정입니다. 1단계에서 생각보다 시간이 적게 소요되긴 했지만, 남은 단계는 얼마나 시간이 걸릴지... 궁금하네요...ㅎㅎ다음주에도 섹션 3-5 복습 + 남은 강의 듣기 + 미션 수행까지 열심히 해보겠습니당 화이팅 https://www.inflearn.com/course/inflearn-warmup-club-study-0

winnercold

인프런, 워밍업 스터디 BE - 두번째 발자국

일주일 동안 학습한 내용스프링 컨테이너JPA트랜잭션 스프링 컨테이너package com.group.libraryapp.controller.fruit; import com.group.libraryapp.dto.fruit.FruitCreateRequest; import com.group.libraryapp.dto.fruit.FruitStatResponse; import com.group.libraryapp.dto.fruit.FruitUpdateRequest; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.web.bind.annotation.*; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; @RestController @RequestMapping("/api/v1") public class FruitController { private final JdbcTemplate jdbcTemplate; public FruitController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }기존의 FruitController 는 JdbcTemplate을 의존하고 있다.FruitController 는 @RestController 어노테이션을 통해 스프링 빈에 등록이 되는데 JdbcTemplate은 어떻게 등록이 되었을까?JdbcTemplate 은 처음 build.gradle 설정 파일을 통해 의존설정을 해 두고 있었다. implementation 'org.springframework.boot:spring-boot-starter-jdbc스프링 빈에 등록 되었다는 것은 무슨 의미일까?서버가 시작되면, 스프링 서버 내부에 거대한 컨테이너를 만들게 되는데, 이 컨테이너 안에는 클래스 정보 뿐 아니라 다양한 설정파일을 통해 등록된 정보등등이 함께 들어가고, 이 때 인스턴스화도 이루어진다.스프링 컨테이너 왜 사용할까?FruitRepository 의 기능을 새로운 클래스로 만들어서 바꾸고 싶은데 인터페이스를 쓰더라도 변경해야하는 코드의 범위만 줄어들 뿐 일일히 들어가서 바꿔줘야했다.그러나, 제어의 역전이라는 컨테이너가 어떤 쿠현 타입을 쓸지 대신 결정해주는 방식을 사용하게 되면 클래스를 다른 클래스로 바꿔 사용하더라도 코드의 변경 없이 우리가 원하는 클래스를 주입할 수 있게 된다.빈을 등록하는 방법@Configuration클래스에 붙이는 어노테이션, @Bean 을 사용할 때 함께 사용한다.@Bean메서드에 붙이는 어노테이션, 메서드에서 반환되는 객체를 스프링 빈에 등록한다.@Configuration + @Bean 언제 쓸까?JdbcTemplate과 같은 외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.@Repository , @Service개발자가 직접 만든 클래스를 스프링 빈에 등록할 때 사용한다.@Component이 어노테이션이 붙은 클래스를 컴포넌트 라고 한다.서버가 뜰 때 자동으로 빈에 등록된다.알고보면 @Repository , @Service 와 같은 어노테이션 을 타고 들어가면 최상단 클래스에 @Component 가 붙어있다.빈을 주입하는 방법생성자를 통해 주입 ( 가장 많이 씀)  private final FruitService fruitService; public FruitController(FruitService fruitService){ this.fruitService = fruitService; }setter 와 @Autowired 사용 ( 누군가 setter 메서드를 만지면 오류가 발생할 수 있다) private final FruitService fruitService; @Autowired public void setFruitController(FruitService fruitService){ this.fruitService = fruitService; }@Autowired 사용 ( test가 힘들다) @Autowired private final FruitService fruitService;JPASQL 을 직접 작성하면 안 좋은 이유문자열을 직접 쓰기 때문에 오타 발생률이 높다.컴파일 시점에 발견되지 않고 런타임 시점 (서버가 가동되고 난 후) 에러가 발견된다.특정 데이터베이스에 종속적이게 된다.테이블을 만들때 마다 CRUD 쿼리가 항상 필요하다 (반복작업이 많아짐)데이터베이스의 테이블과 객체는 패러다임이 다르다ex) 연관관계교실도 학생을 가지고 있고, 학생도 교실을 가지고 있다. (양방향)DB에는 학생만 교실을 가리키게 된다.그래서 등장한 JPA (Java Persistence API)객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙이 규칙을 구현해서 코드로 작성한 것을 HibernateHibernate 이 JPA를 구현하고 있다하고 Hibernate 구현체라고 한다.이 Hibernate은 내부적으로 Jdbc를 사용한다.용어@Entity저장되고, 관리되어야하는 데이터를 의미@Id이 필드를 primary key 로 간주한다.@GeneratedValue : primary key 는 자동 생성되는 값이다.strategey = Identity : DB종류마다 자동 생성 전략이 다르다.JPA 를 사용하기 위해서는 기본 생성자가 꼭 필요하다.@Column : nullable, 길이제한, DB에서 사용되는 이름을 매핑 할 수있는 어노테이션이다.설정JPA를 사용할때 설정을 추가 해주어야 한다. (application.yml)jpa: hibernate: ddl-auto: none properties: hibernate: show_sql: true format_sql: true dialect: org.hibernate.dialect.MySQL8Dialectddl-autocreate : 기존 테이블이 있다면 삭제 후 다시 생성create-drop : 스프링이 종료될 때 테이블을 모두 제거update : 객체와 테이블이 다른 부분만 변경validate : 객체와 테이블이 동일한지 확이 후 실패시 서버 종료 시킴none : 별다른 조치 하지 않음show_sql : sql 을 보여줄 것인지format_sql : sql 을 보여줄 때 예쁘게 포맷해서 보여줄 것인지 설정dialect : DB 를 특정하면 조금씩 다른 SQL 을 수정해준다.Spring Data JPA복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리save(), findById() 등등이 있다.Spring Data JPA를 활용해 조회 쿼리를 작성하는 법find라고 작성시 한개의 데이터만 가져온다.By 뒤에 붙는 필드이름으로 SELECT 쿼리의 Where문이 작성된다.By 앞에 들어갈 수 있는 구문find: 1건을 가져온다. 반환 타입은 객체가 될 수 있고, Optional<타입>이 될 수도 있다.findAll : 쿼리의 결과물이 N 개인 경우 사용, List<타입>반환exists : 쿼리 결과가 존재하는지 확인, 반환타입은 Boolean이다.count : SQL 의 결과 개수를 반환한다. 타입은 longBy 뒤에 올 수 있는 것 (And || Or)findAllByNameAndAgeGreaterThan : 초과GreaterThanEqual : 이상LessThan : 미만LessThanEqual : 이하Between : 사이에StartsWith : ~로 시작하는EndsWith : ~로 끝나는트랜잭션쪼갤 수 없는 업무의 최소 단위주문 기록 저장, 포인트 저장, 결제기록 저장이 한 메서드 안에 있는데, 주문기록 저장, 포인트 저장 만되고 중간에 에러가 발생하여 결제기록 저장이 안 되었다면 ?모든 SQL 을 다 성공 시키거나 다 실패해야 됨start transaction 트랜잭션 시작commit 저장rollback 실패처리트랜잭션을 적용하는 법@Transactional어노테이션이 붙어 있으면 메서드가 시작될때 start transaction 을 해준다. 함수가 예외 없이 끝났다면 commit, 예외가 생기면 롤백을 해준다.@Transactional(readonly=true) : select 쿼리만 사용 할 때 옵션을 쓰게 되면 트랜잭션 사용했을 때 데이터 변경을 위한 불필요한 기능이 빠지게 되어 성능적으로 이점이 있다. 영속성 컨텍스트테이블과 매핑된 Entity 객체를 관리 / 보관하는 역할스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.특징변경감지 (Dirty Check)영속성 컨텍스트 안에서 불러와진 Entity는 명시적으로 save 하지 않더라도, 변경을 감지해 자동으로 저장된다.쓰기 지연DB의 INSERT / UPDATE / DELETE SQL을 바로 날리는 것이 아니라 트랜잭션이 commit될 때 모아서 한번만 날린다. (영속성 컨텍스트가 기억하고 4번 날릴 쿼리를 1번만 날려준다)1차 캐싱영속성 컨텍스트는 1차 캐시를 가지고 있어서, 동일한 엔티티를 여러번 불러올때 매번 DB에 접근하는 것이 아니라 캐시에서 엔티티를 가져와 성능을 향상 시킨다.Cascade옵션엔티티 간의 관계에서 부모 엔티티의 상태 변경이 자식 엔티티에 영향을 미치도록 하는 Cascade옵션을 제공한다.미션6일차과제7일차과제 회고JPA를 쓰면서 데이터도 어떻게 하면 객체지향적으로 짜야할지, 연관관계를 어떻게 매핑해야될지 고민을 하게 되었다. 

백엔드

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

2주차 회고2주차는 스프링 컨테이너와 빈, JPA에 대한 개념을 공부하였다.생각보다 간단하다고 생각이 들면서도 생각보다 어려웠다.JPA까지 사용해보니 java로만 데이터 베이스에 연결할 때보다 코드는 확실히 줄어들어 코드 작성과 가독성이 매우 높아졌지만 아직 스프링에 대한 이해도가 많이 부족한 것 같아 미니 프로젝트를 하며 복습을 계속 해야겠다고 생각이 들었다.최근 다른 프로젝트도 시작하고 새로운 공부도 하고 있어 굉장히 바쁜 날들을 보내고 있지만, 스프링이 제일 재밌게 느껴지는 것 같다.미니 프로젝트 시작은 했는데 서버 실행이 제대로 되지 않아 이 문제를 빨리 해결해야 할 것 같다. 이번 주의 미션은 JPA를 활용하여 생각보다 어렵지 않게 해결할 수 있었다. 다만 미니 프로젝트를 할 때 혼자 DB설계부터 시작하려면 미션을 할 때보다 더 많은 시간이 들고 어렵겠지만, 복습을 꾸준히 하고 많이 찾아보며 해결해내야겠다고 다짐하였다.특히, 과제를 하면서 db의 컬럼명을 카멜 케이스로 지었는데, hibernate는 컬럼을 스네이크 케이스로 인식하여 이것을 알아내는 데에 굉장히 많은 시간이 들었다. 앞으로 DB 컬럼명은 스네이크 케이스로 지을 것..추가로 인텔리제이에 깃허브 이메일을 잘못 등록하여 2월 초부터 이어온 1일 1커밋에 실패하였다. 제발 덜렁대지 말고 뭐든 꼼꼼히 확인하자..  

공간뿌웅이

[인프런 워밍업 클럽 0기] BE 2주차 회고

2주차 학습 내용 정리 6일차스프링 컨테이너의 개념을 배우고, 기존에 작성했던 Controller 코드를 Controller - Service -Repository 계층으로 3단 분리하는 방법으로 배웠습니다. 각 계층의 특징과 역할도 배웠습니다. 또한 FruitRepository를 메모리와 SQL 레포지토리로 나누고 @Primary 어노테이션을 활용해 두 레포지토리를 바꿔가며 동작할 수 있게 과제 코드를 변경해 보았습니다. 7일차JPA의 개념을 배우고, JPA 사용 방법, 엔티티 클래스 생성 방법, 테이블 매칭 방법등을 배우고 유저 테이블에 JPA를 적용해 보았습니다. 그리고 배운 내용을 통해 기존 과제를 JPA 코드로 바꿔 보았습니다.  8일차~Transaction이 무엇이고, 왜 사용해야 하는 지, 그리고 JPA 영속성 컨텍스트의 기능과 특징이 무엇인 지 배워 보았습니다.그리고 이전까지 강의에서 다루었던 내용들을 익힐 수 있도록 미니 프로젝트를 수행하기 시작했습니다.  회고6일차 과제까지는 무난하게 수행할 수 있었습니다. 그러나, 7일차 과제부터는 여러 난관이 있었습니다. 그중 가장 어려웠던 건 JPA 와 DB 테이블 매칭 이었습니다. 필드 이름을 camelCase로 적더라도 실제 데이터베이스에서는 snake_case 의 필드와 매핑되는 정책, 일반적으로 DB 네이밍은 snake_case 형식을 작성한다는 사실을 모른 채 진행하다 보니 많이 헤맸었고 결국 제 시간 안에 과제 제출을 하지 못했습니다. 7일차 과제를 제때 수행하지 못했지만, JPA 와 DB 테이블 매칭 방법을 알 수 있는 좋은 기회였다고 생각합니다. 현재 미니 프로젝트는 1단계까지 완료한 상태이며, 2단계를 진행 중에 있습니다. 다음 주 내로 미니 프로젝트 3단계 까지 완료 하고, 각 단계마다 어떻게 접근을 하였는지를 벨로그에 적을 예정입니다.

백엔드

인프런 워밍업 스터디 클럽 0기- BE 발자국2(~3/3)

일주일 간의 학습내용 요약스프링 컨테이너 이해 및 사용방법Spring Data JPA를 사용한 데이터베이스 조작트랜잭션과 영속성 컨텍스트이번주는 Section4의 주요 내용인 JPA, 트랜잭션에 대해 배웠습니다.학습내용 요약JPA 사용목적 및 사용방법 : 지난주 까지는 JDBC에 직접 접근하는 코드를 생성하여 데이터베이스를 조작했습니다. 그러나 반복적인 작업 및 특정 데이터베이스에 종속적인 단점이 있었습니다. 그래서 JPA를 사용하여 문자열 변수에 작성된 SQL 쿼리를 만들지 않고도 데이터베이스를 조작하는 방법을 배울 수 있었습니다.Spring Data JPA 구체적 사용방법실제 테이블과 매핑되는 @Entity 클래스 생성application.yml에 JPA 관련 설정 추가@Id, @Column 등의 어노테이션을 사용한 설정  Spring Data JPA를 사용한 쿼리 사용방법 : 일반적인 CRUD 쿼리인 SELECT ,UPDATE ,DELETE 등과 동일하게 사용할 수 있는 findBy, save, delete 등의 사용방법에 대해 이해하고 직접 활용하면서 활용도를 높일 수 있었습니다.  @Transactional 어노테이션을 사용한 트랜잭션 적용 : SQL 사용 시 한번에 성공시키거나 하나라도 실패하면 모두 실패시키는 트랜잭션을 적용하는 방법을 배웠습니다. 아쉬웠던 점JPA의 다양한 쿼리 및 트랜잭션 이론을 배우고 다양하게 적용하여 적응력을 높였여야했는데 아직은 약간 부족한 면이 있습니다. 그래서 이번 스터디 클럽이 종료되더라도 JPA를 다양하게 적용하기 위한 연습을 많이할 생각입니다.미션 해결 과정6일차 과제6일차 과제는 기존의 쿼리를 통해 작성된 데이터베이스 접근 코드를 JPA를 사용할 수 있도록 분리하는 과제였습니다. 배운 내용을 토대로 기존의 데이터베이스 접근 쿼리들을 JPA로 변경하는데 집중하여 과제를 제출했습니다. 한 주 간의 강의를 수강하고 미션을 해결하면서 느꼈던 점 중 하나는 JPA를 사용하는 것이 매우 편리하다는 생각을 했습니다. 기존에는 항상 어떻게 쿼리를 만들어 데이터베이스에서 원하는 데이터를 만들지에 대한 고민이 있었습니다. 그러나 그런 걱정할 필요없이 JPA를 사용했을 때 편리한 점이 있다는 것이었습니다. 그래서 다양한 JPA 사용방법을 직접 활용하면서 JPA 활용능력을 높이고싶습니다!

lwisekiml

[인프런 워밍업 클럽 0기] BE 2주차 회고

2주차 학습 내용과 공부한 내용 정리 ORM(Object Relational Mapping)우리가 일반 적으로 알고 있는 애플리케이션 Class와 RDB(Relational DataBase)의 테이블을 매핑(연결)한다는 뜻이며, 기술적으로는 어플리케이션의 객체를 RDB 테이블에 자동으로 영속화 해주는 것이다. JPAJava 진영에서 ORM(Object-Relational Mapping) 기술 표준으로 사용하는 인터페이스 모음자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스인터페이스 이기 때문에 Hibernate, OpenJPA 등이 JPA를 구현함 사용이유는?JPA를 사용하여 얻을 수 있는 가장 큰 장점은 SQL이 아닌 객체 중심으로 개발할 수 있다는 것이다. 이에 생산성이 좋아지고 유지보수 또한 수월해진다. 트랜잭션데이터베이스 트랜잭션은 데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위이다.데이터베이스의 상태를 변경시키기 위해 수행하는 작업 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산을 의미한다. 영속성 컨텍스트란?엔티티를 영구 저장하는 환경엔티티 매니저가 영속성 컨텍스트에 엔티티를 보관하고 관리한다.영속성 컨텍스트에 저장된 상태를 영속 상태, 저장되었다가 분리된 상태를 준영속 상태라고 한다. 마무리남은 한주에는 미니프로젝트를 완료하고 더 나아가서 기능을 추가한다거나 해서 더 발전된 프로그램을 만들고 싶습니다. 

백엔드백엔드워밍업

채 수

과제 7. JPA

1. JPA 이용해서 바꾸기package com.group.libraryapp.domain.fruit; import javax.persistence.*; import java.time.LocalDate; @Entity @Table(name = "fruits") public class Fruit { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String name; @Column(name = "warehousing_date", nullable = false) private LocalDate warehousingDate; @Column(nullable = false) private double price; @Column(nullable = false) private String status; public Long getId() { return id; } public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public double getPrice() { return price; } public String getStatus() { return status; } public void setId(Long id) { this.id = id; } public void setName(String name) { this.name = name; } public void setWarehousingDate(LocalDate warehousingDate) { this.warehousingDate = warehousingDate; } public void setPrice(double price) { this.price = price; } public void setStatus(String status) { this.status = status; } }이렇게 fruit 객체를 만들었다.ckage com.group.libraryapp.domain.fruit; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface FruitRepository extends JpaRepository<Fruit, Long> { @Query("SELECT SUM(f.price) FROM Fruit f WHERE f.name = :name AND f.status = :status") Double getFruitStat(@Param("name") String name, @Param("status") String status); Fruit findByName(String name); } 그리고 JPA 레포지토리를 확장하는 FruitRepository를 새로 만들어줬다.package com.group.libraryapp.service.fruit; import com.group.libraryapp.domain.fruit.Fruit; import com.group.libraryapp.domain.fruit.FruitRepository; import com.group.libraryapp.dto.HW.FruitRequest; import com.group.libraryapp.dto.HW.FruitSoldRequest; import com.group.libraryapp.dto.HW.FruitStatResponse; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class FruitService { private final FruitRepository fruitRepository; public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } @Transactional public FruitRequest addFruit(FruitRequest fruitRequest) { Fruit fruit = new Fruit(); fruit.setName(fruitRequest.getName()); fruit.setWarehousingDate(fruitRequest.getWarehousingDate()); fruit.setPrice(fruitRequest.getPrice()); fruit.setStatus("NOT_SOLD"); fruitRepository.save(fruit); return fruitRequest; } @Transactional public void markFruitAsSold(FruitSoldRequest request) { Fruit fruit = fruitRepository.findById(request.getId()).orElseThrow(IllegalArgumentException::new); fruit.setStatus("SOLD"); fruitRepository.save(fruit); } @Transactional(readOnly = true) public FruitStatResponse getFruitStat(String name) { Double soldAmount = fruitRepository.getFruitStat(name, "SOLD"); Double notSoldAmount = fruitRepository.getFruitStat(name, "NOT_SOLD"); long sold = soldAmount != null ? soldAmount.longValue() : 0; long notSold = notSoldAmount != null ? notSoldAmount.longValue() : 0; return new FruitStatResponse(sold, notSold); } }그리고 이렇게 서비스를 만들어 기존의 코드를 SQL 문이 아닌 JPA를 이용하게 했다.2. 과일 개수 세기FruitRepository에서 과일의 개수를 세는 메소드를 추가해야 한다. 그런 다음에는 FruitService에서 해당 메소드를 호출하여 원하는 기능을 구현할 수 있다.package com.group.libraryapp.domain.fruit; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface FruitRepository extends JpaRepository<Fruit, Long> { Fruit findByName(String name); @Query("SELECT COUNT(f) FROM Fruit f WHERE f.name = :name") long countByName(@Param("name") String name); } package com.group.libraryapp.service.fruit; import com.group.libraryapp.domain.fruit.Fruit; import com.group.libraryapp.dto.HW.FruitStatResponse; import com.group.libraryapp.repository.fruit.FruitRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class FruitService { private final FruitRepository fruitRepository; @Autowired public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } @Transactional(readOnly = true) public long countFruitsByName(String name) { return fruitRepository.countByName(name); } } Copy code @GetMapping("/count") public ResponseEntity<Map<String, Long>> countFruitsByName(@RequestParam("name") String name) { long count = fruitService.countFruitsByName(name); Map<String, Long> responseBody = new HashMap<>(); responseBody.put("count", count); return ResponseEntity.ok(responseBody); }이렇게 만들고 PostMan을 실행해봤다.이렇게 잘 된다!3. 판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록package com.group.libraryapp.domain.fruit; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface FruitRepository extends JpaRepository<Fruit, Long> { @Query("SELECT SUM(f.price) FROM Fruit f WHERE f.name = :name AND f.status = :status") Double getFruitStat(@Param("name") String name, @Param("status") String status); @Query("SELECT COUNT(f) FROM Fruit f WHERE f.name = :name") long countByName(@Param("name") String name); List<Fruit> findByPriceGreaterThanEqualAndStatus(double price, String status); List<Fruit> findByPriceLessThanEqualAndStatus(double price, String status); Fruit findByName(String name); }먼저 FruitRepository를 다음처럼 GTE, LTE를 만들어줬다.ckage com.group.libraryapp.service.fruit; import com.group.libraryapp.domain.fruit.Fruit; import com.group.libraryapp.domain.fruit.FruitRepository; import com.group.libraryapp.dto.HW.FruitListResponse; import com.group.libraryapp.dto.HW.FruitRequest; import com.group.libraryapp.dto.HW.FruitSoldRequest; import com.group.libraryapp.dto.HW.FruitStatResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @Service public class FruitService { private final FruitRepository fruitRepository; @Autowired public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } @Transactional public FruitRequest addFruit(FruitRequest fruitRequest) { Fruit fruit = new Fruit(); fruit.setName(fruitRequest.getName()); fruit.setWarehousingDate(fruitRequest.getWarehousingDate()); fruit.setPrice(fruitRequest.getPrice()); fruit.setStatus("NOT_SOLD"); fruitRepository.save(fruit); return fruitRequest; } @Transactional public void markFruitAsSold(FruitSoldRequest request) { Fruit fruit = fruitRepository.findById(request.getId()).orElseThrow(IllegalArgumentException::new); fruit.setStatus("SOLD"); fruitRepository.save(fruit); } @Transactional(readOnly = true) public FruitStatResponse getFruitStat(String name) { Double soldAmount = fruitRepository.getFruitStat(name, "SOLD"); Double notSoldAmount = fruitRepository.getFruitStat(name, "NOT_SOLD"); long sold = soldAmount != null ? soldAmount.longValue() : 0; long notSold = notSoldAmount != null ? notSoldAmount.longValue() : 0; return new FruitStatResponse(sold, notSold); } @Transactional(readOnly = true) public long countFruitsByName(String name) { return fruitRepository.countByName(name); } @Transactional(readOnly = true) public List<Fruit> listFruitsByPriceOption(String option, double price) { if (option.equalsIgnoreCase("GTE")) { return fruitRepository.findByPriceGreaterThanEqualAndStatus(price, "NOT_SOLD"); } else if (option.equalsIgnoreCase("LTE")) { return fruitRepository.findByPriceLessThanEqualAndStatus(price, "NOT_SOLD"); } else { throw new IllegalArgumentException("Invalid option value. Use 'GTE' or 'LTE'."); } } @Transactional(readOnly = true) public List<FruitListResponse> getFruitsWithPriceGreaterThanOrEqual(double price) { List<Fruit> fruits = fruitRepository.findByPriceGreaterThanEqualAndStatus(price, "NOT_SOLD"); return fruits.stream() .map(this::mapToFruitListResponse) .collect(Collectors.toList()); } @Transactional(readOnly = true) public List<FruitListResponse> getFruitsWithPriceLessThanOrEqual(double price) { List<Fruit> fruits = fruitRepository.findByPriceLessThanEqualAndStatus(price, "NOT_SOLD"); return fruits.stream() .map(this::mapToFruitListResponse) .collect(Collectors.toList()); } private FruitListResponse mapToFruitListResponse(Fruit fruit) { FruitListResponse response = new FruitListResponse(); response.setName(fruit.getName()); response.setPrice(fruit.getPrice()); response.setWarehousingDate(fruit.getWarehousingDate()); return response; } }서비스 부분도 이러한 것들을 추가해서 리스트를 뽑아내게 해줬다.@GetMapping("/list") public ResponseEntity<List<FruitListResponse>> getFruitsByPriceOption( @RequestParam("option") String option, @RequestParam("price") double price) { List<FruitListResponse> fruits; if ("GTE".equals(option)) { fruits = fruitService.getFruitsWithPriceGreaterThanOrEqual(price); } else if ("LTE".equals(option)) { fruits = fruitService.getFruitsWithPriceLessThanOrEqual(price); } else { return ResponseEntity.badRequest().build(); } return ResponseEntity.ok(fruits); }그리고 path를 컨트롤러에 추가해서 작동하게 해줬다.그러니 이렇게 다음처럼 잘 작동했다!

인프런워밍업클럽