블로그

dmstjd0214

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

요일 별 내용 정리Day11배포와 Profile 사용법예전에 배포를 수행하며, 막연하게 AWS를 사용하여 배포를 진행했지만 보안이나 기능적인 측면에서는 고려하지 않고 진행을 했었다.내가 작성한 코드를 작성하며, 하나의 기본 셋팅을 가지고 개발을 진행하고 있었는데, Profile이라는 개념을 알고 적용을 해보았습니다. h2 DB를 메모리에 저장하면 휘발되며, 개발단계에서 사용하는 것을 알았습니다.예전에 테스트 코드를 작성하며 작업을 진행할 때, h2 DB를 활용하여 테스트 마다 초기화를 진행해주면 편하다 생각을 했습니다.spring: config: activate: on-profile: local //... 설정 값on-profile값에 따른 하위에 작성된 설정한 값으로 적용 되도록 합니다. datasource: username: "sa" password: "" url: "jdbc:h2:mem:library:MODE=MYSQL;NON_KEYWORDS=USER" driver-class-name: org.h2.Driver메모리 모드로 사용하며 MySQL 방언을 사용하며 USER의 예약어를 사용하지 않는다. 라는 설정입니다. h2: console: enabled: true # path: /h2-console #h2 Console을 접속을 가능하게하며 , Console에 대한 경로 값 입니다.부트에서 실행할 때 ActiveProfiles 설정 값을 내가 작성한 프로파일명으로 실행됩니다.JUnit을 활용할때는 @ActiveProfiles("프로파일명") 어노테이션을 활용하여 데이터베이스 환경을 변경할 수 있습니다. 테스트 코드를 작성하고 실행할때, 데이터베이스에 대한 정보가 부담스러워 다른 방법이 없었나 찾아봤는데 Profile이라는 기능을 활용하여 테스트에 대해 원활하게 작업을 할수 있다 생각하여 빨리 활용하고싶다는 생각이 들었습니다. git과 github깃허브는 예전에 취업하기전에 포트폴리오를 올려 사용했었고, 취준생일때 git과 github는 같은것이라 생각했는데, 강의에서 나왔듯이 아예 다른 내용인걸 듣고 아직 많이 부족하다 느꼈습니다. 또한 github를 저는 클라우드 형식으로만 사용했는데 다양한 기능을 배울 수 있어 설레는 마음으로 강의를 시작했습니다!git이란? : 버전을 손쉽게 관리를 하도록하는 버전 관리 프로그램입니다.github란? : git으로 관리되는 프로젝트의 코드가 저장되는 저장소저는 이미 깃허브를 사용해서 아이디를 생성하지 않았습니다 .. !!!강의를 진행하기전에 library-app을 이미 푸쉬 작업을 하고있었고, 그 내용을 다시 복습하는 내용으로 강의를 들었습니다.!!하지만 sourcetree를 사용해보았고, 직접 터미널에서 깃을 사용해본적은 없었습니다.git init : 로컬 코드 깃 초기화기존에 올렸던 프로젝트에서 초기화 하니까 다 빨간색으로 반영이 되지않아 매우 놀랐습니다..하지만 강의 순서대로 따라갔더니 정상적으로 되었고 안심을 했습니다.. 깃은 정말 잘 쓰고 활용해야할꺼같습니다.git remote add orign "프로젝트 경로" : 깃 프로젝트 경로 연결git add . : 작성한 내용에 대한 전부를 깃에 반영하겠다.git commit -m : 커밋을 진행git push : 깃허브에 반영이 되도록 올려주는 명령어 또한.. AWS도 예전에 포트폴리오를 사용하고 배포할때 사용해본 경험이 있었습니다. 진짜 기본기를 쌓는다는 마음으로 가볍게 훑고나니 정말 유익했었습니다.  Day12리눅스 명령어 정리chmod 400 :ssh -i : 다른 쉘에 접근하기 위한 프로토콜을 의미mkdir : 폴더를 만드는 명령어ls : 현재 리스트 정보ls -l : 리스트 세부 내용까지drwxrwxr-x : 폴더인지 / 폴더소유자권한owner /폴더 소유그룹 권한group / 아무나 접근할때 권한otherls -a : 숨김파일까지 표시cd : 폴더 이동cd .. : 하위폴더로 이동rmdir : 비어있는 폴더 삭제sudo yum update : 리눅스 관리 프로그램 최신화입니다. 저는 우분투를 사용해서 sudo apt update를 사용했습니다.sudo yum install git : 깃허브 패키지 다운로드sudo yum install java-11-amazon-corretto -y : 자바11버전 jdk설치java -version : 자바버전확인 -> 잘 깔렸는지 확인!!sudo yum install mysql-community-server -y : mysql 설치 !!sudo systemctl status mysqld : mysql 상태 확인 !!sudo systemctl restart mysqld : mysql 재실행 명령어mysql -u root -p : 임시 비밀번호를 알아오는 명령어 예전에 jar파일을 실행하다 렉이 걸려서 멈춘 경험이 있었고 인스턴스 종료를 눌렀는데, 그게 삭제인줄 모르고 가상머신을 날린 기억이 있었다.. 강의를 중간에 보다보니 메모리 대신 disk의 일부를 사용할 수 있도록 하는 명령어를 보고 아 저렇게 하면 메모리를 더 쓸수 있겠구나라고 생각했습니다.sudo dd if=/dev/zero of=/swapfile bs=128M count=16sudo chmod 600 /swapfilesudo mkswap /swapfilesudo swapon /swapfilesudo swapon -s ./gradlew build : 프로젝트를 빌드./gradlew build -x test : 프로젝트를 빌드하는데, 테스트는 생략./gradlew clean : 현재 빌드된 결과물을 제거저는 jar로 파일을 빌드한 후 실행을 진행했는데 직접 리눅스 환경에서 빌드를 진행하고 실행까지 하는것도 있구나라는 방법이 있었고, 나중에 중단배포나 jenkins를 활용하여 작업을 하려고 선행으로 공부했는지는 찾아봐야할꺼같다 생각했습니다!!foreground와 background의 차이를 공부하여 서버를 종료할 때 같이 종료가 되지않도록 배웠습니다.nohup java -jar build/libs/"서버명".jar --spring.profiles.active=dev &을 활용해 백그라운드 실행을 하면됩니다.kill -9 프로세스번호 : 해당 프로세스를 종료하는 명령어vi nohup.out : 편집기로 로그 실행저는 less를 사용하여 작업을 많이 진행했습니다. 리눅스 명령어를 활용해보면서 정말 많이 몰랐고 데이터베이스를 가상머신에 직접깔아서 사용해보았습니다. 저는 아마존 rds를 활용하여 데이터베이스 서비스를 사용했는데 직접 사용도 해보니까 좋았다 생각했습니다. 하지만 같은 가상머신에 존재하면 서버가 죽었을 경우 데이터베이스도 죽을 경우있다 생각했는데 그게 맞는지 잘 모르겠습니다. 또한 GUI 프로그램을 활용하여 개발을 진행해서 기본기가 부족하다 많이 느꼈습니다..저는 우분투로 사용했는데 아마존 리눅스를 많이 사용하는걸 코치님의 강의를 보고 처음알았습니다.. 우분투 말고 다음 토이 프로젝트를 진행할 때, 아마존 리눅스를 활용하여 개발을 진행해봐야겠다는 생각을 했습니다.!!!! Day13plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.1.4' }제가 사용하고 있는 버전입니다 java를 사용하고 springboot 3.2.2를 사용하며 패키지 의존성 매니저는 1.1.4버전을 사용하고있습니다.group = 'com.group' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' }group : 프로젝트 그룹을 의미version : 프로젝트 버전을 의미sourceCompatibility : 프로젝트가 사용하면 JDK버전을 의미합니다.dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-aop' }라이브러리/프레임워크를 표시하는 곳implementaion : 의존성을 가져와 항시 사용 runtimeOnly : 코드 실행할때만 사용!왜 저렇게 나눴는지 찾아보니 빌드를 진행할때, 성능적인 이점을 얻을 수 있도록 구분을 합니다라고 나와있어 구분을 하려는 이유를 알았습니다.!! 스프링과 스프링부트 차이이건 예전부터 너무 궁금해서 많은 사람들에게 물어봤고, 어떤 차이가 있는지 명확하게 몰랐습니다. 예전에 친구랑 프로젝트를 진행하며 스프링과 스프링부트가 뭔차이냐!! 라고하며 기존 방식을 고수했던 경험이있었는데 솔직히 그때 굳이 나누는 의미가 있었냐라고 웃기도 했습니다 ㅎㅎ... 근데 강의를 듣기전에 내용을 보며 확실히 알아가자 생각했습니다.SpringBoot란? Spring Framework에서 사용하는 기능을 편하게 사용하도록 자동으로 설정해주는 프레임워크입니다.간편한 설정간단한 의존성 관리강력한 확장성MSA의 모니터 링yml과 properties , lombokYAML의 문법spring: config: activate: on-profile: dev #spring: 계층은 들여쓰기로 나눕니다. 주석은 #입니다.회사에서는 properties를 활용하는데 관리하기가 불편하면서도 편한거같다 생각했습니다. yml구조로 설정을 하면 가독성이 좋아져 딱딱 나눠져 있어서 좋았습니다.그리고 롬복을 예전부터 많이 사용하여 손에 많이 익었으며,내가 알고있던 내용과 부족했던 내용 새로운 내용을 공부하게 되어 너무너무 좋았습니다.그리구.. 마이그레이션쪽은 이미 3.0을 사용하고있어 제 코드에 직접적으로 사용해보진 못했지만 이런 내용이 있고 다음에 활용할 수 있다 생각했습니다 실무에서나 스프링부트 4.0이 나온다면  강의를 들으면서 점점 스터디가 끝난다는 생각에 아쉽다 생각했습니다..ㅜ 그래도 마무리가 아름다워야 좋다 생각하여 끝까지 열심히 들을 생각입니다!!!   Day14드디어 강의 마무리이며, 마무리 영상을 보면서 내용을 정리하는 시간을 가지려했는데 엄청 도움이 되는말들이 많아 좋았습니다.개발하면서 방향성이 너무 많아 어려웠는데 마무리 강의보면서 방향성을 다시 잡을 수 있었습니다. OOP나 디자인패턴의 실무 스킬을 배워야 스프링의 원리를 알아갈수 있다.스프링에 대한 모듈은 인프라나 보안 이론지식이 필요하다.기반 실무 스킬에 대한 내용이 있었고, 필수적인 내용이라 생각하여 꾸준히 공부를 해나가야 생각했습니다!프레임워크를 배제하고 내 기본기라 생각하면 될꺼라 생각했다.IT 기술 블로그중 신입 개발자, 파일럿 프로젝트 관련 글을 읽기.다른 사람은 어떤방식으로 코드를 작성했고, 왜 그렇게 작성했는지 시간이 된다면, 코틀린을 공부해보자.그리고.. 인스턴스 종료에 대해 나왔는데.. 얼마전에 프로젝트를 진행하며, 종료버튼을 누르는게 삭제인지.. 모르고 삭제된경우가있었다.. 주의를 해야겠다 생각했습니다.. 추가강의도 너무 많이 도움이 됐습니다.미니프로젝트미니프로젝트는... 시간만 많았다면.. 정말 설계부터 완벽하게 설계한다음 진행했어야했는데 저 맛있는 내용을 너무 소화하지못하고 마무리된거같아 아쉬웠습니다. 해당 내용은 스터디가 끝나더라도 마무리를 꼭 지어 끝내야겠다 생각했습니다.팀원분들과 같이 코드리뷰를 진행하며, 모르는내용에 대해 많은 것을 배웠고 행복했던시간이였던거같습니다.설계만 작성하고 그 단계별 개발 진행 사항은 작성하지 못한것이 아쉽습니다. 이또한 천천히 마무리를 진행할 예정입니다.notion : https://silvercastle.notion.site/51409fcd18644974819dc23cdc2e902b?pvs=4github: https://github.com/backgom1/Inflearn_BE0_Study_mini/tree/develop/backgom1/ensmini라이브강의에서 코드리뷰를 진행해주시는 내용이 있었는데 딱딱 제가 부족한것을 찝어줘서 너무 시원했습니다 ㅋㅋㅋ!!도메인계층에 대해 다시한번 생각을 하게 된 계기가 있었고, 강의나 책을 읽어서 공부를 본격적으로 해봐야겠다 생각을 했습니다. 

bbangsomi

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

3주차 학습 내용이전 시간에 개발했던 도서 관리 시스템을 객체지향적인 방법으로 리팩토링하고, 배포를 진행했습니다.객체지향적인 설계 방법 중 JPA 연관관계 매핑을 집중적으로 학습하며 연관관계를 사용하여 개발할 때와 사용하지 않고 개발할 때의 차이점을 이해할 수 있었습니다.도메인 계층이 비즈니스 로직에 들어가면서 서비스 코드가 간결해지고 각자의 역할에 충실할 수 있게 되었습니다. 우리는 더 직관적이고, 유지보수가 용이한 코드로 한걸음 더 다가갈 수 있었습니다 !하지만 지나친 연관관계 사용은 시스템 파악을 어렵게 만들 수 있어 요구사항을 잘 살펴보고, 설계를 고민하며 적용해야 합니다.우리는 AWS의 EC2 서비스를 이용하여 리눅스 명령어를 통해 도서관리 시스템을 배포해 보았습니다.IP 주소가 아닌 도메인 이름으로 사이트에 접속하며 보다 실제 서비스에 가깝게 운영해볼 수 있었습니다.미니 프로젝트지금까지 학습했던 내용을 기반으로 간단한 '출퇴근 사내 시스템' 어플리케이션을 개발했습니다.프로젝트 1단계에서는 팀과 직원을 등록하고, 조회할 수 있는 기능을 개발해 보면서 기존 학습 내용을 정리할 수 있었습니다.프로젝트를 진행하면서 어디까지 에러 처리를 해야 하는지 예외 처리 범위를 파악하기 어려웠고, 데이터 형식은 어떤 걸 사용해야 할지 등 여러 부분에서 고민거리가 생겨났습니다. 기능 구현에 급급하느라 엄밀하게 코드를 작성하지 못해 아쉬움이 남았던 마지막 과제였습니다 ... 🥹마지막 라이브 세션을 통해 다른 러너분들의 코드를 살펴보고, 강사님을 통해 리팩토링 하는 과정을 실시간으로 확인할 수 있었습니다. 리뷰 내용을 참고하여 조금 더 요구사항에 대해 고민해보고, 추후 4단계까지 진행하는 것이 목표입니다 💪마무리3주라는 시간동안 열정적인 러너분들과 하나라도 더 알려주시려는 강사님의 모습을 보며 자극을 받고, 어제의 나보다 성장할 수 있었던 유익한 시간이었습니다. [워밍업 클럽 스터디 0기]에 참여할 수 있어 영광이었습니다 🙇0기를 시작으로 커뮤니티가 더 활성화 되어 다양한 러너분들과 꾸준히 소통하고 싶습니다 🏃성장에 매마른 개발자를 꿈꾼다면, 스터디를 통해 목마름을 해결하시기를 바랍니다 !

백엔드인프런워밍업클럽

7마리상어

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

총 3주 간에 걸친 인프런 워밍업 클럽 스터디가 이제 끝을 보이고 있다. 3주 차에는 이제까지 작성한 것들을 배포를 하는 방법과 Spring Boot에 대한 더 자세한 설명, 그리고 추가적인 꿀팁 영상들을 배웠다. 11일 차기본적인 배포를 위한 준비 우리가 이제까지 작성한 코드들은 우리 컴퓨터에 있기 때문에 다른 곳에서 접속이 불가능하다. 그래서 다른 사람이 우리가 만든 프로그램을 사용하려면 전용 컴퓨터에 우리가 만든 코드를 전송해서 다른 사람이 접속하도록 해야 한다. 여기서 배포에 대해 배웠는데 배포란 최종 사용자에게 SW를 전달하는 과정으로 우리는 우리가 만든 도서 관리 프로그램을 배포하기 위해 git, github, AWS의 EC2 등 배포에 필요한 몇몇 과정들을 배웠다. 11일 차의 내용은 이전에 어느 정도 공부했던 부분이 많아서 이해하는데 더 수월했는데 그럼에도 불구하고, 아직 몰랐던 내용이 많았어서 원래 알던 내용에서 더 많은 것을 얻어갈 수 있었다.12일 차AWS와 EC2 배포이제 앞선 11일 차 강의 때 배운 배포를 직접 해보는 것을 실습 했다. 먼저 AWS의 EC2에 접속해서 리눅스 명령어를 다뤄보았고, 배포를 위해 JAVA, MySQL 등을 설치하여 배포를 위한 준비를 마쳤다. 그리고 이제까지 만들었던 것을 실제로 배포를 해 볼 시간이다. 깃허브에서 우리가 작성한 코드를 가져와서 전용 컴퓨터에서 빌드와 실행을 마쳤고, EC2의 IP주소를 통해 다른 컴퓨터에서 전용 컴퓨터의 접속을 수행할 수 있었다. 이제까지 배포를 해보았던 경험은 없었기에 12일 차에서 직접 배포를 해 본 것이 추후 계속해서 서버에 대한 공부를 할 때, 엄청 큰 도움이 될 것 같았다. 그리고 추가적으로 가비아를 이용해서 도메인을 구입해서 DNS를 적용하여 127.0.0.1 같은 IP주소가 아닌 www.inflearn.com같이 도메인을 사용해서 우리가 평소에 자주 사용하는 방식으로도 접속을 해보았다. 이번 강의 덕분에 생애 최초로 배포를 해볼 수 있었던 좋은 경험이 되었다.13일 차Spring Boot 설정, 버전업 이해하기이제 본격적인 자바와 스프링 부트를 이용한 서버 만들기 및 배포는 끝이 났고, 13일 차에서는 Spring Boot의 이런 면 저런 면에 대해서 공부하게 되었다. 우리가 의존성을 설정할 때 사용한 build.gradle파일부터 Spring과 Spring Boot의 차이, application.yml파일, lombok, 마이그레이션 등 조금 더 스프링 부트에 대한 이해를 넓힐 수 있었다. 그 중에서도 lombok의 사용에 대해서 인상 깊게 강의를 들었는데 이제까지 우리가 생성자 및 getter, setter를 직접 만들어 줬었는데 lombok을 사용해서 @Getter 같이 어노테이션 하나 만으로 그 길던 코드를 줄일 수 있다고 생각하니까 코드를 읽는 것이나 작성하는 데 드는 시간을 많이 줄일 수 있다고 느꼈다. 그리고 이제까지 사용은 해왔지만 자세히는 몰랐던 gradle파일이나 yml파일에 대해 자세히 알게 될 수 있었다.14일 차마무리 및 추가 꿀팁 영상이제 3주 간의 스터디의 마지막 강의인 14일 차의 강의가 다가오게 되었다. 마지막 14일 차에서는 수업을 마무리 하는 영상으로 시작해서 추가적으로 우리가 강의를 보면서 궁금했을 법한 내용들에 대해 자세히 설명해주는 강의였다. 확실히 마지막 강의라 그런지 이전보다는 조금 널널한 마음으로 들었지만 그럼에도 불구하고 정말 궁금했던 내용들을 더 알게 해주어서 마지막까지 완벽한 강의였다. 특히 수업 마무리 영상에서 공부 방향성에 대한 강사님의 개인적인 조언이 담겨져 있었는데 정말 이후에 공부하는 데 큰 도움이 된 것 같다.미니 프로젝트간단한 출퇴근 사내 시스템 개발8일 차부터는 이제까지 주던 과제를 통한 문제 풀이 대신, 미니 프로젝트를 진행하였다. 이제까지 공부했던 내용들이 있었으니 처음에는 여유롭게 작성할 수 있지 않을까 라고 생각했었는데 크나큰 착각이었다. 작성하면서도 어? 이렇게 했었나? 라는 생각이 자주 들었고, 힘들게 작성을 마치고도 컴파일 과정에서 문제가 나거나, API를 전송하면서 어디선가 계속 오류가 발생해서 너무 헷갈리고, 어려웠다. 그래도 이전에 봤던 강의 들을 다시 보고, 헷갈리는 내용들을 검색해보면서 프로젝트를 진행해 나갈 수 있었다. 아직 4단계까지 진행하려면 한참 남았지만 이제까지 공부해왔던 내용을 토대로 만들면서 모르는 부분은 검색하거나 주변에 도움을 구하면서 계속 진행하여 완료 해낼 것이다. 회고길다면 길고 짧다면 짧았던 3주 간의 인프런 워밍업 클럽 스터디 BE 0기가 끝이 나게 되었다. 앞서 1~2주차 발자국을 작성하면서 이 스터디가 끝나고 나면 내가 얼마나 변해져 있을 지에 대한 궁금증이 컸는데 마침내 3주차 강의가 끝이 나면서 직접 몸으로 체험하게 되었다. 지금의 내가 3주 전의 나를 보면 그냥 한낱 개미에 불과할 정도로 단기간에 많은 것들을 알게 된 것 같다. 3주 전에는 확실히 아무것도 모르고, 이해도 잘 안됐었는데 지금도 물론 한참 부족하지만 어느 정도는 돌아가는 구조에 대해서 깨닫게 된 것 같다. 비록 스터디는 이제 끝이 나게 되었지만 아직 미니 프로젝트도 4단계까지 남아있고, 스프링 부트에 대해서 모르는 것투성이기 때문에 앞으로도 개인적으로 공부도 하고, 주변에 맞는 사람들과 프로젝트도 진행하면서 조금 더 성장해 나가려 한다. 이제까지 좋은 지식을 얻게 해주신 최태현 강사님과 스터디를 진행해주신 인프런 워밍업 클럽에 감사하고, 다음에도 비슷한 기회가 생기게 된다면 꼭 참여해서 새로운 것들을 많이 배워가면 좋겠다.

백엔드인프런워밍업클럽

ailen22

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

Day10 객체지향과 JPA 연관관계기존의 코드를 JPA 연관관계를 활용하는 방식으로 변경해보았다.연관관계를 사용하면 좋은 점으로는 각자의 역할에 집중할 수 있게 되고, 개발자가 코드를 읽을 때 이해가 쉬워진다는 장점이 있다. 하지만, 지나치게 사용하면 성능상의 문제가 생길 수 있고 도메인간의 복잡한 연결로 인해 시스템 파악이 어려워진다. @mappedBy : 연관관계의 주인이 아닌 쪽에 생성한다.@JoinColumn : 연관관계의 주인이 활용할 수 있는 어노테이션, 필드의 이름이나 null, 유일성, 업데이트 여부 등을 지정cascade : 한 객체가 저장되거나 삭제될때 변경이 폭포처럼 흘러 연결되어 있는 객체도 함께 저장되거나 삭제됨orphan Removal : 객체 간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션 Day11 기본적인 배포를 위한 준비 배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야하는지 알아보았다.git의 기초적인 사용법에 대해 알아보았다.AWS를 통해 클라우드 컴퓨터를 빌려보았다. --- 구분선을 사용해서 다른 profile이 적용되도록 yml파일을 수정하였다.AWS를 통해 클라우드 컴퓨터를 빌렸는데 중간부터 git에 src파일이 안올라가서 다시 처음부터했지만 해결이 되지않아서 찾아보았더니 소프트웨어를 잘 못 선택했었다. Day12 AWS와 EC2 배포 EC2에 접속해 리눅스 명령어를 다뤄보았다.개발한 서버의 배포를 위해 환경 세팅을 리눅스로 진행하고 실제 배포를 진행한다.foreground와 background의 차이를 이해하고 background 서버를 제어한다.도메인 이름을 사용해 사용자가 IP 대신 이름으로 접속할 수 있게 하였다. 도메인 IP를 설정하여 실행시키는 부분해서 오류가 났다. AWS에서 내 컴퓨터의 sql을 사용하지는 않을 거라는 답변을 받았다. 무엇을 놓쳤는지 다시 강의를 들으면서 놓친 부분을 찾아야겠다.그래서 나머지 부분은 강의만 들었다. 다시 따라해보아야겠다.복기를 하면서 틀린 부분을 찾았다. yml파일에서 비밀번호가 안바뀌어있었다! Day13 Spring Boot 설정, 버전업 이해하기 build.gradle에 대해 이해하고 Spring과 Spring Boot의 차이점에 대해서 알아보았다.application.yml과 application.properties.lombok에대해 알아보았다. Day14 마무리 및 추가 꿀팁 영상 수업을 마무리 하면서 공부 방향성에 대한 조언에 대한 강의를 들었고 사용했던 AWS EC2종료하는 방법과 AWS비용 계산하는 방법에 대해 알아보았다.MyBatis 적용하는 방법과 DB 접근 기술을 비교하고 스프링 부트의 정적 파일 처리에 대해 알아보았다. 3주차 후기이번 주는 AWS를 하면서 막히는 부분이 조금씩 있어서 검색을 몇 시간했는데 그러면서 오류코드가 더 잘 읽히게 되었다.시험 준비를 한다고 프로젝트를 시작 못한 것이 너무 아쉽지만 다음 주 부터 다시 복기하는 시간을 가져야겠다.3주동안 이해하느라 바쁘기도하고 따라가기위해 벅차기도 했지만 얻어가는 것이 정말 많은 3주였다.

chldlsgh76

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

9일차JPA를 활용해서 도서관련 기능들을 더욱 만들어봤습니다.JPA에 기능이 유용했습니다. 10일차이전에 JPA를 활용해 개발을 했을 때 테이블의 연관관계를 그저 외래키를 Long타입으로 가지고 있는 형식으로 사용했습니다. 이렇게 사용하는 것은 그저 Table을 매핑해주는 역할로만 Jpa를 활용하고 있는 상태입니다.그러므로 테이블과 연결된 엔티티를 좀 더 객체지향적인 방식으로 사용할 수 있는 방법에 대해 알아보았습니다.테이블의 연관관계를 정의할 때 1:1, 1:N, N:M의 관계를 갖게 됩니다. 이를 좀 더 객체지향적인 방법으로 각 엔티티가 다른 엔티티를 연결시켜줄 수 있습니다.A, B엔티티가 1:1 연관관계를 가질 경우를 예로 들면, A 엔티티 클래스 안에 멤버 변수로 연결된 엔티티 B를 선언해주고 @OneToOne을 통해 1:1 연관관계를 맺고 있다는 것을 알려줍니다. 이 후 연관관계를 맺은 두 엔티티 중 연관관계의 주인을 정해주면 됩니다.연관관계의 주인은 해당 관계의 우위를 점하고 있다는 뜻이 아니라 어떤 엔티티가 외래키를 관리할지 결정해주는 것입니다.테이블에서는 연관관계를 외래키를 통해 맺고 단방향 연관관계만을 가집니다. 하지만, 엔티티와 같은 경우 연관관계를 갖는 엔티티가 양방향 연관관계를 맺을 경우가 있습니다. 이럴 경우 하나의 엔티티에서 연관관계를 끊거나 다른 엔티티로 변경되었을 경우 이를 실제 DB 테이블에 변경사항을 저장해야 합니다. 이럴 경우 연관관계의 주인을 통해 외래키를 변경하거나 제거함으로써 연관관계를 수정하도록 하는 것입니다.연관관계의 주인은 @JoinColumn으로 선언해줍니다. 양방향 연관관계를 가지고 연관관계의 주인이 아닌 쪽에는 mappedBy를 통해 연관관계의 주인을 알려줍니다. @OneToOne과 @ManyToOne은 Fetch 전략이 EAGER로 되어 있습니다. EAGER전략은 엔티티를 조회하면 연관된 엔티티도 함께 조인을 통해 조회하게 됩니다. 이럴 경우 의도치 않은 join문이 발생하기도 하고, 또한 가장 안 좋은 경우인 N+1개의 쿼리가 발생하는 경우도 발생하게 됩니다. 그렇기 때문에 우선 Fetch 전략은 모두 LAZY로 변경하는 것이 최적화를 위한 좋은 방법입니다. 11일차현재 서버는 로컬에 배포된 상태로 이 서버에 접근하기 위해서는 실제 제가 있는 집에 방문해 보는 것 밖에 없습니다.그러므로 이 서버를 실제 여러 사람들에게 공개하기 위해서는 다른 사람들이 접근할 수 있는 컴퓨팅 시스템에 이를 노출시켜야 합니다. 이러한 컴퓨팅 자원을 원하는 만큼 제공해주는 것을 클라우드 서비스 중 하나인 IaaS라고 합니다.IaaS를 제공하는 회사 중 하나인 AWS의 무료 인스턴스를 통해 웹 서버 배포를 시도해보았습니다.우선 웹 서버의 ip를 할당받고 포트포워딩을 통해 ssh 접속을 시도하거나 웹서버 접근을 위한 포트를 열어줍니다.또한, 빌린 인스턴스에서 서버를 동작시키기 위해서는 현재 작성한 코드를 전부 인스턴스로 이동시켜야 합니다. 모든 코드를 수동으로 전송하는 것은 굉장히 비효율적인 작업이므로 이를 해소하기 위해서 분산 버전 관리 툴인 Git과 GitHub를 이용해보도록 하겠습니다. 12일차AWS를 통해 EC2 인스턴스를 받은 다음, 배포에 필요한 Git, Java, MySQL 등을 설치해줍니다.그리고 GitHub를 통해 지금까지 작성한 코드를 git pull을 통해 받습니다.인스턴스 내에서 코드를 빌드하고 실행시킨다면 해당 인스턴스의 ip주소를 통해 접속이 가능해집니다.또한, 백그라운드 실행을 통해 해당 인스턴스에 대한 ssh접속이 끊어져도 실행이 계속되도록 유지시킬 수 있습니다. 13일차build.gradle의 여러 설정들에 대해 알아보았습니다.Spring 프레임워크를 사용하기 위해서는 트랜잭션, 톰캣, 다른 라이브러리들을 위한 설정들을 xml문법으로 작성해주어야 했습니다. 이러한 불편함을 해소하기 위해 여러 설정들을 자동으로 도와주고, 메이저 라이브러리일 경우 starter를 통해 버전을 작성하지 않더라도 알아서 호환되는 버전으로 사용하도록 도와주는 Spring Boot가 사용되고 있습니다. Spring Boot는 xml 뿐만 아니라 yaml이라는 마크업 언어를 통해 설정을 작성할 수 있습니다. 롬복은 getter나 setter 등 반복적으로 나타나는 코드를 어노테이션 형식으로 지원해주는 기능입니다. Spring Boot나 다른 프레임워크의 버전 업을 시도할 경우 변경해줘야 하는 부분은 무엇인지 차근히 확인하고 변경해주는 것이 좋을 것이다.Spring Boot가 3.0.0으로 넘어오면서 변경된 점은 Java의 최신 버전이 17로 업그레이드 되었고 javax 대신 jakarta 패키지를 사용하게 됐습니다. 이렇게 변경된 점을 IDE나 build.gradle, 실제 코드에 모두 반영시켜야 되기 때문에 신중하게 작업해야될 것 같습니다. 미니 프로젝트https://github.com/inhoru/employee-app 14일차 & 진짜 후기이렇게 모든 강의를 마무리하게 되었습니다. 전반적인 백엔드 개발에 필요한 지식들을 얻어갈 수 있어 좋았습니다.저와 같은 경우에는 이전에 JPA에 관련된 강의도 조금 수강을 한터라 배운 내용을 점검하는 방식으로 진행했던 것 같습니다.Spring Boot를 활용하면서 Spring에 대한 정보보다도 다른 데이터베이스, 리눅스 문법, 설정 파일 등 다양한 지식을 얻을 수 있어 좋았습니다.이후에는 현재 배운 내용들과 함께 좀더 동적으로 쿼리를 다룰 수 있는 방법을 통해 개인 프로젝트를 진행하면서 실제 스프링 동작 원리에 대해서도 깊게 파해쳐보는 시간을 가져보려고 합니다.

유원준

[인프런 워밍업 클럽 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기 - 세번째 발자국

세번째 발자국이 글은 자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고 인프런 워밍업 클럽에 참여하여 쓰는 두번째 회고록입니다.3주차 내용Day 11 객체지향과 JPA 연관관계(강의 33~36강)33강전체적으로 api를 개발할 때 어떤 순서로 짜야하는지 정리할 수 있었다. 그리고 처음부터 강의를 듣지 않고 내가 먼저 구현을 해보고 최태현님은 어떻게 구현을 하시는지 비교해봄으로써 좀 더 집중해서 들을 수 있는 강의였다.api 개발 순서요구사항 정확하게 분석API spec 정의머릿속으로 전체적인 이미지 구상테이블 설계 (4,5번 할 때 스펠링 틀리는 거 각별히 신경)엔티티(객체) 구현 & Repository 구현(둘이는 셋트라고 생각)API 스펙에 맞는 dto 만들기controller, service 도 마저 만들기책 생성 API최태현님과 나의 비교is_return 테이블을 설계할 때 tinyint(1)을 쓰셨다미리 낱개의 실험 데이터를 가정하고 그려보았다요구사항을 제대로 파악하고 주석을 쓴다생성자에 뭔가 고정값이 담긴다거나 파라미터가 너무 많아 보이면 리팩토링 함으로써 깔끔하게 해준다'그'의 요구사항 내용책 정보를 가져온다.대출 기록 정보를 확인해서 대출 중인지 확인한다.만약에 확인했는데 대출 중이라면 예외를 발생시킵니다.유저 정보를 가져온다.책과 유저 정보를 바탕으로 유저 대출 정보에 등록해준다.'나'의 요구사항 내용유저를 찾는다.책정보를 찾는다.만약 내가 빌려가려는 책이 이미 다른 사람이 빌렸다면 예외 처리요청한 책을 내 이름으로 등록한다34강JPA를 쓰는 이유 중에 패러다임을 객체지향적으로 하기 위해서 쓰는 것도 있었다. 그러나, 이전까지의 코드를 살펴보면 절차지향에 가까웠다. 객체들끼리 서로 협력을 하는 것이 아니라 각자의 객체가 스스로 처리했다. BookService에서 반납 기능은 User에서 정보를 가져오고 UserLoanHistroy에서 대출 정보를 가져와 반납으로 처리한다.이러한 패러다임을 객체지향적으로 바꾸려면 객체 간의 관계가 필요하다. 그 관계를 JPA에서 연관 관계로 표현할 수 있다.1:1 관계 - @OneToOnePerson이랑 Address 연관 관계 실험두 개의 테이블에 각각의 연관된 id를 만들어서 실험[결과]address가 주체일 때 address테이블에만 person_id가 저장person이 주체일 때 person테이블에만 address_id가 저장두 개의 엔티티에 전부 mappedBy를 입력해줬을 때다음과 같이 AnnotationException이 발생한다.@Transactional이 끝나기 전에 객체를 가져왔을 때 null이 안나오게 하는 해결책 setter로 한 번에 둘로 이어주게끔 하면 된다.N : 1 관계 - @ManyToOne@JoinColumn연관관계 주인(owner Entity)에게 쓰는 거@Column과 쓰임이 비슷, 필드의 이름을 정할 수 있다.N : M 관계 - @ManyToMany사용 Xcascade 옵션폭포수관계들이 쭉 폭포수처럼 이어져 있게 하는 개념orphanRemoval 옵션orpthan: 고아removal: 제거연결되어 있던 관계에서 한 엔티티가 없어지면 연결되있던 엔티티의 데이터도 사라지는 개념Day 12 기본적인 배포를 위한 준비api구현을 마무리하고 배포를 어떻게 하는지 배웠다. 새롭게 알게 된 내용만 간단하게 추려보았다.37강. 배포란 무엇인가배포란 쉽게 말해서 전용 컴퓨터로 코드(우리의 서버)를 옮긴다! 라고 한다. 최종 사용자에게 SW를 전달하는 과정이란걸 알았다.38강. profile과 H2 DB내 컴퓨터 로컬에 설치한 mysql을 쓰게 해줬던 것처럼 전용 컴퓨터로 내 코드를 옮겼을때도 똑같은 환경을 만들어 줘야 한다는 것을 알 수 있었다.그리고 데이터베이스는 데이터가 영구적이기 때문에 테스트로 활용하기엔 불편한 점이 있는데 이를 해결하기 위해 H2데이터베이스를 쓴다는 걸 알 수 있었다.39~40강. git github개발 처음 시작할 때 바로 깃과 깃허브부터 접했어서 생소한 개념이 있지는 않았다. 새로 배웠다거나 깨달은 건 두가지였다.git push --set-upstream origin main 여태까지 이걸 한번만 하면 다음부터는 git push만 쓰면된다는 걸 모르고 있었다..gitignore언제 생성시켜야 하는지 타이밍을 제대로 알 수 있었다.Day 13 AWS와 EC2 배포배포를 통해 localhost에서만 돌아가는 서버를 aws ec2에 배포해 보았다. 이 과정을 혼자 막연하게 부딪히면서 했다면 하루를 온전히 다 투자해야 됐을 것 같다.네트워크에 대해서 조금 찍먹해 볼 수 있는 경험을 가졌다. ssh, DNS, 인바운드 규칙, IP 같은 키워드를 궁금하게 하는 실습이었다.Day14, 15 Spring Boot 설정, 버전업 이해하기, 마무리 및 추가 꿀팁 영상gradle, .yml, 버전업 등을 통해 스프링 프로젝트를 구성하는 요소를 좀 더 이해할 수 있었다. 게다가 마무리에 추가 영상도 있었다. 질문자들에 대한 답을 영상으로 따로 만든 것이었다.최태현님은 항상 느끼는 거지만 질문에 대한 답을 너무 성실하게 해주신다. 볼 때마다 감탄한다.미니 프로젝트프로젝트 레파지토리: https://github.com/hyungjunn/employee-commute결국 머리에 제일 땀이 많이 나는 건 고민하면서 진행하는 프로젝트인 거 같다. 가장 의미있는 시간이었다. 진행하면서 그리고 다른 사람 코드리뷰를 보면서 느낀 점은 다음과 같다.서비스로직에서의 코드 분리 조금만 복잡한 비지니스 로직을 구현하려고 하면 서비스가 점점 뚱뚱해져서 스스로 느낄 때도 메서드가 여러 일을 하고 있어 알아보기가 힘들어 졌었다. 결국 기본으로 돌아가 객체 지향적인 개념이 너무 중요한 거 같다.연관 관계에 대한 것 JPA를 이 강의를 통해 처음 배웠었다. 또 결국 비판적 사고를 하지 않고 JPA는 연관관계를 무조건 써야 좋은지 알고 프로젝트 하면서 최대한 쓸려고 했었다. 피드백을 듣고 연관 관계가 단점이 많다는걸 이제서야 깨달았다. 연관관계 뿐만 아니라 어느걸 쓸 때 최대한 합당한 이유로 써야하는 습관을 길러야 겠다.일급 컬렉션, 그리고 상황에 따른 변수 선언 서비스 로직에서 코드를 분리할 때 어떤식으로 하는지 보여주셨는데 그게 일급 컬렉션이란 걸 말씀해주셨다. 그리고 도메인 로직에서는 어떠한 값을 담을 것이 필요하면 바로 변수를 새로 선언하는 걸 보여주셨다. 코드를 짤 때 유연해야 된다는걸 너무 크게 느꼈다.이 외에도 너무너무 많다. 결국 계속해서 고민하면서 코드를 만들고 고치는거 밖에 방법이 없다는걸 느꼈다.인프런 워밍업 클럽을 참여하지 않았다면 큰일날 뻔 했다. 마침 너무 이론만 배우고 있던 시기라 사이드 프로젝트를 하고 싶었는데 이 스터디를 통해 사이드 프로젝트를 혼자 만들 수 있을 거 같다는 자신감을 조금 높일 수 있어서 좋았다.

백엔드백엔드인프런워밍업스터디

kamser

인프런 워밍업 0기 3주차 배포

인프런 워밍업 0기 3주차 배포 강의 수강 목차 1. JPA와 연관관계2. 배포를 위한 준비3. AWS와 EC2 배포4. 스프링 부트 설정과 버전 이해하기5. 기타 요약 JPA와 연관관계 JPA는 객체와 테이블을 매핑해주는 ORM으로 테이블끼리 가진 관계를 애노테이션으로 매핑할 수 있습니다. 다대일, 일대일, 일대다와 사용하지 않은 다대다까지 지원하고 있습니다. 연관관계라는건 테이블의 FK키를 어디서 관리하는 지에 따라 옵션 값이 달라집니다. FK 키를 가지고 있는 객체를 연관관계 주인이라고 합니다. 배포를 위한 준비 우리가 만든 애플리케이션을 다른 사람들이 사용할 수 있도록 하려면 1. 다른 사람을 내 컴퓨터를 사용하도록 한다.2. 공용 컴퓨터를 만들고 다른 사람들이 접속할 수 있도록 한다. 1번은 많은 사람들이 사용할 수 없고 24시간 동안 동작할 수 없습니다. 2번처럼 다른 사람들이 접속할 수 있도록 개발된 컴퓨터에 우리가 만든 애플리케이션을 설치하고 다른 사용자가 사용할 수 있도록 설정하는 방식을 사용합니다. 리눅스 운영체제는 여러 사용자가 동시에 접속해서 사용할 수 있도록 발전했기 때문에 해당 운영체제를 컴퓨터에 설치합니다. 현재 우리가 사용하는 MySQL, Spring, Java를 리눅스 컴퓨터에 설치해야합니다. 그러면 컴퓨터를 구매하고, 방화벽을 설정하고, ssh 인증에 대한 고민도 해야합니다. 이 과정을 클라우드 컴퓨팅 기술로 물리적인 컴퓨터를 구매하지 않고 서버 컴퓨터를 사용할 수 있습니다. 서버 컴퓨터를 구매한 뒤, 리눅스를 설치하고, MySQL을 설치하고, Java를 설치하여 내부에서 스프링 부트가 동작할 수 있도록합니다. AWS와 EC2 배포 저는 AWS가 정책이 변할 때마다 혹시 모르는 돈을 지불할 수 있는 경험을 했기 때문에 GCP(구글 클라우드 플랫폼)을 사용했습니다. GCP에서 VM Instance를 구매하고, 포트를 열고 , ssh키를 GUI로 등록하여 접속하는 방식을 선택했습니다. SpringBoot 설정,버전 업 이해하기 스프링 부트 설정은 build.gradle 파일에 작성된 스크립트를 통해서 필요한 설정을 작성할 수 있습니다. 작성되는 언어는 JVM기반의 스크립트 언어인 groovy와 Kotlin을 사용할 수 있는데 현재는 Kotlin도 공식 언어로 채택되었다는 걸 본적이 있습니다. 스크립트에 사용할 플러그인, 의존성, 그리고 작성되는 순서에 따라 실행될 로직을 작성할 수 있습니다. 스프링 부트 버전이 2.x --> 3.x로 올라가면서 JDK 17이 최소 사양으로 변경되었고, 자세한건 공식 스프링 부트 릴리스 항목에서 확인할 수 있습니다. 버전업을 할 경우 build.gradle에서 플러그인 버전을 변경하고, 컴파일 버전도 변경합니다. 그리고 인텔리제이를 사용할 경우 인텔리제이의 컴파일 버전,모듈버전 등 SDK의 버전도 변경해야합니다. 이때 마이그레이션을 할 때 미리 변경된 내용을 찾아주는 라이브러리를 사용할 수 있습니다.runtimeOnly "org.springframework.boot:spring-boot-properties-migrator" 를 추가하여 확인할 수 있습니다.회고 이번 3주차는 스프링 부트에 서비스를 제공하기 위한 준비 단계에 대해 이해를 할 수 있던 한주 였습니다. 동료 직원들과 배포를 위해서 jenkins와 docker등을 스터디하면서 배포를 하는 다양한 기술을 학습을 했지만, 원초적인 배포를 왜 해야하는지를 궁금해하지 않고, 무작정 배포를 해야하는데 어떤 기술이 있고, 요즘 기술은 이걸 사용한다니까 스터디를 해서 배워보자라는 생각이 강했습니다. 강사님께서 말씀하신 내용중에 기억나는 건, 우리 컴퓨터를 다른 사람들에게 보여줄수 없고 24시간 동안 제공할 수 없으니 다른 컴퓨터에 우리가 만든걸 설치해서 제공하는 과정을 배포라고 한다. 이 말이 기억납니다. 추상적으로 배포라는게 뭔지는 알고 있지만, 구체적으로 배포에 대해서 정리를 하니 명확하게 어떤 의미인지 와닿았습니다. 이번 주에 배운 배포에 대해서 생각을 하고 배포를 하면서 지난번에 스터디를 통해 학습한 jenkkins와 Anskble,`Docker`등을 실제로 사용해보면서 킅라우드 서버에 배포하는걸 천천히 해보려고 합니다. 미션 3주차는 미션이 없었기 때문에 미션에 대한 해결 과정을 작성해보려고 합니다. 2단계 미션인 출근 퇴근을 저장하는 방식을 처음에는 직원의 ID가 출근 API를 통해서 들어올 경우데이터를 삽입하고, 퇴근 API를 통해서 들어올 경우 퇴근 데이터 데이터를 삽입하는 방식으로 코드를 작성했습니다. 테이블 데이터 구조는 자동 id,`직원 고유번호`,`찍은시간`,`현재 상태(출근,퇴근)` 필드를 가지고 있습니다. 이렇게 작성한 이유는 출근 퇴근만 기록하면 된다는 생각이였습니다. 로직은 간단하게 출근할 경우  1. 해당 아이디로 등록된 직원이 있는지 조회 - 없는 경우 예외 발생2. 등록된 직원 정보로 출퇴근 기록지 테이블에 INSERT(직원아이디,LocalDateTime.now(),'ATTEND') 퇴근할 경우  1. 해당 아이디로 출퇴근 기록지에 직원아이디 조회 - 없는 경우 예외 발생2. 있는 경우 출퇴근 기록지에 테이블에 INSERT(직원아이디,LocalDateTime.now(),'LEAVE') 실행 이렇게 작성하다보니 문제가 발생했습니다. 1. 출퇴근 기록을 통해서 일한 시간을 가져올 때2. 휴일에 일한 경우, 추가 근무한 경우에는 어떻게 처리할 것인지3. 출근을 동시에 2번 찍었을 경우4. 퇴근을 안찍고 다음날에 출근을 찍은 경우 등 이렇게 테이블을 구조화하니 데이터를 읽어올 때, 저장할 때, 동시성이 발생할 때, 기타 요구사항이 발생할 때 모든 걸 대처할 수 없는 데이터 구조가 되었습니다.해결 과정 한달간 모든 직원의 근무일 마다 근무 시간을 조회해야한다고 한다면 { "직원 id": { "detail": [ { "date": "2024-01-01", "workingMinutes": 480 }, { "date": "2024-01-02", "workingMinutes": 490 } ], "sum": 10560 } } 이런 구조로 응답값을 줘야한다고 할때 2가지 고민이 생겼습니다. 1. workingMinutes를 필드로 저장하는게 아니라 출근 시간,퇴근 시간을 조회후 애플리케이션에서 계산을 한다.2. workingMinutes를 퇴근할 때 출근과 퇴근시간을 계산해서 필드를 추가해서 저장한다. 저는 2번을 선택했습니다. 그 이유는 첫번째는 매번 변경되는 값인 경우 (예: 장바구니 총 금액) 에는 매번 달라지기 때문에 필드에 굳이 저장할 필요가 없지만, 출퇴근 기록은 직원이 임의로 수정할 수 없도록 설계가 되어야하고, 수정을 원할 경우 출퇴근 담당자에게 별도로 요청을 해야 수정이 가능해야하기 때문에 workingMinutes필드는 한번 데이터가 입력되면 매번 수정되는 값이 아닙니다. 두번째는 만약 이 애플리케이션이 몇십몇이 아니라 몇백명 몇천명의 몇년치 데이터를 가져와야하는 경우, 설치된 서버가 사양이 낮아서 소화를 못하는 경우도 있을 거라 생각했습니다.그래서 미리 테이블에 저장해놓는다면 이런 연산 과정이 생략이 될 수 있기 때문에 필드로 추가하는게 맞다고 생각합니다. 출근,퇴근을 별도의 ROW로 관리하지 않고 하나의 ROW로 관리하기로 변경했습니다. 1. 출근은 오늘하고, 퇴근을 새벽에 할 경우 조회하려면 조건이 까다로워집니다. 2. 출퇴근 기록지에서 하루에 대한 정보를 읽어온다면 퇴근(새벽),출근(아침),퇴근(저녁)으로 조회가 될테고 연산이 복잡해집니다. 그래서 데이터 구조를 다음과 같이 변경했습니다. 출퇴근 기록지: id,`직원 id`,`오늘 날짜`,`출근 시간`,`퇴근 시간`,`등록일`,`수정일`,`등록한 위치`,`등록한 사람`,`수정한 사람`  회고 처음에 시간이 없다보니, 빠르게 구현을 해야한다는 생각에 추가 요구사항이나 엣지 케이스에 대한 염두를 하지 않고 데이터 구조를 설계했습니다. 테스트 코드를 작성하다가 이렇게 구조를 작성하면 테스트 하기도 어렵고, 수정하기도 어렵다는 걸 느꼈습니다. 결국은 요구사항은 데이터를 저장하는게 목적이 아니라 급여를 주기 위해서 데이터를 저장하는 게 목적인 기능이였습니다. 정규화도 좋고, 객체지향적인 것도 좋지만, 결국은 유지보수와 기능을 제공하기 위해 어떻게 데이터를 구조화하여 저장할지 고민을 하게 하는 문제였던거 같고 덕분에 데이터 구조를 처음에 잘못짜면 답이 없다는걸 느꼈습니다. 오히려 처음 작성하는게 더 빠르더라구요 국비교육을 받을때 강사님이 했던 말도 기억납니다. 테이블 설계는 정말 오래 고민하고 설계해야한다는 말씀이 기억납니다. 몸소 깨달은 한주가 되었습니다..

[인프런 워밍업 클럽 스터디] 13일차 - Spring Boot 설정, 버전업 이해하기

build.gradle빌드 스크립트라고도 불리며, gradle을 이용해 프로젝트를 빌드하고 의존성을 관리하기 위해 작성되었다.groovy 언어를 사용해 작성되었고, Kotlin이라는 언어를 사용할 수도 있다. org.springframework.boot 플러그인 역할스프링을 빌드했을 때 실행가능한 jar 파일이 나오게 도와주고스프링 애플리케이션을 실행할 수 있게 도와주고또 다른 플러그인들이 잘 적용될 수 있게 해준다. Spring 과 Spring Boot 의 차이점1. 간편한 설정2. 간단한 의존성 관리3. 강력한 확장성MSA에 적합한 모니터링  Lombokgetter, setter, 생성자와 같은 반복되는 보일러 플레이트 코드(boiler plate code)를 제거할 수 있다.lombok 의존성 추가IntelliJ lombok 플러그인 추가IntelliJ Annotation Processor 설정   Spring Boot 2.7.x 에서 3.0.x 로 업데이트 하기Java 최소 버전이 17로 업그레이드 되었다.많은 스프링 프로젝트, Thrid-party Library 가 버전업 되었다.AOT 기초 작업이 이루어졌다. AOT(Ahead of Time) 빌드를 할 때 스프링 애플리케이션을 분석하고 최적화하는 도구애플리케이션 시작 시간과 메모리 사용량을 줄일 수 있게 해준다.javax 대신 jakarta 패키지를 사용하게 된다.모니터링 기능들의 강화외의 다양한 세부적인 변경사항이 많음   

백엔드워밍업클럽스터디백엔드자바스프링부트

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

3주차는 프로젝트 나머지를 진행하려고 했는데 시간이 너무 바빠 그동안 미뤘던 JAVA, Spring 개념 공부를 좀 더 하려고 한다.JAVAJVM운영체제 종속받지 않고 자바를 실행시켜주는 가상 컴퓨터이전에 C 개발 했을 때 윈도우랑 리눅스 돌아가는거 따로 구현함Java compiler.java(고수준언어 - 사람이 읽는 언어) -> .class(저수준언어) 로 변환시켜주는 프로그램JDK설치했을때 bin 안에 javac.exe 파일.class는 기계어는 아님(바이트코드) 그래서 JVM이 기계어로 읽을 수 있도록 해석해줌바이트 코드는 다시 실시간 번역기 또는 JIT 컴파일러에 의해 바이너리 코드로 변환GC가비지 컬렉션유효하지 않은 메모리를 JVM의 가비지 컬렉터가 불필요한 메모리를 알아러 정리C에서는 메모리 해제를 직접 해줘야했는데 이 부분이 너무 어려웠음아래 내용은 따로 가져옴JVM의 Heap영역은 처음 설계될 때 다음의 2가지를 전제(Weak Generational Hypothesis)로 설계되었다. 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다. 즉, 객체는 대부분 일회성되며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다. 그렇기 때문에 객체의 생존 기간에 따라 물리적인 Heap 영역을 나누게 되었고 Young, Old 2가지 영역으로 설계되었다. 초기에는 Perm 영역이 존재하였지만 Java8부터 제거되었다. 출처: https://mangkyu.tistory.com/118JAVA 메모리 구조런타임 데이터 영역 5가지 존재메소드영역힙 영역스택 영역PC 레지스터Native Method Stack자바 프로그램을 실행하면 JVM은 OS로 부터 메모리를 할당받음JVM의 메모리 영역은 3가지스태틱(메소드) 영역 - Class , Static 변수, 생성자, 메소드 같은 것들을 저장. 종료시 해제힙 영역 - 참조형 데이터 저장되는 공간. 스택 영역의 실제 데이터들이 저장되어있음스택 영역 - 힘의 참조값(주소값), 기본 자료형, 메서드 내부 기본자료형 변수. 메서드 종료시에 메모리 삭제참조 : https://lucas-owner.tistory.com/38참조 글 일부 같은 기능을 하는 어플리케이션 일지라도, 메모리 관리에 따라 성능에 차이가 생긴다는 이야기다. 메모리를 관리하지 않고 구성하게 된다면 StackOverFlow 가 발생하여 어플리케이션이 종료될수도 있다는 말이다, 혹은 어플리케이션의 속도가 크게 저하 될수도 있다 + 회사 프로그램이 고객의 정보를 인메모리로 저장했다 메모리 부하가 온 적이 있음. 메모리가 아닌 캐싱으로 해결Spring어노테이션?코드의 특별한 의미, 기능을 수행하도록 하는 기술어노테이션 덕에 코드가 깔끔해짐참조 : https://velog.io/@rara_kim/Spring-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98AnnotationDI / IoC ?DI 의존성 주입AOP란?관점 지향 프로그래밍정리 : 각 로직 공통된 관심사 묶어서 사용 ex) 로깅Bean, Component?참조 : https://giron.tistory.com/91DB인덱스(INDEX)색인. 특정 컬럼 기준으로 정렬해놓은 상태DB의 일부 사용량을 차지함저장성능보단 조회 성능을 올림인덱스 자료구조B+Tree자식노드가 2개 이상인 B-Tree를 개선시킨 자료구조BTree LeafNode를 LinkedList로 연결하여 순차 검색을 용이하게 만듬.해시테이블보다는 나쁜 시간복잡도지만, 일반적으로 사용되는 구조해시 테이블컬럼의 값으로 생성된 해시를 기반으로 인덱스 구현시간복잡도 Map처럼 O(1)부등호(<,>) 같은 연속적인 데이터 순차 검색이 불가능하기 떄문에 사용에 적합하지 않음인덱스 효율성인덱스는 한 테이블당 보통 3~5개가 적당(정규화, 테이블 목적에 따라 개수가 달라짐)카디날리티(중복정도)가 높으면 인덱스 설정에 좋은 컬럼카디날리티가 높다 - 중복도가 낮다(값들이 대부분 다르다 - 고유한 학번)카디날리티가 낮다 - 중복도가 높다(값들이 대부분 같다 - 나이)선택도가 낮아야 인덱스 설정에 좋은 컬럼(일반적으로 5~10%퍼)선택도가 낮다 - 한 컬럼이 갖고있는 값 하나로 적은 row가 찾아진다(카디날리티 높은것과 동일)선택도 계산 10개 데이터 학번 모두 고유, 이름 2씩 같음, 5명씩 나이가 같음 학번 1/10 = 10% 이름 2/10 = 20% 나이 5/10 = 50% 그러므로 학번이 인덱스 설정에 좋은 컬럼조회 활용도 ↑ , 수정 빈도 ↓ - 위 내용 때문에링크 : https://velog.io/@jwpark06/효과적인-DB-index-설정하기참고 링크 : https://dev-coco.tistory.com/158그 외쓰레드동시성 - 하나의 코어 / 멀티 쓰레드병렬성 - 여러개 코어 / 단일 쓰레드후기...참 열심히 여러 가지를 많이 하고 싶었지만 병행은 쉽지 않았다..그래도 포기하기 않고 끝까지 완주가 목표기 때문에 완주를 했다면 나름 목표는 달성했다는 생각이다앞으로도 이런게 많이 나왔으면 좋겠다1기가 나오면 주변 사람들에게 추천할 예정

bbangsomi

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

2주차 학습 내용지금까지 작성했던 코드를 보다 좋은 코드로 리팩토링하면서 스프링 컨테이너와 스프링 빈이 무엇인지 알아보았습니다.기존 메모리 저장 방식에서 데이터베이스 저장 방식으로 변경하면서 Service 코드 변경을 최소화하기 위해 인터페이스를 도입했지만, 변경 코드 범위가 줄어들 뿐 근본적인 문제가 해결되지 않았습니다.제어의 역전 방식을 사용하는 스프링 컨테이너를 적용해 코드 변경 없이 원하는 클래스를 주입할 수 있게 되었습니다. 2주차에 접어들며 드디어 우리는 JPA를 적용할 수 있게 되었습니다 !문자열 SQL을 직접 사용하는 것에 한계를 느끼고, 객체 지향적인 JPA를 활용해 코드를 변경했습니다.이제 Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제할 수 있게 되었으며트랜잭션 적용으로서비스 메소드가 시작할 때 트랜잭션이 시작되고서비스 메소드 로직이 모두 정상적으로 성공하면 commit서비스 메소드 로직 실행 도중, 문제가 발생하면 rollback 이 되도록 처리했습니다.트랜잭션 사용 과정에서 생겨나는 영속성 컨텍스트에 대해 알아보며 1주차보다 더 클린한 코드로 변경해 나갔던 2주차입니다. 2주차 미션2주차 부터는 새로운 것을 만들어내기보다 기존의 코드를 리팩토링하면서 학습 내용을 적용하는 시간을 가져보았습니다.Controller - Service - Repository로 3단 분리하고, 데이터베이스 저장 방식도 나누어보며 더 클린한 코드 작성 방법에 다가갈 수 있었습니다.2주차 리팩토링 과제를 수행하기에 앞서 과제 4에서 마무리하지 못한 부분을 다시 한번 강의를 들으며 차근차근 수정했으며, 다른 수강생분들의 코드도 참고하여 마무리하고 다음 과제를 진행했습니다.처음부터 잘 짜인 코드를 공부했다면 클린 코드의 중요성에 대해 와닿지 않을 수도 있었겠지만, 점차 코드를 발전시키는 과정을 거치면서 좋은 코드의 필요성에 대해 또 한번 느꼈던 미션이었습니다. 마무리개발 과정을 머릿속에서 그려보며 틀을 잡는 것은 여전히 쉽지 않지만, 이번 강의와 과제를 통해 그 과정이 조금은 이해되고 있음을 느꼈습니다. 이제는 API 명세서를 읽고 간단한 API는 직접 만들 수 있으며, Controller - Service - Repository 계층 동작 방식을 이해할 수 있게 되었습니다.점점 클린 코드로 발전해 나가면서 코드 정리가 수월해진 것을 몸소 느끼며 저의 이해도를 통해 다시 한 번 클린 코드의 중요성에 대해 깨달았습니다. 💡이제 과제가 모두 종료되고 마지막 미니 프로젝트를 남겨두고 있습니다. 남은 한 주 잘 마무리하여 배포까지 진행해 보고 싶습니다 🏃🏻‍♀ 

백엔드인프런워밍업클럽

김수용

[인프런 워밍업 클럽] 세 번째 발자국

어떤게 기억에 남는가?이번 3주차에서는 마지막 특강이 있었습니다. 백엔드 코스에서는 미니 프로젝트를 제출한 사람들 중 코드 리뷰를 신청한 사람들을 추첨하여 멘토인 태현님께 리뷰를 받는 형식이었습니다. 정말 감사하게도 저는 코드리뷰 대상자로 당첨되어서 첫 번째로 코드리뷰를 받을 수 있었습니다. 원래 특강 시간이 20시부터 21시까지 1시간만 진행될 예정이었으나 코드 리뷰 특성상 주어진 시간보다 길어져서 22시 50분 쯔음에 끝났습니다. (특강이 끝날 때쯤 되니까 태현님 목소리가 시작할 때보다 많이 쉬어있어서 마음이 좋지 않았습니다..)코드리뷰는 어땠나?코드리뷰는 태현님께서 미니 프로젝트 기능 구현 요구사항대로 기능을 구현하고 그 코드에 대해서 리뷰를 받는 것이었습니다. 정말 운이 좋게도 저는 리뷰 첫 번째 순서에 당첨되어 리뷰를 받을 수 있었다.대충 기억나는 것은 아래와 같습니다.@RequestMapping("/api/teams")을 처럼 사용하는 것이 좋을까? 1. /api/teams/team 이라는 엔드포인트가 조금 어색한 것 같다 -> /api/team이 좀 더 괜찮지 않았을까? 2. 컨트롤러에서 등록만 하는 거라면 굳이 ResponseEntity나 객체 반환을 할 필요는 크게 없다. 상환에 따라서 Client가 POST에서 이런 응답을 주면 좋겠다라는 요청이 있다면 해도 된다. 3. Repository에서 중첩 쿼리는 절대 쓰지 않는다. 이는 대표적인 안티패턴이다. 그렇다면 중첩을 안 시키고 데이터를 SQL을 한 번에 가져오려면? 이때 쓸 수 있는 기능이 sql의 join + group by 같은 기능을 사용한다. 한방 쿼리를 쓰는 것은 좋은 방법이다. 4. 빌더를 바로 서비스에서 사용하기 보다는 DTO에서 toEntity() 메서드를 만들어서 객체를 만드는 것이 좋다. 5. Repository 쿼리에서 CASE 구문을 웬만해서는 사용하지 말자. 6. LocalDateTime.now()가 반복되길래 상수로 선언하고 사용했었는데 이는 LocalDateTime.now()가 서버가 구동되는 시점을 기록하고 상수에 할당하는 것이기 때문에 의도와 다르게 동작할 수 있다. 만약 현재 시간을 기입하고 싶다면 그냥 LocalDateTime.now()를 사용하자. 7. 객체간의 연관 관계는 최대한 맺어주지 않는 게 좋다. 라이프 사이클이 동일할 때만 연관관계를 맺어주자 8. (어려운 내용) 누군가 API 호출을 진짜 기가 막히게 동시에 2번 하게 되면? 현재 구현 상으로는 Attendance가 2개가 생긴다. 스프링은 기본적으로 멀티 스레드 환경에서 구동한다. 그렇다면 어떻게 막는가? Lock 3가지 (낙관적 락 / 비관적 락 / 유저 락) 또는 Unique Key로 막을 수 있다. 9. BaseEntity를 최대한 사용하자 예를 들면 최초 생성일, 마지막 수정일 같은 것을 꼭 상속 받아서 사용할 수 있도록 하자 10. soft delete를 생각하자. 11. @PathVariable 보다 @RequestBody 나 Http Query 사용을 지향하자 (사용 빈도 분석에 유리함)제가 당장 고려할 수 있는 부분들을 중점적으로 기록하였고, 그 외의 기록들은 머리로 최대한 이해하려고 했습니다.제 리뷰가 끝나고는 다른 참가자 분들의 코드 리뷰도 볼 수 있었습니다. 보면서 놀랐던 게 제가 고려하지 못한 부분이나 구현하지 못한 기능을 쉽게 구현하신 듯한 느낌을 받았을 때 많이 놀랐습니다. 이는 제가 앞으로 더 배워야 할 것들이 많음을 다시금 깨닫게 해주는 계기가 된 것 같습니다.

백엔드워밍업클럽

윤대

[강의 정리] - 컨벤션 교정 - {Java/Spring 주니어 개발자를 위한 오답노트}

해당 글은 김우근 강사님의 "Java/Spring 주니어 개발자를 위한 오답노트"를 수강한 내용을 바탕으로 정리한 글입니다!!!강의 링크!!컨벤션이란?Convetion은 사전상 관습을 의미한다. 즉, 코딩에서 컨벤션은 문맥상 코드를 작성할 때 지켜야하는 관례나 관습을 통칭한다. 컨벤션을 지킴으로써 읽고, 관리하기 쉬운 코드를 작성할 수 있게 된다. 이는 개발자들의 협업을 용이하게 하고, 유지 보수를 쉽게 한다.Java 표기법자바의 표기법은 아래와 같다.변수 이름 : camelCase함수 이름 : camelCase클래스 이름 : PascalCase패키지 이름 : alllowercase상수 : UPPER_SNAKE_CASE변수의 타입의 약어로 변수명을 시작하는 표기법을 헝가리안 표기법이라 하는데, 이는 과거 IDE가 좋지 않은 시절의 산물임으로 사용하지 않는다.변수명 작명 규칙줄여 쓰지 않기 줄여 썼을 때, 다른 의미의 단어임에도 축약어가 같은 경우가 존재한다. 그럴 경우, 변수명만을 보고 해당 객체가 어떤 동작을 할 지 알 수가 없다.ex) webSocket -> ws , webServer -> ws단, Identifier -> Id , application -> app 과 같이 관례상 굳어져 축약어를 사용해도 무관한 경우가 존재한다.축약어에 대한 관례코딩이 아닌 글을 쓸 때, 축약어는 모든 글자를 대문자로 쓰는 것이 관례다. 그러나 코딩에서 축약어를 모두 대문자로 쓰게 되면 상수와 구분이 어려워지고, 축약어로 변수명을 시작할 경우 첫글자를 소문자로 해야한다는 규칙과 충돌한다. 따라서, 축약어도 일반 명사로 보고 같은 규칙을 적용한다.Util이라는 이름 작명 지양하기Util이 붙게되면 모든 static 메소드가 모인다. static이 모였다고 해서 반드시 Util인 것이 아니다. 의미에 맞는 이름을 선정해야 한다. (이 부분은 경험이 부족하여 머리로는 이해하였으나, 감각적으로는 이해되지 않았다.)get vs findget -> return type이 T인 경우find -> return typd이 Optional<T>인 경우get 남발하지 않기get은 객체가 가지고 있는 필드값을 그대로 가져올 때만 사용한다. 추가적인 연산을 하고 가져온다면 get이 아닌 적절한 변수명을 짓도록 하자.롬복 getter setter 지양하기getter는 캡슐화를 저해하고 (private로 숨겨둔 필드가 모두 드러난다.) setter는 객체를 불변하지 않게 한다.start end 값 설정하기메소드의 파라미터로 시작값과 끝값을 받는다면, 시작값은 자신을 포함하고 끝값은 자신을 포함하지 않는다. 강의중 몰랐던 단어 간단히 집고 가기!일급 컬렉션일급 컬렉션 -> Collection을 Wrapping 하면서 그 외의 다른 멤버 변수가 없는 상태<ex>public class Cars { private List<Car> carList; public Cars(List<car> carList) { this.carList = carList; } }일급 컬렉션을 사용하면 얻는 이점비지니스에 종속적인 자료구조Collection의 불변성을 보장상태와 행위를 한 곳에서 관리이름이 있는 컬렉션

백엔드JavaConvention

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

미니프로젝트를 하다보니, 강의 마지막 부분에 대한 이해가 부족하다고 느꼈다.그래서, 이번엔 강의 마지막에 나온 연결 관계에 대해 한번 정리하고자 한다.  User entity에 UserLoanHistory를 추가해 주었다.User와 UserLoanHistory는 1:N 관계이다. @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List<UserLoanHistory> userLoanHistories = new ArrayList<>();cascade 설정은 CascadeType.ALL 로 user에서 변경이 발생할 경우 UserLoanHistory에도 반영한다.강의시간에도 이야기하였지만, CasCade옵션의 경우 원하는 로직에 따라 신중하게 사용해야한다. 예컨대, 어떤 사용자가 책을 빌린 다음에 반납하지 않고 회원 탈퇴를 할 경우를 가정해 보자. Cascade 옵션으로 user데이터가 사라질때 대출 기록까지 삭제할 경우 사라진 책을 찾기 어렵게 된다.그래서, 만약 포스팅 글에 달린 댓글 처럼 완벽한 종속 관계가 아닐 경우엔 cascade 옵션을 사용할 때 고민을 많이 해야한다.(참고한 사이트 : https://tecoble.techcourse.co.kr/post/2023-08-14-JPA-Cascade/) orphanremoval은 연결 관계를 끊으면 참고하고 있는 자식 객체를 삭제하는 기능이다.이 경우는 User 객체에서 대출히스토리와의 관계를 끊으면 대출히스토리가 삭제된다.User는 그대로 있고, 히스토리와의 관계를 끊음으로서 자식을 삭제하는 옵션이다.이거는 아래 사이트를 보면서 이해를 했다고는 생각했는데, 직접 실습을 해보면서 좀더 해봐야 겠다.(참고한 사이트 : https://tecoble.techcourse.co.kr/post/2021-08-15-jpa-cascadetype-remove-vs-orphanremoval-true/) UserLoanHistory에 연결관계를 설정한 코드이다.@JoinColumn(nullable = false) @ManyToOne private User user;many to one으로 한명이 여러개의 대출 기록을 가질수 있다고 이해하면 될 것 같다. nullable = false 옵션을 하면 UserLoanHistory이 User를 보고 있는데, 대출 기록은 모든 user에 매핑이 된다는 의미다.즉 inner join이 가능하다.강의 들을때도 그랬고 지금도이지만, 이렇게 연결했을 때 자동으로 Primary key를 FK로 설정을 하는게 맞는지헷갈린다.인터넷에서 찾아봤을 때는, 아래와 같이 명시적으로 외래키를 지정해 줄수 있는 것 같다.강의 시간에 관련해서 나왔었는지 다시 복습할 계획이다.그리고, 그 다음에 헷갈리는 부분은public void loanBook(String bookName){ this.userLoanHistories.add(new UserLoanHistory(this, bookName)); }this.userLoanHistories 를 할 때.User 객체가 정해지면 (사용자가 정해지면) 사용자의 PK (따로 외래키를 설정했을 경우엔 FK)를 가지고 대출 이력을 쭉 가져와서 객체 리스트를 만들고 거기에 새로운 대출 이력 객체를 넣어주는 것이 맞는지 정확하게 이해가 되지 않았다.강의 중에는 암묵적? 묵시적으로 진행되는 부분이라 하셨는데, 연관 관계에 대해 좀 더 개념을 이해해야 활용을 잘 할 수 있을 것 같다.  오늘은 객체의 연관 관계에 대해 고민하였다. 아직 모든게 명확한 상태는 좀만 더 공부하면 마저 이해할 수 있을 것 같다.          

영후이

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

스프링백엔드

채 수

과제 _ 미니 프로젝트

자바로 미니 프로젝트를 만들어보려고 한다.팀 등록 기능 : 회사에 있는 팀을 등록해야하고 '팀 이름'을 필수적으로 가져야한다.직원 등록 기능 : 직원을 등록할 수 있다. '직원 이름' , '팀의 매니저인지 매니저가 아닌지 여부','회사에 들어온 일자', '생일' 이라는 정보를 필수적으로 가져야 한다.팀 조회 기능 : 모든 팀의 정보를 한 번에 조회할 수 있어야 한다.[{"name" : "팀 이름", "manager" : "팀 매니저 이름" (없으면 null), "memberCount" : 팀 인원 수[숫자]}]직원 조회 기능 : 모든 직원의 정보를 한 번에 조회할 수 있어야 한다.[{"name" : "직원 이름", "teamName" : "소속 팀 이름", "role" : "MANAGER" or "MEMBER", "birthday" : "1997-08-18", "workStartDate" : "2024-01-01"}]먼저 프로젝트를 생성하고 객체 지향적 접근 방식을 사용한다.다음과 같은 형식의 프로젝트를 만들었다.https://github.com/soo1e/inflearn_MiniProject코드는 여기서 확인할 수 있다.팀 등록 기능직원 등록 기능팀 조회 기능직원 조회 기능다음처럼 모든 조건을 다 만족시켰다!회고록음 인프런 워밍업 클럽 스터디를 3주동안 진행해봤다. 큰 프로젝트의 구조와 흐름에 대해 알 수 있어서 좋은 시간이었다. 백엔드 개발에 대한 전반적인 중요한 지식들을 얻어갈 수 있어 좋았다. 이러한 배운 걸로 개인 프로젝트를 더욱 진행해서 유능한 개발자가 되기 위해 노력해야겠다. 3월은 대부분의 공고들이 나오는 시즌이다. 이 스터디를 들은 모든 인원이 좋은 결과가 있기를 바라며!

인프런워밍업클럽

킹재

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

이번 주에 배운 내용[JPA에서 연관관계 맵핑하기]create table user ( id bigint auto_increment, name varchar(25), age int, primary key (id) )create table user_loan_history ( id bigint auto_increment, user_id bigint, book_name varchar(255), is_return tinyint(1), primary key (id) )user 테이블과 user_loan_history를 보면, user_loan_history 테이블은 user를 알고 있지만 (user_id 컬럼), user 테이블은 user_loan_history 테이블을 알지 못한다. 즉, 관계의 주도권을 user_loan_history 테이블이 갖고 있는 것이다. 이러한 사실을 JPA 기술을 이용해 반영한다. // User.java @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private final List<CheckoutHistory> checkoutHistories = new ArrayList<>(); // UserLoanHistory.java @ManyToOne private User user;주도권이 없는 쪽에서 @OneToMany와 같은 연관관계 어노테이션을 선언한다. mappedBy = "user"라고 작성하면, JPA가 UserLoanHistory의 user 필드를 연관관계의 주인으로 인식한다. 연관관계의 주인이 설정되어야, 이를 기준으로 테이블이 연결된 채 DB 데이터가 제대로 저장된다.cascade 옵션을 사용하면 User가 삭제 및 저장될 때 연결된 UserLoanHistory도 같이 처리된다.orphanRemoval 옵션을 사용하면 연관관계가 끊어진 데이터를 자동으로 제거해준다. [배포]우리 컴퓨터 대신 외부 컴퓨터 (보통 리눅스 OS 사용)를 가져와 코드를 옮기고, 스프링, MySQL 등을 설치한다면 외부 컴퓨터는 필요한 프로그램만 실행할 것이며, 이용자들은 24시간 우리의 애플리케이션에 접속할 수 있다. 이 과정을 배포라 한다. spring: config: activate: on-profile: local datasource: url: "jdbc:h2:mem:library;MODE=MYSQL;NON_KEYWORDS=USER" # 생략 --- spring: config: activate: on-profile: dev datasource: url: "jdbc:mysql://localhost/library" # 생략똑같은 서버 코드를 실행시키되 실행 환경에 따라 설정을 다르게 하고 싶다면 profile을 이용해보자. application.yml 파일에 예제처럼 작성하면, local profile에선 H2 DB를 사용하고 dev profile에선 MySQL DB를 사용하게 된다. AWS의 EC2 서비스를 이용해 외부 컴퓨터를 빌려보자. EC2는 Elastic Compute Cloud의 약자로, 탄력적인 방식으로 원격 컴퓨터를 사용할 수 있음을 뜻한다. 이때 우리가 빌린 컴퓨터를 AWS에선 인스턴스라고 일컫는다. 인스턴스에서 애플리케이션을 실행하기 위해선 리눅스 명령어를 배울 필요가 있다. 주요 명령어는 다음과 같다. mkdir / rmdir: 디렉토리 생성 / 삭제cd: 현재 위치 변경pwd: 현재 위치 확인ls: 현재 위치에 있는 폴더와 파일 확인 (ls -l로 하면 더 자세히 볼 수 있음) 미니 프로젝트링크: https://github.com/areyouhun/emp-app출퇴근 사내 시스템을 만드는 미션으로, 단계별로 요구되는 기능을 구현해야 한다. 1단계에선 팀 등록, 직원 등록, 팀 조회, 직원 조회 기능을 구현해야 한다. 회고드디어 목표로 했던 서버 배포를 했습니다. 알찬 강의, 아낌없이 주는 선생님, 함께 공부하는 사람들이 있어 커리큘럼을 완수할 수 있었습니다. 하지만 이제 시작이겠죠? 배포 자동화라든지 AWS에는 EC2 말고 어떤 서비스가 있는지를 좀 더 배워야 할 것 같습니다. 3주 동안 함께 공부한 여러분들을 존중하고 존경하며 여기서 마무리하도록 하겠습니다.

백엔드워밍업클럽

Logan

보안전문가가 알려주는 해킹사고 예방하기

안녕하세요! 버그바운티 플랫폼 파인더갭의 Logan입니다. 해킹사고는 왜 일어나고 그렇다면 어떻게 방어할까요?해킹사고를 당하면 어떤일이 생길까요? [보안리포트] “한국 기업 61%, 지난 1년간 사이버 보안 사고 경험”클라우드 클레어 분석자료에 따르면 1년간 최소 100만 달러 ~ 대기업의 경우 46%가 최소 200만 달러 이상 재정적 영향전체 IT 예산의 21% 이상을 사이버 보안에 투자하는 산업은 에너지, 공공 서비스 및 천연자원 산업(38%), 여행, 관광 및 숙박업(32%), 그리고 소매업(27%) 순이었다.http://www.denews.co.kr/news/articleView.html?idxno=27267 Google, 삼성을 비롯한 기업들은 버그바운티를 통해 사전에 보안서비스를 운영하고 있습니다.그렇다면 그들은 왜 자신의 서비스를 대상으로 버그바운티를 할까요? 그 이유는 경쟁사와 사이버 범죄 커뮤니티보다 한 발 앞서 나가기 위함입니다. Pwn2Own 2023에서 대회 대상은 Tesla가 선정되었고 아래와 같이 코멘트 되었습니다."Tesla는 거의 혼자서 커넥티드 카 산업을 발명했습니다. 경쟁사와 사이버 범죄 커뮤니티보다 한 발 앞서 나가기 위해 필요한 것이 무엇인지, 즉 엄격한 테스트와 소프트웨어 버그에 대한 지속적인 조사가 무엇인지 누구보다 잘 알고 있습니다." 저희는 삼성전자, LG U+, NC, 빗썸 코리아, 위대한 상상(요기요), 녹십자 등 다양한 기업과 서비스들의 보안 취약점을 찾기 위해 버그바운티 서비스를 운영하고 있습니다. ‘더 나은 보안문화를 함께 만든다’ 는 슬로건처럼기업의 버그바운티 뿐만 아니라 CJ, KISA(한국 인터넷 진흥원)와 함께 '화이트햇 투게더'라는 사회 공헌 캠페인도 작년부터 진행하고 있습니다. 최근 개인정보 유출 사고가 늘어나는 시점에서 더 많은 보안 활동을 요청받고 있고보안 사고의 대책으로 버그바운티가 채택되는 것은 실질적인 보안 활동이 필요하기 때문일 것입니다. 보안관련하여 문의사항이 있으시다면 언제든지 편하게 문의주시기 바랍니다. https://findthegap.co.kr  

보안보안해킹버그바운티모의해킹해커윤리적해커화이트해커보안인증인증컨설팅isms

Groot

워밍업 클럽 - 백엔드 3주차 발자국

자바와 스프링 부트로 생애 최초 서버 만들기 (링크) 강의를 스터디를 하며 남기는 3주차 발자국(회고)입니다.마지막 회고드디어 강의를 완강했다.배운 것강의를 들으며 기본적인 API 개발과 JPA 사용법을 알 수 있었다. 설명과 강의가 짧게 끊어지며 잘 설계되어 있어서 강의를 보지 않고 혼자 복습할 겸 짤 때도 되게 수월하게 짤 수 있었다.기본적인 강의가 끝나고 난 후에는 여러 기술과 팁을 소개 받을 수 있었다.QueryDSL, gradle, 보일러플레이트 코드를 제거하기 위한 lombok 등 추가적으로 공부해야 하는 여러 키워드들을 얻었다.propertity와 같이 여러 환경에서 작업할 떄의 팁도 얻을 수 있었다.느낀 점한편 개강하고 여러 일정이 겹쳐서 강의를 후반부를 몰아서 급하게 들었다. 강의 자료를 보며 다시 복습하며 몇 개의 내용을 블로그에 남겨보려 한다.미션말고도 미니 프로젝트를 할 때는 강의 자료를 보지 않고 해보려고 했다. 이때 생각보다 버벅거렸고 사소한 부분을 빠뜨려서 버그가 나고는 했다. 이는 내가 스프링에 관한 지식 부족해서 그렇다고 생각한다. jakarta와 같은 내부 패키지를 보며 스프링 프레임워크가 제공해주는 마법적인 일들을 다시 차근히 살펴보아야 겠다는 생각도 든다.아직 2주차 회고에서 언급했던 배포 자동화도 하지 못해서 다음주부터 이것도 추가적으로 진행해보려고 한다.

백엔드

[인프런 워밍업 클럽 스터디] 12일차 - AWS와 EC2 배포

Section 7. 생애 최초 배포하기[목표]EC2에 접속하는 방법을 알아보고, EC2에 접속해 리눅스 명령어를 다뤄본다.개발한 서버의 배포를 위해 환경 셋팅을 리눅스에서 진행하고, 실제 배포를 진행한다.foreground와 background의 차이를 이해하고 background 서버를 제어한다.도메인 이름을 사용해 사용자가 IP 대신 이름으로 접속할 수 있도록 한다.[리눅스 기본 명령어]mkdir : 폴더를 만드는 명령어ls : 현재 위치에서 폴더나 파일을 확인하는 명령어ls -l : 조금 더 자세한 정보를 확인할 수 있다.cd : 폴더 안으로 들어가는 명령어pwd : 현재 위치를 확인하는 명령어cd .. : 상위 폴더로 올라가는 명령어rmdir : 비어있는 폴더(디렉토리)를 제거하는 명령어 sudo yum update : 관리자의 권한으로 설치되어 있는 여러 프로그램을 최신화한다.sudo yum install [프로그램이름] : 관리자의 권한으로 프로그램을 설치한다.sudo systemctl status [프로그램이름] : 프로그램의 상태를 확인한다.suto systemctl restart [프로그램이름] : 프로그램을 재시작한다.chmod : 파일이나 폴더의 권한을 변경한다.ctrl + c : foreground로 실행중인 프로그램을 중단하는 신호nohup [명령어] & : [명령어]를 background에서 동작하게 만드는 명령어rm : 파일을 제거하는 명령어 ps aux : 현재 실행중인 프로그램 목록을 확인할 수 있다. kill -9 [프로그램번호] : 해당 프로그램을 종료시킨다. vi : 리눅스 편집기인 vim을 사용하여 파일을 연다.cat : 파일에 있는 내용물을 모두 출력하는 명령어 tail : 현재 파일의 끝 부분을 출력하는 명령어 tail -f : 현재 파일의 끝 부분을 실시간으로 출력해준다.      

백엔드워밍업클럽스터디백엔드자바스프링부트

제텔

[인프런 워밍업 클럽 0기] BE 미니 프로젝트 1단계

Team 기능Controller@RestController public class TeamController { private final TeamService teamService; public TeamController(TeamService teamService) { this.teamService = teamService; } @PostMapping("/team") public void saveTeam(@RequestBody TeamCreateRequest request) { teamService.saveTeam(request); } @GetMapping("/team") public List<TeamFindResponse> getTeam() { return teamService.getTeams(); } }Domain@Entity public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(nullable = false, length = 25) private String name; private String managerName; private long memberCount; public Team(String name) { this.name = name; } public void setTeamManager(String name){ this.managerName = name; } public void setMemberCount(){ this.memberCount++; } public String getName() { return name; } public String getManagerName() { return managerName; } public long getMemberCount() { return memberCount; } public long getId() { return id; } }Service@Service public class TeamService { private final TeamRepository teamRepository; public TeamService(TeamRepository teamRepository) { this.teamRepository = teamRepository; } @Transactional public void saveTeam(TeamCreateRequest request){ teamRepository.save(new Team(request.getName())); } public List<TeamFindResponse> getTeams(){ List<Team> teams = teamRepository.findAll(); return teams.stream() .map(TeamFindResponse::new) .collect(Collectors.toList()); } }Member 기능Controller@RestController public class MemberController { private final MemberService memberService; public MemberController(MemberService memberService) { this.memberService = memberService; } @PostMapping("/member") public void saveMember(@RequestBody MemberCreateRequest request) { memberService.saveMember(request); } @GetMapping("/member") public List<MemberFindResponse> getMember() { return memberService.getMember(); } }Domain@Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 25) private String name; private Long teamId; @Enumerated(EnumType.STRING) private Role role; private LocalDate birthday; private LocalDate workStartDate; public Member(Long id, String name, Long teamId, Role role, LocalDate birthday, LocalDate workStartDate) { this.id = id; this.name = name; this.teamId = teamId; this.role = role; this.birthday = birthday; this.workStartDate = workStartDate; } //getters }public enum Role { Manager, Member; }Service@Service public class MemberService { private final MemberRepository memberRepository; public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Transactional public void saveMember(MemberCreateRequest request){ Member member = memberRepository.findByName(request.getTeamName()) .orElseThrow(IllegalArgumentException::new); Role role = request.isManager() ? Role.Manager : Role.Member; memberRepository.save( new Member( request.getName(),request.getTeamId(), role, request.getBirthday(), request.getWorkStartDate() ) ); if(role.equals(Role.Manager)){ team.setTeamManager(request.getName()); memberRepository.save(team); } team.setMemberCount(); memberRepository.save(team); } public void getMember() { List<MemberFindResponse> response = new ArrayList<>(); List<Member> members = memberRepository.findAll(); for (Member member : members) { response.add( new MemberFindResponse( member.getName(), member.getTeamId(), member.getRole(), member.getBirthday(), member.getWorkStartDate() ) ); } return response; } } 후기아무래도 기초가 없이 진행하려다 보니 여러가지가 맞물려서 오류가 일어날 때어떻게 개선해야할지 길을 잃는 경우가 많은 것 같다.기능별로 구분하기 위해 Team과 Member를 나누어 각각 기능 구현은 가능하지만테이블을 참조할 경우 어떻게 객체 사이를 오가게 구현해야하지?같은 조금의 응용할 거리가 나와도 구현이 불가능해진다...테이블을 중복해서 생성하는 건 당연히 맞는 방향이 아닌데그럼 member에서 team을 불러오는 게 맞는 건가? 하려면 할 순 있는데 아닌 것 같은데?같은... 데서 구현에 병목들이 생기다보니 확장불가능한 구현만 가능하고2~4단계로는 나아가지 못했다 ㅠㅠ(다른 분들의 깃헙을 참고해도 잘 적용이 안 되는 무지한 나...)

백엔드