seong2808
수강평 작성수
4
평균평점
5.0
블로그
전체 3#카테고리
- 백엔드
#태그
- 인프런
- 워밍업
- 스터디
- 클럽
- 발자국
![[인프런 워밍업 스터디 클럽 1기] 3주차 발자국](https://cdn.inflearn.com/public/files/blogs/c985a2dc-00a1-4ee0-9825-8ee39d6f53bf/11111111111.png?w=260)
2024. 05. 18.
0
[인프런 워밍업 스터디 클럽 1기] 3주차 발자국
발자국현재 44강을 학습 중이며 Mysql 설치과정 중 오류로 인해 헤매고 있지만 남은 기간 미니 프로젝트과 완강을 위해 노력하겠다.3주차 학습 내용DAY11 : 객체지향과 JPA 연관관계34강. JPA 연관관계에 대한 추가적인 기능들1 : 1 관계사람과 실거주지를 예시로 들 수 있다.@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; ... ... private Address address; } @Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; ... ... private Person person; } 서로 연결되어 있긴 하지만 테이블에서는 한쪽만 id를 가지고 있으면 된다. person 테이블이 address 테이블의 id를 가질 수도 있고, 그 반대 일 수도 있다.create table person ( id bigint auto_increment, name varcher(255), address_id bigint, primary key (id) ); create table address ( id bigint auto_increment, ... ... primary key (id) ); 이렇게 한쪽의 id만 가지고 있으면 된다.하지만 이런 경우 연관관계의 주인은 Person이다.서로 @OneToOne 어노테이션을 붙여주어야 한다.또한, 주인이 아닌 곳에 mappedBy를 붙여주어야 한다.@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; ... ... @OneToOne private Address address; } @Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; ... ... @OneToOne(mappedBy = "address") private Person person; } 연관관계의 주인 효과는 객체를 연결시켜주는 것이다.N : 1 관계 - @ManyToOne과 @OneToMany연관관계의 주인은 무조건 N 쪽이다.또한, @ManyToOne은 단방향으로만 사용할 수 있다.@JoinColumn연관관계의 주인이 활용할 수 있는 어노테이션필드의 이름이나 null 여부, 유일성 여부, 업데이트 여부 등을 지정N : M 관계 - @ManyToMany학생과 동아리를 생각하면 된다.한 학생은 여러 동아리에 속할 수 있고, 한 동아리 또한 여러 학생이 들어올 수 있다.하지만 N : M 관계는 구조가 복잡하고, 테이블이 직관적으로 매핑되지 않아 사용하지 않는 것을 추천한다.N : M 관계는 N : 1 관계로 풀어해칠 수 있기 때문이 이러한 방법을 추천한다.cascade 옵션한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 흘러 연결되어 있는 객체도 함께 저장되거나 삭제되는 기능orphanRemoval 옵션객체간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션 35강 책 대출/반납 기능 리페토링과 지연 로딩영속성 컨텍스트의 4번째 능력 - 지연로딩@OneToMany 어노테이션 안에는 fetch 속성이 있다. 기본적으로 LAZY, 지연로딩으로 되어 있지만, 필요한 순간에 가져오는 것이 아닌 한번에 다 가져오고 싶다면 fetch를 EAGER로 설정하면 된다.연관관계를 사용하면 무엇이 좋을까?각자의 역할에 집중 (= 응집성)새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다.연관관계를 사용하는 것이 항상 좋을까?지나치게 사용하면, 성능상의 문제가 생길 수도 있고 도메인 간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수 있다.다양한 부분을 고민해서 연관관계를 사용해야 한다. DAY12 : 기본적인 배포를 위한 준비37강. 배포란 무엇인가?배포최종 사용자에게 SW를 전달하는 과정 → 전용 컴퓨터에 우리의 서버를 옮겨서 실행하는 것우리는 전용 컴퓨터가 없기 때문에 AWS에 서버를 옮겨서 실행하는 일련의 과정을 공부해보자.38강. profile과 H2 DB전용 컴퓨터는 대부분 리눅스 사용하는데 리눅스 환경에 설치한 java와 mysql을 실행한다.똑같은 서버 코드를 실행하지만 우리 컴퓨터와 전용 컴퓨터의 각각의 java와 mysql를 사용해야 한다.여기서 profile이라는 개념이 등장한다.Profile 적용똑같은 서버 코드를 실행하지만 local 이라는 profile을 입력하면, H2 DB를 사용하고 dev 라는 profile을 입력하면, My SQL DB를 사용해보자H2 DB경량 데이터베이스로, 개발 단계에서 많이 사용하며 디스크가 아닌 메모리에 데이터를 저장할 수 있다. 메모리의 데이터는 휘발되기 때문에 개발 단계에서 많이 사용된다. 이러한 이유는 개발단계에서는 수정사항이 많기 때문에 데이터가 휘발되는 것이 장점으로 바뀐다.profile 환경 구성 (application.yml)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 --- 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 http://localhost:8080/h2-console 를 통해 h2 DB에 직접 접근할 수 있다.39강. git과 github이란 무엇인가?git코드를 쉽게 관리할 수 있도록 해주는 버전 관리 프로그램githubgit으로 관리되는 프로젝트의 코드가 저장되는 저장소40강. git 기초 사용법레포지토리 생성인텔리제이 터미널git init : 깃 초기화git remote : 현재 프로젝트에 등록된 저장소 확인git add . : 작업 디렉토리 상의 변경 내용의 전체를 스테이징 영역에 추가git commit : 스테이징 영역의 상태를 버전으로 기록git push : 원격 저장소에 코드 변경분을 업로드git pull : 원격 저장소의 코드를 다운로드41강. AWS의 EC2 사용하기AWS 회원가입과 EC2 인스턴스 생성에 대한 일련의 과정을 배울 수 있었다. DAY13 : AWS와 EC2 배포43강. EC2에 접속해 리눅스 명령어 다뤄보기aws ec2 터미널을 통한 접속방법생성한 키페어 권한 설정chmod 400 //키페어경로 키페어의 경로는 키페어를 터미널로 드래그하면 경로를 바로 쓸 수 있다.ssh 접속ssh -i 키페어경로 ec2-user@퍼블릭주소 aws 콘솔로 직접 접속하는 방법도 있지만 많은 서버를 관리하기에는 불편하다.기본적인 리눅스 명령어mkdir : 폴더를 만드는 명령어ls : 현재 위치에서 폴더나 파일을 확인하는 명령어ls -l : 조금 더 자세한 정보를 확인할 수 있다 이때, drwxrwxr-s 2 ec2-user ec2-user 6 5월 18 08:47 을 볼 수 있다. drwxrwxr-s 2 ec2-user ec2-user 6 5월 18 08:47 r : 읽을 수 있는 권한 w : 쓸 수 있는 권한 x : 실행할 수 있는 권한 초록색 : 소유자의 권한 파란색 : 소유그룹의 권한 주황색 : 아무나 접근했을 때의 권한 2 : 폴더에 걸려 있는 바로가기 개수 ec2-user : 소유주 이름 ec2-user : 소유그룹 6 : 폴더의 크기 (byte) 5월 18 08:47 : 파일의 최종 변경 시각 cd : 폴더 안으로 들어가는 명령어pwd : 현재 위치를 확인하는 명령어cd.. : 상위 폴더로 이동하는 명령어rmdir : 비어있는 폴더를 제거하는 명령어3주차 과제과제6과제4에서 만들었던 API를 분리해보며, Controller - service - Repository 계층에 익숙해져 봅시다. 지난 주차 과제4에서 못한 부분이 많아 2주차 발자국에 따로 작성하지 않았다.이번 과제를 통해 과제4에서 구현하지 못했던 API를 완성하고 각 계층의 역할에 조금 더 이해할 수 있는 시간이 되었던 것 같다.과제7과제6에서 만들었던 Fruit 기능들을 JPA를 이용하도록 변경해보세요.또한, 저장되었던 과일 개수 조회 API, 판매되지 않은 특정 금액 이상 혹은 이하의 과일 목록 조회 API 작성 API를 작성하는 과정은 그렇게 힘들지 않았던 것 같다. 다만 JPA 환경 세팅을 하면서 힘들었던 부분이 있었다.이후 API를 작성하는 과정에서 기억에 남는 부분은 스네이크 케이스 오류이다. JPA를 사용하면서 기존의 쿼리를 어떻게 사용해야 할지 고민하면서 스네이크 케이스로 인해 오류가 발생하였다. 그래서 @Column 어노테이션을 통해 객체의 이름을 바꾸어 매핑하여 사용하였다. 과제나 강의를 들으면서 오류와 마주할 때마다 힘들지만 경험이 쌓이면서 성장하는 느낌을 받는다.
백엔드
・
인프런
・
워밍업
・
스터디
・
클럽
・
발자국
![[인프런 워밍업 스터디 클럽 1기] 2주차 발자국](https://cdn.inflearn.com/public/files/blogs/1ac23cde-8225-46f7-b846-59550de31a21/11111111111.png?w=260)
2024. 05. 12.
0
[인프런 워밍업 스터디 클럽 1기] 2주차 발자국
발자국강의는 진도표에 맞춰 진행하였으며 회고록 또한 그에 맞춰 작성하였다.인프런 워밍업 스터디 클럽 완주를 목표로 남은 기간동안 매일매일 꾸준히 학습하기 위해 노력하겠다.2주차 학습 내용DAY6 : 스프링 컨테이너의 의미와 사용 방법19강. UserController와 스프링 컨테이너UserController의 의아한 점static이 아닌 코드를 사용하려면 인스턴스화 필요인스터스화를 하기 위해선 생성자를 호출하게 된다.하지만 이전 강의까지 new UserController 이렇게 호출한 적이 없다!UserController는 JdbcTemplate에게 의존하고 있다.하지만 JdbcTemplate이라는 클래스를 직접 설정해준 적이 없다!→ 그 이유는 @RestController 어노테이션에게 있다. → SpringBean으로 등록해준다!SpringBean서버가 시작되면, 스프링 서버 내부에 거대한 컨테이너를 만든다.컨테이너 안에 여러 클래스가 들어간다.이때, 다양한 정보도 함께 들어있고, 인스턴스화도 이루어 진다.스프링 빈이란? 컨테이너 안에 들어간 클래스를 말한다.UserController와 다르게 JdbcTemplate은 언제 스프링 빈으로 등록하였을까?→ 의존성 설정을 할 당시에 스프링 빈으로 등록된다.스프링 컨테이너의 역할은 서로 필요한 관계에 있는 스프링 빈끼지 연결을 시켜주는 역할이다.이론 정리서버가 시작되면 스프링 컨테이너(클래스 저장고)가 시작된다.기본적으로 많은 스프링 빈들이 등록된다.우리가 설정해준 스프링 빈이 등록된다.이때 필요한 의존성이 자동으로 설정된다.왜?그렇다면 UserRepository는 JdbcTemplate을 가져오지 못할까?→ UserRepository가 스프링 빈이 아니기 때문이다.실습UserService와 UserRepository를 스프링 빈으로 등록해준 뒤 UserRepository에게만 JdbcTemplate 필요하게 되었고 controller는 service만을 호출하고 service는 repository만을 호출하여 사용할 수 있게 된다.실습상황실습한 코드의 서버가 시작하면,가장 기본적인 스프링 빈이 등록된다.JdbcTemplate을 의존하는 UserRepository가 스프링 빈으로 등록되면서 인스턴스화된다.UserRepository를 의존하는 UserService가 스프링 빈으로 등록된다.UserService를 의존하는 UserController가 스프링 빈으로 등록된다. 20강. 스프링 컨테이너를 왜 사용할까?다음 요구사항을 생각해보자책 이름을 메모리에 저장하는 API를 매우 간단하게 구현하라. Service, Repository는 Spring Bean이 아니어야 한다.구현된 그림BookController —> BookService —> BookMemoryRepository실습진행스프링 빈을 설정하지 않고 메모리에 저장한다는 가정하에 실습 진행추가 요구사항 구현메모리가 아닌 MySQL과 같은 DB를 사용해야 한다. JdbcTemplate은 Repository가 바로 설정할 수 있다고 해보자.구현된 그림BookController —> BookService BookMemoryRepository —> BookMysqlRepository하지만 Repository 뿐만 아니라 Service까지 바꿔야 한다!이런 상황에서 Repository를 다른 Class로 바꾸더라도 Service를 변경하지 않는 방법은? → Interface를 활용하자수정된 구현BookController —> BookService —> BookRepository ← BookMemoryRepository ← BookMysqlRepository실습진행인터페이스 생성하여 레포지토리가 바뀌더라고 쉽게 사용할 수 있게 실습 진행2개의 레포지토리로 인해 에러가 발생하는데 사용하는 레포지토리에 @Primary 어노테이션을 활용해서 조절하면 에러가 해결된다.하지만 Service의 변경 범위가 줄어들었지만 아쉬운 부분이 있다.그래서 등장한 것이 스프링 컨테이너이다.컨테이너를 사용하면, 컨테이너가 Service를 대신 인스턴스화 하고 그 때 알아서 Repository를 결정해준다.이런 방식을 제어의 역전(IoC, Inversion of Control)이라고 한다.컨테이너가 선택해 Service에 넣어주는 과정을 의존성 주입(DI, Dependency Injection)라고 한다. 21강. 스프링 컨테이너를 다루는 방법빈을 등록하는 방법@Configuration클래스에 붙이는 어노테이션@Bean을 사용할 때 함께 사용해 주어야 한다.@Bean메소드에 붙이는 어노테이션메소드에서 반환되는 객체를 스프링 빈에 등록한다.실습진행UserRepository에 Bean를 사용해보자@Configuration public class UserConfiguration { @Bean public UserRepository userRepository(JdbcTemplate jdbcTemplate) { return new UserRepository(jdbcTemplate); } } 언제 @Service, @Repository를 사용해야 할까?개발자가 직접 만든 클래스를 스프링 빈으로 등록할 때사실 위의 실습은 @Repository를 사용하는 것이 관례이다.언제 @Configuration + @Bean을 사용해야 할까?외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때@Component주어진 클래스를 ‘컴포넌트’로 간주한다.이 클래스들은 스프링 서버가 뜰 때 자동으로 감지된다.@Component 덕분에 ****우리가 사용했던 어노테이션이 자동감지 되었다.언제 @Component는 사용해야 할까?컨트롤러, 서비스, 레포지토리가 모두 아니고개발자가 직접 작성한 클래스를 스프링 빈으로 등록할 때 사용되기도 한다.스프링 빈을 주입 받는 방법(가장 권장) 생성자를 이용해 주입 받는 방식setter와 @Autowired 사용 : 누군가 setter를 사용하면 오작동할 수 있음필드에 직접 @Autowired 사용 : 테스트를 어렵게 만드는 요인@Qualifier스프링 빈을 사용하는 쪽, 스프링 빈을 등록하는 쪽 모두 @Qualifier를 사용할 수 있다.스프링 빈을 사용하는 쪽에서만 쓰면, 빈의 이름을 적어주어야 한다. @Primary vs @Qualifier사용하는 쪽이 직접 적어준 @Qualifier가 이긴다. DAY7 : Spring Data JPA를 사용한 데이터베이스 조작23강. 문자열 SQL을 직접 사용하는 것이 너무 어렵다.SQL을 직접 작성하면 아쉬운 점문자열을 작성하기 때문에 실수할 수 있고, 실수를 인지하는 시점이 느리다.컴파일 시점에 발견되지 않고, 런타임 시점에 발견된다.특정 데이터베이스에 종속적이게 된다.반복 작업이 많아진다. CRUD 쿼리가 항상 필요하다.데이터베이스의 테이블과 객체는 패러다임이 다르다.그래서 등장했다.JPA (Java Persistence API) & 자바 진영의 ORM (Object-Relational Mapping)Persistence (영속성) → 서버가 재시작되어도 데이터는 영구적으로 저장되는 속성객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진 규칙HIBERNATE(구현체) — 구현(Implements) —> JPAHIBERNATE은 내부적으로 JDBC를 사용한다. 24강. 유저 테이블에 대응되는 Entity Class 만들기Java 객체와 MySQL Table을 매핑할 것이다.User 객체 활용하여 실습진행User Class에 @Entity 어노테이션을 붙인다.@Entity : 스프링이 User 객체와 user 테이블을 같은 것으로 바라본다.Entity : 저장되고, 관리되어야 하는 데이터고유 id를 설정@Id : 이 필드를 primary key로 간주@GeneratedValue : primary key는 자동 생성되는 값기본 생성자 생성JPA를 사용하기 위해서는 기본 생성자가 꼭 필요하다.// 예시 protected User() {} @Column : 객체의 필드와 Table의 필드를 매핑한다.여러 옵션이 존재한다.// name varchar(20) @Column(nullable = false, length = 20, name = "name") Column은 생략 가능하다. → 옵션이 필요없고 테이블과 동일하다면 생략이 가능하다.JPA를 사용하니 추가적인 설정 필요 (한번만 하면 된다.)application.yml ****jpa: hibernate: ddl-auto: none properties: hibernate: show_sql: true format_sql: true dialect: org.hibernate.dialect.MtSQL8Dialect ddl-auto : 스프링이 시작할 때 DB에 있는 테이블을 어떻게 처리할지create : 기존 테이블이 있다면 삭제 후 다시 생성create-drop : 스프링이 종료될 때 테이블을 모두 제거update : 객체와 테이블이 다른 부분만 변경validate : 객체와 테이블이 동일한지 확인none : 별다른 조치를 하지 않는다show_sql : JPA를 사용해 DB에 SQL을 날릴 때 SQL을 보여줄 것인가format_sql : SQL을 보여줄 때 예쁘게 포맷팅할 것인가dialect : DB을 특정하면 조금씩 다른 SQL을 수정해준다. 25강. Spring Data JPA를 이용해 자동으로 쿼리 날리기SQL을 작성하지 않고, 유저생성/조회/업데이트 기능을 리팩토링UserRepository 인터페이스를 User 옆에 만들어준다.JpaRepository를 상속 받는다.package com.group.libraryapp.domain.user; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { } 저장기능 → save 메서드에 객체를 넣어주면 INSERT SQL이 자동으로 날아간다.@Service public class UserServiceV2 { private final UserRepository userRepository; public UserServiceV2(UserRepository userRepository) { this.userRepository = userRepository; } public void saveUser(UserCreateRequest request) { // u에는 생성한 id가 반환된다. User u = userRepository.save(new User(request.getName(), request.getAge())); } } 조회기능 → findAll 메서드를 사용하면 모든 데이터를 가져온다.// 자바8 문법을 이용한 public List getUsers() { return userRepository.findAll() .stream().map(UserResponse :: new) .collect(Collectors.toList()); } 업데이트기능Optional의 orElseThrow를 사용해 User가 없다면 예외를 던진다.객체를 업데이트해주고 save 메서드를 이용하면 UPDATE 쿼리가 날아간다.public void updateUser(UserUpdateRequest request) { // select * from user where id = ?; // Optional User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); userRepository.save(user); } 이렇게 동작할 수 있는 이유?Spring Data JPA : 복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리우리가 사용한 메서드들은 SimpleJpaRepository에 담겨 있다. 26강. Spring Data JPA를 이용해 다양한 쿼리 작성하기삭제 기능을 Spring Date JPA로 변경하자public void deleteUser(String name) { User user = userRepository.findByName(name); if (user == null) throw new IllegalArgumentException(); userRepository.delete(user); } findeByName 이란 SimpleJpaRepository에 존재하지 않는다. 그러므로 UserRepository에 findByName를 작성해주어야 한다.public interface UserRepository extends JpaRepository { User findByName(String name); } 반환 타입은 User, 유저가 없다면 null이 반환된다.find → 1개 조회By → SELECT 쿼리의 WHERE 문이 작성By 앞에 들어갈 수 있는 구정 정리find : 1개, 반환 타입은 객체 또는 Optional findAll : 쿼리의 결과물이 N개인 경우 사용. List 반환 exists : 쿼리 결과가 존재하는지 확인. 반환 타입은 boolean count : SQL의 결과 개수를 센다. 반환 타입은 long 각 구절은 And 또는 Or로 조합할 수 있다.// List findAllByNameAndAge(String name, int age); SELECT * FROM user WHERE name = ??? AND age = ???; By 뒤에 들어갈 수 있는 구절GreaterThan : 초과 GreaterThanEqual : 이상 LessThan : 미만 LessThanEqual : 이하 Between : 사이에 StartsWith : ~로 시작하는 EndsWith : ~로 끝나는 By 뒤에 들어갈 수 있는 구절 정리//List findAllByAgeBetween(int startAge, int endAge); SELECT * FROM user WHERE age BETWEEN ??? AND ???; DAY8 : 트랜잭션과 영속성 컨테이너27강. 트랜잭션 이론편트랜잭션쪼갤 수 없는 업무의 최소 단위쇼핑몰에 주문을 한다면?주문 기록 저장포인트 기록 저장주문 결제 기록 저장만약 주문 기록과 포인트 기록이 저장되고 주문 결제 기록이 저장되지 않는다면 문제가 발생한다. 이러한 문제를 해결하기 위해 모두 성공시키거나 모두 실패시키자는 개념이 등장한다.이렇게 쪼갤 수 없는 작업 단위를 트랜잭션이라고 한다.트랜잭션 시작하기start transaction; 정상 종료commit; 실패 처리rollback; 트랜잭션을 시작 후 commit이나 rollback를 하지 않으면 확인할 수 없다.이는 묶여서 저장된다는 것을 의미한다. 28강. 트랜잭션 적용과 영속성 컨텍스트@Transactional어노테이션으로 붙이면 간단히 사용가능하다.주의사항IOException과 같은 Checked Exception은 롤백이 일어나지 않는다.영속성 컨텍스트테이블과 매핑된 Entity 객체를 관리/보관하는 역할스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.특수 능력 4가지변경 감지 (Dirty Check)영속성 컨텍스트 안에서 불러와진 Entity는 명시적으로 save하지 않더라도, 변경을 감지해 자동으로 저장된다.@Transactional public void updateUser(UserUpdateRequest request) { // select * from user where id = ?; // Optional User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(request.getName()); // userRepository.save(user); } save()를 명시적으로 하지 않아도 user.updateName()으로 user의 변경을 감지하고 save()를 하지 않아도 적용된다.쓰기 지연DB의 INSERT, UPDATE, DELETE SQL을 바로 날리는 것이 아니라, 트랜잭션이 commit될 때 모아서 한 번에 날린다.1차 캐싱ID를 기준으로 Entity를 기억한다.이렇게 캐싱된 객체는 완전이 동일하다.반복되는 findById(”1”)이 있다면 1번 할때, DB와 통신하여 id가 1인 무엇을 기억하고 다른 2번의 findById(”1”)은 통신하지 않고 조회할 수 있다. DAY9 : 조금 더 복잡한 기능을 API로 구성하기30강. 책 생성 API 개발하기요구사항책을 등록할 수 있다.API 스펙 확인HTTP Method : POSTHTTP Path : /bookHTTP Body (JSON){ "anme" : String } 결과 반환 X (HTTP 상태 200 OK이면 충분)book 테이블 생성create table book ( id bigint auto_increment, name varchar(255), primary key (id) ); @Column의 length 기본값은 255문자열 필드는 최적화를 해야 하는 경우가 아닐 때 여유롭게 설정하는 것이 좋다.book 객체를 만들어 설계했던 테이블과 맵핑 시키기@Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; @Column(nullable = false) private String name; } BookService → loanBook() 생성@Transactional public void saveBook(BookCreateRequest request) { bookRepository.save(new Book(request.getName())); } 31강. 대출 기능 개발하기요구사항사용자가 책을 빌릴 수 있다. 다른 사람이 그책을 진작 빌렸다면, 빌릴 수 없다.API 스펙 확인HTTP Method : POSTHTTP Path : /book/loanHTTP Body (JSON){ "userName" : String, "bookName" : String } 결과 반환 X (HTTP 상태 200 OK이면 충분)user_loan_history 테이블 생성create table user_loan_history ( id bigint auto_increment, user_id bigint, book_name varchar(255), is_return tinyint(1), primary key (id) ); UserLoanHistory 객체를 만들어 설계했던 테이블과 맵핑 시키기public class UserLoanHistory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; private long userId; private String bookName; private boolean isReturn; } boolean으로 처리하면 tnyint에 잘 매핑된다.BookService → loanBook() 생성@Transactional public void loanBook(BookLoanRequest request) { // 1. 책 정보를 가져온다. Book book = bookRepository.findByName(request.getBookName()) .orElseThrow(IllegalAccessError::new); // 2. 대출기록 정보를 확인해서 대출중인지 확인한다. if (userLoanHitstoryRepository.existsByBookNameAndIsReturn(book.getName(), false)) { // 3. 만약에 확인했는데 대출 중이라면 예외를 발생시킨다. throw new IllegalArgumentException("진작 대출되어 있는 책입니다."); }; // 4. 유저 정보를 가져온다. User user = userRepository.findByName(request.getUserName()) .orElseThrow(IllegalAccessError::new); // 5. 유저 정보와 책 정보를 기반으로 UserLoanHistory를 저장한다. userLoanHitstoryRepository.save(new UserLoanHistory(user.getId(), book.getName())); } 32강. 반납기능 개발하기요구사항유저가 책을 반납한다.API 스펙 확인HTTP Method : PUTHTTP Path : /book/returnHTTP Body (JSON){ "userName" : String, "bookName" : String } 결과 반환 X (HTTP 상태 200 OK이면 충분)이번 반납기능을 위한 API와 대출기능을 위한 API의 HTTP Body가 똑같다! 이런 경우 새로 만드는 것이 좋은지? 아니면 기존의 있는 DTO를 활용하는 것이 좋은지? 고민이 되는데 이러한 경우 새로 만드는 것을 추천한다고 한다.이유 : 만약 두 기능 중 한 기능에 변화가 생겼을 때, 유연하고 side-effect 없이 대처할 수 있기 때문이다.BookService → returnBook() 생성@Transactional public void returnBook(BookReturnRequest request) { User user = userRepository.findByName(request.getUserName()) .orElseThrow(IllegalAccessError::new); UserLoanHistory history = userLoanHitstoryRepository.findByUserIdAndBookName(user.getId(), request.getBookName()) .orElseThrow(IllegalAccessError::new); history.doReturn(); } @Transactional을 사용하고 있기 때문에 영속성 컨텍스트의 변경감지기능으로 엔티티객체의 변화를 감지하여 자동으로 업데이트된다!!기능을 완성했지만 한 가지 고민할만한 내용이 존재한다.우리가 ORM을 사용하게 된 이유 중 하나는 “DB 테이블과 객체는 패러다임이 다르기 때문”이다.우리는 DISK(장기기억)와 RAM(메모리, 단기기억)의 차이 때문에 데이터의 영속성을 부여하기 위해서 DB테이블에 데이터를 저장하는 것은 필수이다.하지만 JAVA 언어는 객체지향 언어이고, 대규모 웹 애플리케이션을 다룰 때에도 절차지향적 설계보다 객체지향적 설계가 좋다!그러므로 우리는 좀 더 객체지향적으로 개발할 수 없을까?와 같은 고민을 할 필요가 있다.DAY10 : 객체지향과 JPA 연관관계 - 33강33강. 조금 더 객체지향적으로 개발할 수 없을까?현재 대출기능과 반납기능을 보면 BookService에서 User와 UserLoanHistory를 모두 끌어다가 처리하는 것을 볼 수 있다. 이러한 부분을 수정하여 같은 도메인에 속해 있는 User와 UserLoanHistory가 협업하여 BookService에서 User만 가져와 대출 및 반납을 처리할 수 있게 수정한다.선행조건 : User와 UserLoanHistory가 서로 알아야한다.@Entity public class UserLoanHistory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; @ManyToOne private User user; private String bookName; private boolean isReturn; @ManyToOne : 내가 1이고 너가 Many이다. 즉, N:1 관계이다.N:1 관계학생과 교실을 생각하면 편하다. 학생(다수)와 교실(1)반대로 User에는 @OneToMany를 붙여주어야 한다.@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; @Column(nullable = false, length = 20, name = "name") // name varchar(20) private String name; private Integer age; @OneToMany(mappedBy = "user") private List userLoanHistories = new ArrayList(); 연관관계의 주인테이블을 봤을 때, 누가 관계의 주도권을 가지고 있는가? 위 같은 경우 UserLoanHistory주도권이라는 것은 누가 상대방을 도고 있는가로 생각하면 편하다.@OneToMany(mappedBy = "user") 연관관계의 주인이 아닌 쪽에 mappedBy를 해주어야 한다.2주차 과제과제5제시된 코드를 읽어보며, 더 좋은 코드로 고쳐보기클린코드에 대해 배우고 또 알아보면서 생각보다 많은 항목을 주의할 필요가 있었다.하지만 현재 그 많은 항목들을 주의하면서 코딩하기엔 부족한 점이 많은 것 같다는 생각이 들었다. 실제로 프로젝트를 하면서 느낀 점은 시간에 쫓겨 막 작성할 때가 있는데 특히 내 경우에는 중복 코드가 발생하고 네이밍을 할때 문제가 발생한다. 이번 과제를 통해 앞으로 주의하면서 코드를 작성하고자 한다.
백엔드
・
인프런
・
워밍업
・
스터디
・
클럽
・
발자국
![[인프런 워밍업 스터디 클럽 1기] 1주차 발자국](https://cdn.inflearn.com/public/files/blogs/5cac624b-e4da-4ecf-8c02-9e3e79d0ec96/333476.png?w=260)
2024. 05. 04.
0
[인프런 워밍업 스터디 클럽 1기] 1주차 발자국
발자국강의는 진도표에 맞춰 진행하였으며 회고록 또한 그에 맞춰 작성하였다.인프런 워밍업 스터디 클럽 완주를 목표로 남은 기간동안 매일매일 꾸준히 학습하기 위해 노력하겠다.1주차 학습 내용DAY 1 : 서버개발을 위한 환경 설정 및 네트워크 기초1강 : 스프링 프로젝트를 시작하는 두번째 방법스프링 프로젝트를 시작하는 방법은 크게 2가지가 있다.이미 만들어져 있는 프로젝트를 다운하거나 Spring initializr ( https://start.spring.io/ )를 이용해 새로운 프로젝트를 시작한다.위의 이미지와 같은 홈페이지에 접속하여 프로젝트의 빌드툴, 이름, 서버개발에 사용할 언어, 의존성을 설정하여 시작할 수 있다. 2강 : @SpringBootApplication과 서버@SpringBootApplication 어노테이션 : 스프링을 실행시키기 위한 다양한 설정을 자동으로 처리해 준다.server : 어떠한 기능을 제공하는 프로그램, 프로그램을 실행시키고 있는 컴퓨터를 의미한다. 3강 : 네트워크란 무엇인가?IP : 컴퓨터별 고유 주소port : 여러 프로그램 중 하나만을 특정하는 것을 의미Domain Name : 244.65.51.9 대신 spring.com 으로 표현이러한 체계를 Domain Name System (DNS) 라고 한다. 4강 : HTTP와 API란 무엇인가?HTTP (HyperText Transfer Protocol) : 데이터를 주고 받는 표준Protocol : 표준, 약속GET / portion?color=red&count=2 HOST:spring.com:3000GET : 요청 행위 → HTTP MethodHOST : 요청을 받는 컴퓨터와 프로그램 정보/portion : Path, HTTP 요청을 받는 컴퓨터에게 원하는 자원? & : 구분 기호?color=red&count=2 -> 원하는 조건, Query라고 부른다.각 HTTP Method 마다 사용되는 것이 다르다.GET : Query 사용 (조회)POST : Body 사용 (생성) PUT : Body 사용 (수정) DELETE : Query 사용 (삭제)API (Application Programming Interface) : 정해진 약속을 하여 특정 기능을 수행하는 것URL (Uniform Resource Locator) : 주소창 (ex. http://spring.com:3000/porting?color=red&count=2) 5강 : GET API 개발하고 테스트하기API를 개발하기에 앞서 API를 이루고 있는 요소들을 설계해야한다. 이를 API Specification (명세) 라고 한다.Controller, DTO 생성과 GET API 작성 이후 만들어진 API를 Postman으로 테스트하는 실습을 진행하였다. DAY 2 : 첫 HTTP API 개발6강. POST API 개발하고 테스트하기Controller, DTO 생성과 POST API 작성 이후 만들어진 API를 Postman으로 테스트하는 실습을 진행하였다.데이터를 어떻게 받을까? → HTTP BODY를 통해 받는다.BODY로 데이터를 어떻게 받을까? -> JSONJSON : 객체 표기법, Map 느낌{ "name": "강성관", "age": 26 }JSON의 value에는 리스트, 객체 등등 다양한 타입을 받을 수 있다.@RequestBody : HTTP Body로 들어오는 JSON을 주어진 객체로 변경해주는 어노테이션@PostMapping("/multiply") public int multiplyTwoNumbers(@RequestBody CalculatorMultiplyRequest request) { return request.getNumber1() * request.getNumber2(); }즉, HTTP Body는 CalculatorMultiplyRequest에 매핑된다. 7강. 유저 생성 API 개발도서관 애플리케이션의 요구사항사용자사용자 등록사용자 목록 조회사용자 이름 업데이트사용자 삭제책책 등록 및 삭제사용자 책 대여다른 사람이 그 책을 빌렸다면 빌릴 수 없다책 반납API 설계Method : POSTpath : /userBody (JSON){ "name": String (null 불가능) "age": Integer } 결과 반환 X (HTTP 상태 200 OK이면 충분하다) 8강. 유저 조회 API 개발과 테스트결과 반환이 JSON이다? Controller 에서 getter가 있는 객체를 반환하면 JSON이 된다.Id는 무엇인가?유저별로 겹치지 않는 유일한 번호List에 담겨 있는 유저의 순서를 id로 정하여 진행 DAY 3 : 기본적인 데이터베이스 사용법10강. Database와 MySQLDatebase와 Mysql의 사전 지식과 연결 방법에 대한 설명11강. MySQL에서 테이블 만들기//데이터베이스 만들기 create database 데이터베이스명; //데이터베이스 목록 보기 show databases; //데이터베이스 지우기 drop database 데이터베이스명; //데이터베이스 안으로 들어가기 use 데이터베이스명; //테이블 목록 보기 show tables; //테이블 만들기 create table 테이블명 ( 필드명 타입 부가조건 필드명 타입 부가조건 ... primary key (필드이름) ); MySQL 타입 살펴보기정수 타입tinyint : 1 바이트 정수int : 4 바이트 정수bigint : 8 바이트 정수실수 타입double : 8 바이트 정수decimal(A, B) : 소수점을 B개 가지고 있는 전체 A자릿수 실수 ex) Decimal(4, 2) = 12.23문자열 타입char(A) : A글자가 들어갈 수 있는 문자열varchar(A): 최대 A글자가 들어갈 수 있는 문자열날짜, 시간 타입date : 날짜, yyyy-MM-ddtime : 시간, HH:mm:ssdatetime : 날짜와 시간을 합친 타입, yyyy-MM-dd HH:mm:ss12강. 테이블의 데이터를 조작하기//데이터 넣기 INSERT INTO 테이블명 (필트명, 필트명, ...) VALUES (값, 값, ...); //데이터 조회 (* 대신 필드명을 넣을 수 있다.) SELETE * FROM 테이블명; //데이터 조회 필터 사용 (AND와 OR를 통해 이어붙일 수 있다.) SELETE * FROM 테이블명 WHERE 조건; //데이터 업데이트하기 (조건을 붙이지 않는다면 모든 데이터가 수정된다.) UPDATE 테이블명 SET 필드명 = 값, 필드명 = 값 WHERE 조건; //데이터 삭제 (조건을 붙이지 않는다면 모든 데이터가 삭제된다.) DELETE FROM 테이블명 WHERE 조건; 13강. Spring 에서 Database 사용하기스프일 서버가 MySQL DB에 접근하는 방법application.yml 만들고 설정하기spring: datasource: url: "jdbc:mysql://localhost/library" username: "root" password: "" driver-class-name: com.mysql.cj.jdbc.Driver 이후, User 테이블 생성 후 기존의 실습을 진행하면서 메모리에 저장하던 유저정보를 MySQL에 저장하게 바꾸는 실습을 진행하였다. DAY 4 : 데이터베이스를 사용해 만드는 API14강. 유저 업데이트 API. 삭제 API 개발과 테스트도서관 사용자 이름 업데이트HTTP Method : PUTHTTP Path : /userHTTP Body (JSON){ "id": Long, "name": String // 변경되어야 하는 이름이 들어온다. } 결과 반환 X (HTTP 상태 200 OK이면 충분)사용자 삭제HTTP Method : DELETEHTTP Path : /user쿼리 사용문자열 name결관 반환 X한가지 문제 발생 → 수정과 삭제할 때 데이터베이스에 없는 유저를 수정하거나 삭제 시 200 OK를 반환15강. 유저 업데이트, 삭제 API 예외 처리 하기데이터 존재 여부 확인 후 예외를 던지자!실습 진행!@PutMapping("/user") public void updateUser(@RequestBody UserUpdateRequest request) { // 해당 id를 가지고 있는 User가 있는지 확인하는 쿼리 String readSql = "SELECT * FROM user WHERE id = ?"; // 있다면 0를 가지고 있는 List 생성, 아니면 빈 List 생성, // .isEmpty()를 이용하여 비어있다면 True 아니면 false 반환 boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty(); if (isUserNotExist) { throw new IllegalArgumentException(); } String sql = "UPDATE user SET name = ? WHERE id = ?"; jdbcTemplate.update(sql, request.getName(), request.getId()); } @DeleteMapping("/user") public void deleteUser(@RequestParam String name) { // 해당 name를 가지고 있는 User가 있는지 확인하는 쿼리 String readSql = "SELECT * FROM user WHERE name = ?"; // 있다면 0를 가지고 있는 List 생성, 아니면 빈 List 생성, // .isEmpty()를 이용하여 비어있다면 True 아니면 false 반환 boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty(); if (isUserNotExist) throw new IllegalArgumentException(); String sql = "DELETE FROM user WHERE name = ?"; jdbcTemplate.update(sql, name); } DAY 5 : 클린코드의 개념과 첫 리펙토링17강. 좋은 코드는 왜 중요한가?Code는 요구사항을 표현하는 언어이고 개발자는 요구사항을 구현하기 위해 코드를 읽고 작성한다.하지만 다른 사람이 작성하는 코드와 내가 예전에 작성해둔 코드를 다시 읽었을 때, 다시 이해하는 것은 쉬운 일이 아니다.개발자에게 코드를 읽고 이해하는 것은 필수적이고 피할 수 없다.그럼 코드를 봤을 때, 무엇을 하는지 알 수 없는 코드를 이해하려는 것과 어떤 동작을 하고 무엇을 원하는지 의미를 파악할 수 있는 코드를 이해하는 것은 차이가 크다는 것을 알 수 있다. 18강. Controller 3단 분리하기기존의 Controller에 하나로 뭉쳐있는 코드를 Service와 repository로 나누는 리팩토링 과정의 실습을 진행하였다.Controller와1주차 과제과제 1질문 1. 어노테이션을 사용하는 이유 (효과) 는 무엇일까?질문 2. 나만의 어노테이션을 어떻게 만들 수 있을까? 평소 공부하면서 어노테이션을 사용하는 이유에 대해 명확하게 알지 못했다. 과제를 통해 어노테이션의 사용 이유와 효과, 각각의 기능이 어떻게 동작하는지 알 수 있었다. 특히, 롬복을 통해 getter, setter, 생성자를 자동으로 생성하고 주입하는 기능을 알 수 있었다. 나만의 어노테이션을 만들 때, @Retention 어노테이션을 사용하여 나만의 어노테이션을 정의하고 유지한다는 것을 알게 되었다. 과제 2 API 명세를 통해 GET, POST API 만들기 자신있게 시작했던 모습은 얼마 가지 않아 헤매는 부분이 있었던 것 같다. 총 3문제 중 첫문제만 Request 파일과 Response 파일을 만들 수 있었다. 시간에 쫓겨 못한 부분이 있었다. 다음에는 시간을 가지고 좀 더 많은 고민을 하면서 완성하고 싶다. 과제 3질문 1. 자바의 람다식은 왜 등장했을까?질문 2. 람다식과 익명 클래스는 어떤 관계가 있을까? 람다 도입 배경이 다른 언어에 뒤쳐지지 않고 큰 데이터를 다루기 위해 병렬성 활용과 간결한 코드의 중요성이 올라 갔기 때문이다.알지 못하고 사용했던 때보다 람다의 도입 배경을 알고 적절한 곳에 사용할 수 있을 것 같다. 람다식과 익명 클래스의 관계를 알아보며 람다는 사실 익명 클래스의 객체라는 점이 놀라웠다.
백엔드
・
인프런
・
워밍업
・
스터디
・
클럽
・
발자국




