블로그
전체 12#카테고리
- 백엔드
#태그
- 인프런
- 인프런워밍업클럽
- 스터디1기
- 백엔드
- 워밍업
2024. 06. 06.
0
[인프런 워밍업 클럽 1기] 오프라인 수료식 참여 및 클럽 과정 후기
[인프런 워밍업 클럽 1기] 오프라인 수료식 참여 및 클럽 과정 후기 참여 계기IT 취준생 톡방에서 우연히 워밍업 클럽에 대해 정보를 얻을 수 있었고, 내가 들을만한 좋은 강의가 있을까 찾아보니 마침 최근에 포트폴리오 때매 관심 있었던 스프링으로 직접 서버를 배포하는 방법이 강의 주제로 있어서 신청하게 되었다.무엇보다 혼자 강의를 듣는게 아닌 디스코드 커뮤니티에서 서로 물어보며 강의를 같이 듣는다는 것이 흥미 있었고 모든 과정을 수강 완료하면 인프런 포인트로 일부분 환급 된다는 것이 마음에 들었다.참여 과정과정 자체는 강의 취지에 맞게 Spring Boot를 이용해 기초적인 API 설계와 배포까지 진행하는 정도로 진행했으며, 강의 난이도도 적절했다고 생각한다.무엇보다 다음 아래와 같이 학습 내용을 정리할 수 있도록 제출 기한을 설정해 학습에 대한 일정 관리를 별도로 진행하지 않아도 되서 좋았다. [인프런 워밍업 클럽 1기] BE 1일차 과제 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 2일차 과제 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 3일차 과제 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 1주차 발자국 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 4일차 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 5일차 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 2주차 발자국 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 6일차 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 7일차 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 3주차 발자국 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com)[인프런 워밍업 클럽 1기] BE 미니 프로젝트 - 이용수님의 블로그 - 인프런 | 커뮤니티 (inflearn.com) 오프라인 수료식 참여오프라인 수료식은 인프랩에서 진행했으며, 집에서 인프랩 사무실까지 가까운 거리는 아니였지만, 어차피 취준생이고 시간이 남아돌 뿐더러 가까운 일정에 중요한 시험도 없기 때문에 한번쯤 가보는 것도 괜찮다고 생각했다.인프랩 사무실은 스타트업 캠퍼스 5층에 위치하고 있었다. 내 추측으로는 한층을 대여해서 사용하고 있는것 같았다. 건물 입구 수료식 진행 화면 제공한 저녁식사와 인프런 스티커, 명찰 최종 후기오프라인 수료식까지 모두 진행하고 돌아온 과정을 돌이켜 보니 꽤 많은 경험을 했다고 느꼈다.비록 우수 러너는 되지 못했지만, 우수 러너가 된 스터디원의 학습 방법을 들어보니 우수 러너로 선정된 이유를 알것 같기도 했고, 학습 방법에 대해 많은 배움을 얻었다.네트워킹 시간에 코치님과 직접 만나보고, 꽤 많은 내용, 내가 평소에 궁금했던 내용을 질문하고 답변 받으면서, 내가 가는길이 험난하진 않은지, 어떤것을 바라보고 가는 것이 도움이 될지 내 나름대로 생각을 확실히 할 수 있는 시간이 되었던것 같다.워밍업 클럽을 모두 수료하며 강의 과정을 정리한 내용도 주차별로 정리해뒀으니 앞으로 필요할때마다 과정을 복습하기도 쉬울거 같다.워밍업 클럽 디스코드 커뮤니티에서도 개발자로서 얻어가는 것도 많았다. 개발자로서 가져야할 마음가짐과 같은 심상적인 정보부터, 어떻게 효율적으로 개발해야하는지와 같은 기술적인 정보까지 꽤 다양한 정보를 얻을 수 있었다.이렇게 워밍업 클럽 과정은 끝이 났지만, 개발자로서 나의 과정은 계속해서 진행될 것이다.
백엔드
・
인프런
・
인프런워밍업클럽
・
스터디1기
2024. 05. 23.
0
[인프런 워밍업 클럽 1기] BE 미니 프로젝트
[인프런 워밍업 클럽 1기] BE 미니 프로젝트 본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQg프로젝트 코드 깃허브 링크Re-Note/Attendance-Management: 근태 관리 프로젝트 (github.com)프로젝트 정의프로젝트 코드Teampackage com.inhousecommutingsystem.inhousecommutingsystem.domain; @Entity @Setter @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String manager; @OneToMany(mappedBy = "team") @JsonIgnore private List employees; }TeamControllerpackage com.inhousecommutingsystem.controller; @RestController @RequestMapping("/teams") public class TeamController { private final TeamService teamService; public TeamController(TeamService teamService) { this.teamService = teamService; } @GetMapping public List getAllTeams() { return teamService.getAllTeams(); } @PostMapping public Team saveTeam(@RequestBody Team team) { return teamService.saveTeam(team); } }TeamServicepackage com.inhousecommutingsystem.service; @Service public class TeamService { private final TeamRepository teamRepository; public TeamService(TeamRepository teamRepository) { this.teamRepository = teamRepository; } public List getAllTeams() { return teamRepository.findAll(); } public Team saveTeam(Team team) { return teamRepository.save(team); } }TeamRepositorypackage com.inhousecommutingsystem.repository; import com.inhousecommutingsystem.domain.Team; import org.springframework.data.jpa.repository.JpaRepository; public interface TeamRepository extends JpaRepository { }Employeepackage com.inhousecommutingsystem.domain; @Entity @Setter @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private boolean isManager; private Date birthDate; private Date workStartDate; @ManyToOne @JoinColumn(name = "team_id") private Team team; }EmployeeControllerpackage com.inhousecommutingsystem.controller; @RestController @RequestMapping("/employees") public class EmployeeController { private final EmployeeService employeeService; public EmployeeController(EmployeeService employeeService) { this.employeeService = employeeService; } @GetMapping public List getAllEmployees() { return employeeService.getAllEmployees(); } @PostMapping public Employee saveEmployee(@RequestBody Employee employee) { return employeeService.saveEmployee(employee); } }EmployeeServicepackage com.inhousecommutingsystem.service; @Service public class EmployeeService { private final EmployeeRepository employeeRepository; public EmployeeService(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository; } public List getAllEmployees() { return employeeRepository.findAll(); } public Employee saveEmployee(Employee employee) { return employeeRepository.save(employee); } }EmployeeRepositorypackage com.inhousecommutingsystem.repository; public interface EmployeeRepository extends JpaRepository { }결과 사진
백엔드
・
인프런
・
워밍업
2024. 05. 19.
0
[인프런 워밍업 클럽 1기] BE 3주차 발자국
[인프런 워밍업 클럽 1기] BE 3주차 발자국서론벌써 강의를 들은지 3주차가 지나간다. 강의와 스터디를 하면서 배운 내용을 잊지 않도록 주마다 블로그에 정리하려고 한다.섹션 5 : 책 요구사항 구현하기조금 더 객체지향적으로 개발할 수 없을까?BookService에서 User와 UserLoanHistory를 연결시켜 대출 기능과 반납 기능을 개선할 수 있다.User와 UserLoanHistory가 서로를 알도록 코드를 수정한다.연관 관계 설정@ManyToOne : (대출 기록) N : (대출 기록 소유자) 1 일 때 사용한다.@OneToMany : (대출 기록 소유자) 1 : (대출 기록) N 일 때 사용한다.연관 관계의 주인 설정하기 mappedBy = 주인 필드 이름 : 연관 관계의 주인의 값이 설정되어야만 진정한 데이터가 저장된다.JPA 연관관계에 대한 추가적인 기능들Person과 Address에서 1:1 관게 일시 @OneToOne을 사용한다.연관 관계의 주인이 아닌 쪽에 mappedBy를 사용하여 주인을 설정한다.연관 관계의 주인 효과 : 객체가 연결되는 기준이 된다.상대 테이블을 참고하고 있으면 연관관계의 주인연관관계의 주인이 아니면 mappedBy를 사용연관관계의 주인의 setter가 사용되어야만 테이블 연결연관관계 사용시 주의해야 할 점@Transactionnal public void savePerson(){ Person person = personRepository.save(new Person()); Address address = addressRepository.save(new Address()); person.setAddress(address); System.out.println(address.getPerson()); // null이 됨. } 위 코드에서 아직 트랜잭션이 끝나지 않아, 객체 끼리는 한쪽만 연결된 상태, 반대 쪽을 알 수 없어 null 반환하게 된다.setter를 한 번에 둘을 같이 이어주도록 처리하면 된다.즉, 양쪽 모두 연관관계를 가지고 있을 때는 양쪽 모두 한번에 맺어주는게 좋다.@Transactionnal public void savePerson(){ Person person = personRepository.save(new Person()); Address address = addressRepository.save(new Address()); person.setAddress(address); address.getPerson(); } ... public void setAddress(Address address){ this.address = address; this.address.setPerson(this); } ... public Person getPerson(){ return this.person; } User와 UserLoanHistory 관계에선 @ManyToOne, @OneToMany를 사용한다.@ManyToOne은 단방향으로만 사용할 수도 있다.@JoinColumn : 연관관계의 주인이 활용할 수 있는 어노테이션으로 필드의 이름이나 null 여부, 유일성 여부, 업데이트 여부 등을 지정한다.@ManyToMany는 구조가 복잡하고, 테이블이 직관적으로 매핑되지 않아 사용을 추천하지 않는다.cascade 옵션 : 저장이나 삭제를 할 때 연관관계에 놓인 테이블까지 함께 저장 또는 삭제해준다.orphanRemoval 옵션 : 연관관계가 끊어진 데이터를 자동으로 제거해준다.책 대출/반납 기능 리팩토링과 지연 로딩대출 기능 리팩토링 public void loanBook(String bookName){ this.userLoanHistories.add(new UserLoanHistory(this, bookName)); } 반납 기능 리팩토링 public void returnBook(String bookName){ UserLoanHistory targetHistory = this.userLoanHistories.stream() .filter(history -> history.getBookName().equals(bookName)) .findFirst() .orElseThrow(IllegalArgumentException::new); targetHistory.doReturn(); } User를 가져오는 부분과, 도메인 로직 실행 중간에 출력문을 넣어보면, @Transactional public void returnBook(BookReturnRequest request){ // 유저 정보를 가져온다 User user = userRepository.findByName(request.getUserName()) .orElseThrow(IllegalArgumentException::new); System.out.println("Hello"); user.returnBook(request.getBookName()); } User를 가져오고, Hello 가 출력되고, UserLoanHistory를 가져온다.즉, 영속성 컨텍스트는 지연 로딩이 가능하며, 지연 로딩을 사용하게 되면, 연결되어 있는 객체를 꼭 필요한 순간에만 가져올 수 있다.연관관계 사용의 장점각자의 역할에 집중하게 된다.새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다.테스트 코드 작성이 쉬워진다.연관관계 사용의 단점지나친 사용시, 성능상의 문제가 발생할 수 있다.도메인 간의 복잡한 연결로 인해 시스템 파악이 어려워질 수 있다.복잡한 연결로, 하나의 수정이 연관된 다른 요소에 영향을 미칠 수 있다.즉, 비즈니스 요구사항, 기술적인 요구사항, 도메인 아키텍처 등 여러 부분을 고민해서 연관관계 사용을 선택해야 한다.Section 5 정리.책 생성, 대출 반납 API를 온전히 개발하며 지금까지 다루었던 모든 개념을 실습해 봤다.객체지향적으로 설계하기 위한 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 이해했다.JPA에서 연관관계를 매핑하기 위한 방법을 이해하고, 연관관계를 사용해 개발할 때와 사용하지 않고 개발할 때의 차이점을 이해했다.섹션 6 : 생애 최초 배포 준비하기배포란 무엇인가?배포 : 최종 사용자에게 SW를 전달하는 과정. 즉, 전용 컴퓨터에 우리의 서버를 옮겨 실행시키는 것profile과 H2 DBprofile : 똑같은 서버 코드를 실행시키지만, 실행될 때 설정을 다르게 하고 싶을 때 사용한다.profile 적용, profile로 local을 입력시, H2 DB를 사용하게 하고, dev를 입력시, MySQL DB를 사용하도록 설정하기.H2 DB : 경량 Database, 개발 단계에서 많이 사용하며 디스크가 아닌 메모리에 데이터를 저장할 수 있다. (휘발성이라 개발 단계에서만 사용한다.)H2 DB 사용시 ddl-auto 옵션을 create로 사용하면 테이블을 신경쓰지 않고 코드에만 집중할 수 있다.application.ymlspring: 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 --- spring: config: activate: on-profile: dev datasource: url: "jdbc:mysql://localhost/library" username: "root" password: //패스 워드 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.MySQL8Dialect H2-Console로 로컬에서 링크에 접속하면로컬에서 데이터베이스를 확인할 수 있다.git과 github이란 무엇인가?git : 코드를 쉽게 관리할 수 있도록 해주는 버전 관리 프로그램github : git으로 관리되는 프로젝트의 코드가 저장되는 저장소github에 코드를 저장하는 이유.코드 소실 방지을 위해배포 할 떄 활용 가능 하다.git 기초 사용법git repository 주소 :강의 초반부터 intellij에서 git을 연결하여 사용 중이였다.git add.git commit -m "커밋 메시지"git pushAWS의 EC2 사용하기Section 6 정리.배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야하는지 알아 봤다.스프링 서버를 실행할 때 DB와 같은 설정들을 코드 변경 없이 제어할 수 있는 방법을 알아봤다.git과 github의 차이를 이해하고 git에 대한 기초적인 사용법을 알아봤다.AWS의 EC2가 무엇인지 이해하고, AWS를 통해 클라우드 컴퓨터를 빌려봤다.섹션 7 : 생애 최초 배포하기EC2에 접속해 리눅스 명령어 다뤄보기다운로드 받은 키 페어를 이용하는 방법접속하려는 EC2의 IP주소다운로드 받았던 키 페어접속하기 위한 프로그램 (git CLI)ssh -i 경로/키페어이름.pem ec2-user@IPEC2컴퓨터가 많아 졌을 경우 간편하게 접근 가능하다.AWS 콘솔을 활용하는 방법AWS 화면에서 인스턴스에 연결에서 연결하면 된다.복잡한 명령어를 작성할 필요가 없다.가장 기본적인 리눅스 명령어mkdir folder1 : folder1 폴더 생성cd folder1 : folder1 폴더로 이동pwd : 현재 경로 출력cd.. : 상위 폴더로 이동ls 또는 ls -l : 현재 위치의 폴더 및 파일 목록 출력rmdir folder1 : folder1 폴더 제거배포를 위한 프로그램 설치하기설치해야 할 프로그램 목록git : 코드를 가져오기 위함java : 서버를 구동mysql : 데이터베이스 역할설치 과정sudo yum update sudo yum install git -y ALTER user'root'@'localhost' IDENTIFIED WITH mysql_native_password BY "패스워드"; 설정 후 profile에서도 변경해주어야 한다.git으로 원격 저장소를 최신화 해주면 완료.빌드와 실행, 그리고 접속종료되지 않는 실행가비아를 이용한 도메인 구입, DNS 적용Section 7 정리.EC2에 접속하는 방법을 알아보고, EC2에 접속해 리눅스 명령어를 다뤄봤다.개발한 서버의 배포를 위해 환경 설정을 리눅스에서 진행하고, 실제 배포를 진행해봤다.foreground와 background의 차이를 이해하고 background 서버를 제어해봤다.도메인 이름을 사용해 사용자가 IP대신 이름으로 접속할 수 있도록 했다.섹션 8 : Spring Boot의 이모저모build.gradle 이해하기build.gradle : 빌드 스크립트라고도 불리며, gradle을 이용해 프로젝트를 빌드하고 의존성을 관리하기 위해 작성되었으며, groovy언어를 사용해 작성되었고, kotlin언어를 사용할 수도 있다.plugins 블락 : 다양한 플러그인을 적용하도록 작성할 수 있다.org.springframework.boot 플러그인 역할스프링을 빌드했을때 실행가능한 jar파일이 나오도록 도와준다.스프링 애플리케이션을 실행할 수 있게 도와준다.다른 플러그인들이 잘 적용되도록 도와준다.io.spring.dependency-management외부 라이브러리, 프레임워크의 버전관리에 도움을 주고 서로 얽혀 있는 의존성을 처리하는데 도와준다.javajava 프로젝트를 개발하는데 필요한 기능들을 추가해주고, 다른 JVM언어 Gradle 플러그인을 사용할 수 있는 기반을 마련한다.group : 프로젝트의 그룹version : 프로젝트의 버전sourceCompatibility : 프로젝트가 사용하고 있는 JDK 버전repositories 블락 : 외부 라이브러리/프레임워크를 가져오는 장소 결정dependencies 블락 : 사용하는 라이브러리/프레임워크를 표시하는 곳tests : 테스트 수행시 사용하는 버전 명시.Spring과 Spring BootSpring과 Spring Boot의 차이점간편한 설정간단한 의존성 관리강력한 확장성MSA에 적합한 모니터링application.yml과 application.properties, lomboklombok : getter, setter, 생성자와 같은 반복되는 보일러 플레이트 코드를 제거할 수 있다.lombok 의존성 추가IntelliJ lombok 플러그인 추가IntelliJ Annotation Processor 설정Spring Boot 2.7.x에서 3.0.x로 업데이트하기Java 최소 버전이 17로 업그레이드 됨.많은 스프링 프로젝트, Thrid-party Library가 버전업 됨.AOT 기초 작업이 이루어짐.javax대신 jakarta 패키지를 사용하게 됨.모니터링 기능들이 강화 됨. 3주차 과제과제 6번 : https://www.inflearn.com/blogs/79934번 과제의 API를 Controller - Service - Repository로 분리하기.FruitRepository를 FruitMemoryRepository와 FruitMySqlRepository로 분리하고, @Primary 어노테이션을 사용해 두 Repository를 교차하며 동작해보기.과제 7번 : https://www.inflearn.com/blogs/80176번 과제의 기능들을 JPA를 이용하여 동작하도록 변경하기.과일 Entity Class를 이용해 특정 과을을 기준으로 과일 개수를 카운트하는 API 만들기판매되지 않은 특정 금액 이상 또는 특정 금액 이하의 과일 목록을 반환하는 API 만들기3주차 후기지난주 2주차 회고록에 이어서 인프런 워밍업 클럽 과정의 3주차가 마무리됬다. 이번 주차에서는 섹션 5의 일부분 부터 섹션 8까지 강의를 수강할 수 있었다. 간단하게 각 섹션 별로 강의의 주제를 다시 한번 상기하고 어떤걸 배우고 느꼈는지 확인해보려고 한다.섹션 5의 주제 : 책 요구사항 구현하기객체지향적으로 설계하기 위한 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 이해했다. 또한 JPA에서 연관관계를 매핑하기 위한 방법을 이해하고, 연관관계를 사용해 개발할 때와 사용하지 않고 개발할 때의 차이점을 이해했다.섹션 6의 주제 : 생애 최초 배포 준비하기배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야하는지 알아 봤으며 스프링 서버를 실행할 때 DB와 같은 설정들을 코드 변경 없이 제어할 수 있는 방법을 알아볼 수 있었다. 또한 git과 github의 차이를 이해하고 git에 대한 기초적인 사용법을 알아보고, AWS의 EC2가 무엇인지 이해하고, AWS를 통해 클라우드 컴퓨터를 빌려볼 수 있었다.섹션 7의 주제 : 생애 최초 배포하기EC2에 접속하는 방법을 알아보고, EC2에 접속해 리눅스 명령어를 다뤄봤다. 개발한 서버의 배포를 위해 환경 설정을 리눅스에서 진행하고, 실제 배포를 진행해볼 수 있었다. foreground와 background의 차이를 이해하고 background 서버를 제어해보고, 도메인 이름을 사용해 사용자가 IP대신 이름으로 접속할 수 있도록 했다.섹션 8의 주제 : Spring Boot의 이모저모그동안 진행해온 Spring Boot의 build.gradle과 application.yml 등 다양한 설정 파일의 내부를 확인해볼 수 있었다.2차 중간 점검2차 중간 점검은 질의 응답보단 코드 리뷰를 진행하는 형식으로 진행됬다. 본인은 아직 미니 프로젝트 1번까지 수행하지 못해 직접 피드백을 받지는 못했지만, 고맙게도 올려주신 분들의 코드의 매커니즘 대체로 깔끔해서 작성한 내 코드와 비교해보며 간접적으로 좋은 피드백을 받을 수 있었다.마무리이번 주차는 대체적으로 개발한 코드를 리팩토링 하고 배포하는 부분이 많았다. 특히, 배포하는 부분을 주요 깊게 봤다.처음으로 AWS 계정을 생성해서 배포하는 과정까지 천천히 진행해봤는데, 확실히 금전적으로 사고가 일어날 수 있는 부분이다 보니 강의 자료 외에도 더 알아보고 신중하게 진행했다.이제 미니 프로젝트만 마무리 지으면 벌써 워밍업 클럽의 수료가 눈앞에 다가온다.다음 주차엔 남은 강의도 얼마 없어서 미니 프로젝트에 신경을 많이 쓰게 될것 같다.
백엔드
・
백엔드
・
인프런
・
워밍업
2024. 05. 16.
0
[인프런 워밍업 클럽 1기] BE 7일차
[인프런 워밍업 클럽 1기] BE 7일차본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQg 코드SQL 문create table fruit ( id bigint auto_increment PRIMARY KEY , name varchar(25), warehousing_date date, price bigint, status varchar(10) CHECK (status IN ('SOLD', 'NOT_SOLD')) );Fruit.java@Entity public class Fruit { @Id private Long id = null; @Column(nullable = false, length = 25) private String name; private LocalDate warehousingDate; private long price; @Column(columnDefinition = "varchar(10) CHECK (status IN ('SOLD', 'NOT_SOLD'))") private String status; protected Fruit() { } public Fruit(String name, LocalDate warehousingDate, long price) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("잘못된 name(%s)이 들어왔습니다, name"); } this.name = name; this.price = price; } public Long getId() { return id; } public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } public String getStatus() { return status; } }FruitServiceV2.java@Service public class FruitServiceV2 { private final FruitRepository fruitRepository; private EntityManager entityManager; public FruitServiceV2(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public SaveFruitRequest saveFruit(SaveFruitRequest request) { fruitRepository.save(new Fruit( request.getName(), request.getWarehousingDate(), request.getPrice(), request.getStatus())); return request; } public GetFruitStatResponse getFruitStat(String name) { List fruit = fruitRepository.findByName(name); if (fruit == null) { throw new IllegalArgumentException("해당 과일이 없습니다."); } Long salesAmount = fruitRepository.sumPriceByNameAndStatus(name, "SOLD"); Long notSalesAmount = fruitRepository.sumPriceByNameAndStatus(name, "NOT_SOLD"); return new GetFruitStatResponse( salesAmount != null ? salesAmount : 0L, notSalesAmount != null ? notSalesAmount : 0L); } public ResponseEntity recordSoldFruit(RecordSoldFruitRequest request) { Fruit fruit = fruitRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); fruit.setStatus("SOLD"); fruitRepository.save(fruit); return ResponseEntity.ok().build(); } }FruitController.java@RestController public class FruitController { private final FruitServiceV2 fruitService; public FruitController(FruitServiceV2 fruitService) { this.fruitService = fruitService; } @PostMapping("/api/v1/fruit") public SaveFruitRequest saveFruit(@RequestBody SaveFruitRequest request) { return fruitService.saveFruit(request); } @PutMapping("/api/v1/fruit") public ResponseEntity recordSoldFruit(@RequestBody RecordSoldFruitRequest request){ return fruitService.recordSoldFruit(request); } @GetMapping("/api/v1/fruit/stat") public GetFruitStatResponse getFruitStat(@RequestParam String name) { return fruitService.getFruitStat(name); } }코드++FruitServiceV2.javapublic CountFruitResponse countFruit(String name) { long count = fruitRepository.countByName(name); return new CountFruitResponse(count); }CountFruitResponse.javapublic class CountFruitResponse { private long count; public CountFruitResponse(long count) { this.count = count; } public long getCount() { return count; } public void setCount(long count) { this.count = count; } }++FruitController@GetMapping("/api/v1/fruit/count") public CountFruitResponse countFruit(@RequestParam String name) { return fruitService.countFruit(name); }++FruitResponsitorylong countByName(String name);결과POST_MAN count코드FruitInfo.javapublic class FruitInfo { private String name; private long price; private LocalDate warehousingDate; public FruitInfo(String name, long price, LocalDate warehousingDate) { this.name = name; this.price = price; this.warehousingDate = warehousingDate; } public String getName() { return name; } public long getPrice() { return price; } public LocalDate getWarehousingDate() { return warehousingDate; } public void setName(String name) { this.name = name; } public void setPrice(long price) { this.price = price; } public void setWarehousingDate(LocalDate warehousingDate) { this.warehousingDate = warehousingDate; } }++FruitController.java@GetMapping("/api/v1/fruit/list") public List getFruitList(@RequestParam String option, @RequestParam long price) { return fruitService.getFruitList(option, price); }++FruitRepository.javaList findByPriceGreaterThanEqual(long price); List findByPriceLessThanEqual(long price);++FruitServiceV2.java public List getFruitList(String option, long price) { List fruits = getFruitsByOption(option, price); return convertToFruitInfoList(fruits); } private List getFruitsByOption(String option, long price) { if ("GTE".equals(option)) { return fruitRepository.findByPriceGreaterThanEqual(price); } else if ("LTE".equals(option)) { return fruitRepository.findByPriceLessThanEqual(price); } else { throw new IllegalArgumentException("GTE와 LTE 중 하나를 입력하세요"); } } private FruitInfo convertToFruitInfo(Fruit fruit) { return new FruitInfo(fruit.getName(), fruit.getPrice(), fruit.getWarehousingDate()); } private List convertToFruitInfoList(List fruits) { return fruits.stream() .map(this::convertToFruitInfo) .collect(Collectors.toList()); }결과POST_MAN LTE 경우POST MAN GTE 경우
백엔드
・
백엔드
・
인프런
・
워밍업
2024. 05. 13.
0
[인프런 워밍업 클럽 1기] BE 6일차
[인프런 워밍업 클럽 1기] BE 6일차본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQgFruitController.java@RestController public class FruitController { private final JdbcTemplate jdbcTemplate; public FruitController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @PostMapping("/api/v1/fruit") public void saveFruit(@RequestBody FruitRequest request) { String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice()); } @PutMapping("/api/v1/fruit") public void updateFruit(@RequestBody SoldRequest request) { String readSql = "SELECT * FROM fruit WHERE id = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String updateSql = "UPDATE fruit SET status = true WHERE id = ?"; jdbcTemplate.update(updateSql, request.getId()); } @GetMapping("/api/v1/fruit/stat") public SalesSumResponse statFruit(@RequestParam String name) { String salesSql = "SELECT sum(price) FROM fruit WHERE status = true GROUP BY name HAVING name = ?"; String notSalesSql = "SELECT sum(price) FROM fruit WHERE status = false GROUP BY name HAVING name = ?"; long salesAmount = jdbcTemplate.queryForObject(salesSql, long.class, name); long notSalesAmount = jdbcTemplate.queryForObject(notSalesSql, long.class, name); return new SalesSumResponse(salesAmount, notSalesAmount); } } Controller-Service-Repository 구조로 리팩토링FruitController.java @RestController public class FruitController { private final FruitService fruitService; @Autowired public FruitController(FruitService fruitService) { this.fruitService = fruitService; } @PostMapping("/api/v1/fruit") public void addFruit(@RequestBody Fruit fruit) { fruitService.addFruit(fruit); } @PutMapping("/api/v1/fruit") public void updateFruit(@RequestBody Fruit fruit) { fruitService.updateFruit(fruit); } @GetMapping("/api/v1/fruit/stat") public FruitSalesData getFruitStats(@RequestParam String name) { return fruitService.getFruitSales(name); } } FruitService.java@Service public class FruitService { private final FruitRepository fruitRepository; @Autowired public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public void addFruit(Fruit fruit){ fruitRepository.save(fruit); } public void updateFruit(Fruit fruit){ fruitRepository.update(fruit); } public FruitSalesData getFruitSales(String name) { return fruitRepository.getFruitSalesData(name); } }FruitRepository@Repository public class FruitRepository { private final JdbcTemplate jdbcTemplate; public FruitRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void save(Fruit fruit) { String sql = "INSERT INTO Fruits(name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, fruit.getName(), fruit.getWarehousingDate(), fruit.getPrice()); } public void update(Fruit fruit) { String sql = "UPDATE Fruits SET status = false WHERE id = ?"; jdbcTemplate.update(sql, fruit.getId()); } public FruitSalesData getFruitSalesData(String name) { String salesAmountSql = "SELECT COALESCE(SUM(price), 0) FROM Fruits WHERE name = ? AND status = true"; Long salesAmount = jdbcTemplate.queryForObject(salesAmountSql, new Object[]{name}, Long.class); String notSalesAmountSql = "SELECT COALESCE(SUM(price), 0) FROM Fruits WHERE name = ? AND status = false"; Long notSalesAmount = jdbcTemplate.queryForObject(notSalesAmountSql, new Object[]{name}, Long.class); return new FruitSalesData(salesAmount, notSalesAmount); } }FruitRepository.javapublic interface FruitRepository { void save(Fruit fruit); void update(Fruit fruit); FruitSalesData getFruitSalesData(String name); }FruitMemoryRepository@Repository public class FruitMemoryRepository implements FruitRepository { private final List fruits; // 가정: 모든 과일 데이터가 이미 메모리에 로드되어 있다고 가정 @Autowired public FruitMySqlRepository(List fruits) { this.fruits = fruits; } @Override public void save(Fruit fruit) { fruits.add(fruit); } @Override public void update(Fruit fruit) { // id로 과일을 찾아 상태를 변경 fruits.stream() .filter(f -> f.getId() == fruit.getId()) .findFirst() .ifPresent(f -> f.setStatus(false)); } @Override public FruitSalesData getFruitSalesData(String name) { // 주어진 이름의 과일에 대한 판매 및 비판매 금액 계산 Long salesAmount = fruits.stream() .filter(f -> f.getName().equals(name) && f.isStatus()) .mapToLong(Fruit::getPrice) .sum(); Long notSalesAmount = fruits.stream() .filter(f -> f.getName().equals(name) && !f.isStatus()) .mapToLong(Fruit::getPrice) .sum(); return new FruitSalesData(salesAmount, notSalesAmount); } }FruitMySqlRepository@Repository @Primary public class FruitMySqlRepository implements FruitRepository { private final JdbcTemplate jdbcTemplate; @Autowired public FruitMySqlRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void save(Fruit fruit) { String sql = "INSERT INTO Fruits(name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, fruit.getName(), fruit.getWarehousingDate(), fruit.getPrice()); } @Override public void update(Fruit fruit) { String sql = "UPDATE Fruits SET status = false WHERE id = ?"; jdbcTemplate.update(sql, fruit.getId()); } @Override public FruitSalesData getFruitSalesData(String name) { String salesAmountSql = "SELECT COALESCE(SUM(price), 0) FROM Fruits WHERE name = ? AND status = true"; Long salesAmount = jdbcTemplate.queryForObject(salesAmountSql, new Object[]{name}, Long.class); String notSalesAmountSql = "SELECT COALESCE(SUM(price), 0) FROM Fruits WHERE name = ? AND status = false"; Long notSalesAmount = jdbcTemplate.queryForObject(notSalesAmountSql, new Object[]{name}, Long.class); return new FruitSalesData(salesAmount, notSalesAmount); } }
백엔드
・
백엔드
・
인프런
・
워밍업
2024. 05. 12.
0
[인프런 워밍업 클럽 1기] BE 2주차 발자국
인프런 워밍업 클럽 2주차 발자국서론벌써 강의를 들은지 2주차가 지나간다. 지난 주차와 마찬가지로 강의와 스터디를 하면서 배운 내용을 잊지 않도록 주마다 블로그에 정리하려고 한다.섹션 3 : 역할의 분리와 스프링 컨테이너UserController와 스프링 컨테이너UserController는 JdbcTemplate에 의존한다.@RestController가 UserController 클래스를 API의 진입지점으로 만들어주고, 스프링 빈으로 등록시킨다.서버 시작 후 동작 과정스프링 컨테이너(클래스 저장소)가 시작된다.기본적으로 많은 스프링 빈들이 등록된다.우리가 설정해준 스프링 빈이 등록된다.필요한 의존성이 자동으로 설정된다.서버 시작 후 세개의 클래스 동작 과정JdbcTemplate을 의존하는 UserRepository가 스프링 빈으로 등록된다.UserRepository를 의존하는 UserService가 스프링 빈으로 등록된다.UserService를 의존하는 UserController가 스프링 빈으로 등록된다.스프링 컨테이너를 사용하는 이유 다음 시간에스프링 컨테이너를 왜 사용할까?스프링 컨테이너를 사용하면 컨테이너가 서비스를 대신 인스턴스화 하고, 알아서 레포지토리를 결정해준다.의존성 주입 (DI, Dependency Injection) : 컨테이너가 선택해 서비스에 넣어주는 과정.제어의 역전 (IoC, Inversion of Control) : 스프링 컨테이너가 어떤 구현 타입을 쓸지 대신 결정해주는 방식.@Primary 를 활용해서 어떤 Repository가 주입될지 조절할 수 있다.스프링 컨테이너를 다루는 방법빈을 등록하는 방법@RestController, @Service, @Repository를 사용하는 방법.@Configuration + @Bean를 사용하는 방법@Configuration : 클래스에 붙이는 어노테이션, @Bean과 함께 사용한다.@Bean : 메소드에 붙이는 어노테이션, 메소드에서 반환되는 객체를 스프링 빈에 등록한다.@Service, @Repository를 사용하는 경우개발자가 직접 만든 클래스를 스프링 빈으로 등록할 때 사용한다.@Configuration + @Bean을 사용하는 경우외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.@Component : 주어진 클래스를 컴포넌트로 간주하여, 이 클래스들은 스프링 서버가 뜰 때 자동으로 감지된다.@Component를 사용하는 경우컨트롤러, 서비스, 리포지토리가 모두 아니고, 개발자가 직접 작성한 클래스를 스프링 빈으로 등록할 때 사용된다.스프링 빈을 가져오는 방법생성자를 이용해 주입받는 방식setter와 @Autowired 사용 누군가 setter를 사용하면 오작동 할 수 있다.필드에 직접 @Autowired 사용 테스트를 어렵게 만드는 요인이다.@Qualifier : 여러 개의 클래스 후보군이 있을때 그 중 특정 클래스를 가져오고 싶은 경우 사용하는 어노테이션스프링 빈을 사용하는 쪽, 등록하는 쪽 모두 @Qualifier를 사용할 수 있다.스프링 빈을 사용하는 쪽에서만 쓰면, 빈의 이름을 적어주어야 한다.양쪽 모두 사용하면, @Qualifier끼리 연결된다.@Primary 와 @Qualifier 중 우선 순위는? 사용하는 쪽에서 직접 적어준 @Qualifier가 우선한다.Section 3 정리좋은 코드가 왜 중요한지 이해하고, 원래 있던 Controller 코드를 보다 좋은 코드로 리팩토링함.스프링 컨테이너와 스프링 빈이 무엇인지 이해함.스프링 컨테이너가 왜 필요한지, 좋은 코드와 어떻게 연관이 있는지 이해함.스프링 빈을 다루는 여러 방법을 이해함.섹션 4 생애 최초 JPA 사용하기문자열 SQL을 직접 사용하는 것이 너무 어렵다SQL을 직접 작성하면 나타나는 문제점문자열을 작성하기 때문에 실수할 수 있고, 실수를 인지하는 시점이 느리다. (컴파일 시점에 발견되지 않고, 런타임 시점에 발견된다.)특정 데이터베이스에 종속적이게 된다.반복 작업이 많아진다. 테이블을 하나 만들 때마다 CRUD 쿼리가 항상 필요하다.데이터베이스의 테이블과 객체는 패러다임이 다르다.JPA (Java Persistence API) : 객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙JPA는 규칙일 뿐이고, 규칙을 실제로 구현한 건 HIBERNATE이며 HIBERNATE 는 내부적으로 JDBC를 사용한다.JPA를 활용해 매핑하는 것 다음 시간에 알아볼 예정.유저 테이블에 대응되는 Entity Class 만들기Java객체와 MySQL 테이블을 매핑.@Entity : 스프링이 User객체와 user테이블을 같은 것으로 바라보게한다.@Id : 이 필드를 primary key로 간주한다.@GeneratedValue : primary key는 자동 생성되는 값이다.JPA를 사용하기 위해서는 기본 생성자가 꼭 필요하다.@Column : 객체의 필드와 테이블의 필드를 매핑한다. null의 여부, 길이 제한, DB의 Column 이름 등을 정의한다. 단 객체의 필드와 테이블의 필드가 동일한 경우 생략할 수 있다.JPA를 사용하기 위한 추가적인 설정application.yml jpa: hibernate: ddl-auto: none // 스프링이 시작할 때 DB에 있는 테이블을 어떻게 처리할지 설정. // create : 기존 테이블이 있다면 삭제 후 다시 생성 // create-drop : 스프링이 종료될 때 테이블을 모두 제거 // update : 객체와 테이블이 다른 부분만 변경 // validate : 객체와 테이블이 동일한지 확인 // none : 별다른 조치를 하지 않는다. properties: hibernate: show_sql: true // JPA를 사용해 DB에 SQL을 날릴 때 SQL을 보여줄지 말지. format_sql: true // SQL을 보여줄 때 이쁘게 포맷팅 할지 말지. dialect: org.hibernate.dialect.MySQL8Dialect // 이 옵션으로 DB를 특정하면 조금씩 다른 SQL을 수정해준다. Spring Data JPA를 이용해 자동으로 쿼리 날리기SQL을 작성하지 않고, 유저 생성 / 조회 / 업데이트 기능을 리팩토링하기.UserRepository 인터페이스 생성UserRepository 인터페이스는 JpaRepository를 상속 받음유저 저장 기능 : save 메소드에 객체를 넣어주면 INSERT SQL이 자동으로 날라가게끔 한다.유저 조회 기능 : findAll을 사용해 모든 데이터를 가져오도록 한다.유저 업데이트 기능 : id를 이용해 User를 가져와 User의 유,무를 확인하고, User가 있으면 update 쿼리를 달려 데이터를 수정한다.사용한 기능save : 주어지는 객체를 저장하거나 업데이트 시켜준다.findAll : 주어지는 객체가 매핑된 테이블의 모든 데이터를 가져온다.findById : id를 기준으로 특정한 1개의 데이터를 가져온다.Spring Data JPA : 복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리Spring Data JPA를 이용해 다양한 쿼리 작성하기삭제 기능을 Spring Data JPA로 변경하기.Spring Data JPA를 활용해 조회 쿼리를 작성하는 방법public interface UserRepository extends JpaRepository { Optional findByName(String name); }반환 타입을 User로, 유저가 없으면 null이 반환되도록 한다.함수 이름만 작성하면, 알아서 SQL이 조립된다.find라고 작성하면, 1개의 데이터만 가져온다.By 뒤에 붙는 필드 이름으로 SELECT 쿼리의 WEHRE문이 작성된다.SELECT * FROM user WHERE name = ?;public void deleteUser(String name) { User user = userRepository.findByName(name) .orElseThrow(IllegalArgumentException::new); userRepository.delete(user); }주어지는 데이터를 DB에서 제거한다.DELETE SQL;다양한 Spring Data JPA 쿼리By 앞에 들어갈 수 있는 구절find : 1건을 가져온다. 반환 타입은 객체 또는 Optional이 될 수 있다.findAll : 쿼리의 결과물이 N개인 경우 사용한다. List 을 반환한다.exists : 쿼리 결과가 존재하는지 확인한다. Boolean 타입으로 반환한다.count : SQL의 결과 개수를 센다. Long타입으로 반환한다.By 뒤에 들어갈 수 있는 구절 (AND, OR로 조합 가능)GreaterThan : 초과GreaterThanEqual : 이상LessThan : 미만LessThanEqual : 이하Between : 사이에StartsWith : ~로 시작하는EndsWith : ~로 끝나는트랜잭션에 대해 다음 시간에 알아볼 예정.트랜잭션 이론편트랜잭션 : 쪼갤 수 없는 업무의 최소 단위 모두 다 성공시키거나 모두 다 실패시키거나 하기 위한 것.트랜잭션 시작 : start transaction;트랜잭션 종료 : commit;트랜잭션 실패 처리 : rollback;트랜잭션을 통해 하나의 함수에서 여러 개의 Insert 쿼리 중 모두 성공하면 반영하고, 하나라도 실패하면 반영이 안되도록 할 수 있다.트랜잭션 적용과 영속성 컨테스트서비스 메소드가 시작할 때 트랜잭션이 시작된다.서비스 메소드 로직이 모두 정상적으로 성공하면 commit이 된다.서비스 메소드 로직 실행 도중 문제가 생기면 rollback 되도록 한다.@Transactional 으로 간단하게 적용 가능하다.조회만 하는 SQL의 경우 @Transactional(readOnly = true) 로 읽기만 가능하도록 할 수 있다.IOException과 같은 Checked Exception은 롤백이 일어나지 않는다.영속성 컨텍스트 : 테이블과 매핑된 Entity 객체를 관리/보관하는 역할스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.영속성 컨텍스트의 특수 능력 4가지변경 감지 (Dirty Check) : 영속성 컨텍스트 안에서 불러와진 Entity는 명시적으로 save하지 않더라도, 변경을 감지해 자동으로 저장된다.쓰기 지연 : DB의 INSERT / UPDATE / DELETE SQL을 바로 날리는 것이 아니라, 트랜잭션이 commit될 때 모아서 한 번만 날린다.1차 캐싱 : ID를 기준으로 Entity를 기억하고, 캐싱된 객체는 완전히 동일하다.Section 4 정리문자열 SQL을 직접 사용하는 것의 한계를 이해하고, 해결책인 JPA, Hibernate, Spring Data JPA가 무엇인지 이해한다.Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제할 수 있다.트랜잭션이 왜 필요한지 이해하고, 스프링에서 트랜잭션을 제어하는 방법을 익힌다.영속성 컨텍스트와 트랜잭션의 관계를 이해하고, 영속성 컨텍스트의 특징을 알아본다.섹션 5 책 요구사항 구현하기책 생성 API 개발하기요구사항 : 도서관에 책을 등록할 수 있다.API 스펙HTTP Method : POSTHTTP Path : /bookHTTP Body (JSON) :{ "name": String //책 이름 }결과 반환 : 상태코드 200대출 기능 개발하기요구사항 : 사용자가 책을 빌릴 수 있다. 단 다른 사람이 그 책을 빌렸다면, 빌릴 수 없다.API 스펙HTTP Method : POSTHTTP Path : /book/loanHTTP Body (JSON) :{ "userName": String "bookName": String }결과 반환 : 상태코드 200반납 기능 개발하기요구사항 : 사용자가 책을 반납할 수 있다.API 스펙HTTP Method : POSTHTTP Path : /book/returnHTTP Body (JSON) :{ "userName": String "bookName": String }결과 반환 : 상태코드 200지금 까지 작성한 코드를 조금 더 객체지향적으로 만드는 방법User와 UserLoanHistory가 직접 협업할 수 있게 처리하는 방법 다음 시간에 알아볼 예정.Section 5 정리책 생성, 대출 반납 API를 온전히 개발하며 지금까지 다루었던 모든 개념을 실습해 봤다.2주차 과제과제 4번 : https://www.inflearn.com/blogs/7855과일 정보를 저장하는 API 만들기API에서 long을 사용한 이유팔린 과일 정보를 기록하는 API 만들기특정 과일을 기준으로 팔린 금액, 팔리지 않은 금액을 조회하는 API 만들기sum, group by 키워드를 사용해 구현해보기과제 5번 : https://www.inflearn.com/blogs/7873제시된 주사위 코드를 클린 코드로 변환해보기숫자 범위가 달라지더라도 동작되도록 변환해보기2주차 후기지난주 1주차 회고록에 이어서 벌써, 인프런 워밍업 클럽 과정의 2주차가 마무리됬다. 이번 주차에서는 섹션 3의 일부분 부터 섹션 5의 일부분의 강의를 수강할 수 있었다. 간단하게 각 섹션 별로 강의의 주제를 다시한번 상기하고 어떤걸 배우고 느꼈는지 확인해보려고 한다.섹션 3의 주제 : 역할의 분리와 스프링 컨테이너역할의 분리에 대해서는 이전 회고록에서 상세히 다루었기 때문에 다루지 않겠다.스프링 컨테이너와 스프링 빈에 무엇인지 이해하고 어떻게 동작하는지 구조를 파악할 수 있었으며, 스프링 컨테이너를 사용하는 것이 좋은 코드와 어떻게 연관이 있는지 이해하고 스프링 빈을 다루는 여러 방법을 배울 수 있었다.섹션 4의 주제 : 생애 최초 JPA 사용하기문자열 SQL을 직접 사용하는 것의 한계를 이해하고, 해결책인 JPA, Hibernate, Spring Data JPA가 무엇인지 이해할 수 있었다. Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제해 볼 수 있었다. 트랜잭션이 왜 필요한지 이해하고, 스프링에서 트랜잭션을 제어하는 방법을 익혔다. 영속성 컨텍스트와 트랜잭션의 관계와 특징을 이해할 수 있었다.섹션 5의 주제 : 책 요구사항 구현하기책 생성, 대출 반납 API를 온전히 개발하며 지금까지 다루었던 모든 개념을 실습해 볼 수 있었다.깜짝 라이브 강의5번 과제인 클린 코드로 변환하는 방법에 대해 깜짝 라이브 강의를 열어 자세한 코드 리뷰를 진행했다. 코치님이 직접 코드를 리팩토링 하는 과정을 지켜보고 따라해보며 크게 느낀점이 있었는데, 내가 항상 클린 코드를 만든다고 만들지만 그 끝은 끝이 없구나 하고 생각이 들었다.일단 코치님이 진행한 수준까지 라도 충분히 이해하고 적용하는 것까지 진행하는 것이 우선적이라 생각이 들었다.그 외에도 코치님과 비슷하게 리팩토링을 수행한 수강생에 대해 알려주셨는데, 분명히 다른 사람 코드를 살펴보고 이해하는 것이 중요하다고 생각했고, 다른 수강생의 코드를 보고 워밍업 클럽 커뮤니티에서 서로 물어볼 수 있다는 부분에 있어 그렇게 학습하는 것이 강의 취지에 맞게 학습하는 것이라 생각했다.마무리이번 2주차 강의를 듣고 회고록을 작성하면서 앞으로도 지금처럼 성실함을 중점으로 꾸준하게 강의를 듣고 기록하는 것이 중요하다고 생각했다. 다음주는 벌써 배포에 대해 배우는 시점이 온다. 개인적으로 제일 관심이 있던 부분이기 때문에 더 확실하게 이해하기 위해 주말에 미리 선행을 진행 해보려고 한다.
백엔드
・
백엔드
・
인프런
・
워밍업
2024. 05. 08.
0
[인프런 워밍업 클럽 1기] BE 5일차
[인프런 워밍업 클럽 1기] BE 5일차본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQg문제 1 풀이 과정, 한 걸음 더사용자로부터 입력을 받는 부분, 주사위를 던지고 계산하는 부분, 결과를 출력하는 부분으로 메소드를 분리하여 구현했다.숫자 범위가 달라지더라도 동작하도록 코드를 수정했다.DiceRoller.javapackage com.group.libraryapp.controller.assignment3; import java.util.Scanner; public class DiceRoller { public static void main(String[] args) { int numOfFaces = getNumOfFaces(); // 주사위 면의 수를 입력 받음 int[] faceCounts = rollDice(numOfFaces); // 주사위를 던져 각 숫자의 출현 횟수를 계산 printResult(faceCounts); // 결과 출력 } // 사용자로부터 주사위 면의 수를 입력받는 메소드 private static int getNumOfFaces() { System.out.println("주사위 면의 수를 입력하세요:"); Scanner scanner = new Scanner(System.in); return scanner.nextInt(); } // 주사위를 던져 각 숫자의 출현 횟수를 계산하는 메소드 private static int[] rollDice(int numOfFaces) { int[] faceCounts = new int[numOfFaces]; for (int i = 0; i 결과 - 6 입력결과 - 20 입력
백엔드
・
백엔드
・
인프런
・
워밍업
2024. 05. 07.
0
[인프런 워밍업 클럽 1기] BE 4일차
[인프런 워밍업 클럽 1기] BE 4일차본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQg문제 1구현 과정구현 목표 : 과일 정보를 입력해 요청하면 상태코드 반환하여 응답함.메서드 타입 : POST경로 : /api/v1/fruitHTTP 요청 Body : { "name" : String, "warehousingDate" : LocalDate, "price" : long } SQL 쿼리문create table fruit ( id bigint auto_increment, name varchar(30), warehousingDate date, price bigint, status boolean default false, primary key (id) );FruitController.javapackage com.group.libraryapp.controller.assingment2.fruit; import com.group.libraryapp.dto.assignment2.request.FruitRequest; import com.group.libraryapp.dto.assignment2.request.SoldRequest; import com.group.libraryapp.dto.assignment2.response.SalesSumResponse; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.*; @RestController public class FruitController { private final JdbcTemplate jdbcTemplate; public FruitController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @PostMapping("/api/v1/fruit") public void saveFruit(@RequestBody FruitRequest request) { String sql = "INSERT INTO fruit (name, warehousingDate, price) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice()); }FruitRequest.javapackage com.group.libraryapp.dto.assignment2.request; import java.time.LocalDate; public class FruitRequest { private long id; private String name; private LocalDate warehousingDate; private long price; public long getId() { return id; } public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } }한 걸음 더 API에서 long을 사용한 이유null을 사용할 수 있다.int를 사용할 경우 기본값이 0이여서 값이 없어서 0으로 초기화 된건지, 실제 값이 0인지 데이터만 보고 판별하기 어려운 문제점이 있다.반면 Long을 사용할 경우 값이 없으면 null로 초기화되고, 실제 값이 0이면 0으로 저장되기 때문에 값의 유무를 쉽게 판별할 수 있다는 점에서 Long을 주로 사용한다.int보다 더 넓은 범위의 값을 제공한다.정수형 타입 : 데이터의 표현 범위Int : -2,147,483,648 ~ 2,147,483,647Long : -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807대규모 데이터를 처리하는 경우 데이터의 범위가 중요한데, long 자료형은 int 자료형 보다 월등히 더 넓은 범위의 값을 제공한다.문제 2구현 과정구현 목표 : id를 입력해 요청하면 상태코드 반환하여 응답함.메서드 타입 : PUT경로 : /api/v1/fruitHTTP 요청 Body : { "id" : long }SoldRequest.javapackage com.group.libraryapp.dto.assignment2.request; public class SoldRequest { private long id; public long getId() { return id; } }++FruitController.java ... @PutMapping("/api/v1/fruit") public void updateFruit(@RequestBody SoldRequest request) { String readSql = "SELECT * FROM fruit WHERE id = ?"; boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); if (isFruitNotExist) { throw new IllegalArgumentException(); } String updateSql = "UPDATE fruit SET is_sold = true WHERE id = ?"; jdbcTemplate.update(updateSql, request.getId()); } ...문제 3구현 과정구현 목표 : 과일 정보를 입력해 요청하면 팔린 금액, 팔리지 않은 금액을 반환하여 응답함.메서드 타입 : GET경로 : /api/v1/fruit/statHTTP 요청 Body : { "salesAmount" : long, "notSalesAmount" : long }SalesSumResponse.javapackage com.group.libraryapp.dto.assignment2.response; public class SalesSumResponse { private long salesAmount; private long notSalesAmount; public SalesSumResponse(long salesAmount, long notSalesAmount) { this.salesAmount = salesAmount; this.notSalesAmount = notSalesAmount; } public long getSalesAmount() { return salesAmount; } public long getNotSalesAmount() { return notSalesAmount; } }++FruitController.java@GetMapping("/api/v1/fruit/stat") public SalesSumResponse getAmountInfo(@RequestParam String name) { String sql = "SELECT * FROM fruit WHERE name = ?"; List list = jdbcTemplate.query(sql, (rs, rowNum) -> new SumResponse(rs.getLong("price"), rs.getBoolean("status")), name); long salesAmount = list.stream() .filter(SumResponse::status) .mapToLong(SumResponse::getPrice) .sum(); long notSalesAmount = list.stream() .filter(res -> !res.status()) .mapToLong(SumResponse::getPrice) .sum(); return new SalesSumResponse(salesAmount, notSalesAmount); }한 걸음 더sum, group by 키워드를 사용해 구현하기++FruitController.java... @GetMapping("/api/v1/fruit/stat") public SalesSumResponse statFruit(@RequestParam String name) { String salesSql = "SELECT sum(price) FROM fruit WHERE is_sold = true GROUP BY name HAVING name = ?"; String notSalesSql = "SELECT sum(price) FROM fruit WHERE is_sold = false GROUP BY name HAVING name = ?"; long salesAmount = jdbcTemplate.queryForObject(salesSql, long.class, name); long notSalesAmount = jdbcTemplate.queryForObject(notSalesSql, long.class, name); return new SalesSumResponse(salesAmount, notSalesAmount); } ...JPA Entity클래스에서 id를 int가 아닌 Long 타입으로 하는 이유 (tistory.com)
백엔드
・
백엔드
・
인프런
・
워밍업
2024. 05. 05.
0
[인프런 워밍업 클럽 1기] BE 1주차 발자국
인프런 워밍업 클럽 1주차 발자국서론IT 취준생 톡방에서 우연히 워밍업 클럽에 대해 정보를 얻을 수 있었고, 내가 들을만한 좋은 강의가 있을까 찾아보니 마침 최근에 포트폴리오 때매 관심 있었던 스프링으로 직접 서버를 배포하는 방법이 강의 주제로 있어서 신청하게 되었다.무엇보다 혼자 강의를 듣는게 아닌 디스코드 커뮤니티에서 서로 물어보며 강의를 같이 듣는다는 것이 흥미 있었고 모든 과정을 수강 완료하면 인프런 포인트로 5만이나 환급된다는 것이 마음에 들었다.앞으로 강의와 스터디를 하면서 배운 내용을 잊지 않도록 주마다 개인 블로그와 인프런 블로그에 정리하려고 한다.섹션 0 : 소개와 준비, 수업자료강의 소개 영상강의 제목 : [All-In-One] 자바와 스프링 부트로 생애 최초 서버 만들기 누구나 쉽게 개발부터 배포까지!!강의 소개 중 서버 개발자, 백엔드 개발자가 되기 위해선 다방면에서 부족함이 없어야 한다는 점에 공감했다.강의에서 다루는 내용은 다음과 같다고 한다. Java를 기반으로 Linux, Spring boot, mysql, gradle, JPA, Github, aws 등...그 외 서버 개발에 필요한 전반적인 이론 / 개념 설명도 같이 진행하는 것 같다.강의 자료(완성된 코드)강의 자료는 간단하게 강의에서 사용하는 초기 Project 파일과 PPT, PDF 파일로 구성되어 있었다.환경 설정하기강의를 시작하기에 앞서 Java, IntelliJ, PostMan, MySQL, git 설치를 진행했다.다른 환경은 이전에 경험해본적이 있지만, PostMan 은 처음 경험해보는 것이라 이렇게 좋은 도구도 있구나 생각했다.스프링 프로젝트를 시작하는 첫 번째 방법프로젝트의 초기 셋팅 과정을 진행했다.이전에 인프런에서 강의자료를 다운로드 받을 수 있었고 자료 내부에 있는 초기 Project 파일을 열어 실행시켜보니 터미널에 스프링 부트가 실행되는 것을 확인할 수 있었다.섹션 1 : 생애 최초 API 만들기Java를 공부하기 전에 알아두면 좋을 것들 #1 (JVM, JDK)강의를 시작하기 전에 JVM과 JDK에 대해 간단하게 확인했다.정리 1.JAVA라는 언어를 컴퓨터가 알아먹기 까지 과정컴파일 : 인간이 이해하기 쉬운 언어를 기게어로 번역하는 과정컴파일러 : 컴파일을 하는 프로그램바이트 코드 : 0과 1로 이루어진 코드, 컴퓨터가 이해할 수 있다.정리 2.OS마다 0과 1의 조합이 다르다.원래, OS 마다 다른 컴파일러가 필요하는게 맞다.그러나 JAVA는 JVM이 0과 1을 OS에 맞게 번역해준다.JVM은 JAVA 외에 다른 언어에서도 사용된다.정리 3.JVM JDK를 설치하면 JRE, JVM도 같이 설치됨, 즉 JAVA의 버전 = JDK의 버전JVM : 자바 가상 머신 (Java Virtual Machine) 의 약어OS 별로 존재한다.바이너리 코드를 읽고 검증하고 실행한다.JRE : 자바 실행 환경 (Java Runtime Environment) 의 약어JRE = JVM + 자바 프로그램 실행에 필요한 라이브러리 파일 등JVM의 실행 환경을 구현한다.JDK : 자바 개발 도구 (Java Development Kit) 의 약어JDK = JRE + 개발을 위한 도구컴파일러, 디버그 도구 등이 포함된다.정리 4.JDK는 버전이 존재하고, 각 버전별로 새로운 기능이 추가되거나 기존 기능이 사라짐.JDK는 종류가 존재하고, 기능 자체는 모두 동일하지만 성능과 비용에 약간의 차이가 존재함.LTS(Long Time Support) 버전 : 오래 써도 좋도록 지원하는 버전JDK의 종류Oracle JDK : 오라클에서 만든 JDK, 개인 무료, 기업 유료Open JDK : Oracle JDK와 비슷한 성능, 언제나 무료Java를 공부하기 전에 알아두면 좋을 것듯 #2 (빌드, 빌드툴)앞서 알아본 내용에 더해 빌드와 빌드툴에 대해 간단하게 확인했다.정리 1.빌드 : 소스 코드 파일을 컴퓨터에서 실행할 수 있는 독립 SW 가공물로 변환시키는 과정빌드 과정 세분화소스 코드를 컴파일테스트 코드를 컴파일테스트 코드를 실행테스트 코드 리포트를 작성기타 추가 설정한 작업들을 진행패키징을 수행최종 SW 결과물을 만들어냄실행 : 내가 작성한 코드나 테스트 코드를 컴파일을 거쳐, 작동시켜 보는 것 독립적인 SW 가공물이 있을 수도 없을 수도 있다.빌드 툴 : 소스코드이 빌드 과정을 자동으로 처리 해주는 프로그램으로 외부 소스 코드 (외부 라이브러리) 등을 자동 추가, 관리해준다.현재는 주로 maven, gradle 2가지가 많이 쓰인다.빌드 툴 종류Ant설정을 위해 xml 사용간단하고 사용하기 쉬움복잡한 처리를 하려 하면 빌드 스크립트가 장황해져 관리가 어려움외부 라이브러리를 관리하는 구조가 없음Maven설정을 위해 xml 사용외부 라이브러리 관리 가능장황한 빌드 스크립트 문제 해결특정 경우에 xml이 복잡해짐xml 자체의 한계가 존재함Gradle설정을 위해 groovy 언어 사용외부 라이브러리 관리 가능유연하게 빌드 스크립트를 작성할 수 있음성능이 뛰어남스프링 프로젝트를 시작하는 두 번째 방법스프링 프로젝트를 설정해 시작하고 실행하는 과정을 진행start.spring.io 사이트에서 스프링 프로젝트의 초기 설정을 비교적 간단하게 진행할 수 있다.또한 2024년 기준 11버전 선택이 존재하지 않아 자료로 주어진 초기 프로젝트 파일로 진행하려고 한다.라이브러리 : 프로그래밍을 개발할 때 미리 만들어져 있는 기능을 가져다 사용하는 것.프레임워크 : 프로그래밍을 개발할 때 미리 만들어져 있는 구조에 코드를 가져다 끼워 넣는 것.@SpringBootApplication과 서버어노테이션 : 자바에서 어노테이션(Annotation)이란 소스 코드에 메타데이터를 추가하는 방법을 제공하는 기능이다. 서버 : 어떠한 기능을 제공하는 프로그램, 그 프로그램을 실행시키고 있는 컴퓨터서버가 정해진 기능을 동작하려면 서버에게 요청을 해야한다.네트워크란 무엇인가?컴퓨터별 고유주소 IP와 인터넷을 통해 데이터를 주고 받을 수 있다.데이터를 받는 컴퓨터는 IP 244.66.51.9, port:3000과 같은 주소로 받음.IP 주소를 외우기 어려워 외우기 쉽게 이름으로 변경한 DNS (Domain Name System) 이 나타남.HTTP와 API란 무엇인가?HTTP : HyperText Transfer Protocol의 약어로서 데이터를 주고 받는 표준.HTTP 메소드의 종류GET : 데이터 제공 요청, 쿼리 사용POST : 데이터 저장 요청, 바디 사용PUT : 데이터 수정 요청, 바디 사용DELETE : 데이터 삭제 요청, 쿼리 사용요청 메소드에 따라 쿼리를 사용할지 바디를 사용할지 다르다.API : Application Programming Interface의 약어로서 정해진 약속을 하여, 특정 기능을 수행하는 것.HTTP 요청 문법첫째줄 (메소드 패스 쿼리)헤더 (여러 줄 가능)한줄 공백바디 (여러 줄 가능)URL : Uniform Resource Locator의 약어로서 사용하고 있는 프로토콜 + 구분기호 + 도메인 이름:포트 + 자원 경로 + 구분기호 + 쿼리 로 구성되어 있다.HTTP 응답 문법첫째줄 (상태코드)헤더 (여러 줄 가능)한줄 공백바디 (여러 줄 가능)정리 1.웹을 통한 컴퓨터 간의 통신은 HTTP라는 표준화된 방식으로 동작함.HTTP 요청은 HTTP 메소드와 Path 등으로 이루어짐요청에서 데이터를 전달하기 위한 2가지 방법은 쿼리와 바디HTTP 응답은 상태 코드가 핵심이다.클라이언트와 서버는 HTTP를 주고 받으며 기능을 동작하며 이때 정해진 규칙을 API라고 함.GET API 개발하고 테스트하기덧셈 API 구현HTTP Method : GETHTTP Path : /add쿼리(key, value) : int number1 / int number2API 반환 결과 : 숫자 - 두 숫자의 덧셈 결과@RestController : 주어진 Class를 Controller로 등록@RequestParam : 주어지는 쿼리를 함수 파라미터에 넣음.POST API 개발하고 테스트하기곱셈 API 구현HTTP Method : POSTHTTP Path : /multiplyHTTP Body(Json) : { "number1":숫자, "number2":숫자 }API 반환 결과 : 숫자(곱셈 결과)@RequestBody : HTTP Body로 들어오는 JSON을 CalculatorMultiplyRequest로 바꿈.유저 생성 API 개발사용자 등록 API 구현HTTP Method : POSTHTTP Path : /userHTTP Body(Json) : { "name":String (null 금지), "age":Integer }API 반환 결과 : 상태코드 200유저 조회 API 개발과 테스트사용자 조회 API 구현 - HTTP Method : GET - HTTP Path : /user - 쿼리 : 없음 - API 반환 결과 : [{ "id":Long,"name":String(null 금지),"age":Integer }]Controller에서 getter가 있는 객체를 반환하면 JSON이 된다. Section1 정리.스프링 프로젝트를 시작하는 방법과 실행하는 방법네트워크, IP, 도메인, 포트, HTTP 요청과 응답 구조, 클라이언트 - 서버 구조, API와 같은 기반 지식Spring Boot를 이용해 GET API와 POST API를 만드는 방법섹션 2. 생에 최초 Database 조작하기Database와 MySQLRDB : Relational Database의 약어로서 데이터를 표처럼 구조화 시켜 저장하는 도구SQL : Structured Query Language의 약어로서 표처럼 구조화된 데이터를 조회하는 언어MySQL에서 테이블 만들기폴더 = 데이터베이스엑셀 파일 = 테이블엑셀 파일의 헤더 = 테이블의 필드 정의데이터베이스를 만들고 이동해서, 테이블을 만든다. 이때 테이블 이름, 타입, 부가조건을 지정한다.데이터베이스 생성 : create database [데이터베이스 이름];테이터베이스로 이동 : use [데이터베이스 이름];테이블 생성 : create table [테이블 이름] ([필드1 이름] [타입] [부가조건], [필드2 이름] [타입] [부가조건]... primary key([필드이름]));테이블 제거 : drop table [테이블 이름];DDL(Data Definition Language) : 데이터 정의어테이블의 데이터를 조작하기데이터 넣기 = 생성, Create데이터 조회 = 읽기, Retrieve or Read데이터 수정 = 업데이트, Update데이터 삭제 = 제거, Delete데이터 넣기 : insert into [테이블 이름] (필드1 이름, 필드2 이름,...) values(값1, 값2,...) / auto_increment : 값이 자동 증가데이터 조회 : select * from [테이블 이름] where [조건];데이터 수정 : update [테이블 이름] set 필드1 이름 = 값, 필드2 이름 = 값, ... where [조건];데이터 삭제 : delete from [테이블 이름] where [조건]; / 조건을 붙이지 않을시 모든 데이터가 삭제된다.DML(Data Manipulation Language) : 데이터 조작어Spring에서 Database 사용하기Spring 서버가 MySQL DB에 접근하게 하기.application.yml 설정spring: datasource: url: "jdbc:mysql://localhost/library" // jdbc:mysql:// -jdbc 로 mysql에 접근 username: "[계정 명]" password: "[계정 비밀번호]" driver-class-name: com.mysql.cj.jdbc.Driver // 데이터베이스에 접근하기 위한 프로그램 POST API 변경private final JdbcTemplate jdbcTemplate; // jdbcTemplate를 이용해 SQL을 날리는 형태로 변경. public UserController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; // 생성자를 만들어 jdbcTemplate을 파라미터로 넣으면, 자동으로 들어옴. } @PostMapping("/user") // POST /user public void saveUser(@RequestBody UserCreatedRequest request) { String sql = "INSERT INTO user (name, age) VALUES (?, ?)"; // SQL을 만들어 문자열 변수로 저장함. jdbcTemplate.update(sql, request.getName(), request.getAge()); // INSERT, UPDATE, DELETE 쿼리에 사용되는 update 함수 정의 } // 첫 파라미터로 sql을 받고, ?를 대산할 값을 차례로 넣으면 됨. GET API 변경@GetMapping("/user") public List getUsers() { String sql = "SELECT * FROM user"; return jdbcTemplate.query(sql, new RowMapper() { @Override public UserResponse mapRow(ResultSet rs, int rowNum) throws SQLException { long id = rs.getLong("id"); String name = rs.getString("name"); int age = rs.getInt("age"); return new UserResponse(id, name, age); // UserResponse에 생성자 추가 } }); } // jdbcTemplate.query(sql, RowMapper 구현 익명 클래스) 유저 업데이트 API, 삭제 API 개발과 테스트사용자 수정 API 구현 HTTP Method : PUTHTTP Path : /userHTTP Body(Json) : { "id":Long, "name":String }API 반환 결과 : 상태코드 200 사용자 삭제 API 구현HTTP Method : DELETEHTTP Path : /user쿼리(key, value) : 문자열 nameAPI 반환 결과 : 상태코드 200 만약 업데이트나 삭제를 할때 존재하지 않는 유저가 있다면, 예외를 던져서 처리해줘야 할 것이다.유저 업데이트 API, 삭제 API 예외 처리 하기없는 유저를 업데이트 하거나 삭제하려 하면, API에서 예외를 던져서 처리할 수 있다.예외를 처리하는 API는 실제 데이터에서 데이터 존재 여부를 확인해서 처리한다.String readSql = "SELECT * FROM user WHERE id = ?"; // id를 기준으로 유저가 존재하는지 확인 boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); // SELECT의 결과가 있으면 0으로 변환되며 결론적으로 id가 있으면 0이 있는 리스트 반환, id가 없으면 아무겂도 없는 리스트 반환. if (isUserNotExist) { // 유저가 존재하지 않으면 예외를 던진다. throw new IllegalArgumentException(); }[업데이트, 삭제 테스트 포스트맨 획인해보기]Section2 정리디스크와 메모리 차이를 이해하고, Database의 필요성을 이해한다.SQL을 이용해 MySQL Database를 조작할 수 있다.스프링 서버를 이용해 Database에 접근하고 데이터를 저장, 조회, 업데이트, 삭제할 수 있다.API의 예외 상황을 알아보고 예외를 처리할 수 있다.하나의 Controller 클래스에 너무 많은 역할을 수행 중이며 해결하기 위한 방법을 알아볼 예정.섹션 3. 역할의 분리와 스프링 컨테이너좋은 코드(Clean Code)는 왜 중요한가?코드는 요구사항을 표현하는 언어이고 개발자는 요구사항을 구현하기 위해 코드를 읽고 작성한다.좋지 않은 코드를 작성하며 시간이 쌓이면, 시간이 지날 수록 유지보수가 힘들어 생산성이 낮아진다.코드만 보고도 의미를 파악할 수 있게끔 작성하는 것이 중요하다.함수는 최대한 작게 만들고 한 가지 일만 수행하도록 작성하는 것이 중요하다.현재 우리가 작성한 Controller의 동작 과정[1] API의 진입 지점으로써 HTTP Body를 객체로 변환한다.[2] 현재 유저가 있는지 없는지 확인하고 예외 처리를 진행한다.[3] SQL을 사용해 실제 Database와의 통신을 담당한다.Controller 를 각 과정에 맞게 3단 분리를 진행할 예정.Controller를 3단 분리하기 - Service와 Repository현재 Controller 함수의 역할API의 진입 지점으로써 HTTP Body를 객체로 변환한다. = Controller 역할현재 유저가 있는지 없는지 확인하고 예외 처리를 진행한다. = Service의 역할SQL을 사용해 실제 Database와의 통신을 담당한다. = Repository의 역할Repository에서 바로 jdbcTemplate을 가져올 수는 없는지 알아볼 예정.1주차 과제과제 1번 : https://www.inflearn.com/blogs/7658어노테이션을 사용하는 이유 (효과) 는 무엇일까?나만의 어노테이션은 어떻게 만들 수 있을까?과제 2번 : https://www.inflearn.com/blogs/7692다양한 GET API, POST API 만들기과제 3번 : https://www.inflearn.com/blogs/7716자바의 람다식은 왜 등장했을까?람다식과 익명 클래스는 어떤 관계가 있을까?람다식의 문법은 어떻게 될까?1주차 후기지난주 사전 OT 부터 시작해서 벌써, 인프런 워밍업 클럽 과정의 1주차가 마무리됬다.이번 주차에서는 섹션 0 부터 섹션 3의 일부분의 강의를 수강할 수 있었고, 1차 중간점검 시간을 가질 수 있었다.간단하게 각 섹션 별로 강의의 주제를 다시한번 상기하고 어떤걸 배우고 느꼈는지 확인해보려고 한다.섹션 0의 주제 : 소개와 준비, 수업 자료강의 소개를 들으면서 서버 개발자, 백엔드 개발자가 되기 위해선 다방면에서 부족함이 없어야 한다는 점에 예상치 못하게 공감을 받을 수 있었다.강의 자료 또한 섹션별로 적절하게 준비되어 사전에 어떻게 공부를 진행하고 회고록을 작성해야 할지 가이드 라인이 되기도 했다.환경 설정을 진행하면서 PostMan라는 새로운 도구에 대해 알게 되었고, 오랜만에 다루어본 MySQL도 꺼내볼 수 있었다.섹션 1의 주제 : 생애 최초 API 만들기자바로 프로젝트를 진행하기 전에 JVM과 JDK에 대해 알아보고, 빌드의 개념과 빌드 툴의 종류에 대한 내용을 정리하면서 다시 한번 개념적인 부분을 정립할 수 있었다.간단하게 스프링 프로젝트를 시작하고 실행해보았고, 강의 중간에 네트워크, IP, 도메인, 포트, HTTP 요청과 응답 구조, 클라이언트 - 서버 구조, API와 같은 기반 지식에 대해 설명을 들을 수 있었다.강의 주제에 맞게 Spring Boot를 이용해 기본적인 GET API와 POST API를 만들어보고 어떤 원리로 동작하는지 알아볼 수 있었다.섹션 2의 주제 : 생애 최초 Database 조작하기이전 섹션에서 수행한 과정의 문제점인 서버 재실행시 데이터가 초기화 되는 부분에 관련하여 컴퓨터의 디스크와 메모리 차이를 이해하면서 Database에 저장하는 것의 필요성을 이해할 수 있었다.IntelliJ와 MySQL을 연결하는 방법을 배웠고, 생애 최초로 조작하는건 아니지만 SQL을 이용해 MySQL Database를 조작하는 기본적인 방법에 대해 다시 상기시킬 수 있었다.기존에 만들어둔 스프링 서버의 API를 변경하여 Database에 접근하고 데이터를 저장, 조회, 업데이트, 삭제하는 방법에 대해 배웠고, API의 예외 상황에 대해 예외를 처리하는 과정을 학습할 수 있었다.섹션 3의 주제 : 역할의 분리와 스프링 컨테이너하나의 컨트롤러 함수에 수많은 기능이 몰려있으면 생기는 문제점에 대해 인식하면서, 좋은 코드가 왜 중요한지 이해할 수 있었고, 이전 섹션에서 작성한 Controller 코드를 직접 Controller-Service-Repository로 분리하면서 어떻게 하면 좋은 코드로 리팩토링 할 수 있을지 고민하는 과정을 겪었다.1차 중간점검커뮤니티로 사전에 코치님에게 다양한 질문을 올려 답변을 해주는 실시간 Q&A를 진행했다. 다양한 분야에서 종사하는 강의생분들이 질문을 올려주셨고, 솔직히 말하면 내가 겪은 질문이여서 공감이 되기도 했고, 내가 미래에 겪을 질문이라서 과연 어떻게 답변해주실까 궁금하기도 했다.강의 내용에 관한 현업에서의 추가적인 질문도 있었지만, 대부분 진로나 취업의 과정에서 현업자의 시각에 대해 조언을 요청하는 질문이 많았다. Q&A 시작전에는 마땅한 질문이 생각나지 않았는데, 코치님의 답변을 듣고 몰입이 되면서 궁금한 부분이 생겨나기도 했다.강의 막바지에 내가 물어본 질문은"깃허브 블로그에 개인 포트폴리오 부분을 추가해서 기업 지원할때 링크를 첨부하려고 하는데 개인 블로그를 오픈해두는게 좋을까요?"였다.질문하게된 이유는 개발자로 지원할때 보통 PDF 파일이나 노션 링크로 포트폴리오를 등록하는 경우가 대부분인데, 나는 깃허브 블로그를 운영중이라 깃허브 블로그의 메인페이지를 포트폴리오 화면으로 바꾸어 노션 링크를 대신하려고 준비중이였다. 이 과정에서 하나의 고민거리가 있었는데 앞서 과정을 진행하면 포트폴리오 화면을 통해 개인 블로그로 이동할 수 있었는데, 과연 개인 블로그를 오픈해두는 것이. 지원하는 과정에서 메리트가 있을지 디메리트가 있을지 였다.코치님이 조언해주신 부분은 포트폴리오 화면으로 프로젝트의 진행 과정 중 일어났던 과정을 잘 정리할 수 있으면 개인 블로그를 첨부하지 않아도 될것 같다고 하셨고, 포트폴리오 화면으로 충분히 전달하지 못한 경우에는 블로그를 사용하여 보여주는 것도 괜찮다고 하셨다. 앞으로 어떻게 포트폴리오를 준비할지 좋은 조언을 주셨다고 생각한다.마무리아직 1주차 이지만 전반적으로 강의에 만족하고 있으며 강의 내용도 잘 따라가고 있다고 생각한다. 중간 점검 과정을 통해 코치님에게 좋은 조언을 받기도 했고, 남은 과정도 열심히 진행해서 적어도 강의 주제인 자바-스프링-서버-배포는 확실하게 기반을 다지겠다고 마음을 가졌다.
백엔드
・
인프런
・
워밍업
・
백엔드
2024. 05. 03.
1
[인프런 워밍업 클럽 1기] BE 3일차 과제
[인프런 워밍업 클럽 1기] BE 3일차 과제본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQg[키워드]익명 클래스 / 람다 / 함수형 프로그래밍 / @FunctionalInterface / 스트림 API / 메소드 레퍼런스키워드를 참고해 찾아본 각 키워드의 정의와 특징을 간단하게 정리해보고 질문에 답변해보려고 한다. 익명 클래스정의이름이 없는 클래스로, 클래스의 정의와 동시에 객체를 생성할 수 있는 방법이다.특징클래스 이름이 없어 특정한 위치에서 직접적으로 선언되며, 이와 동시에 객체가 인스턴스화 된다.주로 일회성으로, 재사용이 필요 없는 특정 상황이나 동작을 구현하는 데에 주로 사용된다.복잡한 클래스를 별도로 선언할 필요 없이 필요한 구현 부분만을 직접 작성할 수 있다.코드를 더욱 간결하고 읽게 쉽게 만들며, 불필요한 클래스 선언을 줄여준다. 람다정의코드 가독성을 위해 도입된 익명 클래스 객체를 더욱 더 간결하고 명확하게 표현할 수 있도록 하는 것이다.특징함수의 이름과 반환값이 없어져 익명 함수의 한 종류로 취급한다.함수를 변수처럼 전달하거나 반환하는 데 사용된다.익명 함수를 사용하는 것보다 더 적은 코드로 동일한 내용을 구현할 수 있다. 함수형 프로그래밍정의프로그래밍 패러다임 중 하나로, 순수 함수를 기반으로 데이터 처리와 상태 변화를 최소화하는 방식의 프로그래밍 기법이다.특징동일한 입력에 대해 항상 같은 결과를 반환하며, 외부 상태를 변경하지 않는 함수인 순수 함수를 사용한다.순수 함수를 사용하여 코드의 복잡성에 따른 부작용을 최소화하여 프로그램의 유지 보수와 테스트를 용이하게 하도록 한다. @FunctionalInterface정의함수형 인터페이스를 선언할 때 사용하는 어노테이션이다.특징함수형 인터페이스는 하나의 추상 메서드만을 가지고 있어야 한다.인터페이스에 어노테이션을 사용하면 해당 인터페이스가 함수형 인터페이스인지 검사한다.다음과 같은 형식으로 함수형 인터페이스를 선언한다.@FunctionalInterface interface MyFunction { void myMethod(); } // 함수형 인터페이스의 구현 MyFunction func = () -> System.out.println("함수형 인터페이스의 메소드 구현"); 스트림 API정의컬렉션, 배열 등의 저장 요소를 조작 및 가공, 변환하여 원하는 값으로 반환해주는 인터페이스이다.특징원본 데이터를 조회하여 별도의 Stream 객체로 생성을 하기 때문에 배열의 정렬이나 필터링 작업을 하더라도 원본 데이터를 변경하지 않는다.이미 사용이 되어 닫히면 재사용이 불가능하며 새로운 Stream을 생성해주어야 한다.Stream 내에서 내부적으로 반복문을 처리하기에 간결한 소스코드의 작성이 가능하다. 메소드 레퍼런스정의메소드를 직접 참조하여 람다 표현식을 더 간결하게 만들어주는 방법이다.특징기존의 메서드 정의를 재활용해 람다와 같이 사용할 수 있다.메소드 레퍼런스의 종류종류 : 정적 메서드 참조 람다 표현식 : (x) -> ClassName.method(x) 메서드 참조 : ClassName::method 종류 : 인스턴스 메서드 참조 람다 표현식 : (x) -> obj.method(x) 메서드 참조 : obj::method 종류 : 매개변수의 메서드 참조 람다 표현식 : (obj, x) -> obj.method(x) 메서드 참조 : ClassName::method 종류 : 생성자 참조 람다 표현식 : (x, y) -> new ClassName(x, y) 메서드 참조 : ClassName::new [질문]Q. 자바의 람다식은 왜 등장했을까?A. 궁극적으로 람다식이 등장한 이유는 불필요한 코드를 줄이고, 가독성을 높이기 위해서이다.질문에 대한 자료를 찾아보며 다음과 같은 등장 배경을 찾아볼 수 있었다.람다를 지원하기 전의 자바는 클래스에서 메서드를 정의하고, 필요할 때 메서드를 호출하는 형태의 완전한 명령형 프로그래밍 패러다임을 고수하고 있었다.그러나 자바를 사용하는 개발자들은 개발 규모가 커지면서 복잡하게 얽힌 코드를 유지보수 하는 것이 힘들어졌다.모든 것을 순수 함수로 나누어 문제를 해결하는 함수형 프로그래밍 패러다임이 나타나면서 자바에서도 이러한 함수형 프로그래밍 패러다임의 이점을 가져오기 위해 람다식이 등장하게 되었다.람다식을 이용하면 함수를 일급 객체로 다루어 함수형 프로그래밍의 패러다임을 자바에서도 적용할 수 있게 되었다.즉, 람다식이 등장하며 자바는 객체지향 언어이며 함수형 언어의 기능을 갖추게 되었다고 할 수 있다.그렇게 함수형 프로그래밍 패러다임을 통해 기존 코드에서 불필요한 코드를 줄이고, 가독성을 높일 수 있어 유지보수 측면에 도움이 되었다.이러한 이유로 불필요한 코드를 줄이고, 가독성을 높이기 위해서 함수형 프로그래밍 패러다임을 도입하려 했고 그러기 위해 람다식이 등장했다는 것으로 이해했다.Q. 람다식과 익명 클래스는 어떤 관계가 있을까?A. 람다식과 익명 클래스는 공통적으로 함수형 프로그래밍을 지원하기 위해 익명 함수를 만드는데 사용되지만 다른 부분도 존재하며 주로 익명 클래스를 대체하여 더 간결하고 가독성 높은 코드를 작성하기 위해 람다식을 사용한다.람다식이름이 없는 메서드입니다.추상 및 구체 클래스를 확장할 수 없습니다.단일 추상 메서드를 포함하는 인터페이스를 구현할 수 있습니다.인스턴스 변수를 선언할 수 없습니다.람다 표현식을 인스턴스화 할 수 없습니다.람다 표현식 내부의 this 키워드는 현재 외부 클래스 객체를 나타냅니다.익명 클래스이름이 없는 클래스이다.추상 및 구체 클래스를 확장할 수 있다.여러 추상 메서드를 포함하는 인터페이스를 구현할 수 있다.익명 내부 클래스 내부에서 인스턴스 변수를 선언할 수 있다.익명 내부 클래스를 인스턴스화 할 수 있다.익명 내부 클래스 내부의 this 키워드는 현재 익명 내부 클래스 객체를 참조한다. 다른 부분이 있음에도 불구하고 람다식을 사용하는 이유는 람다식을 사용하면 익명 클래스를 사용한 것보다 더 간결하고 가독성 높은 코드를 작성할 수 있다.예를 들어, 문자열 리스트를 정렬하는 예제를 익명 클래스와 람다식으로 구현한 과정을 비교해보겠다.익명 클래스 Collections.sort(words, new Comparator() { // 익명 클래스 정의 public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } });람다식Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));코드를 상당히 줄여주고 보기 편해진 것을 확인할 수 있다. Q. 람다식의 문법은 어떻게 될까?람다식은 기본적으로 매개변수(Parameter) 와 화살표(->)와 실행문(expression)으로 구성된다.다음과 같은 매커니즘으로 람다식을 구현한다.기본 : (int num) -> {System.out.println(num);} 단일 실행문은 중괄호 제거 : (int num) -> System.out.println(num); 단일 인자는 타입 생략 : (num) -> System.out.println(num); 단일 인자는 소괄호 제거 : num -> System.out.println(num); 인자가 없으면 소괄호 필수 : () -> System.out.println("매개변수 없음"); 인자가 여러개면 소괄호 필수 : (x, y) -> System.out.println(x, y); 인자가 없고 반환값이 있으면 : () -> {return value;}; 실행코드가 return문 뿐이면 return 키워드 생략 가능 : () -> value; 매개변수, 리턴타입 둘다 있으면 : (x, y) -> x+y; [Java] 익명 클래스 (Anonymous Class)란? — 개발자의 서랍 (tistory.com)[Java] Stream API -1 이해하기: 용어 및 Stream 생성 — Contributor9 (tistory.com)코드 가독성 높이는 자바 람다식과 함수형 인터페이스 | 요즘IT (wishket.com)[Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) - (2/5) - MangKyu's Diary (tistory.com)[Java]익명 내부 클래스와 람다식의 차이점 (tistory.com)람다식(feat. 익명 구현 클래스 vs 람다식) (tistory.com)
인프런
・
워밍업
・
백엔드