🖤인프런만의 100% 블프 이벤트🖤

블로그

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

채널톡 아이콘