사나래
수강평 작성수
-
평균평점
-
블로그
전체 12#카테고리
- 백엔드
![[인프런워밍업스터디_BE_2기] 2주차 발자국](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 10. 14.
0
[인프런워밍업스터디_BE_2기] 2주차 발자국
Readable Code: 읽기 좋은 코드를 작성하는 사고법 수강 후 작성한 글입니다. 2주차 정리2주차에는 이전까지 리팩토링한 코드를 좀 더 다듬고, 주어진 코드를 직접 리팩토링해보는 시간을 가졌다. 업무 시간에도 계속 어렵게 느껴졌던 패키지 정리나 클래스 정리 부분이 많은 도움이 되었고, 시간이 부족해 리팩토링을 제대로 해보지는 못하였지만 시야를 많이 넓힐 수 있었다.마지막 섹션8에서 은탄환은 없다는 제목으로 조언을 해주신 것이 가장 기억에 남았다. 좋은 코드를 작성하기 위해 책을 읽거나 강의를 듣고, 많은 자료를 찾아보면서 어느샌가 '좋은 코드' 라는 것에 목이 매여있던 것 같다. 특히나 업무가 바쁜 지금 시간내에 코드를 작성하는 것이 더 중요함에도 작성한 코드가 마음에 들지 않아 다시 리팩토링을 하기도 하는 등 어떤 것을 우선해야하는지 잠시 잊었던 것 같다. 2주차 과제 정리2주차 Day7 과제[섹션 7. 리팩토링 연습]의 "연습 프로젝트 소개" 강의를 보고,'스터디 카페 이용권 서택 시스템' 프로젝트에서 지금까지 배운 내용을 기반으로 리팩토링을 진행해 봅시다.https://github.com/HyeongSeop-Kim/readable-code/tree/main/src/main/java/cleancode/studycafe 2주차를 마치며아쉬웠던 점0기부터 시간이 부족해도 매번 진도에 맞춰 따라갔는데 이번주에는 업무에 치여서 결국 진도를 맞추지 못했다... 그래서 발자국도 하루 늦게 쓰고 있다ㅠ 칭찬할 점그래도 끝까지 하는 것이 중요하다고 생각해 포기하지 않고 진도를 따라가고 있는 것은 칭찬할 수 있지 않을까..! 보완할 점회사 일이 너무 바빠서 어떻게 보완해야할 지 잘 모르겠지만.. 끝까지 최선을 다해봐야겠다!
백엔드
![[인프런워밍업스터디_BE_2기] 1주차 발자국!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 10. 06.
0
[인프런워밍업스터디_BE_2기] 1주차 발자국!
Readable Code: 읽기 좋은 코드를 작성하는 사고법 수강 후 작성한 글입니다. 1주차 정리1주차에는 추상 섹션에서 추상과 구체에 대한 개념 정리, 이름짓기 등 간단한 부분을 배웠다. 이중에서 한 클래스 또는 메서드 내에서 추상화 레벨을 맞추는 부분은 기존에 생각하지 못하던 부분이라 해당 섹션의 내용 중 가장 인상 깊었다.다음 섹션3 논리, 사고의 흐름에서는 사고의 depth를 줄이는 부분이 기억에 남았다. 기존에 중첩된 for문이나 if문 등을 보면 강박적으로 이를 해소하고자 했던 것 같은데, 눈에 보이는 depth가 아니라 사고 과정을 줄이는 것이 중요하다는 것을 듣고 실무에서 이 부분을 놓치지 않도록 계속 신경 써야겠다는 생각이 들었다.섹션 4에서는 객체를 설계할 때 생각할 것과 SOLID에 대해 배웠다. 1주차 내용중 가장 와닿았던 내용이 있었는데, 그것은 'getter은 굉장히 폭력적'이라는 것이다. 객체 지향에 대해 공부하면서 이를 실무에 적용했을 때 'A.getAA().getAAA()..'와 같이 연속된 getter를 사용하여 비교하거나하는 등 뭔가 가독성이 떨어진다는 느낌을 받을 때가 많았다. 그런데 이렇게 무작정 getter를 쓰기보다는 다른 방법을 생각해보고 적용해보면서 이렇게 해결할 수도 있구나! 하면서 클래스를 만들면 습관적으로 getter를 만들던 내 자신을 돌아보게 되었다.섹션 5에서는 본격적으로 객체 지향을 적용한 다양한 리팩터링을 진행하였는데, 평소 존재는 알고 있었지만 활용하지 못했던 일급컬렉션과 같은 개념을 잘 알 수 있게 되었고, ValuObject나 Enum 처럼 사용하고 있었음에도 충분히 활용하지 못한 부분이 있다는 것을 알 수 있어 굉장히 유익했다. 1주차 과제 정리1주차 Day2 과제"추상과 구체" 강의를 듣고, 생각나는 추상과 구체의 예시가 있다면 한번 3~5문장 정도로 적어봅시다. 일상 생활, 자연 현상, 혹은 알고 있는 개발 지식 등 어느 것이든 상관 없습니다. 추상에서 구체로, 또는 구체에서 추상으로 방향은 상관 없으나, 어떤 것이 추상이고 어떤 것이 구체 레벨인지 잘 드러나게 작성해 보아요 :) ex) 우리가 상대방에게 소리를 내어 말을 하고 듣는 과정을 구체 레벨에서 표현한다면? - 폐에서 나온 공기가 성대를 통과한다. - 이는 나의 입술을 통해 외부로 방출되고, 상대방과 나 사이에 있는 공기를 진동시킨다. - 공기를 통해 전달된 진동은 고막, 달팽이관 등을 거쳐 청각세포, 청신경을 통해 뇌로 전달된다.추상: 총을 쏜다. 구체: 약실에 탄약을 넣는다.총의 방아쇠를 당긴다.공이치기가 공이를 때린다.뇌관이 폭발하며 탄약 내 화약을 연소시킨다.화약이 연소되며 발생한 가스가 탄환을 밀어낸다. 1주차 Day4 과제아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; } https://github.com/HyeongSeop-Kim/readable-code/tree/main/src/main/java/cleancode/day4/tobe SOLID에 대하여 자기만의 언어로 정리해 봅시다. 저는 SOLID 원칙을 파인만 기법을 적용해서 초등학생에게 설명할 수 있도록 최대한 간단한 비유를 들어서 정리해봤습니다. 단일 책임 원칙 (Single Responsibility Principle) -> 프로그램에서 하나의 클래스나 함수는 한 가지 책임을 져야해. -> 장난감을 정리할 때 각자 한 가지 역할만 맡는 거야. 한 사람은 로봇 장난감을 모으고, 또 다른 사람은 자동차 장난감을 정리해. 그러면 일이 더 깔끔하고 빠르게 끝날 수 있겠지? 개방-폐쇄 원칙 (Open-Closed Principle) -> 프로그램은 쉽게 확장할 수 있어야 하지만, 기존 코드를 고치지 않아도 새 기능을 추가할 수 있어야해. -> 게임을 만들 때, 새로운 사냥터를 추가하고 싶어. 그런데 게임 자체를 다시 만들 필요가 있을까? 그냥 새로운 사냥터를 추가할 수 있게 하면 만들면 돼. 리스코프 치환 원칙 (Liskov Substitution Principle) -> 부모 클래스를 사용하는 곳에 자식 클래스를 넣어도 문제없이 작동해야 해. -> 청소기는 로봇 청소기도 있고, 일반 청소기도 있고, 작은 손청소기도 있잖아? 어떤 청소기를 사용하더라도 청소기라면 다 청소를 해줄 수 있어. 인터페이스 분리 원칙 (Interface Segregation Principle) -> 하나의 큰 인터페이스를 여러 개로 나눠서 필요한 것만 사용할 수 있게 하는 거야. -> 학교에서 준비물을 챙길 때, 매번 모든 준비물을 챙겨두는 것보다 미술 시간이 있는 날에는 미술 도구만 챙기고, 체육 시간이 있는 날에는 체육복만 챙기는 게 더 가볍고 좋겠지? 의존성 역전 원칙 (Dependency Inversion Principle) -> 프로그램에서 높은 수준의 클래스가 낮은 수준의 세부 사항에 의존하지 않게 해야 해. -> 핸드폰 충전기가 핸드폰마다 전부 다르면 너무 불편하겠지? 한 가지 충전기로 모든 핸드폰을 충전하게 하면 핸드폰을 바꾸더라도 기존에 사용하던 충전기를 사용할 수 있겠지? 1주차를 마치며이번 인프런 워밍업 스터디 2기의 소식을 들었을 때 기존에 듣고 있었던 테스트 코드에 대한 강의와 더불어 관심을 가지고 있던 Readable Code에 대한 내용이 있어 신청을 하고 싶었다. 하지만 현재 실무에서 맡고 있는 프로젝트 일정이 빠듯해서 계속 고민을 하게 되었는데, 이번 기회를 놓치면 다음에는 기회가 없을 수도 있을 것 같고, 0기에서의 좋은 기억도 있어 신청을하였다. 아쉬웠던 점역시 업무가 바쁘다보니 퇴근 이후 강의를 듣는 것도 빠듯했다. 그래서 과제를 기한에 맞춰 제출하긴 하였지만 시간이 부족하다보니 깊은 고민을 해보지 못한 것이 가장 아쉬웠던 것 같다. 칭찬할 점시간이 없는 와중에도 일정표에 따라 진도를 맞춰간 것은 칭찬할 점이라고 생각한다. 보완할 점주말을 조금 더 활용하여 시간이 부족한 부분을 채워야할 것 같다.
백엔드
![[인프런워밍업스터디_BE_0기] 3주차 정리!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 03. 10.
0
[인프런워밍업스터디_BE_0기] 3주차 정리!
섹션 5. 책 요구사항 구현하기[섹션 목표]1. 책 생성, 대출, 반납 API를 온전히 개발하며 지금까지 다루었던 모든 개념을 실습해본다.2. 객체지향적으로 설계하기 위한 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 이해한다.3. JPA에서 연관관계를 매핑하는 방법을 이해하고, 연관관계를 사용해 개발할 때와 사용하지 않고 개발할 때의 차이점을 이해한다.객체지향적 설계를 위한 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 알아보자기존 UserLoanHistory를 다음과 같이 변경해주었다.@Entity public class UserLoanHistory { // ..중략 @ManyToOne private User user; public UserLoanHistory(User user, String bookName) { this.user = user; this.bookName = bookName; this.isReturn = false; } }그리고 User도 다음과 같이 변경하였다.@Entity public class User { // ...중략 @OneToMany(mappedBy = "user") private List userLoanHistoryLists = new ArrayList(); // ...중략 }이렇게 엔티티를 매핑해줄 때는 연관관계의 주인에 대해 꼭 알아야한다. 이 연관관계의 주인은 쉽게 생각하면 테이블을 보았을 때 누가 관계의 주도권을 가지고 있는지를 의미한다. 현재 user와 user_loan_history 테이블에서 user_loan_history는 user_id를 컬럼으로 가지고 있어 user를 알고 있지만 user는 user_loan_history를 알지 못한다. 즉 연관관계의 주인이 user_loan_history라는 것이다. 그래서 mappedBy = "user"라고 적음으로써 UserLoanHistory의 user 필드가 연관관계의 주인이라는 것을 JPA에게 알려주는 것이다! 이렇게 연관관계를 이어주고 나면 다음과 같은 어노테이션과 속성들을 사용할 수 있다.@JoinColumn : 연관관계의 주인이 활용할 수 있는 어노테이션으로 다른 테이블을 가리키는 필드의 이름이나 null 여부, 유일성 여부 등을 정해줄 수 있다.cascade 옵션 : 객체가 저장되거나 삭제될 때 연결되어 있는 객체도 함께 저장되거나 삭제되는 기능orphanRemoval 옵션 : 연결이 끊어지면 그 데이터를 삭제하는 옵션 그리고 지난 주에 알아보았던 영속성 컨텍스트의 능력중 4번째 능력도 이번 시간에 알아볼 수 있었다. 영속성 컨텍스트의 4번째 능력은 바로 지연 로딩 이다. 이번에 연관관계를 매핑해보았는데, User를 가져올 때마다 List를 가져온다면 어떨까? 사용하지도 않는데 쿼리를 쓸 데 없이 한 번 더 날리는 꼴이 될 것이다. 지연 로딩은 List가 필요한 순간이 왔을 때 가져올 수 있도록 하는 기능이다! 연관관계를 사용해 개발할 때와 사용하지 않고 개발할 때의 차이점?이러한 연관관계를 사용했을 때 좋은 점은 무엇일까? 이번 시간 연관관계를 활용하여 User와 UserLoanHistory가 서로 협업하게 되었다. 이로 인해 Service 코드가 간결해졌고, 우리의 비즈니스 로직이 도메인 계층으로 옮겨가게되었다! 이런 식으로 각 도메인과 계층이 자신의 역할에만 집중할 수 있게 되면 테스트 코드를 작성하기도 더욱 쉬워진다! 그리고 코드를 이해하기도 편해진다!이렇게 좋은 연관관계지만 사용했을 때의 단점 또한 존재한다. 연관관계가 복잡하게 얽히게 되면 해당 시스템을 파악하기도 어려워지고, 서로 강하게 연결되어있기 때문에 한 곳을 수정하면 다른 곳까지 영향을 주게 된다. 그래서 언제 연관관계를 쓰고 언제 쓰지 않는 것이 좋을까?! 정답은 없지만 고려해보면 좋을 점은 '라이프 사이클'이다. 두 객체의 생성과 종료가 함께 이루어질 경우에 연관관계를 사용하는 것을 고려해볼만하다! 섹션 6. 생애 최초 배포 준비하기[섹션 목표]1. 배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야 하는지 알아본다.2. 스프링 서버를 실행할 때 DB와 같은 설정들을 코드 변경 없이 제어할 수 있는 방법을 알아본다.3. git과 github의 차이를 이해하고 git에 대한 기초적인 사용법을 알아본다.4. AWS의 EC2가 무엇인지 이해하고, AWS를 통해 클라우드 컴퓨터를 빌려본다.배포란 무엇일까?지난 강의를 통해 만들어진 우리의 애플리케이션을 다른 사람들이 사용하게 하고 싶다면 어떻게 해야할까? 컴퓨터에 애플리케이션이 동작하기 위해 필요한 스프링, MySQL들을 설치하고 애플리케이션을 실행시킨 뒤 이 곳으로 접속하게 하면 될 것이다. 이러한 과정을 우리는 배포라고 한다!스프링 서버를 실행할 때 DB와 같은 설정들을 코드 변경 없이 제어하는 방법스프링 서버를 실행할 때 스프링은 profile을 확인하여 해당 profile의 설정을 적용한다! 따라서 다음과 같이 spring.config.activate.on-profile 을 통해 profile의 이름을 지정해주고, 해당 profile을 통해 실행하도록 하면 된다.--- local 프로필 spring: config: activate: on-profile: local datasource: url: "jdbc:h2:mem:library;MODE=MYSQL;NON_KEYWORDS=USER" username: "sa" password: "" driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: create properties: hibernate: show_sql: true format_sql: true dialect: org.hibernate.dialect.H2Dialect h2: console: enabled: true path: /h2-console --- dev 프로필 spring: config: activate: on-profile: dev datasource: url: "jdbc:mysql://localhost/library" username: "root" password: "1234" driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: none properties: hibernate: show_sql: true format_sql: true dialect: org.hibernate.dialect.MySQL8Dialectgit과 github의 차이점과 git을 사용하기 위한 간단한 명령어를 알아보자git은 코드를 쉽게 관리할 수 있도록 해주는 버전 관리 프로그램이다. 그러면 github는 무엇일까? github는 git으로 관리되는 코드가 저장되는 저장소이다. 그럼 git을 사용하기 위한 간단한 명령어에 대해 알아보자!git init : 프로젝트를 git이 관리하도록 한다.git remote add origin 주소 : 프로젝트의 git 저장소를 해당 주소로 설정한다.git add 폴더 혹은 파일 이름 : 해당 폴더 또는 파일을 저장소에 저장할 수 있도록 추가한다.git status : 현재 관리 중인 파일들의 상태를 보여준다.git commit -m "메세지" : 저장소에 저장할 때 표시할 메세지를 추가하고 저장소에 저장할 준비를 한다.git push : 저장소에 저장한다.AWS의 EC2란?EC2란 Elastic Compute Cloud의 약자로 탄력적으로 원격 컴퓨터를 사용할 수 있다는 뜻이다. 즉, 필요할 때 컴퓨터를 빌렸다가 언제든지 컴퓨터를 반납하는 등 탄력적으로 원격 컴퓨터를 사용할 수 있다는 것이다. 이번 시간에는 AWS에 회원가입을 하여 원격 컴퓨터를 빌려보았다. 다음 시간에는 실제로 배포를 해보자! 섹션 7. 생애 최초 배포하기[섹션 목표]1. EC2에 접속하는 방법을 알아보고, EC2에 접속해 리눅스 명령어를 다뤄본다.2. 개발한 서버의 배포를 위해 환경 세팅을 리눅스에서 진행하고, 실제 배포를 진행한다.3. foreground와 background의 차이를 이해하고 background 서버를 제어한다.4. 도메인 이름을 사용해 사용자가 IP 대신 이름으로 접속할 수 있도록 한다.EC2에 접속해보고 리눅스 명령어에 대해 알아보자EC2에 접속하기 위해서는 다음 세 가지를 준비해야한다.우리가 접속하려 하는 EC2의 IP 주소키 페어 (pem 키)접속하기 위한 프로그램Windows - git CLI (git bash)Mac - terminal git CLI를 열어 다음 명령어를 통해 EC2에 접속해보자-- 처음 ssh 접속할 때 사용할 명령어 chmod 400 경로/키페어이름.pem ssh -i 경로/키페어이름.pem ec2-user@ip 주소위 방법 외에 AWS 콘솔 환경에서 연결할 컴퓨터를 선택하고 연결 -> 인스턴스에 연결 -> 연결을 눌러 접속할 수도 있다. 이제 간단한 리눅스 명령어에 대해 알아보자mkdir 폴더이름 : 폴더를 만드는 명령어ls : 현재 위치에서 폴더나 파일을 확인하는 명령어, -l 을 통해 조금 더 자세한 정보를 확인할 수 있다.cd 폴더 : 폴더 안으로 들어가는 명령어pwd : 현재 위치를 확인하는 명령어rmdir : 피어있는 폴더를 제거하는 명령어sudo yum update : 관리자의 권한으로 설치되어 있는 여러 프로그램을 최신화한다.sudo yum install 프로그램 이름 : 관리자의 권한으로 프로그램을 설치한다.sudo systemctl status 프로그램 : 관리자의 권한으로 프로그램의 상태를 확인한다.sudo systemctl restart 프로그램 : 관리자의 권한으로 프로그램을 재시작한다.chmod : 파일이나 폴더의 권한을 변경한다.ctrl + c : foreground로 실행중인 프로그램을 중단하는 신호nohup [명령어] & : 명령어를 background로 실행시킨다.rm 파일명 : 파일을 제거하는 명령어vi 파일명 : 리눅스 편집기인 vim을 사용하여 파일을 연다.cat : 파일에 있는 내용물을 모두 출력하는 명령어tail : 현재 파일의 끝 부분을 출력하는 명령어, -f 를 사용하여 끝 부분을 실시간으로 출력할 수 있다.ps aux : 현재 실행중인 프로그램 목록을 확인할 수 있다.kill -9 프로그램 번호 : 해당 프로그램을 종료시킨다. Gradle 관련 명령어도 정리하면 다음과 같다../gradlew build : 프로젝트를 빌드한다../gradlew clean : 현재 빌드된 결과물을 제거한다. 미니 프로젝트시간 관계상 1단계까지 밖에 구현하지 못하였지만.. 스터디가 끝난 뒤에도 추가로 4단계까지 구현해보려고 한다!미니프로젝트 : https://github.com/HyeongSeop-Kim/warming-up-study-assignment/tree/master/mini-project 3주차를 마치며아쉬웠던 점마지막 주까지 일/학습 병행의 어려움은 두고두고 아쉬움이 남는다...ㅠ 3주동안 나름의 해결을 위해 이 방법, 저 방법 동원해보았지만 해답을 찾지 못한 점이 너무 아쉽다.. 특히 시간이 흐를 수록 쌓이는 피로 덕분에 회사 업무에 지장을 주기도 하고, 이를 해결하기 위해 하루는 공부를 건너뛰기도 하는 등 아쉬움이 많았다. 칭찬할 점그래도.. 모든 진도를 끝까지 따라간 점은 역시 칭찬할만 하지 않을까..! 보완할 점스터디 이후에 혼자 공부할 때 어떤 방법으로 공부해야할 지 정리도 하고 생각해봐야겠다..!
![[인프런워밍업스터디_BE_0기] 2주차 정리!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 03. 03.
0
[인프런워밍업스터디_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 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 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주차를 마치며아쉬웠던 점이번주 역시 역시 가장 아쉬운건 일과 학습의 병행이었다. 주말에 미리 이번주 분량의 강의를 모두 들어두었지만 역시 시간이 부족하다...ㅠㅠㅠ 칭찬할 점잠을 줄여가며 과제를 완성하고, 커뮤니티에서 만난 사람들과 함께 과제에 대한 코드 리뷰도 도전해보는 등 최선을 다한 점은 역시 칭찬할 점이다! 일에 지장을 전혀 주지 않았다고는 할 수 없지만... 업무도 크게 무리 없이 진행하였던 나에게 따봉 하나를 선사한다...ㅎㅎ 보완할 점현실적으로 시간이 부족하지 않도록 최선을 다하기란 힘들다는 것을 느꼈다... 마지막 한 주는 그보다는 주어진 시간을 조금 더 알차게 활용하는 것을 목표로 해봐야겠다!
![[인프런워밍업스터디_BE_0기] 일곱 번째 과제!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 02. 27.
1
[인프런워밍업스터디_BE_0기] 일곱 번째 과제!
문제 1여섯번째 과제에서 만들었던 Fruit 기능들을 JPA를 이용하도록 변경해보세요! 오늘의 과제는 어제까지 선생님께 배운 다형성을 활용할 기회다! MemoryRepository를 사용하더라도, MySqlRepository를 사용하더라도 @Primary만 바꿔주면 되도록 변경해두었고, 주말에 미리 예습해두었던 JPA에서 사용할 수 있도록 Repository의 메서드도 맞춰둔 상황! JpaRepository만 만들고 바꿔주면 되겠지?! [JPA를 적용한 Repository]public interface FruitJpaRepository extends JpaRepository { List findAllByName(String name); @Query("select new com.day4.fruitapp.dto.fruit.model.FruitStat(f.soldYn, sum(f.price)) from Fruit f where f.name = :name group by f.soldYn") List getStats(@Param("name") String name); }하지만 역시 놓친 부분이 있었으니..[기존 서비스 코드]@Service public class FruitServiceV1 implements FruitService{ // 중략 ... @Override public void sellFruit(FruitSellReqeust reqeust) { Fruit fruit = fruitRepository.findById(reqeust.getId()) .orElseThrow(() -> new FruitNotFoundException("과일 정보를 찾을 수 없습니다.")); if (fruit.isSold()) { throw new FruitNotFoundException("이미 팔린 과일입니다."); } fruitRepository.sell(fruit.getId()); // getStats처럼 만들어야하나...? } // 후략 ... }팔린 과일에 대한 정보를 업데이트하는 부분인데.. FruitJpaRepository의 getStats 처럼 @Query 를 통해 구현할까도 싶었지만! sell이라는 팔렸다는 표현은 Fruit 도메인에 더 잘어울리는 것 같으니 다음과 같이 변경하자! @Service public class FruitServiceV2 implements FruitService { // 중략 ... @Override public void sellFruit(FruitSellReqeust reqeust) { Fruit fruit = fruitRepository.findById(reqeust.getId()) .orElseThrow(() -> new FruitNotFoundException("과일 정보를 찾을 수 없습니다.")); if (fruit.isSold()) { throw new FruitNotFoundException("이미 팔린 과일입니다."); } fruit.sell(); fruitRepository.save(fruit); } // 후략 ... }각 api의 요청 결과도 그대로 잘 나온다! 문제 2특정 과일을 기준으로 우리 가게를 거쳐갔던 과일 개수를 세고 싶다.예를 들어1. (1, 사과, 3000원, 판매 O)2. (2, 바나나, 4000원, 판매X)3. (3, 사과, 3000원, 판매 O)와 같은 세 데이터가 있을 때 사과를 기준으로 개수를 센다면 2를 반환한다. API의 스펙은 다음과 같다.1. HTTP method: GET2. HTTP path: /api/v1/fruit/count3. HTTP query: name=과일이름4. 예시 GET /api/v1/fruit/count?name=사과5. HTTP 응답 Body{"count" : long}6. HTTP 응답 예시{"count" : 2} 이번 요구사항은 간단하다. 바로 구현해보자![컨트롤러 클래스]@RestController public class FruitController { // 중략 ... @GetMapping("/api/v1/fruit/count") public FruitCountResponse getCount(@RequestParam String name) { return fruitService.getCount(name); } } [서비스 클래스]@Service public class FruitServiceImpl implements FruitService { // 중략 ... @Override public FruitCountResponse getCount(String name) { return new FruitCountResponse(fruitRepository.countByName(name)); } } [리포지토리 클래스]public interface FruitJpaRepository extends JpaRepository { List findAllByName(String name); @Query("select new com.day4.fruitapp.dto.fruit.model.FruitStat(f.soldYn, sum(f.price)) from Fruit f where f.name = :name group by f.soldYn") List getStats(@Param("name") String name); long countByName(String name); }DB에 없는 과일의 개수를 요청할 때 예외처리를 할 것인지 고민되었지만 거쳐갔던 과일의 개수를 반환하는 것이 요구사항이니 거쳐갔던 적이 없는 과일은 0을 반환하는 것이 맞는 것 같아 예외처리하지 않았다.이제 API 요청 결과를 보자! 문제 3아직 판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록을 받고 싶다.API 스펙은 다음과 같다.1. HTTP method: GET2. HTTP path: /api/v1/fruit/list3. HTTP query: option=GTE또는option=LTE, price=기준 금액- GTE : greater than equal의 의미- LTE : less than equal의 의미4. 예시 GET /api/v1/fruit/count?option=GTE&price=3000- 판매되지 않은 3000원 이상의 과일 목록을 반환한다.5. HTTP 응답 Body[{"name" : String,"price" : long,"warehousingDate" : LocalDate}, ...]6. HTTP 응답 예시[{"name" : "사과","price" : 4000,"warehousingDate" : "2024-01-05"},{"name" : "바나나","price" : 6000,"warehousingDate : "2024-01-8"}] 이번 요구사항은 조금 복잡하지만 그래도 어렵진 않다! LTE, GTE라는 파라미터를 문자열로 받기 보다는 Enum으로 관리하면 좋을 것 같다.. 바로 구현해보자! [요청 클래스와 Enum 클래스]public class NotSoldFruitRequest { private PriceComparison option; private long price; public NotSoldFruitRequest(PriceComparison option, long price) { this.option = option; this.price = price; } public PriceComparison getOption() { return option; } public long getPrice() { return price; } } public enum PriceComparison { GTE, LTE } [컨트롤러 클래스]@RestController public class FruitController { // 중략 ... @GetMapping("/api/v1/fruit/list") public List getNotSoldFruits(NotSoldFruitRequest request) { return fruitService.getNotSoldFruits(request); } } [서비스 클래스]@Service public class FruitServiceImpl implements FruitService { // 중략 ... @Override public List getNotSoldFruits(NotSoldFruitRequest request) { List fruits = switch (request.getOption()) { case GTE -> fruitRepository.findAllBySoldYnAndPriceGreaterThanEqualOrderByName(request.getPrice()); case LTE -> fruitRepository.findAllBySoldYnAndPriceLessThanEqualOrderByName(request.getPrice()); }; return fruits.stream().map(NotSoldFruitResoponse::new).collect(Collectors.toList()); } } [리포지터리 클래스]public interface FruitJpaRepository extends JpaRepository { // 중략 ... List findAllBySoldYnAndPriceGreaterThanEqualOrderByName(String soldYn, long price); List findAllBySoldYnAndPriceLessThanEqualOrderByName(String soldYn, long price); }"N"을 직접 파라미터로 넘겨주는게 마음에 들지 않지만 시간이 부족하니 오늘은 여기까지..ㅠ 이제 요청해보자
백엔드
![[인프런워밍업스터디_BE_0기] 여섯 번째 과제!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 02. 26.
0
[인프런워밍업스터디_BE_0기] 여섯 번째 과제!
시간이 부족하지 않도록 강의도 주말에 미리 들어두었건만.. 과제 시간이 너무 오래걸린다....ㅠㅠㅠㅠㅠ 오늘도 우선 코드만 남겨둬야겠다.... 이미 팔린 과일을 다시 구매하려고 요청을 했을 때의 예외 처리나, MemoryRepository 에서의 예외 처리 등등등 부족한 부분이 많지만 시간이 부족한 관계로 우선 기능 구현만 해두었다....https://github.com/HyeongSeop-Kim/warming-up-study-assignment/tree/master/day6/fruit-app/src/main/java/com/day4/fruitapp
![[인프런워밍업스터디_BE_0기] 1주차 정리!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 02. 25.
0
[인프런워밍업스터디_BE_0기] 1주차 정리!
섹션 1. 생애 최초 API 만들기[섹션 목표]1. 스프링 부트 프로젝트를 설정해 시작하고 실행할 수 있다.2. 서버란 무엇인지, 네트워크와 HTTP, API는 무엇인지, JSON은 무엇인지 등 서버 개발에 필요한 다양한 개념을 이해한다.3. 스프링 부트를 이용해 간단한 GET API / POST API 를 만들어본다. 스프링 부트 프로젝트를 만들어보자!우선 https://start.spring.io/ 로 접속한다. 해당 링크로 접속하면 아래와 같은 화면(작성일 기준)을 볼 수 있다.시간이 지나면 화면 구성이 바뀔 수 있다.Project : 프로젝트에서 사용될 '빌드 툴'을 고르는 항목Language : 개발 시 사용될 언어를 고르는 항목Spring Boot : 스프링 부트의 버전을 고르는 항목, SNAPTHOT, M1 등 숫자 옆에 무언가 붙어 있는 경우 안정성이 떨어질 수 있다.Project Metadata : 프로젝트와 관련된 설정 및 이름을 정하는 항목Java : 프로젝트에 사용될 Java 버전을 정하는 항목Dependencies : 프로젝트에서 사용될 라이브러리나 프레임워크를 추가하는 항목ADD DEPENDENCIES.. 를 눌러서 추가할 수 있다.라이브러리 : 프로그램을 개발할 때 미리 만들어진 기능을 가져와 사용하는 것프레임워크 : 프로그램을 개발할 때 미리 만들어진 구조에 코드를 끼워 넣는 것GENERATE 버튼을 누르면 만들어진 프로젝트가 다운로드 받아진다!위 과정을 거쳐 만들어진 압축파일의 압축을 풀고 IDE에서 해당 프로젝트를 열어주면 끝! 서버란 무엇일까?서버하면 생각나는 이미지...서버란 무엇일까? 우선 구글에 한 번 검색해보자.서버(server)는 클라이언트에게 네트워크를 통해 정보나 서비스를 제공하는 컴퓨터 시스템으로 컴퓨터 프로그램 또는 장치를 의미한다. ( 출처 : 서버 - 위키백과, 우리 모두의 백과사전 (wikipedia.org) )위키에 따르면 서버는 크게 두 가지를 의미한다.클라이언트에게 정보나 서비스를 제공하는 컴퓨터 프로그램위 프로그램을 실행하는 장치 즉, 컴퓨터 보통 우리가 서버를 개발한다고 할 때, 하드웨어를 만들진 않으니까.. 클라이언트에게 서비스를 제공하는 프로그램을 말하는 것이다. 그럼 클라이언트는 무엇일까? 이번에도 구글에 검색해보자!의뢰인, 고객이라는 뜻으로 컴퓨터 용어로는 네트워크를 통하여 서버라는 다른 컴퓨터 시스템 상의 원격 서비스에 접속할 수 있는 응용 프로그램이나 서비스를 의미한다. ( 출처 : 클라이언트 (컴퓨팅) - 위키백과, 우리 모두의 백과사전 (wikipedia.org) )조금 어렵지만 간단하게 정리하면 서버로부터 서비스를 요청하고 제공받는게 클라이언트이다. 이 때 클라이언트는 '네트워크'를 통해 서버에 요청을 한다고 한다. 여기서 다음 키워드인 네트워크에 대해서 알아보자. 네트워크 그리고 HTTP, API란?노드들이 자원을 공유할 수 있게하는 디지털 전기통신망의 하나로 즉, 분산되어 있는 컴퓨터를 연결하는 것을 말한다.( 출처 : https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%93%A8%ED%84%B0_%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC )네트워크는 컴퓨터를 연결하는 것을 말한다고 한다. 즉, 네트워크를 통해서 서버와 클라이언트가 연결되고 이 네트워크를 통해서 클라이언트는 서버로 요청을 보내는 것이다. 그럼 어떻게 요청을 보낼까? 이 때 등장하는 것이 HTTP 와 API 이다.HTTP는 Hyper Text Transfer Protocol의 약자로 클라이언트와 서버 사이에 이러우저니는 요청/응답 프로토콜이다. ( 출처 : https://ko.wikipedia.org/wiki/HTTP )API 는 응용 프로그램에서 사용할 수 있도록 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스이다. ( 출처 : https://ko.wikipedia.org/wiki/API )어려운 용어가 많이 나왔다. 내가 이해한 내용을 토대로 간단히 요약하자면 HTTP는 네트워크로 통신할 때 정해진 규약이고, API는 HTTP 를 통해 전달받은 내용을 토대로 미리 정해진(만들어진) 기능을 수행하는 것이다. GET API 와 POST API를 만들어 보자선생님과 함께 다음과 같이 코드를 만들어보았다. 위에서부터 차근차근 정리해보자// 컨트롤러 클래스 @RestController public class CalculatorController { @GetMapping("/addV1") public int addTwoNumbers(@RequestParam int number1, @RequestParam int number2) { return number1 + number2; } @GetMapping("/addV2") public int addTwoNumbers(CalculatorAddRequest request) { return request.getNumber1() + request.getNumber2(); } @PostMapping("/multiply") public int multiplyTwoNumbers(@RequestBody CalculatorMultiplyRequest request) { return request.getNumber1() * request.getNumber2(); } } // 요청 클래스 public class CalculatorAddRequest { private final int number1; private final int number2; public CalculatorAddRequest(int number1, int number2) { this.number1 = number1; this.number2 = number2; } public int getNumber1() { return nubmer1; } public int getNumber2() { return nubmer1; } } public class CalculatorMultiplyRequest { private final int number1; private final int number2; public int getNumber1() { return nubmer1; } public int getNumber2() { return nubmer1; } }@RestController : 해당 클래스를 API의 진입 지점 즉, Controller로 만들어준다.@GetMapping("/addV1") : 해당 메서드를 HTTP Method가 GET이고 path가 /addV1인 API로 지정한다.@RequestParam : HTTP 쿼리로 들어온 값을 메서드의 파라미터에 넣어준다.메서드의 파라미터가 많아졌을 때 일일이 @RequestParam 을 사용한다면 코드가 길어질 것이다.이럴 때는 CalculatorAddRequest 와 같이 클래스를 만들어주면 파라미터를 한 번에 받을 수 있다.@PostMapping("/multiply") : 해당 메서드를 HTTP Method가 POST이고 path가 /multiply인 API로 지정한다.@RequestBody : HTTP 바디로 들어온 JSON 데이터를 객체로 바꿔준다.@RequestBody를 사용하는 경우에는 CalculatorMultiplyRequest 와 같이 생성자를 만들지 않아도 괜찮다.위에서 배운 내용을 바탕으로 유저 생성 및 조회 API를 만들어보았는데, 한 가지 문제가 있다. 바로 서버를 껐다 키면 생성했던 유저 데이터가 사라진다는 것..! 다음 섹션을 통해 이를 해결해보자. 섹션 2. 생애 최초 Database 조작하기[섹션 목표]1. 디스크와 메모리의 차이를 이해하고, Database의 필요성을 이해한다.2. MySQL Database를 SQL과 함께 조작할 수 있다.3. 스프링 서버를 이용해 Database에 접근하고 데이터를 저장, 조회, 업데이트, 삭제할 수 있다.4. API의 예외 상황을 알아보고 예외를 처리할 수 있다.디스크와 메모리의 차이 그리고 Database컴퓨터의 핵심 부품은 CPU, RAM, DISK 세 가지이다. 이 중 RAM은 단기기억장치 디스크는 장기기억장치인데, 우리가 이전 섹션에서 서버를 실행시킨 뒤 저장한 유저 정보는 메모리 즉 RAM에 저장되었다. 그래서 서버를 종료하면 생성되었던 유저 정보가 사라졌던 것이다. 이를 해결하기 위해서 우리는 주로 Database를 사용하게 된다. 여기서 Database란 데이터를 구조화시켜 저장한 것이다. MySQL Database를 SQL과 함께 조작해보자.강의를 시작하기 전 설치해두었던 MySQL을 한 번 사용해보자! DDL (Data Definition Language) : 데이터를 정의하기 위한 SQL// 데이터베이스 생성 create database library; // 데이터베이스 조회 show databases; // 데이터베이스 삭제 drop database library; // 데이터베이스 접속 use library; // 테이블 생성 create table user ( id bigint auto_increment, name varchar(25), age int, primary key (id) ); // 테이블 목록 조회 show tables; // 테이블 삭제 drop table user;테이블 생성 시에는 컬럼의 이름과 타입, 부가조건을 설정하는데 간단히 정리하면 다음과 같다.MySQL 타입정수타입: tinyint, int, bigint실수타입: double, decimal(A,B)문자열타입: char(A), varchar(A)날짜, 시간타입: date, time, datetime부가조건auto_increment: 데이터를 넣지 않더라도 1부터 1씩 증가하며 자동으로 기록된다.primary key: 데이터를 식별할 기본 키로 지정한다.default: 데이터를 넣지 않았을 때 들어갈 기본값을 지정한다.not null: NULL을 허용하지 않기 때문에 데이터가 반드시 들어가야한다.이 외에도 많은 타입과 조건이 있지만 이번 글에서는 다루지 않겠다. 2. DML ( Data Manipulation Language) : 데이터를 조작하기 위한 SQL// 데이터 추가 (Create) insert into user (name, age) values ('철수', 20); // 데이터 조회 (Read) select * from user; // 데이터 수정 (Update) update user set name = '영희' where name = '철수'; // 데이터 삭제 (Delete) delete from user where '철수';이렇게 데이터를 조작하는 4가지 방법을 각각의 앞글자를 따서 CRUD라고 한다! 스프링 서버를 이용해 Database에 접근하고 데이터를 조작해보자자, 이제 스프링에서 DB에 접근해보자. 먼저 src/main/resources 경로의 application.yml 파일을 만들고 다음과 같이 DB설정 정보를 적어준다.spring: datasource: url: "jdbc:mysql://localhost/library" username: "root" password: "1234" driver-class-name: com.mysql.cj.jdbc.Driver그리고 JdbcTemplate을 활용해 다음과 같이 코드를 작성하면 Database에 SQL로 데이터를 조작할 수 있다.public class UserController { private final JdbcTemplate jdbcTemplate; public UserController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @PostMapping("/user") public void saveUser(@RequestBody UserCreateRequest request) { String sql = "insert into user(name, age) values(?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getAge()); } @GetMapping("/user") public List getUsers() { String sql = "select * from user"; return jdbcTemplate.query(sql, (rs, rowNum) -> { long id = rs.getLong("id"); String name = rs.getString("name"); int age = rs.getInt("age"); return new UserResponse(id, name, age); }); } }작성한 생성 sql문에서 ?에 해당하는 부분은 jdbcTemplate.update에서 적어준 파라미터로 채워져 DB에 전달된다.작성한 조회 sql문은 jdbcTemplate.query를 통해 DB에 전달되고 조회된 데이터를 (rs, rowNum)->{} 람다식을 통해 가공하여 반환한다. API 예외 상황을 처리해보자이제 우리가 만든 서버를 통해 Database에 접근하여 데이터를 조작할 수 있게 되었다. 그리고 이를 토대로 유저 정보를 수정하고 삭제하는 API도 만들었다. 그런데 현재 존재하지 않는 유저를 수정하거나 삭제하려고 하면 어떻게 될까? Postman을 통해 요청을 해보면 200 OK가 돌아오는 것을 알 수 있다.실제로 수정되거나 삭제된 데이터가 없는데도 성공했다는 응답이 오는 것은 이상하다. 다음과 같이 예외를 처리해보자@PutMapping("/user") public void updateUser(UserUpdateRequest request) { if (userJdbcRepository.isUserNotExist(request.getId())) { throw new IllegalArgumentException(); } userJdbcRepository.updateUserName(request.getName(), request.getId()); }isUserNotExist를 통해 유저가 존재하는지 확인하고 존재하지 않는 경우 IllegalArgumentException 예외를 던지도록 하였다. 이렇게 변경한 뒤 Postman을 통해 요청하면 500 Internal Server Error 로 응답이 오는 것을 확인할 수 있다!이와 같이 우리는 API를 통해 들어온 요청을 처리하던 중 발생하는 예외상황을 클라이언트에 알릴 수 있도록 예외처리를 해주어야한다. 섹션 3. 역할의 분리와 스프링 컨테이너[섹션 목표]1. 좋은 코드가 왜 중요한지 이해하고, 원래 있던 Controller 코드를 보다 좋은 코드로 리팩토링한다.2. 스프링 컨테이너와 스프링 빈이 무엇인지 이해한다.3. 스프링 컨테이너가 왜 필요한지, 좋은 코드와 어떻게 연관이 있는지 이해한다.4. 스프링 빈을 다루는 여러 방법을 이해한다.좋은 코드 (Clean Code)는 왜 중요할까?개발자는 새로운 코드를 작성하기도 하지만 기존의 코드를 수정하기도 한다. 특히 실무에서는 기존의 코드에 새로운 기능을 추가하거나 수정하는 일이 더욱 많다고 한다. 이를 위해서는 기존의 코드를 먼저 이해해야할텐데... 기존 코드가 이해하기 어렵게 작성되어 있다면 이해하는데 많은 공과 시간을 들여야할 것이다. 따라서 우리는 가능한 이해하기 쉽도록 좋은 코드를 만들어야 하는 것이다! 기존의 Controller를 분리해 더 좋은 코드로 만들어보자기존의 컨트롤러를 Controller, Service, Repository 세 개로 분리하였다. 이렇게 각 클래스가 각자의 역할을 가지고 단계별로 동작하는 것을 Layered Architecture라고 부른다. 1주차는 여기까지..! 섹션의 나머지 부분은 다음주에 계속 배워볼 것이다. 1주차 과제 정리각 과제를 블로그로 정리해두어 아래에 링크를 남긴다.Day 1 : https://www.inflearn.com/blogs/6567Day 2 : https://www.inflearn.com/blogs/6603Day 3 : https://www.inflearn.com/blogs/6672Day 4 : https://www.inflearn.com/blogs/6705Day 5 : https://www.inflearn.com/blogs/6728 1주차를 마치며실무를 하며 많은 부족함을 느끼고 인프런에서 여러 강의를 듣고 있었지만 어떻게 공부해나가야할지 방향성을 잡지 못하고 있던 와중에 이번 스터디를 발견했다. 이번 스터디를 통해 어떤 방법으로 공부해야할지 방향성을 잡는 것을 목표로 잡고 1주차를 맞이하였다. 아쉬웠던 점역시 가장 아쉬운건 일과 학습의 병행이었다. 집과 회사의 거리가 멀다보니 퇴근 이후 집에 도착하면 8시였다. 밥 먹고 운동하고 씻고... 공부를 할 시간이 1~2시간 밖에 없다보니 강의를 듣고 과제를 제출하는 것도 빠듯했다.물론 공부할 시간이 부족할 것을 생각해 주말에 미리 이번주 분량의 강의를 들어두었지만 그럼에도 시간이 부족했다. 과제를 하는 시간도 꽤 오래 걸렸지만 아무래도 평소에 공부할 때 글로 정리하는 습관이 없다보니 블로그에 정리하는 것이 시간을 많이 잡아먹었다. 칭찬할 점그럼에도 진도에 맞춰서 모든 강의를 수강하고, 과제를 제출한 것을 칭찬하고 싶다! 퇴근길 지하철에서 앉아서 갈 때 수업을 듣고 노트북으로 직접 코드도 쳐볼 수 있도록 세팅해서 실제로 강의를 듣기도 하고 (다른 사람이 불편하지 않도록 어깨를 한껏 움츠린채 코드를 치기란...ㅠㅠ), 운동을 쉬기도 하면서(이건 아쉬운 점이기도 하지만..) 일정에 맞추기 위해 많은 노력을 했던 것이 자랑스럽다. 보완할 점역시 주말을 많이 활용해야겠다는 생각이 들었다. 이번 주는 시간에 쫓겨 운동을 쉬기도 하고 과제를 해결한 부분도 아쉬운 부분이 많았는데 다음 주는 시간이 부족하단 느낌이 들지 않도록 최선을 다해봐야겠다!
백엔드
![[인프런워밍업스터디_BE_0기] 다섯 번째 과제!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 02. 23.
0
[인프런워밍업스터디_BE_0기] 다섯 번째 과제!
오늘의 과제는 주어진 코드를 최대한 깔끔하게 리팩터링 하는 것이다. 주어진 코드는 다음과 같다. public class Main { public static void main(String[] args) { System.out.println("숫자를 입력하세요 : "); Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int r1 = 0, r2 = 0, r3= 0, r4 = 0, r5 = 0, r6 = 0; for (int i = 0; i = 0 && b = 1 && b = 2 && b = 3 && b = 4 && b = 5 && b 1. 코드분석우선 코드를 정확하게 이해해야 리팩터링을 할 수 있을 것이다. 각 코드가 어떤 기능을 하고 있는지 주석을 달아보자!public class Main { public static void main(String[] args) { // 시행 횟수를 입력 받는다. System.out.println("숫자를 입력하세요 : "); Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); // 숫자 별로 나온 횟수를 저장할 변수를 선언한다. int r1 = 0, r2 = 0, r3= 0, r4 = 0, r5 = 0, r6 = 0; // 입력받은 시행 횟수만큼 주사위를 던지고 변수에 저장한다. for (int i = 0; i = 0 && b = 1 && b = 2 && b = 3 && b = 4 && b = 5 && b 2. 변수명 리팩터링주석을 달고 나니 어떻게 분리해야할지 조금 감이 잡히는 것 같다. 다음으로 의도를 알 수 없는 변수의 이름을 변경해주자!public class Main { public static void main(String[] args) { // 시행 횟수를 입력 받는다. System.out.println("숫자를 입력하세요 : "); Scanner scanner = new Scanner(System.in); int trialsNumber = scanner.nextInt(); // 숫자 별로 나온 횟수를 저장할 변수를 선언한다. int count1 = 0, count2 = 0, count3= 0, count4 = 0, count5 = 0, count6 = 0; // 입력받은 시행 횟수만큼 주사위를 던지고 변수에 저장한다. for (int i = 0; i = 0 && numberOfDice = 1 && numberOfDice = 2 && numberOfDice = 3 && numberOfDice = 4 && numberOfDice = 5 && numberOfDice 3. 메서드 추출자! 이제 위에서부터 차근차근 기능을 분리해보자.우선 시행횟수를 입력받는 부분은 메서드로 추출할 수 있을 것 같다.public class Main { public static void main(String[] args) { // 시행 횟수를 입력 받는다. int trialsNumber = getTrialsNumber(); // 숫자 별로 나온 횟수를 저장할 변수를 선언한다. int count1 = 0, count2 = 0, count3= 0, count4 = 0, count5 = 0, count6 = 0; // 입력받은 시행 횟수만큼 주사위를 던지고 변수에 저장한다. for (int i = 0; i = 0 && numberOfDice = 1 && numberOfDice = 2 && numberOfDice = 3 && numberOfDice = 4 && numberOfDice = 5 && numberOfDice 4. 클래스 분리 후 메서드 추출다음으로 변수를 6개 선언하고 다음 로직에서 계속 해당 변수를 사용하고 있다. 그렇다면 변수를 포함해서 그 아래 기능까지 클래스로 묶고 싶다! 그리고 각 기능을 메서드로 추출해보자!public class Main { public static void main(String[] args) { // 시행 횟수를 입력 받는다. int trialsNumber = getTrialsNumber(); // 숫자 별로 나온 횟수를 저장할 변수를 선언한다. -> 주사위 객체를 선언한다. Dice dice = new Dice(); // 입력받은 시행 횟수만큼 주사위를 던지고 변수에 저장한다. dice.rollTheDice(trialsNumber); // 결과를 출력한다. dice.printStat(); } private static class Dice { private int count1 = 0; private int count2 = 0; private int count3 = 0; private int count4 = 0; private int count5 = 0; private int count6 = 0; public void rollTheDice(int trialsNumber) { for (int i = 0; i = 0 && numberOfDice = 1 && numberOfDice = 2 && numberOfDice = 3 && numberOfDice = 4 && numberOfDice = 5 && numberOfDice 5. 다중 조건문 제거이제 메인은 4줄의 코드로 어느정도 정리된 것 같으니 새로 만든 클래스 내의 메서드들을 우선 정리하고 싶다.rollTheDice 메서드를 정리할 방법이 없을까 생각하다보니 주사위의 숫자는 1~6의 정수로 이루어져 있으니까 배열로 표현할 수 있을 것 같다!public class Main { // 생략.... private static class Dice { private final int[] diceCount = {0, 0, 0, 0, 0, 0}; public void rollTheDice(int trialsNumber) { for (int i = 0; i 6. 반복된 기능을 반복문으로 변경훨씬 깔끔해진 것 같다! 결과를 출력해주는 printStat은 동일한 형식의 문자열을 출력하니 반복문을 사용하면 될 것 같다.public class Main { // 생략.... private static class Dice { private final int[] diceCount = {0, 0, 0, 0, 0, 0}; public void rollTheDice(int trialsNumber) { for (int i = 0; i 7. 역할에 맞도록 다시 클래스 분리이제 Dice 클래스에서 더 정리할 부분이 없는지 다시 한 번 살펴보니까 Dice 라는 클래스 이름은 주사위를 의미 하는데 클래스 내의 기능들은 주사위를 던진 횟수를 저장하고 출력하는게 주요 기능으로 보인다. 그렇다면 클래스 이름을 바꿀까..? 생각해봤지만 '주사위를 던지는' 기능은 Dice와 어울리는 것 같다.. 그러니 다시 클래스를 분리해보자.public class Main { public static void main(String[] args) { // 시행 횟수를 입력 받는다. int trialsNumber = getTrialsNumber(); // 숫자 별로 나온 횟수를 저장할 변수를 선언한다. Dice dice = new Dice(); DiceStat diceStat = new DiceStat(); // 입력받은 시행 횟수만큼 주사위를 던지고 변수에 저장한다. for (int i = 0; i 클래스를 분리 하면서 rollTheDice 메서드도 각 클래스의 역할에 맞게 분리하였다. 그리고 이를 호출해주는 클래스도 그에 맞게 변경하였다. 추가적으로 diceCount라는 이름이 주사위의 숫자가 몇 번 나왔는지 저장하는 변수라는 의미를 잘 전달하지 못 하는 것 같아. diceNumberCount로 변경하였다. 메인문의 길이는 좀 더 길어졌지만 각 클래스가 자신의 역할을 더 명확히 표현하게 된 것 같으니 만족!더 정리할 부분이 있는지 찾아보면서 다음과 같은 고민이 있었지만 이정도 수정으로 마무리지었다. 시행횟수를 입력받는 메서드를 Dice 클래스에 넣어야할까?-> 시행횟수를 입력받는 행위는 주사위 라는 도메인과 상관이 없는 것 같으니 PASS! trialsNumber 변수와 numberOfDice 변수는 한 번만 사용되고 있는데 변수에 메서드를 인라인할까?-> trialsNumber는 반복문에서 반복 횟수를 지정할 때 쓰고 있다. 이곳으로 인라인하면 사용자에게 입력받는 메서드라는 파악이 힘들 것 같으니 PASS!-> countDiceNumber 메서드는 주사위의 숫자를 받아 저장한다. rollOneDece 메서드는 주사위를 굴린다는 행위를 잘 표현하고 있지만 어떤 주사위 숫자가 나왔는지를 표현하지는 않으니 메서드이름을 변경할까 고민해봤지만 행위를 그대로 표현하고 numberOfDice 변수로 주사위를 굴려 나온 숫자를 표현하기로 했다!8. 패키지 생성 후 클래스 이동이제 dice 패키지를 만들고 Main 내부에 만들 클래스를 dice 패키지로 옮기도록 하자! 9. 추가 과제 수행!!현재 코드는 주사위가 1~6까지만 있다는 가정으로 작성되었다.따라서 주사위가 1~12까지 있거나 1~20까지 있다면 많은 코드를 수정해야한다.주사위의 숫자 범위가 달라지더라도 코드를 적게 수정할 수 있도록 고민해보자!주사위의 숫자 범위가 달라졌을 때 내가 리팩터링한 코드에서 수정할 부분은 두 군데이다.Dice의 rollOneDice 내 상수 6DiceStat의 diceNumberCount 크기이걸 이렇게 바꾸자.생성자로 받을 수도 있겠지만 현재 요구사항에서 주사위의 숫자 범위는 가변형이 아니니 주사위 면의 수를 Dice 내 전역 상수로 선언하자! DiceStat의 diceNumberCount 크기를 DiceStat 생성 시에 Parameter로 받도록 변경하자! 이 때 배열의 크기는 1보다 작을 수 없으니 예외처리도 해주도록 하자! 이렇게 바꾸면 주사위의 숫자 범위가 변경되어도 Dice 객체의 상수값 한개만 변경해주면 된다.최종 수정본은 다음과 같다! [메인]public class Main { public static void main(String[] args) { int trialsNumber = getTrialsNumber(); Dice dice = new Dice(); DiceStat diceStat = new DiceStat(dice.getDiceSize()); for (int i = 0; i [Dice]public class Dice { private static final int DICE_SIZE = 6; public int rollOneDice() { return (int) Math.floor(Math.random() * DICE_SIZE); } public int getDiceSize() { return DICE_SIZE; } } [DiceStat]public class DiceStat { private final int[] diceNumberCount; public DiceStat(int diceSize) { if (diceSize
백엔드
![[인프런워밍업스터디_BE_0기] 네 번째 과제!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 02. 22.
0
[인프런워밍업스터디_BE_0기] 네 번째 과제!
평소에도 부족하지만 오늘은 정말 시간이 부족했다..ㅠ 우선 코드를 올리고 주말을 활용해서 글을 다듬어야할 것 같다.. 컨트롤러 클래스@RestController public class FruitController { private final JdbcTemplate jdbcTemplate; public FruitController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @PostMapping("/api/v1/fruit") public void storeFruit(@RequestBody FruitStoreRequest request) { String sql = "insert into fruit (name, warehousing_date, price) values (?, ?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice()); } @PutMapping("/api/v1/fruit") public void sellFruit(@RequestBody FruitSellReqeust reqeust) { String sql = "update fruit set sold_yn = 'Y' where id = ?"; jdbcTemplate.update(sql, reqeust.getId()); } @GetMapping("/api/v1/fruit/stat") public FruitStatResponse getStat(@RequestParam String name) { String sql = "select sold_yn, sum(price) as amount from fruit where name = ? group by sold_yn"; List stats = jdbcTemplate.query(sql, new Object[]{name}, (rs, rowNum) -> new FruitStat(rs.getString("sold_yn"), rs.getLong("amount"))); FruitStatResponse response = new FruitStatResponse(); stats.forEach(response::setStat); return response; } } 모델public class FruitStat { private String soldYn; private long amount; public FruitStat(String soldYn, long amount) { this.soldYn = soldYn; this.amount = amount; } public String getSoldYn() { return soldYn; } public long getAmount() { return amount; } } 요청 클래스public class FruitSellReqeust { private long id; public long getId() { return id; } } public class FruitStoreRequest { private String name; private LocalDate warehousingDate; private long price; public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } } 응답 클래스public class FruitStatResponse { private long salesAmount; private long notSalesAmount; public void setStat(FruitStat stat) { if (stat.getSoldYn().equals("Y")) { this.salesAmount = stat.getAmount(); } else { this.notSalesAmount = stat.getAmount(); } } public long getSalesAmount() { return salesAmount; } public long getNotSalesAmount() { return notSalesAmount; } }
백엔드
![[인프런워밍업스터디_BE_0기] 세 번째 과제!](https://cdn.inflearn.com/public/main/blog/default_thumbnail.png?w=260)
2024. 02. 21.
0
[인프런워밍업스터디_BE_0기] 세 번째 과제!
[질문]1. 자바의 람다식은 왜 등장했을까?2. 람다식과 익명 클래스는 어떤 관계가 있을까? - 람다식의 문법은 어떻게 될까? 다음 키워드를 참고해서 질문에 답해보자!익명 클래스, 람다, 함수형 프로그래밍, @FunctionalInterface, 스트림 API, 메소드 레퍼런스1. 익명 클래스'익명' 말그대로 이름이 없는 클래스라는 뜻이다. 우리가 클래스의 이름을 짓는(=만드는) 이유는 무엇일까? 그 클래스를 다음에도 사용하기 위해서이다! 이런 의미에서 익명 클래스는 단발성으로 사용되는 클래스로 프로그램에서 일시적으로 한 번만 사용되고 버려진다. 즉 재사용이 되지 않는다는 것이다.만약 한 번만 사용되고 버려질 클래스가 있다면 굳이 이 클래스를 영구적으로 정의하기 보다는 한 번 정의하여 사용한 뒤 바로 제거하는게 유지보수적으로 보나 메모리와 같은 자원적인 측면으로 보나 더 좋은 방법일 것이다.하지만 이와 같은 익명클래스는 코드 내부에서 클래스를 정의하다보니 코드 가독성이 떨어지게 되는데... 여기서 다음 키워드인 람다가 등장한다! 2. 람다람다 표현식이란 다음과 같이 괄호() 와 화살표 -> 를 사용해 함수를 간결하게 선언하는 방법으로 Java 8에서 등장한 람다 표현식은 위와 같이 코드 가독성이 떨어지는 익명 클래스의 대안이 되어주었다! 이런 람다 표현식이 등장한 이유는 다음과 같다.코드를 간결하게 만들어 가독성을 높일 수 있기 때문에컬렉션 요소를 필터링하거나 매핑하는등 보다 쉽게 처리할 수 있기 때문에병렬처리에 용이하기 때문에함수형 인터페이스를 간편하게 구현할 수 있기 때문에 오늘도 시간이 부족해서 전부 정리하지 못했다...ㅠ 주말을 활용해서 정리하지 못한 나머지 키워드도 꼭 정리해야겠다.. 그럼 다시 처음으로 돌아가서 질문에 답해보자![질문 1] 자바의 람다식은 왜 등장했을까?코드를 간결하게 만들어 가독성을 높일 수 있기 때문에컬렉션 요소를 필터링하거나 매핑하는등 보다 쉽게 처리할 수 있기 때문에병렬처리에 용이하기 때문에함수형 인터페이스를 간편하게 구현할 수 있기 때문에 [질문 2] 람다식과 익명 클래스는 어떤 관계가 있을까?람다식과 익명클래스는 서로 비슷한 기능을 하지만 람다식은 기존에 사용되던 익명 클래스의 단점을 보완한 대안이라고 볼 수 있으며 이러한 람다식의 문법은 다음과 같다.(parameter) -> expression참고 자료인프런 강의 최태현 - 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) https://www.inflearn.com/course/lecture?courseSlug=java-to-kotlin&unitId=110633기술 블로그 https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B5%EB%AA%85-%ED%81%B4%EB%9E%98%EC%8A%A4Anonymous-Class-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%95%98%EA%B8%B0https://mangkyu.tistory.com/113https://developer-talk.tistory.com/499
백엔드




