블로그
전체 192024. 11. 07.
0
인프런 워밍업 클럽 2기 후기 - 백엔드 프로젝트(Kotlin, Spring)
인프런 워밍업 클럽 0기에서 좋은 지식을 쌓고 오랜만에 워밍업 클럽 2기를 시작한다는 소식을 듣고 살펴보니 커리큘럼과 강의들이 다 재편성되어서 바로 신청했습니다. 두 개의 강의를 신청하기엔 진도를 따라가는데 급급할 것 같아서 가장 듣고 싶었던 백엔드 프로젝트를 신청하게 되었는데Kotlin을 한번도 사용해보지 않아서 약간의 두려움이 있었습니다. 그래도 저번 0기에서 들었던 부분과 겹치는 부분이 있어서 좋았지만 대부분 새로운 내용을 학습하는데 시간이 걸렸고 항상 만족하는 부분은 다른 사람의 코딩을 보면서 ' 아, 이런 생각을 할 수 도 있구나. ' 하는 부분이였습니다.공부할때는 항상 너무 어렵고 힘들다가도 한 번 완주를 하면 알아듣는 부분이 늘어가는 성취감이 쌓이는 재미가 있습니다. 중간에 포기하고 그냥 듣지말까 하다가도 하루에 정해진 강의를 따라가다보면 꼭 얻는게 있으니 다들 꼭 완주는 하셨으면 좋겠습니다!항상 만족하고 얻어가는게 많은 인프런 워밍업 클럽!2기도 너무 만족스럽게 끝을 내게 되었습니다. 아쉬운 부분도 많지만 완주를 하면서 뿌듯함과 또 보너스 잎도 들어오니 항상 재밌고 만족스럽게 듣고 있습니다. 더욱 성장해가는 개발자를 위해 화이팅!!
인프런
・
인프런워밍업클럽
・
스터디2기
2024. 10. 27.
0
[인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring)] 4주차 발자국
4주차 발자국타임리프 문법th:onclick 해당 HTML 요소를 클릭했을 때 호출할 메소드를 정의th:for label이 어떤 input을 위한 것인지 정의, input의 id와 연결th:placeholder 사용자가 어떤 값을 입력해야할지 안내하는 내용을 표시 뷰 개발지금까지 배웠던 내용이 눈에 보이는 모습을 보고 조금씩 고쳐가면서 1,2,3주차를 학습하면서 했던 내용들이 점차 머릿속으로 더 잘 들어오게 되었다.head, header, footer 등 여러 공통 요소를 별도의 fragment로 분리해서 보기도 편하고 재사용성을 높였다. 오타를 신경쓰면서 작성하는데 오타 오류가 너무 많아서 놀랐다. 클래스 명을 잘 못 작성해서 몇 시간동안 붙잡고 있던적도 있었는데 그러는 과정에서도 오류를 읽는 방법이 늘어서 오류가 나면 전에 비해서 금방 찾게 되서 오히려 좋은 것 같기도 하다. 미션ERD설계부터 뭔가 복잡한 것 같아서 내용을 좀 간결하고 결합성 있게 다시 설계하다보니 갈아엎고 이해하면서 작성하느라 아직 미션 5를 제출하지 못하였는데 내일까지 열심히 만들어야겠다.
2024. 10. 20.
0
[인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring)] 3주차 발자국
3주차 발자국 컨트롤러 개발어노테이션@SpringBootTest 실제 애플리케이션과 유사한 환경을 구성하여 테스트를 실행@AutoConfigureMockMVC MockMVC 객체가 자동으로 구성되어 컨트롤 러를 모의로 테스트 할 수 있음 Thymeleaf 문법xmlns:th 타임리프의 네임스페이스를 선언th:fragment 템플릿의 일부를 재사용 가능한 fragment로 정의th:replace 해당 요소를 다른 요소로 대체할 때 사용th:href 링크의 URL을 동적으로 설정th:each 반복할 데이터의 개수만큼 HTML 요소를 생성th:class HTML 요소의 클래스를 동적으로 설정th:text 텍스트 컨텐츠를 동적으로 설정th:if 조건이 참일 경우 해당 HTML 요소를 표시하고, 거짓일 경우 표시하지 않음 관리자(admin) 개발오류의 종류Throwable 오류의 최상위 클래스Error 애플리케이션에서 대응할 수 없는 오류Exception 애플리케이션에서 대응할 수 있는 오류Unchecked Exception RuntimeException을 상속하는 모든 예외 (@Transactional에서 롤백의 대상) Checked Exception Exception을 상속하며 RuntimeException이 아닌 모든 예외, try-catch를 이용해 반드시 대응 코드를 작성어노테이션@ExceptionHandler 컨트롤러에서 던진 예외를 잡아 처리해주는 역할,컨트롤러마다 예외를 처리하는 중복 코드를 작성할 필요없이, 같은 예외를 공통적으로 처리할 수 있게 해줌@ControllerAdvice 범위 내의 모든 컨트롤러 클래스에 @ExceptionHandler를 공통적으로 적용 [미션4] 조회 REST API 만들기 미션을 만드는데 미리미리 강의랑 진도를 맞춰놓지않아 시간이 오래걸려서 조회 컨트롤러만 만들고 제출해버렸다. 오늘, 내일 시간을 들여서 고쳐야 한다. 조금씩 이해는 가는데 완전히 이해가 가지 않아서 반복학습을 해야할 것 같다. 3주차에 접어들면서 화면 사이트가 눈에 보이니까 만드는 재미가 있던 것 같다. 강의를 보면서 따라가기 급했지만 점차 이해가 가는 내용이 많아져서 즐거웠다. 그리고 자꾸 마지막 s를 빼먹어서 오타오류가 자주 난다. 확인 또 확인!
2024. 10. 13.
0
[인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring)] 2주차 발자국
2주차 발자국테스트 코드 작성규모가 큰 운영 서비스 이거나 운영 기간이 오래 된 서비스 같은 경우 테스트 코드의 가치가 커진다. 메소드를 사용하는 다른 메소드들에서 의도하지 않은 결과가 나올 수 도 있어서 서비스 전체 기능에 미치는 사이드 이펙트들을 사전에 감지할 가능성이 올라간다. 어노테이션@DataJpaTest @Entity 및 @Repository 어노테이션이 부여된 클래스들을 검색하 여 테스트 환경을 설정@TestInstance 테스트 인스턴스의 라이프사이클을 지정@BeforeAll 테스트 클래스 내의 모든 @Test 메소드 실행 전에 한 번 실행@Test 테스트 메소드를 지정@Autowired 의존성을 주입할 때 사용@DisplayName 테스트 클래스나 메소드의 이름을 정의 리포지토리 성능 개선N+1 문제 대표적인 JPA의 단점, JPA에서는 먼저 부모 테이블에서 조회를 한 후 엔티티의 연관관계를 바탕으로 조회해온 데이터의 개수만큼 부모에 매핑된 자식 테이블의 데이터를 조회하기 위해 데이터베이스를 호출 그래서 N+1번의 쿼리가 사용 Fetch TypeLazy : 런타임에서 연관관계 필드를 호출하는 경우 데이터베이스를 호출 (부모 데이터가 100개가 조회되더라도, 단 1개의 부모 데이터에서만 자식 데이터를 사용할 경우 1개의 쿼리만 나감)Eager : 부모 데이터를 조회하는 즉시 자식 데이터를 조회 (실제로 자식 데이터를 사용하지 않더라도, 조회된 부모 개수만큼 쿼리가 나감) Fetch Join : JPA에 의존하지 않고 직접 JPQL 쿼리를 보냄. Join을 활용해 한번에 부모와 자식 데이터를 조회 (하지만 OneToMany, 또는 ManyToMany 관계의 자식 엔티티가 여러 개일 경우, 하나만 조인할 수 있다는 한계가 있음) Batch Fetch Size : IN 절을 사용해서 여러 건의 데이터를 한번에 조회 (1+N의 쿼리가 1+(N/batch_fetch_size) 정도의 수준으로 줄어든다고 할 수 있음. 하지만 DBMS에 따라 IN 절의 파라미터 개수 제한이 있기도 하고, 한 번에 많은 데이터를 불러오는 것은 애플리케이션이나 데이터베이스 에 부담을 줄 수 있기 때문에 적절한 개수 설 정이 필요) 어노테이션@Transactional 트랜잭션을 간편하게 열고 닫을 수 있게 해줌rollbackFor 어떤 예외가 발생했을 때 롤백할지를 정의readOnly 읽기 전용 트랜잭션으로 설정. JPA를 사용할 경우, 더티체킹 등을 수행하지 않기때문에 성능상 이점이 있음isoation 트랜잭션 고립 수준을 정의@ExtendWith JUnit5에서 테스트 확장을 지원하는 어노테이션@InjectMocks Mockito에서 테스트 대상이 되는 클래스에 인스턴스를 주입하기 위해 사용@Mock Mockito에서 Mock객체를 생성할 때 사용 미션 [미션 3] REST API 설계하기설계할때 네가지 기능(PUT, GET, POST, DELETE)를 주로 사용하는데 ERD를 설계한 후 API를 설계하니까 POST를 사용하려는 계획이 많아졌다. 처음부터 잘 생각하고 다시 작성해야할 것 같다. 너무 어렵게 생각하지말고 간단히 해서 완성을 해보는 것이 좋을 것 같다는 생각도 들었다. 이번주는 저번주보다 더 알아듣기 편해졌다. 1주차가 가장 힘든 것 같다. 2주차부터는 수업에 알아듣는 것이 많아졌다. 그리고 오타도 많아져서 에러가 좀 났었다. 오타확인 필수!
2024. 10. 06.
0
[인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring)] 1주차 발자국
1주차 발자국 웹 개발 기본 개념프레임워크와 라이브러리웹 프레임워크프레임워크가 주도권을 가지고 있음동적 웹 서비스 개발을 편리하게 만들어주는 도구웹을 개발할 때 공통적으로 요구되는 기능들을 보다 편리하게 사용할 수 있도록 함라이브러리사용자가 주도권을 가지고 원하는 것을 만들 수 있음 프로젝트 환경구성DependenciesSpring Web : Spring MVC를 사용하여 RESTful을 포함한 웹을 구축Spring Data JPA: Spring Data 및 Hibernate를 사용하여 Java Persistence API로 SQL 저장소의 데이터를 유지H2 Database: 작은(2MB) 설치 공간으로 임베디드 및 서버 모드와 브라우저 기반 콘솔 응용 프로그램을 지원MySQL Driver: MySQL JDBC 드라이버Thymeleaf: HTML을 브라우저 및 정적 프로토타입으로 올바르게 표시Validation: Hibernate 유효성 검사기를 사용한 Bean 유효성 검사(Spring Security) 어노테이션 정리Id : 테이블의 기본 키(PK)에 대응함을 선언 Entity : JPA 엔티티임을 선언GeneratedValue : @Id의 생성 전략을 선언GenerationType.IDENTITY : PK 생성을 DBMS에 위임(MySQL에서는 auto_increment를 사용)GenerationType.TABLE : PK 생성 전용 테이블을 사용GenerationType.SEQUENCE : DBMS의 시퀀스 오브젝트를 사용하여 PK를 생성GenerationType.AUTO : JPA의 알고리즘에 따라 DBMS에 적합한 전략을 선택Column : 필드가 컬럼명이 기본 전략에 의해 변환된 값과 다 르거나 구체적인 속성을 따로 정의해주기 위해 사용MappedSupperClass : 여러 엔티티의 공통 컬럼을 갖고 있는 상위 클래스에 사용CreatedDate : 생성된 시간을 자동으로 저장LastModifiedDate : 수정된 시간을 자동으로 저장Enumerated : Enum 타입의 필드를 데이터베이스 컬럼과 매핑할 때 사용EnumType.ORDINAL : Enum 내부에 정의된 순서대로 번호를 지정해 값을 저장EnumType.STRING : Enum의 이름을 값으로 사용Columnnullable : 해당 컬럼이 null일 수 있는지 정의updatable : 해당 컬럼의 값이 변경될 수 있는지 정의OneToMany : 한 엔티티가 여러 다른 엔티티와 관계를 맺는 경우ManyToOne : 여러 엔티티가 한 엔티티와 관계를 맺는 경우 미션[미션1]테이블설계, [미션2] 깃허브 리포지토리에 프로젝트 올리기깃허브 리포지토리에 프로젝트 올리는 것은 문제가 없었으나 테이블 설계를 하면서 신경쓸 부분이 많아서 제출을 급하게 하게된 것이 아쉬웠다.다시 생각해보고 구상해서 테이블을 설계해야 할 것 같다. 그리고 ReadMe 파일도 제출 기한에 쫓겨 급박하게 작성해서 제출하느라 아쉬운 점이 많았다. 다시 보기 깔끔하게 올려야할 것 같다. https://github.com/lee-soohyun/StudentManagement
2024. 05. 09.
0
Public key for mysql-community-common.rpm is not
[AWS EC2] EC2에서 MySQL 설치시Public key for mysql-community-common.rpm is not installed 에러현상 sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023 sudo yum install mysql-community-server [에러메세지]가 나타나는 이유MySQL에서 사용하고 있는 GnuPG의 유효기간이 만료되었기 때문입니다.GnuPG(GNU Privacy Guard)란 패키지 무결성과 신뢰성을 확인하는 또 다른 방법은 암호화 서명을 사용하는 것을 의미하며, MySQL 다운로드 가능한 패키지는 GnuPG를 사용하여 서명되기 때문에 유효기간이 만료되어 설치되지 않는 것입니다.
2024. 03. 17.
3
[인프런 워밍업 클럽 BE 0기] 인프런 워밍업 클럽 후기
[인프런 워밍업 클럽 후기]인프런 강의를 듣던 중 인프런 워밍업 클럽을 모집한다는 소식을 듣고 고민하다가 결국은 도전해보자하여 듣게 되었는데 많은 것을 얻었던 시간이였습니다. 2일차부터 과제를 제출하는데 굉장히 어려움이 있었고 강의를 듣는 시간보다 그 과제를 제출하기 위해 공부했던 시간이 거의 4배는 되었던 것 같습니다. 질문도 하나씩 읽어보면 이해가 안가서 '기본지식도 없이 뛰어든 사람은 나뿐인가? 다른분들은 다들 너무 잘하시는 것 같은데'라는 생각이 들기도 하였습니다. 그래도 시간을 할애하다보니 조금씩 이해가 되어가기 시작했고 거의 마지막 주에 다시 질문들을 읽어보니 이해가 가고 도움을 얻기도 하였습니다.중간에 1차 OT를 까먹고 못들어서 스트레스도 굉장히 받았는데 그래도 포기하지 않고 과제를 제출하고 강의를 듣다보니 만회할 기회를 주셔서 감사하였습니다. 그리고 다른 분들의 코드를 보면서 정말 다양한 방법으로 풀어나가는 모습을 보면서 신기하기도 하였습니다. 그러면서 문제를 푸는데 있어서 새로운 방법을 알게되기도 하였습니다. 미션이 없었다면 사실 강의를 이해하지 못한채로 끝나지 않았을까 싶기도 합니다. 다른 러너분들도 열정이 넘치셔서 같이 얼떨결에 끌려간 것 같지만 정말 후회는 되지않는 수업이였던 것 같습니다. 마지막까지 열심히 Q&A를 진행하시는 모습을 보고 많은 감동을 느꼈습니다. 정말 감사합니다!
인프런
・
인프런워밍업클럽
・
스터디0기
2024. 03. 02.
0
[인프런 워밍업 클럽 BE 0기] 발자국 3주차
Day10 객체지향과 JPA 연관관계기존의 코드를 JPA 연관관계를 활용하는 방식으로 변경해보았다.연관관계를 사용하면 좋은 점으로는 각자의 역할에 집중할 수 있게 되고, 개발자가 코드를 읽을 때 이해가 쉬워진다는 장점이 있다. 하지만, 지나치게 사용하면 성능상의 문제가 생길 수 있고 도메인간의 복잡한 연결로 인해 시스템 파악이 어려워진다. @mappedBy : 연관관계의 주인이 아닌 쪽에 생성한다.@JoinColumn : 연관관계의 주인이 활용할 수 있는 어노테이션, 필드의 이름이나 null, 유일성, 업데이트 여부 등을 지정cascade : 한 객체가 저장되거나 삭제될때 변경이 폭포처럼 흘러 연결되어 있는 객체도 함께 저장되거나 삭제됨orphan Removal : 객체 간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션 Day11 기본적인 배포를 위한 준비 배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야하는지 알아보았다.git의 기초적인 사용법에 대해 알아보았다.AWS를 통해 클라우드 컴퓨터를 빌려보았다. --- 구분선을 사용해서 다른 profile이 적용되도록 yml파일을 수정하였다.AWS를 통해 클라우드 컴퓨터를 빌렸는데 중간부터 git에 src파일이 안올라가서 다시 처음부터했지만 해결이 되지않아서 찾아보았더니 소프트웨어를 잘 못 선택했었다. Day12 AWS와 EC2 배포 EC2에 접속해 리눅스 명령어를 다뤄보았다.개발한 서버의 배포를 위해 환경 세팅을 리눅스로 진행하고 실제 배포를 진행한다.foreground와 background의 차이를 이해하고 background 서버를 제어한다.도메인 이름을 사용해 사용자가 IP 대신 이름으로 접속할 수 있게 하였다. 도메인 IP를 설정하여 실행시키는 부분해서 오류가 났다. AWS에서 내 컴퓨터의 sql을 사용하지는 않을 거라는 답변을 받았다. 무엇을 놓쳤는지 다시 강의를 들으면서 놓친 부분을 찾아야겠다.그래서 나머지 부분은 강의만 들었다. 다시 따라해보아야겠다.복기를 하면서 틀린 부분을 찾았다. yml파일에서 비밀번호가 안바뀌어있었다! Day13 Spring Boot 설정, 버전업 이해하기 build.gradle에 대해 이해하고 Spring과 Spring Boot의 차이점에 대해서 알아보았다.application.yml과 application.properties.lombok에대해 알아보았다. Day14 마무리 및 추가 꿀팁 영상 수업을 마무리 하면서 공부 방향성에 대한 조언에 대한 강의를 들었고 사용했던 AWS EC2종료하는 방법과 AWS비용 계산하는 방법에 대해 알아보았다.MyBatis 적용하는 방법과 DB 접근 기술을 비교하고 스프링 부트의 정적 파일 처리에 대해 알아보았다. 3주차 후기이번 주는 AWS를 하면서 막히는 부분이 조금씩 있어서 검색을 몇 시간했는데 그러면서 오류코드가 더 잘 읽히게 되었다.시험 준비를 한다고 프로젝트를 시작 못한 것이 너무 아쉽지만 다음 주 부터 다시 복기하는 시간을 가져야겠다.3주동안 이해하느라 바쁘기도하고 따라가기위해 벅차기도 했지만 얻어가는 것이 정말 많은 3주였다.
2024. 02. 29.
0
[인프런 워밍업 클럽 BE 0기] 발자국 2주차
Day6 스프링 컨테이너의 의미와 사용 방법스프링 컨테이너와 스프링 빈이 무엇인지 이해하고 스프링 컨테이너가 왜 필요한지, 좋은 코드와 어떻게 연관이 있는지 이해하였다. 스프링 빈을 다루는 여러가지 방법을 이해한다.@RestControllerUserController 클래스를 API의 진입 지점으로 만들 뿐 아니라 스프링 빈으로 등록시킨다. 스프링 빈, 서버가 시작되면 스프링 서버내부에 거대한 컨테이너를 만든다. 컨테이너 안에는 클래스가 들어가게 된다. @Primary VS @Qualifier 우선 순위?-> 사용하는 쪽에서 직접 적어준 @Qualifier가 우선으로 적용된다. 미션https://www.inflearn.com/blogs/6852 문제 #1은 강의를 들으면서 따라갈 수 있었는데 문제 #2의 MySql과 Memory로 repository를 나누는데 다른 사람들도 memory부분은 못하신 분들이 많았다.memory부분은 아직 어떻게 해야할 지 감이 안 잡힌다. Day7 Spring Data JPA를 사용한 데이터베이스 조작문자열 SQL을 직접 사용하는 것의 한계를 이해하고 해결책인 JPA, Hibernate, Spring Data JPA가 무엇인지 이해하였고Spring Data JPA를 이용해 데이터를 생성, 조회, 수정, 삭제를 해보았다. 미션https://www.inflearn.com/blogs/6893 JPA로 바꾸는 방법을 이해하는데 시간이 걸렸지만 문제 #1, #2는 혼자 잘 만들어볼 수 있었다.문제 #3에서 다른 분들의 코드들을 보면서 GTE와 LTE로 나누는 부분이 조금 힘들었었다.코치님이 enum class를 사용하면, 코드만 보고도 option에 들어오는 문자열의 종류를 확인할 수 있고, 유지보수가 훨 씬 간결해지기 때문에 사용하기에 딱 적절하다는 팁을 남겨주셔서 머리를 탁치게 되는 시간 이였다. Day8 트랜잭션과 영속성 컨텍스트트랜잭션이 왜 필요한지 이해하고 스프링에서 트랜잭션을 제어하는 방법을 익혀보았다.영속성 컨텍스트와 트랜잭션의 관계를 이해하고 영속성 컨텍스트의 특징을 알아보았다. @TransactionalSELECT쿼리만 사용한다면 read Only옵션을 사용할 수 있다.영속성 컨텍스트란, 테이블과 매핑된 Entity 객체를 관리 / 보관하는 역할을 한다. 스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고 트랜잭션이 종료되면 영속성 컨텍스트가 종료된다. Day9 조금 더 복잡한 기능을 API로 구성하기 책 등록, 책 대출, 책 반납 API를 빠르게 만들어 보면서 지금까지 다루었던 모든 개념을 실습해 보았다. 2주차 후기2주차에 들어서니까 1주차에 비해서 코드 읽히는 속도가 빨라졌고 이해가 가는 것이 많아졌다질문 답변에서도 나만 이렇게 아무것도 모르나 생각이 들었지만 이제 조금씩 그 질문들이 이해가 간다.아직 복습이 완벽하지 않은데 워밍업 클럽이 끝나고서도 열심히 해야겠다.
2024. 02. 28.
0
[인프런 워밍업 클럽 BE 0기] 미니 프로젝트 1단계
프로젝트 1단계프로젝트 최초 설정이 이루어져야 합니다.(스프링 부트를 시작하는 첫 번째 방법을 떠올려보세요) 팀 등록 기능회사에 있는 팀을 등록할 수 있어야 합니다. 팀이 가져야할 필 수 정보는 다음과 같습니다.팀 이름직원 등록 기능직원을 등록할 수 있어야 합니다. 직원이 가져야할 필수 정보는 다음과 같습니다.직원 이름팀의 매니저인지 매니저가 아닌지 여부회사에 들어온 일자생일팀 조회 기능모든 팀의 정보를 한 번에 조회할 수 있어야 합니다.[ { "name": "팀 이름" "manager": "팀 매니저 이름" //없을 경우 null "memberCount": 팀 인원 수 [숫자] }, ... ]직원 조회 기능모든 직원의 정볼르 한 번에 조회할 수 있어야 합니다.[ { "name": "직원 이름", "teamName": "소속 팀 이름", "role": "MANAGER" or "MEMBER", //팀의 매니저인지 멤버인지 "birthday": "1989-01-01", "workStartDate": "2024-01-01", }, ... ] Controller@RestController public class CompanyController { private CompanyService companyService; public CompanyController (CompanyService companyService) { this.companyService = companyService; } @PostMapping("/team") public void saveTeam(@RequestBody TeamCreateRequest request) { companyService.saveTeam(request); } @PostMapping("/member") public void saveMember(@RequestBody MemberCreateRequest request) { companyService.saveMember(request); } @GetMapping("/team") public List getTeam() { return companyService.getTeam(); } @GetMapping("/member") public List getMember() { return companyService.getMember(); } } Teampublic class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; private String manager; public Team(String name) { this.name = name; } public void setTeamManager(String name) { this.manager = name; } } Memberpublic class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; private String teamName; @Enumerated(EnumType.STRING) private Role role; private LocalDate birthday; private LocalDate workStartDate; public Member(String name, String teamName, Role role, LocalDate birthday, LocalDate workStartDate) { this.name = name; this.teamName = teamName; this.role = role; this.birthday = birthday; this.workStartDate = workStartDate; } }Role(Enum 클래스)public enum Role { Manager, Member; } TeamCreateRequest@Getter public class TeamCreateRequest { private String name; }MemberCreateRequest@Getter public class MemberCreateRequest { private String name; private String teamName; @JsonProperty("managerORmember") private boolean memberORmeber; private LocalDate birthday; private LocalDate workStartDate; } Repository@Repository public interface MemberRepository extends JpaRepository { } @Repository public interface TeamRepository extends JpaRepository { Optional findByName(String name); }Response@Getter public class TeamResponse { private String name; private String manager; public TeamResponse(Team team) { this.name = team.getName(); this.manager = team.getManager(); } } @Getter public class MemberResponse { private String name; private String teamName; private Role role; private LocalDate birthday; private LocalDate workStartDate; public MemberResponse(Member member) { this.name = member.getName(); this.teamName = member.getTeamName(); this.role = member.getRole(); this.birthday = member.getBirthday(); this.workStartDate = member.getWorkStartDate(); } } CompanyService@Service public class CompanyService { private final TeamRepository teamRepository; private final MemberRepository memberRepository; public CompanyService(TeamRepository teamRepository, MemberRepository memberRepository) { this.teamRepository = teamRepository; this.memberRepository = memberRepository; } @Transactional public void saveTeam(TeamCreateRequest request) { teamRepository.save(new Team(request.getName())); } @Transactional public void saveMember(MemberCreateRequest request) { Team team = teamRepository.findByName(request.getTeamName()) .orElseThrow(IllegalArgumentException::new); Role role = request.isMemberORmeber() ? Role.Manager : Role.Member; memberRepository.save(new Member(request.getName(), team.getName(), role, request.getBirthday(), request.getWorkStartDate())); if(role.equals(Role.Manager)) { team.setTeamManager(request.getName()); teamRepository.save(team); } teamRepository.save(team); } public List getTeam() { List teams = teamRepository.findAll(); return teams.stream() .map(TeamResponse::new) .collect(Collectors.toList()); } public List getMember() { List members = memberRepository.findAll(); return members.stream() .map(MemberResponse::new) .collect(Collectors.toList()); } }