블로그

요즘 백엔드 취업 시장에서 코프링이 핫하다던데?

코틀린(Kotlin)은 젯브레인즈(JetBrains)에서 개발한 크로스 플랫폼 범용 프로그래밍 언어입니다. JVM 기반의 언어이면서 자바(Java)와 100% 호환되도록 설계되었습니다. 구글은 2019년부터 코틀린을 안드로이드 개발 공식 언어로 지정했어요. 간결한 문법, 안정성, 다양한 기능이 있다는 장점과 함께 전 세계적으로 사랑받고 있는 언어입니다.그동안 백엔드에선 자바 언어와 스프링 프레임워크의 조합이 가장 압도적인 점유율을 차지하고 있었는데요. 최근엔 코틀린을 도입하거나 자바를 코틀린으로 대체하려는 기업이 늘면서 코틀린 언어와 스프링 프레임워크의 조합, 일명 '코프링'이 주목받기 시작했습니다. 실제로 현재 취업 시장을 살펴보면 코틀린 언어를 다루는 능력을 자격이나 우대 사항으로 기재해 두는 기업을 어렵지 않게 찾아볼 수 있어요. 하지만 비교적 최근에 주목받고 있는 만큼 백엔드 현업에서의 코틀린 혹은 코프링 관련 사례나 자료를 찾는 건 쉽지 않죠.앞으로 사용이 더 늘어날 것으로 전망되는 코틀린, 코틀린과 코프링의 세계에 발 빠르게 뛰어들고 싶다면 지금 시도해 보는 건 어떨까요?•••Java 개발자를 위한실무밀착형 코프링을 배우고 싶다면?지금 인프런 프리즘 [자바 개발자를 위한 실전 코프링 입문 (Kotlin + Spring)]을 통해 학습해보세요.https://www.inflearn.com/roadmaps/703•••인프런 프리즘 브랜드 스토리 읽어보기 >>

백엔드코틀린Kotlin스프링SpringSpringBoot스프링부트코프링백엔드인프런프리즘InflearnPrism

ppusda

인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring) / 4주차 발자국

⭐ 1주 동안 배운 내용을 정리하고 회고하는 시간을 가져보자. 16~18 일차 - Admin ViewView3주차에 이어서 Admin View를 만들어보게 되었다.이번에도 마찬가지로 중복되는 부분을 fragment와 layout으로 분리하는 작업을 진행했다.그리고 사용자가 직접 업데이트 하는 부분이 있기에, 추가적으로 script-util 파일을 통해 API에 요청을 보내도록 했다. 19 일차 - GCP, Docker암호화배포 이전에 설정 값을 암호화 하기 위해 jasypt를 사용하게 되었다.Jasypt?애플리케이션을 배포할 때 공개되면 안되는 값들이 평문으로 올라가는 것을 막기 위해 암호화할 수 있는 라이브러리이다.아래는 암호화한 DB 비밀번호가 들어있는 설정파일의 일부분이다. datasource: url: jdbc:mysql://mysql:3306/portfolio username: root password: ENC(ZeronFrlX1yD4JW496HshMgc9t1kUrQi) driver-class-name: com.mysql.cj.jdbc.Driver ENC(암호화 내용)으로 내용이 암호화 되었음을 표시해야한다. 배포Docker를 이용해서 빌드 한 이미지를 Docker hub에 올리고 이를 서버에서 내려받아 사용했다.이후 GCP를 이용하여 서버를 생성하고, Docker를 이용해 MySQL과 프로젝트를 빌드 시켰다.마지막으로 도메인을 연결하고, 이를 제출하며 마지막 7번째 미션도 해결 할 수 있었다.결과물은 아래와 같다.Dongguk's Portfolio 20일차 - 서브 미션, 코드 리뷰3주차 때와 일정이 거의 겹쳐 3주차 발자국에 포함시키지 못했던 미션 5는 4주차에서 소개하겠다.각 API마다 3개의 테스트 코드를 작성해야 했기에 열심히 작성해서 테스트를 진행해보았다. 이번에도 관련 내용은 README에 정리했다.https://github.com/ppusda/MML 이후 마지막에는 최종 점검과 코드 리뷰 시간이 진행되었다.https://github.com/ppusda/MML/pull/1많이 배워갈 수 있었던 좋은 시간이 되었던 것 같아서 조언 받은 내용을 정리해보고자 한다. Rest API와 VersioningRest APIAPI를 작성할 때 Restful 하도록 고려를 하는 편이라고 생각했지만, 주의하지 못했던 부분이 있었다.자원을 복수형으로 명시/user ⇒ /usersVersioning사실 지금까지 코드를 작성해보면서 “API에 버저닝이 굳이 필요할까?” 라고 생각해서 배제 했었다.하지만 이번 코드리뷰를 받아보면서 버저닝의 중요성을 알게되었다.API의 기능이나 데이터 구조가 변경될 때, 기존 클라이언트가 정상적으로 작동하도록 하기 위해 버전 관리를 통해 이전 버전을 유지하도록 사용할 수 있다.⇒ 사용자가 원하는 버전을 사용하도록 할 수 있고, 업데이트 시기를 사용자가 정하도록 할 수 있다.이로 인해 호환성과 안정성을 챙기고 사용자 경험을 개선시킬 수 있다는 것을 알게되었다. find 와 get의 차이이 부분에 대해서는 진지하게 고민해 본 적은 없었던 것 같다.간단하게 find는 “DB에서 찾기”, get은 “찾아온 데이터를 가져오기”라고만 생각하고 코드를 작성했었다. 하지만 코치님께서 소개해주신 내용을 통해 좀 더 시선을 달리하게 되었다.How and why to decide between naming methods with "get" and "find" prefixes get~은 실패하지 않으며 짧은 시간으로 요소를 가져올 때 사용되는 단어.find~는 실패할 수 있으며 긴 시간이 소요되어 요소를 찾아낼 때 사용되는 단어. 위 내용을 참고해서 앞으로 메서드 네이밍을 사용할 때 주의해야 할 것 같다. Test code 방향성요즘에 가장 관심이 많이가고 그만큼 잘 모르겠는 내용이 테스트 코드이다.이번에 테스트 코드를 강의를 기반으로 혼자 프로젝트에 작성해다보니까 방향성에 대해 의문이 많이 들었다. “이런 테스트 코드를 어떻게 짜면 좋은건지 모르겠다” 라고 질문 드렸는데, 그 부분에 대해서 커버리지를 최대한으로 채워보면서 감을 익히면 좋을 것 같다고 말씀해주셨다. 추가로 관련해서 좋은 레퍼런스를 추천해주셨다.토스ㅣSLASH 21 - 테스트 커버리지 100% 내용을 들어보고 서브 미션 프로젝트를 고도화 할 때 좀 더 좋은 테스트 코드를 작성하도록 노력해볼까 한다. 총 회고4주차로 스터디가 마무리 되었다.이번 4주차에는 일정이 너무 많고 도저히 시간이 안나기도 했지만, 강의를 보면서 따라 친 부분들이 잘못 입력되어서 동작하지 않는 곳들을 찾아내는데 고생을 좀 하게 되었다.이상하게 로컬 환경에서는 잘되었는데, 배포만 하면 고장이 나서 원인을 찾기가 힘들었지만, 너무 간단한 오타들이어서 허무했다. 강의도 끝나고 더 이상 미션도 없어서 강제성은 사라졌지만 계속해서 나름대로 고도화를 진행해보려고 한다.최종 점검에서 아직 나의 부족한 모습들을 많이 보게 되었지만, 여러모로 자극도 많이 받아서 힘들어도 계속해서 학습을 이어나가려고 한다. 코치님도 너무 친절하셨고 꾸준하기가 쉽지 않아서 많이 몰아들었지만, 어찌저찌 마무리를 하게 되었다.이상의 내용은 개인 블로그에 완전 총 회고로 다시 한 번 작성하겠다. 모두 화이팅! (o゚v゚)ノ

백엔드워밍업클럽백엔드2기Kotlin

ppusda

인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring) / 3주차 발자국

⭐ 1주 동안 배운 내용을 정리하고 회고하는 시간을 가져보자. 11 ~ 12 일차 - Controller, Test, ViewTest이전과 이어서 Controller를 개발하고 이에 대한 Test를 작성하게 되었다.아래는 작성한 테스트 코드의 일부분이다.@DisplayName("[API 컨트롤러 테스트]") @SpringBootTest // Spring boot를 실제로 띄운 다음에 테스트가 진행 됨 @AutoConfigureMockMvc // MockMvc 관련 설정 세팅 class PresentationApiControllerTest( @Autowired private val mockMvc: MockMvc ) { // 생략 private fun performGet(uri: String): MvcResult { return mockMvc .perform(MockMvcRequestBuilders.get(uri)) .andDo(MockMvcResultHandlers.print()) .andReturn() } } @SpringBootTestSpring Boot 애플리케이션을 테스트 할 때 사용되는 어노테이션이다.실제 애플리케이션 환경과 유사한 환경을 구성하여 테스트를 진행할 수 있다.@AutoConfigureMockMvcSpringMVC를 모의로 테스트 할 때 사용되는 어노테이션이다.MockMvc 객체가 자동으로 구성하고 테스트 환경을 유사하게 구성할 수 있다.MockMvc는 실제 서버를 실행하지 않고도 컨트롤러의 요청 및 응답을 테스트 할 수 있도록 한다.여기서 SpringBootTest와 AutoConfigureMockMvc를 같이 사용하는 것에 모순을 느끼게 되었고, 좀 더 내용을 찾아보게 되었다.@WebMvcTestMVC 레이어를 테스트하는데 사용되는 어노테이션이다.특정 컨트롤러와 그에 관련된 컴포넌트들만 주입받아 특정 컨트롤러의 동작을 집중적으로 테스트 할 수 있다.MockMvc를 자동으로 설정하고 주입하여 요청 및 응답을 테스트 할 수 있도록 한다.기본적으로 Service, Repository를 제외하기에 테스트가 가볍고 빠르게 진행될 수 있다. ⇒ 필요하다면 Mock 객체를 통해 주입할 수 있다.결론적으로 말하자면 아래와 같다.💡 Contorller를 집중적으로 테스트하기 위해서는 @SpringBootTest, @AutoConfigureMockMvc 를 사용하는 것 보다 @WebMvcTest를 사용하는 것이 Controller 단위 테스트 패턴에 적합하다. 그렇다고 @SpringBootTest를 사용하면 안된다는 것은 아니다.실제로 통합 테스트를 작성할 때 (=Controller, Service, Repository 등 여러 컴포넌트가 함께 작동할 때)는 SpringBootTest를 사용하여 실제로 적용될 설정이나 복잡한 시나리오가 정상적으로 작동되는지 확인할 때 사용할 수 있다. ViewThymeleaf를 활용해서 포트폴리오 페이지를 만들어 보게되었다.강사님 처럼 고양이 사진을 넣어보고 싶었지만, 어울리는 사진을 넣기가 힘들기에 가지고 있던 다른 사진으로 대체해보았다.기존 코드에서 중복되는 부분을 fragment와 layout으로 분리하였고, 이를 이용하여 더 깔끔한 코드를 만들 수 있었다. 13 ~ 15 일차 - Admin Controller, ServiceKotlin13 ~ 15일차에 접어들면서 대부분의 Kotlin 문법에 대해서는 꽤 익숙해진 것 같다.3 ~ 5일차에는 변수 선언과 함수 선언 그리고 문자열 작성에 대한 차이점을 다뤄봤었다.이번에는 강의에서 만나봤던 차이점 중 간편하다고 느꼈던 차이점에 대해서 정리해보고자 한다. 먼저, for 문이 간편하게 바뀌었다는 생각을 많이 하게 되었다.변수 선언같은 부분도 사라지고, .. 을 이용해 1에서 10까지의 범위를 지정할 수 있었다.물론, until과 같이 이전 값 까지 증가와 같은 기능과 step()을 이용하여 증가 값을 설정할 수도 있다.for (i in 1..10) // Kotlin for (int i = 1; i <= 10; i++) // Java  두번째로 @RequiredArgsConstructor를 사용하지 않아도 되는 부분이 매우 편했다.Kotlin은 class 생성 시 기본 생성자를 매우 쉽게 작성할 수 있고, 이를 프로퍼티로 생성 해준다. 여기서의 프로퍼티는 자바에서의 필드 뿐만 아니라 getter, setter를 포함한다고 생각하면 된다.이에 대한 자세한 내용은 아래 블로그를 참고하면 도움이 될 것이라고 생각한다.[Java/Kotlin] 필드(Field)와 프로퍼티(Property)는 무슨 차이가 있을까? 이때, val로 매개변수를 선언하게되면 Java 로 생각하자면 불변 필드와 해당 필드에 대한 생성자까지 만들어지게 된다.@Service class AdminAchievementService( private val achievementRepository: AchievementRepository ) @Service public class AdminAchievementService { private final AchievementRepository achievementRepository; public AdminAchievementService(AchievementRepository achievementRepository) { this.achievementRepository = achievementRepository; } // 생략 } 그렇기에 Java에서 생성자 주입을 통해 의존성을 주입 할 수 있게 되며 별도의 @RequiredArgsConstructor 어노테이션을 사용하지 않아도 된다. 마지막으로 그외에도 다양한 편의 기능들이다.val pageAttributes = mutableMapOf<String, Any>( Pair("menuName", "Resume"), Pair("pageName", table.name), Pair("editable", true), Pair("deletable", false), Pair("hasDetails", false) ) // Kotlin에서 Map에 데이터를 넣어줄 때 Pair를 사용할 수 있음 val id = line.slice(0..endIndex).toLong() // Long 타입으로 변환 Pair와 같이 Map에 데이터를 더 편하게 넣을 수 있도록 Key-Value 형식으로 값을 설정할 수 있었고,String의 경우도 단순히 toLong()과 같은 기능으로 변환을 쉽게 할 수 있었다. 이런 것들이 별 것 아닌 거 같아보여도 실제로 쌓이다 보면 꽤나 피로한 요소들이어서 정말 좋다고 느꼈던 것 같다.아직 모르는 부분도 분명히 많을 것이지만 Kotlin을 통해서 만족감을 느낄 수 있었다. 퍼사드 패턴이 과연 유리한가?다음으로 정리할 내용은 퍼사드 패턴에 대해서다.퍼사드 패턴(Facade Pattern)은 구조 패턴의 한 종류로 복잡한 서브 클래스들의 공통적인 기능을 정의하는 상위 수준의 인터페이스를 제공하는 패턴이다. 이는 사실 8 ~ 10일차에서 Service, Repository 부분에서 소개했던 한 번에 의존관계를 주입받을 수 있는 Repository 를 생성했다는 내용에서 사용 된 패턴이다.사용할 때는 사실 모르고 사용했지만, Admin 부분에 대한 강의에서는 퍼사드 패턴을 사용하지 않고 필요한 컴포넌트들만 주입받아 사용하며 강사님께서 “퍼사드 패턴과 이런 방식 중에 뭐가 더 유리한지 생각해보면 좋을 것 같다”고 말씀해주셨다. 기존에 코드를 작성할 때는 디자인 패턴에 대해서 모르기도 했었지만 항상 필요한 것만 불러와서 써도 괜찮다고 생각하고 있었다.하지만, 실제로 퍼사드 패턴을 사용하면서 느낀 점은 “컴포넌트를 묶어 사용할 수 있다면 코드가 많이 줄겠다” 였다.실제로 Presentation 부분에서는 컴포넌트를 묶음으로써 생성자 부분에 작성하게 된 코드를 줄일 수 있었다. 하지만, 반대로 생각해보면 묶을 필요가 없다면 굳이 사용할 필요가 없는 패턴이기도 하다.실제로 Admin 부분을 개발하면서는 각 도메인 부분으로 폴더를 나눠 서비스 로직을 작성했으며, 각 서비스 단에서 필요한 리포지토리만 불러와 사용하였다. 결론적으로 상황에 따라 유리할 수도 있고, 아닐 수도 있다.학습하다 보면 항상 결론은 위와 같이 나오는 것 같은데 그만큼 상황에 맞는 구조와 코드를 잘 적용시키는게 중요하다는 생각을 하게되었다. 서브 미션이번 3주차에는 미션 4를 수행하게 되었다.미션 4는 조회 API를 만들고 이에 대한 테스트 코드를 작성하는 것이 내용이었고 열심히 작성해서 제출하였다.이후 미션 내용을 README에도 정리해보았다.https://github.com/ppusda/MML서브 미션을 진행하면서도 약간의 배운 점이 생겨서 이 또한 정리해보고자 한다. TargetEntity와 MappedBymappedBy의 경우는 많이 사용해봤지만, TargetEntity의 경우는 사용한 적이 없어 어떤 차이가 있는지 궁금하게되었다.targetEntity관계의 대상 엔티티를 명시적으로 지정할 때 사용된다. (관계 대상을 명시적으로 지정)주로 @OneToMany, @ManyToMany와 같은 어노테이션에서 사용하며, 관계의 상대 엔티티 클래스의 이름을 지정한다.mappedBy관계의 주체와 종속성을 정의합니다. (관계의 주체를 설정함)주로 양방향 관계에서 사용되며, 관계의 주체가 되는 쪽에서 어떤 필드가 관계를 관리하는지를 지정한다. 총 회고3주차에는 드디어 화면을 직접 개발해보면서 좀 더 포트폴리오에 다가가고 있다는 생각이 들었다.미션을 따라서 진행하면서 좀 더 나만의 포트폴리오 처럼 꾸밀 수 있는 방법은 뭐가 있을까에 대해서도 생각해보려고 한다. 개발적으로도 성장하고 있는 것이 느껴지기도 하지만 그와 동시에 많이 멀었다는 생각도 계속하게 된다.특히 강사님의 코드를 보며 학습하다보니까 구조나 코드가 정말 정갈하다는 생각을 하게되었다.위에서 정리했던 Kotlin의 특성이나 디자인 패턴을 정말 잘 적용한다는 생각이 들었고, 앞으로 나도 그렇게 할 수 있는 개발자가 되고 싶다는 생각을 하게 되었다. Kotlin에 대해서도 많이 익숙해진 것 같다.Kotlin이 Java의 상위호환이라는 말을 많이 들었는데, 이전 경험이 있어서 크게 기대는 하지 않고 학습을 진행했다.하지만 정말 편한 점이 많이 있었고, 더 공부해서 코루틴 같은 개념도 적용해보고 싶다고 생각했다. 앞으로도 계속 개발자로 공부할 수 있도록 노력해야겠다.모두 화이팅! (o゚v゚)ノ

백엔드워밍업클럽백엔드2기Kotlin

river_bori

인프런 워밍업 클럽 2기 - 백엔드 프로젝트 (Spring, Kotlin) 2주차 발자국

일주일간 학습한 내용 요약개발 - Domain프로젝트 생성Jar로 해야지 스프링부트에서 제공하는 내장 키트를 사용할 수 있다.Dependensies: 프로젝트에서 쓸 외부 라이브러리들을 추가해 주는 작업6개의 라이브러리 추가Spring web: MVC사용 등Thymeleaf: 템플릿과 데이터를 합쳐서 최종적으로 완성된 html 파일을 만들어준다. (없으면 개발자가 html 파일까지 코드를 짜야함)Spring Data JPA: JPA에 껍데기를 씌서 사용성을 높임My SQL DriverH2 Database: 인메모리 DB로 스프링이 켜질때 같이 켜짐(스프링과 같은 메모리 사용), 꺼질때 데이터 사라짐Validation: 검증기능그외Spring Security: 로그인 기능에 사용하지만 지금 설치하면 스프링 킬때마다 로그인 해야해서 일단 설치 제외 IntelliJ 설정프로젝트 스트럭쳐프리퍼런시스 Git과 GithubGit 용어commit: 현재 작업한 내용을 하나의 버전으로 반영(저장)rollback:작업한 내용 이전 버전으로 되돌리기branch: 하나의 프로젝트에서 독립적, 병렬적인 버전으로 가지같이 여러명이 동시에 개발이 가능하게 만듬merge: 서로 다른 branch를 합치는 동작conflict(충돌): merge할 때 하나의 파일이 두 브랜치에서 수정이 발생해, 어떤 수정본을 반영할지 알 수 없는 상황repository: github의 저장소 (=remote repository) push: 원격 저장소로 브랜치를 업로드 하는 동작pull: 다운받는 것Git 명령어git status, add, pull, clone, push, commit -m터미널에서'pwd' 입력 => 폴더의 경로 확인'git init' 입력 => 깃 폴더 초기화'git status'입력 => 깃에서 관리하지 않는 파일들이 빨간색으로 표시됨. 그 중 관리하지 않아도 된는 파일들을 배제하고 등록해줌.'.gitignore' 파일에 입력해서 배제 가능함 => gitignore.io 에서 목록 개발환경 입력 후 복사 가능git add README.md : 특정 파일을 기초적 대상으로 추가하는 명령어git commit -m "first commit" : 현재 변경된 내용을 새 버전으로 반영하는 명령어'-m' 옵션을 통해 ""(쌍따옴표)안에 있는 내용을 커밋 메시지로 입력 git remote add origin http://github.com/주소경로: 원경 저장소를 추가 프로젝트 환경 변수 설정데이터 소스와 jpa설정 => 설정해놓은 값을 복붙함.위와 관련된 중요 개념이런 설정 값들은 보통 상수(=변하지 않는 값)다.DB url 등자바 코드에 " "(literal, 문자열 방식)으로 관리해도 되지만, 같은 값을 여러 클래스에서 사용할 때, 값이 수정되면 모든 클래스에서 사용한 값들을 다 찾아서 수정해줘야 한다. 이때, 하나라도 놓치면 에러가 난다.실제 운영할 때는 개발용 서버, 운영용 서버, 개발용 DB, 운영용 DB로 나눠서 사용한다.서로 다른 서버 컴퓨터에서 똑같은 프로그램이 돌아가는데 서로 다른 DB서버에 붙어있다. 개발DB와 운영DB의 주소는 다르다 => 환경변수(=환경마다 바뀌는 값)개발서버에게 개발DB URL을, 운영서버에게 운영DB URL을 알려줘야하는데 " "(문자열 방식)으로는 관리가 어렵다.Spring Profile과 application.ymlSpring Profile: 스프링은 돌아가는 애플리케이션의 프로필을 정의하는 기능을 제공, 스프링을 실행시키는 시점에서 환경변수로 정의 가능개발 서버에 스프링 프로젝트를 띄울 때 dev라는 프로필로 돌릴거라고 지정하고, 운영서버에 prod라는 프로필로 지정하면, 각자 dev, prod로 세팅이 된다. 아무것도 세팅을 안하면 기본값은 default라는 이름으로 돈다. No active profile set, falling back to 1 default profile: "default" application.yml: 스프링은 프로필마다 환경변수를 설정하는 기능을 제공, application.properties: YML 파일과 똑같은 기능을 한다. (문법이 좀 다르다)기존에 있는 properties 파일을 yml로 변경. => application-default.ymlyml 파일을 복사해서 application-docker.yml 을 만듦.application-{Profile} 형식으로 위와 같이 파일을 네이밍 해주면, Profile에 따라 상수 값 설정이 가능하다.스프링이 실행될 때 프로필이 default면 application-default.yml 에서 환경변수를 가져오고, 프로필 이름이 docker면 application-docker.yml 에서 환경변수를 가져온다.키-밸류 형식으로 등록할 수 있다. 같은 키에 값만 다르게 등록한 것. 소스 코드에는 String DATASOURE_URL_PROPERTY = "spring.datasource.url";로 등록해주면 프로필에 따라 각 키에 맵핑값을 찾아 각 DB와 연결하고 동작한다. ymljpa에 대한 설정open-in-view: false => 나중에 따로 설명show-sql: true => sql을 로그에 보이게 할지hibernate: ddl-auto: create => JPA 엔티티를 바탕으로 jpa에서 데이터베이스에 테이블을 새로 만들어주는 기능, 개발 테스트할 때는 써도 되지만 운영에서는 무조건 None으로.properties: hibernate: format_sql: false => sql 로그를 찍을 때 좀 더 보기 쉽고 이쁘게 만들어주는 것, 근데 한줄로 보이게 false처리# default_batch_fetch_size: 10 => 강의에서 따로 설명 예정datasource에 대한 설정(docker.yml과 내용이 다름)url: jdbc:h2:mem:portfolio => db에 url을 알려주고username: sapassword: => 접속하기 위해 필요한 사용자명과 pw알려준다.driver-class-name: org.h2.Driverh2에 대한 설정(default.yml에만 있다. Mysql에는 아래와 같은 설정이 없기 때문)console: => H2에서 DB에 접속하기 위해 사용하는 H2콘솔enabled: true => 을 사용하고path: /h2-console => 어떤 경로로 접속할 건지 지정해주는 옵션클래스 생성도메인 패키지에서 개발할 클래스들을 미리 껍데기만 만듦포트폴리오 패키지도메인 패키지constant (in 상수 관련 클래스)entity (in 총 11개의 클래스)repository (in 총 8개의 인터페이스)configuration (암호관련-나중에 만듦)entity 패키지 (11개 클래스)BaseEntity(추상클래스):모든 테이블들이 공통적으로 갖는 Created Date Time, Updated Date Time 컬럼들은 각 클래스에 직접 넣지 않고 상속을 활용할 예정@MappedSupercass: 이 어노테이션이 있는 클래스를 상속 받는 엔티티 클래스가 이 클래스 안에 있는 필드들을 해당 엔티티에 있는 테이블의 컬럼과 맵핑 할 수 있다.Achievement: BaseEntitiy클래스를 상속받는다.@Entity: 이 어노테이션을 달아줘야 JPA에서 테이블과 맵핑되는 엔티티 클래스라는 것을 알 수 있다.@Id: JPA엔티티에는 필수인 어노테이션, 필드 위에 입력. (var id가 하나의 필드) @Id를 붙여줘야지 이 필드가 PK라는 것을 알 수 있다.=> match case(설정)을 끄면 자동완성을 도와준다.@GeneratedValue(strategy = GenerationType.{다양}: PK생성 전략을 정해준다. strategy 파라미터를 통해 정한다.{다양}TABLE: pk를 만들기 위한 테이블을 전용으로 만들어 PK생성(?)SEQUENCE: DB가 제공하는 순서대로 번호를 지정해주는 시퀀스라는 기능을 사용(MySQL에서는 사용불가)IDENTITY: 기본 키 생성을 DB에 위임. MySQL의 경우 Auto Increment라는 기능을 이용. => 이거로 사용AUTO: JPA가 내부 알고리즘을 따라 자동적으로 결정하는데 MySQL에서는 AUTO로 하면 앞의 TABLE을 사용한다.@Column(name = "achievement_id"): 이 필드가 DB에서 어떤 이름을 가진 컬럼이랑 맵핑되는지 개발자가 직접 지정해주는 기능.안붙여도 필드는 CamelCase(isCamelCase), DB는 SnakeCase(is_snake_case)로 되어 있으면 알아서 맵핑 컬럼을 찾아준다.테이블 pk는 테이블명_id로 지정하고, 코틀린 엔티티에서는 필드명을 id로만 지정. (나중에 이해 안가면 강의 다시 듣기 (7:00) )엔티티 인스턴스를 사용할 때val achievement: Achievement로 변수명을 해줌. 필드명을 id로 줄이지 않으면 achievement.achievementId로 id를 조회해야 함.achievement.id로 직관적이고 보기도 좋게 사용하고 싶음=> 때문에@Column(name = "achievement_id") var id: Long? = null로 지정자료형 뒤에 ?를 붙이면 null이 허용된다는 의미, 코틀린은 자바보다 null에 대해 엄격하다.id는 엔티티를 처음 생성할 때 들어가지 않고 이 엔티티를 DB에 저장할 때 DB에서 생성해 주는 값이기 때문에 인스턴스를 처음 만든 순간에는 null일 수 밖에 없다.Achievement 클래스를 복사해서 다른 클래스들을 만든다. (@Column의 name등 바꾸기) repository 패키지 (8개 인터페이스)Spring Data JPA RepositoryRepository: DB 접근하는 역할Spring Data JPA: 스프링에서 jpa를 좀더 쉽게 쓰기 위해 한번 랩핑한 라이브러리인터페이스를 추가하는 것만으로 DB CRUD와 관련된 기본적인 기능을 사용 가능각 엔티티에 대응해 interface로 각각 repository를 만들어야한다.BaseEntity 클래스 제외스프링을 시작할 때 SpringDataJPA에서 인터페이스를 보고 알아서 repository 클래스를 만든다. AchievementRepositoryinterface AchievementRepository : JpaRepository<Achievement, Long>JpaRepository<Achievement, Long>를 상속받음.<>을 Generic으로 명칭 나머지 엔티티에 대응하는 레퍼지토리 만들기...Detail 클래스에 대응하는 repository는 안만든다. JPA가 연관관계를 가진 엔티티를 통해서 엔티티를 불러올 수 있기 때문...Skill 클래스는 따로 만들어준다 => 왜? 뭔가 다르데 constant 패키지SkillType: enum 클래스 => 상수값(언어, 프레임워크, BD, Tool) 엔티티 개발 - 연관관계 없음BaseEntity.kt@CreatedDate: JPA엔티티가 생성된 시간을 자동으로 세팅@Column(nullable = false, updatable = false): 지난번 Name 파라미터를 이용해 필드와 맵핑될 Column의 이름을 별도로 지정해주는 기능(@Column(name = "achievement_id")) 설정함. 그것과는 다른 기능을 설정. 위 내용은 null일 수 없고, 변경 불가능 하다는 뜻. (다른 엔티티와) 연관관계가 없는 엔티티Skill 같은 경우, 프로젝트와 프로젝트 스킬을 통해서 연관관계를 가지지만, 스킬을 통해 프로젝트에 직접 접근하는 일이 없다 -> 때문에 연관관계가 없는 엔티티와 다를게 없다.엔티티 같은 경우, 연관관계에 상관없이 생성자를 이용해 처음 인스턴스를 생성할 때 필요한 값들을 전부 받으려고 한다.때문에 기본 생성자부터 만든다생성자(영어: constructor, 혹은 약자로 ctor)는 객체 지향 프로그래밍에서 객체의 초기화를 담당하는 서브루틴을 가리킨다. 생성자는 객체가 처음 생성될 때 호출되어 멤버 변수를 초기화하고, 필요에 따라 자원을 할당하기도 한다. 객체의 생성 시에 호출되기 때문에 생성자라는 이름이 붙었다.[위키백과]Achievement.kt기본 생성자를 만든다id 아래에 필드들을 만든다. -> 생성자에서 받은 값들을 넣어준다. (초기화한다.)Introduction.kt와 Link.kt도 비슷하다.Skill.kt생성자 중 type: String(일단은 문자열로 받음)=> 데이터를 처음 만들 때, 어드민 프론트에서 데이터를 받아서 세팅을 해주는데, 어드민에서는 이런 타입 같은 것을 알 방법이 없기에 문자로 보냄=> 문자로 받고 생성자 내부적으로 타입 스트링에 맞는 스킬 타입을 찾아 필드에 넣어줄 것임.var type: SkillType = SkillType.valueOf(type)SkillType.kt에서 문자열과 일치하는 enum을 찾아서 리턴해줌.jpa에서 활용하려면 좀 더 지정해줘야 함.@Column(name = "skill_type")(type을 예약어로 쓰는 DB가 있기 때문에 테이블 컬럼명으로 'type' 쓰는 것을 지양해야 함.)@Enumerated(value = EnumType.STRING)자료형이 enum클래스일 때 쓰는 어노테이션.EnumType.{STRING|ORDINAL} 두 개 중 선택 가능ORDINAL: enum이 선언된 순서대로 1, 2, 3...의 값을 DB에 넣어줌.1) DB를 봤을 때 직관적으로 이 데이터의 실질적 의미를 알기 어렵다.2) 어떤 개발자가 enum의 순서를 바꿨을 때, 데이터의 정합성이 깨짐STRING: enum의 이름 그대로 DB에 넣음(지정 필수)(DB의 용량을 약간 더 차지하는 단점 존재)HttpInterface.kthttp 요청 정보를 저장하는 엔티티class HttpInterface(httpServletRequest: HttpServletRequest): 스프링에서 요청을 받을 때 그 request의 정보를 여기에 담아서 준다. => 클라이언트 정보를 꺼낸다.var cookies: String? = httpServletRequest.cookies?.map{"${it.name}:${it.value}"}?.toString():.map{ }은 cookies라는 객체가 배열인데 안의 것들을 하나씩 순차적으로 돌면서 {중괄호}안에 들어간 함수대로 변환해주는 기능. it은 cookies 객체. cookies안에 name과 value가 있어 중괄호 안의 방식으로 포맷팅 되어진다.=> 쿠키에는 이용자가 본 내용, 상품 구매 내역, 신용카드 번호, 아이디(ID), 비밀번호 IP 주소 등이 배열로 담겨 있어 위 작업은 그 중 name과 value를 꺼내는 동작이다.(?).toString()으로 문자열로 바꾼다. => "name:value"HTTP 쿠키(HTTP cookie)란 웹 서버에 의해 사용자의 컴퓨터에 저장되는, '이름을 가진 작은 크기의 데이터'이다. 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 사용자의 웹 브라우저를 통해 인터넷 사용자의 컴퓨터나 다른 기기에 설치되는 작은 기록 정보 파일을 일컫는다. 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다. 이 기록 파일에 담긴 정보는 인터넷 사용자가 같은 웹사이트를 방문할 때마다 읽히고 수시로 새로운 정보로 바뀐다. 이 수단은 넷스케이프의 프로그램 개발자였던 루 몬툴리가 고안한 뒤로 오늘날 많은 서버 및 웹사이트들이 브라우저의 신속성을 위해 즐겨 쓰고 있다. (=> 신속성 = 서버크기 예측..?)쿠키는 소프트웨어가 아니다. 쿠키는 컴퓨터 내에서 프로그램처럼 실행될 수 없으며 바이러스를 옮길 수도, 악성코드를 설치할 수도 없다. 하지만 스파이웨어를 통해 유저의 브라우징 행동을 추적하는데에 사용될 수 있고, 누군가의 쿠키를 훔쳐서 해당 사용자의 웹 계정 접근권한을 획득할 수도 있다.[위키백과]referer: nullable한 필드, http 요청 정보에서 referer을 가져온다. 구글을 통해 검색해 어떤 사이트에 들어갔을 때, google.com의 도메인이 referrer(조회인)가 되는 것임웹 브라우저로 월드 와이드 웹을 서핑할 때, 하이퍼링크를 통해 각각의 사이트로 방문시 남는 흔적, 웹 사이트의 서버 관리자가 사이트 방문객이 어떤 경로로 자신의 사이트를 방문했는지 알아볼 때 유용, referer은 but 조작 가능, HTTP 리퍼러를 정의한 RFC에서 'referrer'을 'referer'로 잘못 입력한 것이 계속 사용됨[위키백과]localAddr, remoteAddr, remoteHost:클라이언트와 관련된 ip 주소들requestUri: 우리 서버에서 어떤 uri로 접속을 했는지, 메인이면 그냥 루트 or /, 프로젝트면 /프로젝트, resume면 /resume 로 어떤 uri로 접속했는지 그 정보가 들어온다. (referer과 다른 점은 어디에서 검색해서 사이트에 들어왔는지와, 사이트에서 이동 경로 추적 차이..?)통합 자원 식별자(Uniform Resource Identifier, URI)는 인터넷에 있는 자원을 나타내는 유일한 주소이다. URI의 존재는 인터넷에서 요구되는 기본조건으로서 인터넷 프로토콜에 항상 붙어 다닌다.URI의 하위개념으로 URL, URN 이 있다. [위키백과]userAgent:사용하는 브라우저 정보, 크롬, 사파리, 모바일, 데스크탑 등등 엔티티 개발 - 연관관계 있음Experience.kt생성자에 초기값을 넣는다.필드를 선언한다.Experience Entity는 ExperienceDetail과 1:N의 관계jpa에서는 List로 N쪽에 해당하는 필드를 가져올 수 있다.@OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) @JoinColumn(name = "experience_id") var details: MutableList<ExperienceDetail> = mutableListOf()@OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL]):One은 Experience, Many는 ExperienceDetail. 아래 필드가 1대 다의 관계를 가지고 있다고 jpa에 알려주는 어노테이션(targetEntity = ExperienceDetail::class,...): 어노테이션의 옵션 => targetEntity는 나중에 별도의 강의에서 설명 예정fetch = FetchType.{EAGER|LAZY}:EAGER은 더 열심히고 열정적인 경찰이래, 사건이 일어나면 용의자인 experience를 잡아야하는데, experienceDetail이 자식같은 관계니까 연관된 detail까지 다 잡아온다.개발자가 DB에서 experience만 조회하려고 했는데, detail까지 같이 인스턴스 안에 들어가 있다.그래서 EAGER는 쓰면 안된다. 오래걸린다. N+1의 문제인다(부모를 조회하려고 쿼리가 나가고 그다음 자식이 있다는 것 알고 자식을 조회하려고 쿼리가 N번 더 왔다갔다함, 부모 100명 조회 1번, 자식 100명 조회 100번 => 총 101번 쿼리 발송)LAZY는 좀더 효율적이다. 부모를 조사하다가 자식도 혐의가 있을 때만 잡으러 간다.부모 엔티티에서 실제로 자식 엔티티 필드를 호출하는 그 순간에만 조회쿼리가 나간다. 호출한 부모 엔티티의 자식 엔티티를 모두 조회해야할 때에는 EAGER과 다를 바가 없기 때문에 근본적인 해결책은 안된다.처음부터 부모와 자식을 한꺼번에 조회하는 방법은 레포지토리 개발하면서 설명할 예정cascade = [CascadeType.ALL]:영속성 콘테스트와 관련있는 개념, experience 엔티티가 영속성 콘테스트와 관련해서 발생하는 모든 변화에 자식 엔티티도 똑같이 적용할지 정해주는 옵션. ALL이면 모두 똑같이 적용한다는 뜻@JoinColumn(name = "experience_id")맵핑에 기준이 되는 컬럼을 알려준다.var details: MutableList<ExperienceDetail> = mutableListOf()mutableListOf: 빈 리스트를 만들어 준다.Mutable: '변할 수 있다' 라는 뜻 fun getEndYearMonth():종료연월을 각각 널체크하고 처리하면 서비스 코드가 복잡해지기 때문에 필요한 데이터를 한 번에 깔끔하게 서비스에서 가져올 수 있도록 엔티티 안에서 묶어줌fun update(생성자 모두 받음): put...각각 호출해서 수정하는 것보다 update하나를 호출해서 모두 한꺼번에 데이터 변경 가능하게 함jpa는 엔티티의 데이터를 바꾸기만하면 트랜젝션이 끝날 때, 처음 데이터를 가져올 때 따로 백업했던 스냅샷과 지금 엔티티의 상태를 비교해서 수정된 부분이 있으면 알아서 업데이트를 날린다.fun addDetails(details...):null 체크를 포함한 기본 방어 로직, 사용하는 쪽에서 깔끔한 디테일 데이터 추가 가능ExperienceDetail.kt: 연관관계 없는 엔티티와 비슷experienceDetail만 가지고는 experience를 찾을 수 없는 일대다 단방향 연관관계fun update(content: String, isActive: Boolean):Project.kt, ProjectDetail.kt 는 experience, ...detail과 비슷@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]) var skills: MutableList<ProjectSkill> = mutableListOf()@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]):mappedBy:양방향 연관관계에서 연관관계의 주인을 지정할 때 사용. ProjectSkill.kt안에 var project를 추가하는데, 이 var project를 통해 맵핑이 되고 맵핑하는 것은 ProjectSkill(연관관계에서 주인)이다.cascade = [CascadeType.PERSIST]:영속성 '전이'와 관련된 설정,cascade를 별도로 지정하지 않을 경우, Project 엔티티를 생성하고 save() 메소드를 호출해 영속성 컨텍스트에 persist한는 등 따로 persist를 해줘도 엔티티에 포함된 skills, 즉 ProjectSkills 엔티티들은 persist 되지 않는다.CascadeType.PERSIST를 지정해주면, Project 엔티티만 persist 해도, 거기 포함된 skills의 엔티티들이 모두 같이 persist가 된다.PERSIST 외에도 DETACH, MERGE, REMOVE, REFRESH 등의 상태를 적용할 수 있다.var skills: MutableList<ProjectSkill> = mutableListOf()ProjectSkill.kt: 다대일의 관계라서 프로젝트와 스킬을 각각 연결@ManyToOne(targetEntity = Project::class, fetch = FetchType.LAZY) @JoinColumn(name = "project_id", nullable = false) var project: Project = project @ManyToOne(targetEntity = Skill::class, fetch = FetchType.LAZY) @JoinColumn(name = "skill_id", nullable = false) var skill: Skill = skill 데이터베이스 초기화프로필소개글 3줄깃허브, 링크드인 링크학력/경력(Experience)수상/자격증(Achievement)기술스택(Skill)프로젝트(Project)사용기술: 기술스택(Skill)과 다대다 관계데이터 초기화 코드 작성도메인 패키지 안에 DataInitializer.kt 생성 (개발 편의를 위해 임의로 만든 것임)총 6개의 repository에 의존한다.생성자로 6개 입력 private val achievementRepository: AchievementRepository 등등 => DataInitializer를 빈으로 등록하려면 생성자인 repository들도 빈으로 등록됨 => 이런식으로 스프링 초기화가 진행됨class DataInitializer( //이게 바로 생성자 주입 private val achievementRepository: AchievementRepository, private val introductionRepository: IntroductionRepository, private val linkRepository: LinkRepository, private val skillRepository: SkillRepository, private val projectRepository: ProjectRepository, private val experienceRepository: ExperienceRepository )@Component: 스프링에서 관리하는 인스턴스 => bean(빈)스프링 처음 실행시 컴포넌트 스캔 과정을 거침이때, 스프링에게 어떤 것을 빈으로 등록할지 알려주는 역할자바로 클래스를 사용하려면 개발자가 직접 생성자를 이용해서 클래스의 인스턴스를 만들어야하는데, 스프링 프레임워크가 개발자 대신 인스턴스 제어를 한다.다른 인스턴스에서 이렇게 만들어진 빈들을 사용하려면 그 인스턴스를 주입받아 사용한다. =>"의존성 주입DI" => DI 방법은 잠시 후 해볼 예정 이런 빈들을 사용할 때에는 생성자, setter, 필드 주입등의 방식을 통해 의존성 주입을 받아 사용 가능@Controller, @Service, @Repository:세 어노테이션에는 component의 기능이 포함되어 있다.@Profile(value = ["default"]):스프링이 빈으로 등록하는데 프로필이 default일 때만 이 클래스를 생성해서 빈으로 등록한다. (개발자가 임의로 데이터 등록 못하게) @PostConstruct:메인 메소드가 실행이 되면서 스프링을 구축한다. 이때 Spring DI가 컴포넌트 스캔을 해서 인스턴스(빈)를 생성하고 의존성을 주입한다. 이런 식으로 스프링 프로젝트를 construct(구축)한다. 이런 스프링 초기화 작업이 완료되면, PostConstruct가 붙은 메소드를 찾아서 한번 더 실행한다(이때는 빈들이 다 등록되어 있어서 필요한 빈을 찾아 사용가능, 그 빈들을 이용해 테스트 데이터를 초기화 함 ). 이게 끝나면 스프링 실행이 완료된 것.fun initializerData(): //이게 아마 메인메소드println(" "): => logger를 써라java의 'System.out.println'과 똑같다.내부적으로 Synchronized를 달고 있어 성능에 좋지 않다. (자원을 하나씩 순차적으로 여러 스레드가 사용하고 있고, 동시에 사용할 수 없다. 그래서 성능에 안좋고 운영에 절대 쓰면 안된다.)logger:출력하려는 내용과 더불어 시간, 스레드 등 여러 정보들이 같이 출력됨. (때문에 강의할 때는 깔끔하게 보기위해 println을 쓸것임)val achievements = mutableListOf<Achievement>(엔티티 2개 입력함):mutableListOf: 리스트로 정의한다.2개의 Achievement Entity를 가진 리스트를 achievements필드에 초기화 함 => 엔티티를 만들어 주입받는 jpa repository들을 이용해 DB에 데이터를 넣어주는 작업achievementRepository.saveAll(achievements-리스트): 레파지토리에 리스트로 insert한다.achievementRepository interface에 아무것도 없는데 메소드에 사용이 가능하다(Spring Data JPA에서 만들어주는 기능이다.AchievementRepository가 상속하는 JpaRepository에 다양한 메소드들이 정의되어 있다.스프링이 실행되면서, 만든 인터페이스와 상속하는 인터페이스들 안에 실제 동작하는 기능을 가진 코드를 가지고 있는 repository 클래스를 만들어준다. 그 클래스들이 빈으로 등록된다.때문에 기본적인 기능들은 - list로 insert하는 것 등등 - 개발자가 하나하나 쿼리를 짤 필요 없이 간단하게 사용 가능하다. ) => 헷갈림 val introductions = mutableListOf<Introduction>(3개의 엔티티): ..복붙experienceRepository.saveAll(mutableListOf(experience1, experience2))experience를 리스트로 만들어 saveAll() 함수로 넘김. saveAll()을 통해 영속성 컨텍스트에 들어감. 현재 트랜잭션이 종료 될 때 영속성 컨텍스트에 있는 내용들이 insert로 DB에 들어감. 그때, Experience가 가진 detail들이 같이 insert로 들어간다.experience1.addDetails( mutableListOf( ExperienceDetail(content = "GPA 4.3/4.5", isActive = true), ExperienceDetail(content = "소프트웨어 연구 학회 활동", isActive = true) ) ) experience2.addDetails( mutableListOf( ExperienceDetail(content = "유기묘 위치 공유 서비스 개발", isActive = true), ExperienceDetail(content = "신입 교육 프로그램 우수상 수상", isActive = true) ) )이 때 만약, Experience.kt 안의 var details에 @OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL])에서 CascadeType을 ALL로 안하면, DB에 Experience는 입력되지만 detail은 insert 쿼리에서 제외된다.만든 엔티티(Skill)를 변수에 다 할당해준다.나중에 Project에서 projectSkill과 연결해서 재사용할 예정임.val java = Skill(name = "Java", type = SkillType.LANGUAGE.name, isActive = true) val kotlin = Skill(name = "Kotlin", type = SkillType.LANGUAGE.name, isActive = true) val python = Skill(name = "Python", type = SkillType.LANGUAGE.name, isActive = true) val spring = Skill(name = "Spring", type = SkillType.FRAMEWORK.name, isActive = true) val django = Skill(name = "Django", type = SkillType.FRAMEWORK.name, isActive = true) val mysql = Skill(name = "MySQL", type = SkillType.DATABASE.name, isActive = true) val redis = Skill(name = "Redis", type = SkillType.DATABASE.name, isActive = true) val kafka = Skill(name = "Kafka", type = SkillType.TOOL.name, isActive = true) skillRepository.saveAll(mutableListOf(java, kotlin, python, spring, django, mysql, redis, kafka))변수로 초기화를 하지 않고Skill(name = "Java", type = SkillType.LANGUAGE.name, isActive = true)생성자만 가지고 skillRepository를 이용해 한번에 DB에 넣으면 나중에 project에서 가져오기 복잡해진다. 그래서 미리 정의해준다.Project는 experience와 비슷addDetails()와 같이, skills.addAll() 함수로 묶어서 project1에 넣어줄 수 있다.// 방법1 project1.addDetails( mutableListOf( ProjectDetail(content = "구글 맵스를 활용한 유기묘 발견 지역 정보 제공 API 개발", url = null, isActive = true), ProjectDetail(content = "Redis 적용하여 인기 게시글의 조회 속도 1.5초 → 0.5초로 개선", url = null, isActive = true) ) ) // 방법2 => 다양한 방법이 있다 project1.skills.addAll( mutableListOf( ProjectSkill(project = project1, skill = java), ProjectSkill(project = project1, skill = spring), ProjectSkill(project = project1, skill = mysql), ProjectSkill(project = project1, skill = redis) ) )val: 불변(Immutable) 변수로, 값의 읽기만 허용되는 변수. 값(Value)의 약자이다.변수를 선언할 때 지정한 값에서 더이상 변경하지 않는 경우var: 가변(Mutable) 변수로, 값의 읽기와 쓰기가 모두 허용되는 변수. 변수(Variable)의 약자이다.변수의 값을 바꿔야 하는 경우출처: https://kotlinworld.com/173 리포지토리 개발JAP엔티티를 미리 정의해 두고 인터페이스만 만들면 Spring이 실행되면서, 리포지토리 인터페이스를 기반으로 리포지토리 클래스들을 만들어서 Spring Bean으로 등록한다.@Entity class Experience(... interface AchievementRepository : JpaRepository<Achievement, Long> {...서비스 Bean에서 리포지토리 빈들을 주입받아서 바로 사용 가능하다. 이때 사용하는 기능들은 Insert, Update, ID로 조회하기, ID로 삭제하기 등이 있다. 특정 컬럼 조회하기 등은 기본 메소드에 없다.인터페이스에 미리 정해진 규칙대로 메소드 이름을 정의해주면, 메소드 이름을 기반으로 쿼리를 작성해준다. => A부터 Z까지의 컬럼이 있을 때, 개발자가 A, B를 조회하고 싶다면 'Find by A and B' 이런 식으로 메소드 이름을 정의하고 파라미터로 A와 B를 넣어 주도록 인터페이스에 메소드를 정의하면 된다.// select * from achievement where is_active = :isActive fun findAllByIsActive(isActive: Boolean): List<Achievement>SkillRepository.kt 에는 메소드를 하나 더 만든다.// select * from skill where lower(name) = lower(:name) and skill_type = :type fun findByNameIgnoreCaseAndType(name: String, type: SkillType): Optional<Skill>Optional<Skill> 로 Skill 단건을 조회하게 함.case를 무시하라고 했기에, 전부 다 대문자나 소문자로 변경(컬럼도) => 뭔가 추가적인 지식이 있는 듯위 내용을 순수한 쿼리로 작성하면, 구체적인 DB 시스템에 종속된다. => 예를 들어, lower 함수 같은 경우. mySQL은 lower이라고 써도 오라클이나 다른 DBMS에서는 같은 기능을 다른 함수로 쓸 수 있기 때문.=> Spring Data JPA에서 이런 부분을 개발자가 신경 안쓰게 하기 위해 'IgnoreCase'로 각각 DBMS에 맞게 변경해줌 리포지토리 테스트 코드 작성테스트 코드는 매우 정말 중요하다.=> 강의용 프로젝트같이 규모가 작은 경우에는 덜 중요할 수 있다.IntelliJ는 특정 클래스의 테스트 클래스를 쉽게 만들어주는 기능을 제공한다.DataInitializerTest.kt[테스트할 클래스 -> 마우스 오른쪽 -> Generate -> test]도메인 등 원래 클래스가 있던 것과 같은 경로로 test패키지 안에 test 클래스가 생성된다.=> DataInitializerTest.kt 삭제 (테스트할 대상이 Spring Data JPA Repository Interface 이기 때문)인터페이스여서 테스트 클래스를 만들 수 없고 같은 규칙으로 직접 만듦test>kotlin>com>bohui>portfolio>domain 안에 패키지 '리포지토리'를 만든다.테스트 코드 작성은 많은 작업이 필요해서 오래 걸린다. => 때문에 찐 테스트 코드를 작성하지 않고, 작성하는 방식을 보여줄 예정Experience와 Project Repository 에 대해서만 테스트 클래스 생성ExperienceRepositoryTest.kt@DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExperienceRepositoryTest( @Autowired val experienceRepository: ExperienceRepository // 테스트할 대상을 주입받음 ) @DataJpaTest:jpa 관련 테스트 할 때 사용하는 어노테이션.이 테스트 코드가 실행될 때, jpa 사용이 가능한 만큼 스프링 빈을 만들어 준다.Transactional이라는 어노테이션을 가지고 있다.@Transactional: 테스트 메소드 하나를 하나의 트랜잭션으로 보고, 메소드가 종료될 때 그 트랜잭션에서 발생한 모든 작업을 롤백함.테스트 코드에서 중요 원칙 중 하나는 독립적으로 항상 같은 결과를 내야한다는 것. => 인메모리 DB를 쓰면 상관없지만 안쓰면 롤백해야함. 안하면 테스트 코드 돌릴 때마다 테스트용 데이터가 계속 쌓여 다음 테스트에 영향을 줄 수 있다.=> 때문에 Transactional 어노테이션을 달아서 자동 롤백이 되게 함.@TestInstance(TestInstance.Lifecycle.PER_CLASS):TestInstance.Lifecycle.PER_CLASS:TestInstance의 라이프 사이클이 클래스 단위가 됨.원래 기본값으로, 이 테스트 코드를 돌리는 로직이 NewExperienceRepositoryTest 해서 메소드 한개 돌리고 또 NewExperienceRepositoryTest 해서 두번째 메소드 돌리는 식으로 수행이 됨. (같은 클래스에 있는 메소드들 이지만, 메소드 마다 인스턴스 생성)라이프 사이클을 클래스 레벨로 해주면, 인스턴스를 한번 만들어서 그 안에 있는 여러 메소드들을 수행한다.그래도, 메소드마다 TestInstance를 만들어 테스트를 돌리면, 메소드간 의존적이지 않다는(독립적) 장점이 있다.내부적 메소드 간에 의도적으로 의존적이게 만들고 싶을 때, 클래스를 만들어서 메소드를 1번, 2번, 3번 다 돌리는게 낫다...(이해가 더 필요)@BeforeAll: 테스트 데이터를 초기화하는 메소드. 다른 메소드가 돌기 전에 제일 처음에 딱 한번 돌아야 함. 때문에, TestInstance의 라이프 사이클을 클래스 단위로 해줘야 함.'DataInitializer.kt'는 개발 편의를 위해 임의로 만든 것으로, 이렇게 데이터를 초기화하는 방식은 좋지는 않다(왜?). 때문에 독립적으로 사용하기 위해 BeforeAll로 초기화할 예정. 그리고 @DataJpaTest를 사용하면 그 스프링 데이터 jpa를 테스트하기에 필요한 기능들만 초기화 가능하다. 그래서 테스트 돌릴 때, DataInitializer의 내용들은 빈으로 등록 안됨.TestInstance.Lifecycle.PER_METHOD:라이프사이클을 메소드로 할때, BeforeAll이 돌아 초기화를 해줬지만 다음에 돌아가는 테스트 메소드들은 BeforeAll의 영향을 받을 수 없다.@Autowired val experienceRepository: ExperienceRepository: 생성자로 테스트할 대상을 주입받음private fun createExperience(n: Int): Experience:테스트 데이터 초기화를 할 때 더미 엔티티를 만들어주는 기능, 받은 'n'의 개수만큼 이 'Experience' 안에 디테일을 넣어준다.val experience: 비어있는 더미 객체(entity) 생성기능단위로로 메소드를 분리해 주는 것이 구조적으로 소스 코드를 파악하기 더 용이하다.@BeforeAll: 테스트 데이터 초기화Assertions (org,assertj.core.api)Assertions.assertThat(beforeInitialize).hasSize(0): 테스트를 검증하는 메소드 (의도한대로 동작을 했는지)'beforeInitialize'에서 받은 데이터의 사이즈를 체크'0' 이면 테스트를 통과, 그 외에는 테스트 실패// 테스트 데이터 초기화 @BeforeAll fun beforeAll() { println("----- 데이터 초기화 이전 조회 시작 -----") val beforeInitialize = experienceRepository.findAll() assertThat(beforeInitialize).hasSize(0) // 테스트를 검증하는 메소드 println("----- 데이터 초기화 이전 조회 종료 -----") println("----- 테스트 데이터 초기화 시작 -----") val experiences = mutableListOf<Experience>() for (i in 1..DATA_SIZE) { val experience = createExperience(i) experiences.add(experience) } experienceRepository.saveAll(experiences) println("----- 테스트 데이터 초기화 종료 -----") }@Test: 메소드를 테스트 메소드로 인식되게 함@Test fun testFindAll() { println("----- findAll 테스트 시작 -----") val experiences = experienceRepository.findAll() assertThat(experiences).hasSize(DATA_SIZE) println("experiences.size: ${experiences.size}") for (experience in experiences) { assertThat(experience.details).hasSize(experience.title.toInt()) println("experience.details.size: ${experience.details.size}") } println("----- findAll 테스트 종료 -----") 리포지토리 성능 개선JPQL의 fact join을 활용해 jpa에서 발생하는 n+문제를 해결하고, ProjectRepository와 ExperienceRepository의 성능을 개선.ExperienceRepositoryTest.kt의 fun testFindAllByIsActive() 실행11개의 쿼리가 실행됨 => jpa에서의 n+1 문제부모데이터 1번 조회 (결과: 10개) -> 각 자식데이터 조회 10번 JPA에서 proxy를 쓰는데, proxy는 가짜 객체이다.디테일을 바로 가져오는게 아닌 한번 랩핑된 가짜객체를 가지고 있고, 그 가짜 객체 안에 var details가 호출될 때 Query가 나가는 로직이 있음.=> 디테일 호출 -> 가짜 객체 호출 -> 가짜 객체에서 진짜 데이터를 안가지고 있으니, DB에서 쿼리를 가져옴근데 너무 비효율적임 => FetchJoin 활용ExperienceRepository.kt의 fun findAllByIsActive위에 @Query("select e from Experience e left join fetch e.details where e.isActive = :isActive") 달아줌- 'e' alias 별칭jpql: 자바의 객체지향적인 쿼리. sql과 비슷한데, 좀 더 객체의 관점에서 작성할 수 있는 sql. JPA에서 JPQL을 가지고 실제로 DBMS에 맞는 쿼리로 바꿔서 DB로 쿼리를 보냄 (ex. @Query)쿼리가 한개만 나갔다.ProjectRepository.ktprojectSkill, projectDetail과 관계를 맺고 있다.패치조인의 단점, 한계점이 위와 같이 여러 개의 엔티티와 관계를 맺고 있을 때, 이것들을 한꺼번에 조회할 수 없다.=> 네이티브 쿼리로 풀거나, 쿼리 DSL or something=> yml의 default_batch_fetch_size: 10 을 통해 어느정도의 성능 문제를 해결 => n+1의 완전히 해결하는 것이 아닌 fetchSize의 값에 따라, m의 팻치사이즈가 n번 나가는 쿼리를 n/m으로 줄여준다.

백엔드SpringBootKotlinWeb

river_bori

인프런 워밍업 클럽 2기 - 백엔드 프로젝트 (Spring, Kotlin) 1주차 발자국

일주일간 학습한 내용 요약웹 개발 기본과 프로젝트 준비웹 서비스를 구성하는 요소클라이언트(브라우저, 서버컴 중 요청쪽 등) - 서버(응답주체, CRUD작업, 서버컴퓨터 집합인 클러스터를 구성) - DB(DBMS)DBMS 서버의 IP주소는 인터넷 세팅을 할때 등록해서 컴퓨터에 알려줘야 한다.웹 프레임워크와 Spring웹 프레임워크: 동적 웹 서비스 개발을 편리하게 만들어주는 도구백엔드 : (Java, Kotlin - Spring), (JavaScript, TypeScript - Express.js, Nest.js), (Python - Django), (Rudy - Rudy On Rails)프론트엔드 : (JavaScript - React(라이브러리), Angular, Vue.js) 프레임워크 vs 라이브러리 -> 제어의 주도권 차이프레임워크: DIY 가구 키트 - 사용자가 틀 안에서 주어진 것을 활용하여 원하는 것을 만드는 것라이브러리: 공구 상자 - 사용자가 주도권을 가지고 원하는 것을 만듦Spring Framework: Java기반의 웹 프레임워크, 웹 서버 개발의 상당 부분을 편리하게 모듈화 해놓음MVC 패턴: 요청 처리, 데이터, 화면 간의 결합도를 낮춰 유지보수 용이하게 함View: 사용자와 상호작용Controller: 요청받아 작업을 수행Model: 데이터 담는다. View는 데이터를 꺼내고, Controller는 데이터를 넣는다. 디자인 패턴? 경험적으로 특정 문제 상황을 해결하기에 최적이라고 생각되는 설계, 방법론.코드의 가독성, 유지보수성, 결합도, 응집도 등을 고민 하며 최적화된 코드를 작성하는 자세가 중요레이어드 아키텍처: 데이터를 컨트롤러에서 받아서 모델에 넣기 전까지 처리하는 과정을 이해하기 쉽고 관리하기 쉽게 구조화하는 방법 중 하나. 입력받은 데이터에 이상은 없는지 검증하고, 한번 가공해서 받은 데이터 기반으로 다른 데이터를 조회하는 등을 할 수 있다.MVC의 컨트롤러와 분리Controller(Presentation) - Service(Business) - Repository(Data Access) 각 레이어 별 역할 분리컨트롤러: 사용자와 상호작용서비스: 주요 로직 처리레파지토리: DB와의 상호작용스프링 Bean과 의존성 주입(Dependency Injection)스프링Bean: 스프링에서 관리하는 인스턴스어플리케이션 동작에 필요한 클래스들을 개발자가 직접 만들지 않고 스프링이 만들어서 관리하도록 위임한다. "제어의 역전 IoC(Inversion of Control)" 제어의 주체가 개발자가 아닌 프레임 워크가 되었다.스프링부트에서는 어노테이션으로 간단하게 스프링 빈을 정의해서 스프링이 그 인스턴스를 만들게 할 수 있다. (컨트롤러,서비스,레파지토리,컴포넌트 등의 어노테이션)스프링 실행 - 컴포넌트 스캔 - 클래스들을 하나하나 살피면서 어노테이션이 있으면 Bean으로 만듦(인스턴스를 만들어 빈으로 등록)의존성 주입(DI)는 역제어(IoC)의 한 형태레퍼지토리를 서비스에 넣어주고, 서비스를 컨트롤러에 넣어주고..의존성 주입"주입"은 의존성(서비스)를 사용하려는 객체(클라이언트)로 전달하는 것. 어떤 서비스를 호출하려는 클라이언트는 그 서비스가 어떻게 구성되었는지 알지 못해야 한다.의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것. 이는 가독성과 코드 재사용을 높혀줌.클래스는 더 이상 객체 생성에 대한 책임이 없음.클라이언트의 생성에 대한 의존성을 클라이언트의 행위로부터 분리"매개변수 전달"과 동일하게 동작한다. 주입으로써 "파라미터 전달"은 클라이언트를 세부 사항과 분리하기 위해 수행되고 있는 부가적인 의미를 전달의존성 주입은 네 가지 역할을 담당하는 객체 및 인터페이스를 전제로 한다.사용될 서비스 객체사용하는 서비스에 의존하는 클라이언트 객체클라이언트의 서비스 사용 방법을 정의하는 인터페이스서비스를 생성하고 클라이언트로 주입하는 책임을 갖는 주입자비유하자면,서비스 - 전기, 가스, 하이브리드 또는 디젤 자동차클라이언트 - 엔진에 상관 없이 동일하게 차를 사용하는 운전자인터페이스 - 운전자가 기어와 같은 엔진의 세부 사항을 이해할 필요가 없도록 보장해주는 자동변속기주입자 - 아이에게 어떤 차를 사줄지 결정하고 구매해준 부모사용될 수 있는 모든 객체는 서비스로 여겨진다. 다른 객체를 사용하는 모든 객체는 클라이언트로 여겨진다.[위키백과]생성자(costructor) 주입수정자(setter) 주입필드(Field) 주입추가 공부 필요 - 이해가 안됨생성자 주입 방식을 권장의존성이 바뀌는 것을 방지할 수 있다순환참조 시 컴파일 오류가 발생해, 런타임 단계에서의 메소드가 서로 호출하는 스택오버플로우 에러 방지한다.의존하는 빈이 누락되면 컴파일 오류가 난다.HTTP와 REST APIHTTP: 네트워크로 통신하는 두 컴포넌트 간 통신규약HTTP 요청/응답Request: Start Line(HTTP 메서드, URL, HTTP 버전), Header(키/밸류 형태 메타데이터, 컨텐츠의 길이/유형, 클라이언트 정보), Body(본문, JSON 포맷)Response: Start Line(HTTP 상태코드 - 아래 있음)HTTP 요청메서드GET: 서버 자원을 조회하고 가져온다. 브라우저 주소창은 항상 GET 메서드로 요청,POST: 리소스를 생성메소드(CREATE)PUT, PATCH, DELETEHTTP 상태 코드200 OK: 요청이 정상처리됨300 Multiple Choices(Redirection): 사파리에서 네이버 입력 시, 네이버 서버가 300번대 코드와 m.naver... 주소를 주면 사파리가 다시 m.naver...으로 요청하고 m.naver..서버가 200번대 코드와 html 파일을 사파리에 준다 400 Bad Request: 클라이언트의 오류500 Internal Server Error: 서버의 오류(DB 다운 등)--> 응답코드는 서버 개발자가 정한다.Http는 규약이지만 자유도가 높은 규약이다.REST API: URL도 개발자가 정하기 나름이지만, 일종의 표준처럼 사용하는 아키텍처(기억 장치의 주소 방식)로 REST API를 사용한다.* 아키텍처: 기능 면에서 본 컴퓨터의 구성 방식. 기억 장치의 주소 방식, 입출력 장치의 채널 구조 따위REST 원칙을 알면 새로운 API를 봐도 어떤 API인지 직관적인 추측이 가능해진다. -> 개발자 간의 커뮤니케이션 비용이 줄어든다.REST API의 핵심URL 이용한 자원 표현: URL만 보고 어떤 요청인지 이해 가능HTTP 메서드 이용한 행위의 표현: 의미에 맞는 적절한 메서드 사용HATEOAS(헤이티어스) 준수: 응답에 링크 포함해 클라이언트의 다음 행동을 가이드 클라이언트에서 서버로 데이터 전달 방법Query Parameter: URL에 있다. ((get) inflearn.com/roadmaps?terms=5&page=1) -->'?'뒤 내용이 쿼리파라미터이다.HTTP Request Body: Http 메시지 안에 들어있기 때문에 Post등을 쓰지 않는 이상 눈으로 보기 어렵다.Path Variable(경로변수): URL에 있다.코딩 컨벤션: 코딩을 하는 프로그래머 사이의 규칙 규약, 읽고 관리하기 쉬운 코드 작성을 위한 코딩 스타일 규약데이터베이스란DBMS: 데이터를 체계적으로 관리하기 위한 프로그램관계형/비관계형 데이터베이스로 나뉜다관계형 DB(RDBMS): 행과 열로 이루어진 표의 형태후보키: 유일성(중복x) 최소성(최소한의 컬럼조합 사용)기본키PK: 후보키 중 하나학과와 학생은 1:N의 관계복수전공시학생 join 학과를 하면 학번의 유일성이 깨진다때문에 중간에 맵핑 역할을 하는 테이블 필요 "학생전공"테이블 학생전공ID(기본키)학과와 학생이 N:M 관계 가능해짐학생전공 join 학생 join 학과DB는 프로젝트의 골격과도 같기 때문에 서비스 운영 중 DB구조를 바꾸는 것은 매우 어렵다. 프로젝트 설계가 중요하다.오라클(유료), MySQL(유/무료),PostgreSQL(꾸준히 점유율 올라가는 오픈소스DB, 플러그인 확장성이 좋다.)비관계형 DB: 관계형DB를 제외한 모든 종류의 DB(키-값형, 문서형 등)개발하려는 서비스에 따라 비관계형DB도 공부 필요MongoDB: 문서형, 데이터를 비정형적으로 저장가능Redis: 키-값, 주로 캐시 용도로 사용(자주 조회된 데이터를 레디스에 저장해서 사용-빠르다)  JPA란JAP: Java Persistence API, 자바 ORM 기술의 표준 인터페이스, Java의 객체를 관계형DB의 테이블로 또는 그 반대로 변환해주는 기능/맵핑을 해주는 라이브러리서비스를 좀더 객체지향적인 관점에서 설계 및 유지보수할 수 있게 도와주고개발자가 직접 작성해야하는 코드를 줄여준다.ORM: Object Relational Mapping, 객체 관계 매핑, 객체지향 프로그래밍의 인스턴스와 관계형DB를 매핑해주는 기술학생, 학과 테이블 -> 클래스 (class Project)각 컬럼들 -> 클래스에 있는 필드 (val title)각 레코드 -> 클래스로 만들어진 인스턴스여러 레코드 -> 클래스의 리스트 (List<ExperienceDetail>)이렇게 테이블에 매핑되는 자바 클래스를 엔티티라고 한다. (class Project)DB엔티티에 해당하는 자바 클래스 = JPA 엔티티ORM은 테이블들이 갖는 관계까지 이용해 DB를 좀 더 쉽게 다룰 수 있는 기능을 제공ORM의 장점원래 자바로 DB를 다루려면 SQL문을 개발자가 직접 작성해서 DB로 보내야한다.JPA는 개발자가 정의해둔 엔티티의 필드들과 연관관계를 통해서 테이블 구조를 이해하고 있고 이를 바탕으로 대신 SQL을 작성함 => 개발 생산성 증가엔티티라고 부르는 Java 객체를 기반으로 쿼리 작성하기 때문에 데이터를 객체지향적으로 관점에서 접근 가능스프링과 구체적인 RDBMS(오라클, MySQL 등)에 대한 종속성을 끊어 의존성이 줄어든다. -> DB를 쉽게 변경 가능하다(ex. 오라클에서 MySQL로) ORM의 단점충분한 학습이 없으면 의도와 다르게 쿼리가 동작할 수 있음 ex: (n+1) 직접 쿼리를 작성했을 때는 한번만 쿼리가 전송됨, JPA를 사용하면 쿼리가 여러번 전송됨. => 해결법은 있지만 별도로 공부가 필요간단한 쿼리작성에는 효과적, 복잡한 쿼리에서는 한계점이 있어 불가피하게 구체적인 RDBMS에 종송적인 네이티브 쿼리 작성 => 의존성을 줄이는 장점이 사라짐트랜잭션: 데이터베이스의 개념, 여러 DB 작업을 하나로 묶는 논리적 단위(계좌이체 예시)커밋: 트랜잭션으로 묶인 모든 작업을 DB에 영구히 반영하는 작업롤백: 트랜잭션으로 묶인 모든 작업을 원상복구 하는 작업영속성 컨텍스트: 어플리케이션의 로직과 DB 사이에 있는 임시 메모리 또는 버퍼 공간개발자가 java코드로 CRUD 명령을 수행할 때 JPA는 이 영속성 컨텍스트를 거쳐 DB와 상호작용트랜잭션과 주기가 같다. 하나의 트랜잭션의 DB 작업들을 영속성 컨텍스트를 거쳐 좀 더 효율적으로 처리 영속성 컨텍스트 처리과정데이터 조회시 1차 캐시라는 곳에서 해당 데이터가 있는지 먼저 확인 -> 데이터 없으면 DB로 조회쿼리 날림1차 캐시(엔티티1)에 조회한 데이터를 저장 -> 엔티티1 상태를 스냅샷 저장그 후 또 같은 엔티티 조회시 DB까지 쿼리가 날라가지 않고 1차 캐시에 있는 데이터를 그대로 가져감(쿼리 날리는 과정이 생략)영속성 컨텍스트 더티체킹(변경감지)데이터 처음 조회 시 처음 상태를 스냅샷 저장 후 영속성 컨텍스트가 종료될 때 현재 있는 엔티티1와 엔티티1스냅샷을 비교두개가 다를 경우 업데이트가 있다고 JPA가 판단하고 알아서 쿼리를 작성해 DB에 전송함.영속성 컨텍스트 쓰기 지원한 트랜잭션 안에서 for문을 가지고 1~100까지 데이터를 insert할 때,for문 반복이 수행될 때마다 DB로 insert 쿼리를 전송하지 않고, 영속성 컨텍스트에 저장할 데이터를 넣어 뒀다가 트랜잭션 종료될 시점에 한번에 모든 쿼리를 날림때문에 JPA로 insert를 날린 시점과 log에서 insert, update 쿼리가 찍히는 시점이 차이가 날 수 있다.영속성 컨텍스트 플러시(Flush)개발자가 원하는 시점에 DB에 쿼리 전송 가능패키지 구조[패키지 구조]패키지: 연관된 자바파일들을 묶어주는 디렉토리(폴더)다른 개발자들이 프로젝트 구조를 이해하는데 도움이 된다다양한 방법론이 있다.presentation(방문자), admin(관리자), domain(프로젝트가 공통으로 가진 뼈대(코어)가 되는 기능들이 모인 패키지, DB 접근 기능 등)도메인 패키지가 베이스가 되고 그 위에 방문자, 관리자 패키지를 병렬로 쌍은 느낌의 구조 각 패키지의 하위 패키지 => 다양한 방법론 중 하나의 구조다.domain: 4개의 하위 패키지configuration(암호와 관련된 설정 클래스 들어갈 패키지),constant(도메인 패키지에 사용되는 상수들),entity(DB의 테이블에 대응하는 java객체들이 들어갈 패키지) ,repository(각 엔티티 별 DB에서 CRUD를 수행할 객체들)presentation:  5개의 하위 패키지controller,dto(화면에서 필요로 하는 데이터를 담는 클래스),interceptor(컨트롤러까지 요청이 들어가기 전에 그 모든 컨트롤러에 대해 공통 처리를 해주는 클래스, 인터셉터에서 각 화면을 조회할 때 그 요청에 대한 정보를 따로 DB에 저장하는 기능 개발),repository(DB와 직접 상호작용을 하지 않고 도메인의 레퍼지토리 패키지를 활용해서 프레젠테이션 레이어에서 필요로 하는 DB작업을 쉽게 할 수 있도록 중간에 랩핑 해주는 기능, 디자인패턴 중 Facade 패턴),=> 퍼사드(Facade) 패턴: 복잡한 시스템을 보다 쉽게 사용 가능하도록 단순화된 인터페이스를 제공. 시스템의 복잡성 숨기고, 사용자 친화정 인터페이스로 시스템 접근을 용이하게 함.service=> 도메인과 프레젠테이션 패키지를 결합한 것도 하나의 프로젝트로 볼 수 있다. 나중에 확장 될때, 도메인과 어드민을 또 따로 결합해 사용할 수 있도록 만들어진 구성 admin:advice(컨트롤러 어드바이스, 예외를 처리하는 클래스),context(데이터베이스에 만들 테이블들 별로 context 패키지 안에 하위 패키지들이 있다.각 테이블 별로 화면 조회부터 데이터 추가/수정/삭제 기능들을 분리해 넣을 예정ex) achievement(이 안에 각각 controller, service=view,form 하위 패키지가 또 있다), dashboard, experience, ...etc),=> 새로운 테이블이 추가되고 관리할 기능이 필요하다면, 컨텍스트 하위에다가 새로운 패키지를 만들어 기존 기능에 대해 영향이 없이 확장성있게 개발 가능하게 구성됨data(API 공통 응답 포맷, 입력 폼 정보, 테이블 정보 등을 담는 클래스),exception(어드민 레이어에서 따로 커스터마이징한 예외들 들어갈 패키지),interceptor(화면에서 보여지는 사이드바를 초기화 하는 클래스),security(로그인 관련된 기능들)테이블 설계(명세)메인페이지(=index page) 사용 테이블introduction 테이블 (소개글 등)created_date_time, updated_date_time 컬럼은 메타 데이터 타입으로 데이터의 생성과 수정을 추적하기 위한 목적이다.link 테이블 (링크 아이콘 등)name을 기반으로 이미지 Resume 페이지 사용 테이블experience(학력, 경력, 한줄 요약 등)experience_detail(어떤 경험에 대한 내용인지, 설명 등)=> experience와 experience_detail은 1:N으로 묶인 테이블achievement(수상, 자격증 등)skill(사용할 줄 아는 기술들)Projects 페이지 사용 테이블projectproject_detail(url 컬럼이 추가로 있다)project_skill(프로젝트와 skill을 맵핑해주는 테이블)=> 프로젝트와 스킬은 N:M의 관계기타http_interface(사용자가 페이지 조회를 한번 할 때마다 사용자의 브라우저에서 서버로 요청이 들어올 때 요청의 정보를 저장하는 테이블.조회수, 일자별 조회수 통계, ip주소를 통한 중복방문제거, 모바일/데스크탑 방문 통계 등이 가능하다)=> interceptor를 사용해 보기 위해 넣었다.개발 환경 구성윈도우 사용자는 이 강의를 스킵하고 강의자료로 진행Homebrew(Mac Only)JDK(zulu): 버전 17로IntelliJ IDEAH2 DatabaseDBeaverDocker

백엔드SpringBootKotlinWeb

ppusda

인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring) / 2주차 발자국

⭐ 1주 동안 배운 내용을 정리하고 회고하는 시간을 가져보자. 6 ~ 7일차 - Repository, TestFetchType과 N+1 문제강의에서 FetchType에 따른 쿼리 작동 과정을 보여주셨고 이 때 발생할 수 있는 N+1 문제에 대해서 언급이 되었다.N+1 문제는 JPA를 사용하여 Entity를 조회할 때 발생할 수 있는 문제로 아래와 같이 부모 엔티티를 조회할 때, 연관 되어있는 자식 엔티티들의 수 N 만큼의 쿼리가 발생하여 성능에 지장을 줄 수 있는 문제다. 지연로딩을 사용했을 때는 각 엔티티를 실제 사용할 때 마다 쿼리가 발생하였고, 즉시로딩으로 변경하자 모든 엔티티의 정보를 한꺼번에 수집하여 두 경우 모두 N+1 문제가 발생함을 볼 수 있었다.이를 해결하기 위해 JPQL에서 Fetch Join 쿼리를 작성하고 application.yml 을 수정하게 되었다. Fetch Join과 Fetch SizeJPA에서 쿼리를 직접 작성하기 위해서는 JPQL을 사용할 수 있다.아래는 강의에서 작성했던 쿼리 부분이다.@Query("select e from Experience e left join fetch e.details where e.isActive = :isActive") fun findAllByIsActive(isActive: Boolean): List<Experience> Fetch Join을 사용하여 연관관계를 한번에 조회할 수 있었고, 단 한번의 쿼리로 줄어든 것을 볼 수 있다.하지만 이 경우에도 @~ToMany의 관계를 갖는 자식 엔티티가 여러 개인 경우에는 적용할 수 없다는 한계가 있다.이는 MultipleBagFetchException 이 발생하기 때문인데, 그 이유는 다수의 자식 엔티티를 Fetch Join하게 될 경우에 중복이 발생하고 일관성이 떨어지게 된다.그렇기에 JPA에서 이를 방지하기 위해 MultipleBagFetchException 를 통해 두 개 이상의 자식 엔티티를 Fetch Join 하는 것을 막아두었다. 이를 해결하기 위해 Fetch Size를 조정하여 해결할 수 있다.spring: jpa: properties: hibernate: default_batch_fetch_size: 10 기존의 문제점은 자식 엔티티가 여러 개일 경우 하나의 Fetch Join만 사용가능하며, 그로 인해 N개의 쿼리가 더 발생한다는 점이었다.default_batch_fetch_size 를 조정하여 되면 부모 엔티티의 Key를 이용하여 in 절을 통해 조정한 default_batch_fetch_size 만큼씩 자식 엔티티를 조회할 수 있다.Test6~7일차에는 이러한 내용들을 테스트 해볼 수 있는 Repository 테스트 코드를 작성하게 되었다.아래 내용은 강의에서 작성한 코드의 일부분이다.@DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) // 클래스 간 독립적으로 실행 됨. class ExperienceRepositoryTest( @Autowired val experienceRepository: ExperienceRepository ) { @DataJpaTestJPA 관련 테스트를 위한 설정을 제공하는 어노테이션이다.그렇기에 데이터에 접근할 수 있는 레이어인 리포지토리 테스트 시 많이 사용된다.내장 데이터베이스를 설정하고, @Entity 및 @Repository 어노테이션이 부여된 클래스들을 통해 테스트 환경을 구성하는 역할을 한다.@TestInstance테스트 인스턴스의 라이프사이클을 지정하기 위해 사용된다.기본적으로 JUnit 5는 각 @Test 메서드마다 새로운 테스트 인스턴스를 생성하게 되어있다.이는 테스트 환경을 어떻게 구성할 것이느냐에 따라 달라지겠고, 상황에 맞춰 사용하면 될 것 같다. 8 ~ 10일차 - DTO, Service, TestDTOKotlin에서는 Java의 Record처럼 data class를 통해 DTO를 선언해 줄 수 있었다.추가적인 생성자를 선언하기 위해서는 constructor 를 이용하여 만들어 줄 수 있었다.map, filter와 같은 컬렉션 함수를 적용할 때 람다식에서 이름을 지정해주지 않아도 it으로 사용할 수 있다.data class ProjectDTO( // 생략 val details: List<ProjectDetailDTO>, val skills: List<SkillDTO>? ) { constructor(project: Project) : this( // 생략 details = project.details.filter { it.isActive }.map { ProjectDetailDTO(it) }, skills = project.skills.map { it.skill }.filter { it.isActive }.map { SkillDTO(it) } ) } Service, Repository도메인에서 관리해야할 Repository가 많아짐에 따라 한 번에 의존관계를 주입받을 수 있는 Repository를 생성했다.각각 필요한 부분만 주입받게 되면 후에 관리하기가 힘들어 지는 상황을 예방할 수 있다.추가적으로 각 리포지토리의 기능들을 래핑하여 캡슐화 하는 형태의 코드를 작성하여 Service 단에서 사용하기 유용하도록 코드를 작성하였다.@Repository class PresentationRepository( // Presentation 에서 필요한 리포지토리들을 한 번에 주입받아서 활용하기 위함. // A, B, C 형태로 따로따로 주입받으면 후에 관리하기가 힘들기 때문 private val achievementRepository: AchievementRepository, // 생략 ) { fun getActiveAchievements(): List<Achievement> { return achievementRepository.findAllByIsActive(true) } // 생략 } Test - Mockito8~10일차에는 Service에 구현한 기능들에 대해서 테스트 코드를 작성하게 되었다.아래 내용은 강의에서 작성한 코드의 일부분이다.@ExtendWith(MockitoExtension::class) // Mockito Extension 추가 class PresentationServiceTest { @InjectMocks // Mock을 주입받을 대상, 테스트를 할 대상 lateinit var presentationService: PresentationService // Mock을 만든 이후 초기화를 진행하기 위해 lateinit @Mock lateinit var presentationRepository: PresentationRepository } @ExtendWith테스트 확장을 지원하는 어노테이션으로 Mock 객체의 생성 및 초기화를 자동으로 처리하게 해주는 역할을 해준다.@InjectMocksMockito에서 테스트 대상이 되는 클래스에 인스턴스를 생성하고, @Mock 이 사용된 필드를 찾아 객체를 자동으로 주입하기 위해 사용된다.위에서는 테스트할 대상인 PresentationService의 인스턴스를 생성하며, PresentationRepository 에 Mock 객체를 주입하기 위한 용도로 사용된다.@MockMockito에서 Mock 객체를 생성할 때 사용한다.Mock 객체는 모의 객체로 실제 객체의 동작을 흉내낼 수 있다.아래는 강의에서 사용된 Mock 객체가 실제 객체의 동작을 흉내낸 부분이다.Mockito.`when`(presentationRepository.getActiveIntroductions()) .thenReturn(activeIntroductions) when에서 정의한 내용을 시도했을 때, activeIntroductions 의 내용을 반환 하도록 Mocking을 한 것이다.presentationRepository가 실제 데이터베이스와 상호작용이 발생하지 않도록 동작을 했다고 속이는 것이며, 이러한 동작을 통해 테스트를 독립적이고 일관되게 유지할 수 있다. 서브 미션2주차의 서브 미션에는 API 설계가 예정되어 있었다.이를 위해 RESTful 하도록 API를 설계하도록 노력해봤고 결과물은 아래 리포지토리에서 볼 수 있다.https://github.com/ppusda/MML 총 회고이번 2주차는 쉬는 날 겹쳐있어 진도가 많이 나가질 못했다.하지만 의외로 많은 걸 배울 수 있었다.단순히 Kotlin으로 Spring을 접근하는 방법 뿐만 아니라 약간의 복습과 거들어 N+1 문제, Test code와 같이 아직 부족한 부분에 대해서 좀 더 학습할 수 있었다.특히 Test code에 대한 부분은 조금 공부해보니 흥미가 더 생겨서 향후에 Mockito 동작 과정에 대해서 자세하게 뜯어볼 의향도 생겼다. 앞으로도 부족한 부분을 채워나가면서 학습해나가야겠다.모두 화이팅! (o゚v゚)ノ 참고https://jojoldu.tistory.com/457

백엔드워밍업클럽백엔드2기Kotlin

ppusda

인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring) / 1주차 발자국

⭐ 1주 동안 배운 내용을 정리하고 회고하는 시간을 가져보자. 복습한다고 생각하고 간단하게 요약하며 정리해보자. ╰(°▽°)╯1일차 - 웹 기본 개념 이해하기웹 서비스의 구성 요소클라이언트 [요청하는 주체] ↔ 서버 [응답하는 주체] ↔ 데이터베이스 [데이터 집합]클라이언트는 요청하는 주체이며, 사용자 혹은 고객이라고도 표현한다.서버는 응답하는 주체이며, 요청받은 결과를 클라이언트 측으로 응답한다.데이터베이스는 데이터의 집합이며 이를 관리하는 DBMS를 일반적으로 DB라고 부른다. 브라우저에 주소를 입력하면 벌어지는 일클라이언트가 브라우저에 https://www.google.com 을 입력하면 어떻게 될까?클라이언트가 보낸 주소를 DNS 서버에서 IP로 변환하여 알려준다.클라이언트는 해당 IP 주소로 서버에 요청을 하며, 서버는 요청 데이터를 처리한다.처리 완료된 응답 데이터를 다시 클라이언트 측에 전달한다. DNS? (Domain Name System)DNS는 사용자가 흔히 보는 www.google.com과 같은 도메인 이름을 IP로 변환하는 시스템이다.DNS 서버는 이러한 변환 역할을 대신 수행해주는 서버이며, KT, SKT, LG, Google 등이 이러한 서버를 제공한다. 웹 프레임워크Framework, 프레임워크?프레임워크는 공통적으로 요구되는 기능들을 보다 편리하게 만들 수 있도록 해주는 뼈대, 구조이다.제어의 주도권을 프레임워크가 가지고 있으며, 틀 안에서 주어진 것을 활용해야한다. Library, 라이브러리?라이브러리는 활용 가능한 도구들의 집합으로 제어의 주도권을 사용자가 가지고 있으며 정해진 틀 없이 사용자가 원하는 것을 만들 수 있다. Spring FrameworkMVC 패턴 (Model-View-Controller)소프트웨어 아키텍쳐 디자인 패턴으로 Model, View, Controller로 각 역할을 분담하여 결합도를 낮추고 유지보수를 용이하게 할 수 있다.Model - 데이터 처리View - 보여지는 화면 처리Controller - 클라이언트 요청 처리 레이어드 아키텍처 (Controller-Service-Repository)가장 대중적인 소프트웨어 아키텍처로 Controller, Service, Repository로 구분하여 사용한다.Controller - 클라이언트 요청 처리Service - 비즈니스 로직 (핵심 로직) 처리Repository - 데이터베이스 접근 처리 Spring Bean과 의존성 주입Spring Bean은 스프링에서 관리되는 객체를 뜻한다.이는 스프링 컨테이너가 직접 관리하기에 제어의 역전(Inversion of Control; IOC)라고 부르며 스프링에서 의존성을 주입해주어 객체를 사용할 수 있다.생성자 주입, 수정자 주입, 필드 주입의 방법이 있지만, 런타임 시 수정자를 통해 의존성이 바뀌거나 의존하는 Bean을 누락했을 시 컴파일 단에서 오류를 잡아낼 수 있기 때문에 생성자 주입을 추천한다.생성자 주입@Service class PresentationService (private val presentationRepository: PresentationRepository) { ... } 수정자 주입private lateinit var presentationRepository: PresentationRepository @Autowired fun setPresentationRepository(presentationRepository: PresentationRepository) { this.presentationRepository = presentationRepository } 필드 주입@Autowired private lateinit var presentationRepository: PresentationRepository HTTP와 REST APIHTTP (Hyper Text Transfer Protocol) 는 서버와 클라이언트 간 어떻게 데이터를 교환할지를 정해놓은 통신 규약이다.HTTP 요청 메서드GET - 읽기 작업을 처리할 때 사용됨POST - 쓰기 작업(생성)을 처리할 때 사용됨PUT (전체 수정) / PATCH (부분 수정) - 쓰기 작업(업데이트)를 처리할 때 사용 됨DELETE - 삭제 작업을 처리할 때 사용 됨HTTP 상태 코드2xx : 정상 처리3xx : 리다이렉션 - 페이지 이동이 이루어져야 함4xx : Bad Request - 클라이언트 측 요청 오류5xx : Internal Server Error - 서버 측 오류REST API클라이언트와 서버 간 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 규칙이다.RESTful 하게 API를 작성하려면 URL만으로 어떤 자원에 대해 어떻게 처리할 건지 파악할 수 있어야 한다.POST /members ⇒ O, Post는 생성을 처리할 때 사용 / member 생성하기 위한 요청임을 알 수 있다.POST /createNewMember ⇒ X, RESTful 하지 않음 2일차 - 데이터베이스 기본 개념데이터베이스1일차에 소개했던 것 처럼 데이터의 집합이다.이를 관리하는 툴을 DBMS (Database Management System)이라고 부른다. RDBMS, 관계형 데이터베이스행과 열로 이루어진 표의 형태로 저장되는 데이터베이스하나의 행은 데이터, 열은 각 데이터의 특징이라고 할 수 있으며 이것들이 모여 하나의 테이블(표)이 된다.각 테이블을 조인하여 정보 간의 관계나 링크를 설정할 수 있는 기능이 있으며, 이를 통해 여러 데이터 간의 관계를 설정할 수 있기에 관계형 데이터베이스라고 부른다.⇒ Oracle, MySQL, PostgreSQL 등이 이에 해당된다. NoSQL, 비관계형 데이터베이스관계형 데이터베이스를 제외한 모든 종류의 데이터베이스를 비관계형 데이터베이스로 분류한다.⇒ MongoDB, Redis 등이 이에 해당된다. JPAJPA (Java Persistence API)는 자바 진영 ORM 기술의 표준이다.ORM (Object Relational Mapping)은 객체 관계를 매핑해주는 기술로 객체지향 프로그래밍의 객체와 데이터베이스를 매핑해주는 역할을 한다.장점특정 DB에 대한 의존성을 줄일 수 있음쿼리를 직접 작성하지 않아도 됨데이터에 객체지향적인 관점에서 접근이 가능함단점ORM 만으로는 한계가 있기에, 복잡한 쿼리의 경우 네이티브 쿼리를 작성해야 함JPA에 대한 충분한 학습이 이루어진 후 사용해야 함. 영속성 컨텍스트JPA에서 Entity를 관리하는 저장공간이다.애플리케이션과 데이터베이스 사이에서 Entity를 보관하는 가상 데이터베이스 역할을 한다.아래에선 이 영속성 컨텍스트의 3가지의 특징을 소개하려고 한다.1차 캐시영속성 컨텍스트 내부의 캐시를 1차 캐시라고 부른다.조회한 결과를 캐시 공간에서 먼저 찾아보고 쿼리를 수행 할지 결정한다.더티 체킹영속성 컨텍스트 내에서 Entity의 변화가 감지됬을 경우 이를 데이터베이스에 자동으로 적용하는 기능을 더티 체킹 또는 변경 감지라고 부른다.최초로 데이터를 조회할 때의 스냅샷을 보관해두고 이를 트랜잭션 종료 시점과 비교하여 변경된 내용을 적용하는 것이다.쓰기 지연쓰기 작업을 즉시 수행하지 않고, 영속성 컨텍스트 내에 모아두었다가 트랜잭션이 종료될 때 한 번에 수행한다. 트랜잭션트랜잭션은 데이터베이스에 적용할 여러 작업을 하나로 묶어주는 논리적 단위이다.커밋, Commit : 트랜잭션으로 묶인 모든 작업을 데이터베이스에 영구히 반영하는 작업롤백, Rollback : 트랜잭션으로 묶인 모든 작업을 원상복구 하는 작업 3일차 ~ 5일차 (실습)이번 실습 내용에서는 기존에 알고 있던 내용은 제외하고 실습을 진행하면서 알게 된 Kotlin 문법에 대해서 조금 정리해볼까 한다.@Entity class Achievement( // 여기가 생성자 title: String, description: String, achievedDate: LocalDate?, // null 허용 (=일 수도 있다.) / 반대로 !!는 null이 아니다를 표현함 host: String, isActive: Boolean ) : BaseEntity() { // BaseEntity 상속 // 생략 var isActive: Boolean = isActive } 위는 실습 중에 작성한 Entity의 일부이다.Java와는 다르게 Class명 옆에서 생성자를 정의할 수 있고, 변수명: 타입 과 같은 구조를 가지고 있다.타 클래스 상속을 위해서 : BaseEntity()와 같이 사용한 부분을 볼 수 있다.변수를 선언할 때는 var 또는 val을 쓴다.var ⇒ 읽기/쓰기가 가능한 일반 변수val ⇒ 읽기만 가능한 final 변수 nullable을 표현할 수 있다.? ⇒ null 일 수도 있다fun getEndYearMonth(): String { if (endYear == null || endMonth == null) { return "Present" } return "${endYear}.${endMonth}" } 이어서 함수 부분이다.Java와는 다르게 메서드를 정의할 때 fun을 통해 선언하며, String을 반환할 때 프론트에서 사용하던 것 처럼 ${} 사이에 변수를 넣어 처리를 할 수 있었다. 번외 - 서브 미션 과제워밍업 클럽 2기 - 백엔드(Kotlin, Spring) 과정에서는 서브 미션을 수행하도록 과제가 주어진다.현재 기획한 내용은 음악 플레이리스트를 만들 수 있는 미니 프로젝트를 기획하고 테이블 설계까지 완료 하였다.https://github.com/ppusda/MML 단순하게 요즘 듣는 음악 리스트를 공유하기 위한 프로젝트로 기획하게 되었으며, 설계한 테이블은 아래와 같다.User 별로 플레이리스트를 생성하여 음악 목록을 만들 수 있게 해두었고, 목록을 관리하기 위해 각 테이블을 분리하였다.미션 내용이 단순히 CRUD 까지 밖에 없어서 임의로 데이터를 추가하거나, 향후 시간이 난다면 실제 데이터를 불러와서 이용할 수 있게 해볼까 한다. 총 회고오랜만에 기초부터 복습하는 느낌이 들어 면접 준비한다는 느낌으로 중요한 내용만 추려서 정리해보았다.아직 1주차라서 많은 내용을 다뤄보지는 못한 것 같고, 앞으로 실습 면에서 Kotlin 문법을 기억 속에서 되찾으며 여러모로 부족한 부분을 찾아가며 배워보면서 즐겨 볼 생각이다.  모두 화이팅! (o゚v゚)ノ

백엔드워밍업클럽백엔드2기Kotlin

채널톡 아이콘