Help
RSS
API
Feed
Maltego
Contact
Domain > blog.sapzil.org
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2021-02-14
18.197.211.107
(
ClassC
)
2024-12-26
185.199.108.153
(
ClassC
)
Port 80
HTTP/1.1 301 Moved PermanentlyConnection: keep-aliveContent-Length: 162Server: GitHub.comContent-Type: text/htmlLocation: https://blog.sapzil.org/X-GitHub-Request-Id: 5686:2AB1E9:AC081F:B2B433:676CF33BAccept-Ranges: bytesAge: 0Date: Thu, 26 Dec 2024 06:10:07 GMTVia: 1.1 varnishX-Served-By: cache-bfi-krnt7300095-BFIX-Cache: MISSX-Cache-Hits: 0X-Timer: S1735193408.811878,VS0,VE78Vary: Accept-EncodingX-Fastly-Request-ID: 8e9fc067fab1cfc16f77a86edbd19d20b6357875 html>head>title>301 Moved Permanently/title>/head>body>center>h1>301 Moved Permanently/h1>/center>hr>center>nginx/center>/body>/html>
Port 443
HTTP/1.1 200 OKConnection: keep-aliveContent-Length: 305531Server: GitHub.comContent-Type: text/html; charsetutf-8Last-Modified: Wed, 23 Oct 2024 14:02:55 GMTAccess-Control-Allow-Origin: *ETag: 6719020f-4a97bexpires: Thu, 26 Dec 2024 06:20:07 GMTCache-Control: max-age600x-proxy-cache: MISSX-GitHub-Request-Id: EC7D:EA6C6:ADD142:B48A25:676CF33AAccept-Ranges: bytesAge: 0Date: Thu, 26 Dec 2024 06:10:08 GMTVia: 1.1 varnishX-Served-By: cache-bfi-krnt7300070-BFIX-Cache: MISSX-Cache-Hits: 0X-Timer: S1735193408.945733,VS0,VE79Vary: Accept-EncodingX-Fastly-Request-ID: 79b57e793bcf6bbe877624f51c4919114f36cac7 !doctype html>html langko dirltr classblog-wrapper blog-list-page plugin-blog plugin-id-default data-has-hydratedfalse>head>meta charsetUTF-8>meta namegenerator contentDocusaurus v3.5.2>title data-rhtrue>The Sapzil | The Sapzil/title>meta data-rhtrue nameviewport contentwidthdevice-width,initial-scale1>meta data-rhtrue nametwitter:card contentsummary_large_image>meta data-rhtrue propertyog:url contenthttps://blog.sapzil.org/>meta data-rhtrue propertyog:locale contentko>meta data-rhtrue namedocusaurus_locale contentko>meta data-rhtrue namedocsearch:language contentko>meta data-rhtrue propertyog:title contentThe Sapzil | The Sapzil>meta data-rhtrue namedescription contentBlog>meta data-rhtrue propertyog:description contentBlog>meta data-rhtrue namedocusaurus_tag contentblog_posts_list>meta data-rhtrue namedocsearch:docusaurus_tag contentblog_posts_list>link data-rhtrue relicon href/img/favicon.ico>link data-rhtrue relcanonical hrefhttps://blog.sapzil.org/>link data-rhtrue relalternate hrefhttps://blog.sapzil.org/ hreflangko>link data-rhtrue relalternate hrefhttps://blog.sapzil.org/ hreflangx-default>script data-rhtrue>function insertBanner(){var ndocument.createElement(div);n.id__docusaurus-base-url-issue-banner-container;n.innerHTML\ndiv id__docusaurus-base-url-issue-banner styleborder: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;>\n p stylefont-weight: bold; font-size: 30px;>Your Docusaurus site did not load properly./p>\n p>A very common reason is a wrong site a hrefhttps://docusaurus.io/docs/docusaurus.config.js/#baseUrl stylefont-weight: bold;>baseUrl configuration/a>./p>\n p>Current configured baseUrl span stylefont-weight: bold; color: red;>//span> (default value)/p>\n p>We suggest trying baseUrl span id__docusaurus-base-url-issue-banner-suggestion-container stylefont-weight: bold; color: green;>/span>/p>\n/div>\n,document.body.prepend(n);var edocument.getElementById(__docusaurus-base-url-issue-banner-suggestion-container),swindow.location.pathname,o/s.substr(-1)?s:s+/;e.innerHTMLo}document.addEventListener(DOMContentLoaded,(function(){void 0window.docusaurus&&insertBanner()}))/script>script data-rhtrue typeapplication/ld+json>{@context:https://schema.org,@type:Blog,@id:https://blog.sapzil.org/,mainEntityOfPage:https://blog.sapzil.org/,headline:Blog,description:Blog,blogPost:{@type:BlogPosting,@id:https://blog.sapzil.org/2023/01/22/k3s,mainEntityOfPage:https://blog.sapzil.org/2023/01/22/k3s,url:https://blog.sapzil.org/2023/01/22/k3s,headline:나의 k3s 구성 둘러보기,name:나의 k3s 구성 둘러보기,description:개인 서버 운영의 역사 (TMI 대방출),datePublished:2023-01-22T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2022/03/04/gradle-convention-plugins,mainEntityOfPage:https://blog.sapzil.org/2022/03/04/gradle-convention-plugins,url:https://blog.sapzil.org/2022/03/04/gradle-convention-plugins,headline:Gradle Convention Plugins 삽질기,name:Gradle Convention Plugins 삽질기,description:오랜만에 Spring Boot 프로젝트를 멀티 모듈로 구성하려고 Gradle 문서를 읽다보니 멀티 프로젝트에서 subprojects {}, allprojects {}의 사용을 더이상 권장하지 않는다는 내용을 보게 되었다.,datePublished:2022-03-04T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2021/05/09/nix,mainEntityOfPage:https://blog.sapzil.org/2021/05/09/nix,url:https://blog.sapzil.org/2021/05/09/nix,headline:‘순수 함수형’ 패키지 관리자 Nix 맛보기,name:‘순수 함수형’ 패키지 관리자 Nix 맛보기,description:Nix는 Linux와 macOS를 지원하는 패키지 관리 시스템입니다.,datePublished:2021-05-09T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2019/12/29/microservices-1,mainEntityOfPage:https://blog.sapzil.org/2019/12/29/microservices-1,url:https://blog.sapzil.org/2019/12/29/microservices-1,headline:나도 MSA 한번 해보자 (1),name:나도 MSA 한번 해보자 (1),description:마이크로서비스 아키텍처에 대한 이야기는 최소 5년 전부터 꾸준히 들려왔던 걸로 기억한다. 하지만 회사에서 하던 프로젝트가 (MSA라고 하기는 조금 뭐하지만 어쨌든) 여러 서비스의 조합으로 구성되어 있었는데, 나쁜 경험을 많이 해서 막연한 거부감이 있었다. 그래서 서비스를 어떻게 잘 나누는 것이 좋은지 가끔 생각해보긴 했어도 웬만하면 모노리스가 낫지라는 마음가짐으로 살아왔다. (DHH가 작성한 The Majestic Monolith라는 글의 영향도 어느 정도 있었다.),datePublished:2019-12-29T00:00:00.000Z,author:,image:{@type:ImageObject,@id:https://blog.sapzil.org/public/img/2019-12-msa.jpg,url:https://blog.sapzil.org/public/img/2019-12-msa.jpg,contentUrl:https://blog.sapzil.org/public/img/2019-12-msa.jpg,caption:title image for the blog post: 나도 MSA 한번 해보자 (1)},keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2019/06/09/docker-desktop-for-windows-home,mainEntityOfPage:https://blog.sapzil.org/2019/06/09/docker-desktop-for-windows-home,url:https://blog.sapzil.org/2019/06/09/docker-desktop-for-windows-home,headline:Windows 10 Home에 Docker Desktop 설치하기,name:Windows 10 Home에 Docker Desktop 설치하기,description:Docker Desktop for Windows를 설치하려면 Hyper-V를 지원하는 OS가 필요합니다. Home은 여기에 포함되지 않으므로, VirtualBox 기반의 레거시 Docker Toolbox를 사용하라고 친절하게 나와있습니다. 하지만 저는 최신 버전을 쓰고 싶었기에 방법이 없을까 찾아보던 중 Docker 포럼의 한 글을 발견했습니다. 따라해보니까 잘 되어서 정리해 둡니다. (어쩌면 윈도우 라이센스 위반일 수도 있지만...),datePublished:2019-06-09T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2018/08/26/kotlin-jpa-pitfalls-embeddable,mainEntityOfPage:https://blog.sapzil.org/2018/08/26/kotlin-jpa-pitfalls-embeddable,url:https://blog.sapzil.org/2018/08/26/kotlin-jpa-pitfalls-embeddable,headline:Kotlin에서 JPA 사용할 때 주의할 점 (2) - Embeddable, IdClass,name:Kotlin에서 JPA 사용할 때 주의할 점 (2) - Embeddable, IdClass,description:Kotlin에서 JPA 사용할 때 주의할 점을 쓴 이후로 직장에서 하는 프로젝트에도 Kotlin + JPA를 사용하게 되었습니다. 그러다보니 좀 더 고급 기능을 사용하게 되고 또 여러가지 새로운 어려움에 부딪혔습니다.,datePublished:2018-08-26T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2018/06/20/gradle-subproject-grouping,mainEntityOfPage:https://blog.sapzil.org/2018/06/20/gradle-subproject-grouping,url:https://blog.sapzil.org/2018/06/20/gradle-subproject-grouping,headline:Gradle에서 서브 프로젝트를 한 디렉토리에 몰아넣기,name:Gradle에서 서브 프로젝트를 한 디렉토리에 몰아넣기,description:한 문장으로 요약이 잘 안돼서 제목이 이상한데, 읽어보시면 뭔지 알 수 있습니다.,datePublished:2018-06-20T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2018/01/21/taming-maven-transitive-dependencies,mainEntityOfPage:https://blog.sapzil.org/2018/01/21/taming-maven-transitive-dependencies,url:https://blog.sapzil.org/2018/01/21/taming-maven-transitive-dependencies,headline:Maven의 Transitive Dependency 길들이기,name:Maven의 Transitive Dependency 길들이기,description:NullPointerException만큼 무서운 ClassNotFoundException을 피하는 방법,datePublished:2018-01-21T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2017/11/02/kotlin-jpa-pitfalls,mainEntityOfPage:https://blog.sapzil.org/2017/11/02/kotlin-jpa-pitfalls,url:https://blog.sapzil.org/2017/11/02/kotlin-jpa-pitfalls,headline:Kotlin에서 JPA 사용할 때 주의할 점,name:Kotlin에서 JPA 사용할 때 주의할 점,description:Kotlin에서 JPA를 사용해봅시다! Java에서 쓸 때와 별로 다를 것은 없습니다. 하지만 엔티티 클래스를 데이터 클래스로 선언하였을 때 런타임 프록시 객체를 사용하는 Hibernate/JPA의 기능들이 잘 작동하지 않을 수 있어 주의가 필요합니다.,datePublished:2017-11-02T00:00:00.000Z,author:,keywords:},{@type:BlogPosting,@id:https://blog.sapzil.org/2017/07/16/redux-observable,mainEntityOfPage:https://blog.sapzil.org/2017/07/16/redux-observable,url:https://blog.sapzil.org/2017/07/16/redux-observable,headline:redux-observable 사용하기,name:redux-observable 사용하기,description:redux-observable은 RxJS로 Redux에서 비동기 액션을 처리할 수 있게 해줍니다.,datePublished:2017-07-16T00:00:00.000Z,author:,image:{@type:ImageObject,@id:https://blog.sapzil.org/public/img/2017-07-redux-observable.png,url:https://blog.sapzil.org/public/img/2017-07-redux-observable.png,contentUrl:https://blog.sapzil.org/public/img/2017-07-redux-observable.png,caption:title image for the blog post: redux-observable 사용하기},keywords:}}/script>link relalternate typeapplication/rss+xml href/rss.xml titleThe Sapzil RSS Feed>link relalternate typeapplication/atom+xml href/atom.xml titleThe Sapzil Atom Feed>link relpreconnect hrefhttps://www.google-analytics.com>link relpreconnect hrefhttps://www.googletagmanager.com>script async srchttps://www.googletagmanager.com/gtag/js?idG-KFBZE189TB>/script>script>function gtag(){dataLayer.push(arguments)}window.dataLayerwindow.dataLayer||,gtag(js,new Date),gtag(config,G-KFBZE189TB,{})/script>link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css integritysha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM crossoriginanonymous>link relstylesheet href/assets/css/styles.94625bf3.css>script src/assets/js/runtime~main.1eeb77a4.js deferdefer>/script>script src/assets/js/main.53e841f1.js deferdefer>/script>/head>body classnavigation-with-keyboard>script>!function(){function t(t){document.documentElement.setAttribute(data-theme,t)}var efunction(){try{return new URLSearchParams(window.location.search).get(docusaurus-theme)}catch(t){}}()||function(){try{return window.localStorage.getItem(theme)}catch(t){}}();t(null!e?e:light)}(),function(){try{const nnew URLSearchParams(window.location.search).entries();for(vart,eof n)if(t.startsWith(docusaurus-data-)){var at.replace(docusaurus-data-,data-);document.documentElement.setAttribute(a,e)}}catch(t){}}()/script>div id__docusaurus>div roleregion aria-label본문으로 건너뛰기>a classskipToContent_fXgn href#__docusaurus_skipToContent_fallback>본문으로 건너뛰기/a>/div>nav aria-label메인 classnavbar navbar--fixed-top>div classnavbar__inner>div classnavbar__items>button aria-label사이드바 펼치거나 접기 aria-expandedfalse classnavbar__toggle clean-btn typebutton>svg width30 height30 viewBox0 0 30 30 aria-hiddentrue>path strokecurrentColor stroke-linecapround stroke-miterlimit10 stroke-width2 dM4 7h22M4 15h22M4 23h22>/path>/svg>/button>a classnavbar__brand href/>b classnavbar__title text--truncate>👷 The Sapzil/b>/a>a aria-currentpage classnavbar__item navbar__link navbar__link--active href/>Posts/a>a classnavbar__item navbar__link href/tags/>Tags/a>/div>div classnavbar__items navbar__items--right>div classnavbarSearchContainer_Bca1>/div>/div>/div>div rolepresentation classnavbar-sidebar__backdrop>/div>/nav>div id__docusaurus_skipToContent_fallback classmain-wrapper mainWrapper_z2l0>div classcontainer margin-vert--lg>div classrow>aside classcol col--3>nav classsidebar_re4s thin-scrollbar aria-label최근 블로그 문서 둘러보기>div classsidebarItemTitle_pO2u margin-bottom--md>Recent posts/div>div rolegroup>h3 classyearGroupHeading_rMGB>2023/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2023/01/22/k3s/>나의 k3s 구성 둘러보기/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2022/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2022/03/04/gradle-convention-plugins/>Gradle Convention Plugins 삽질기/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2021/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2021/05/09/nix/>‘순수 함수형’ 패키지 관리자 Nix 맛보기/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2019/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2019/12/29/microservices-1/>나도 MSA 한번 해보자 (1)/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2019/06/09/docker-desktop-for-windows-home/>Windows 10 Home에 Docker Desktop 설치하기/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2018/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2018/08/26/kotlin-jpa-pitfalls-embeddable/>Kotlin에서 JPA 사용할 때 주의할 점 (2) - Embeddable, IdClass/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2018/06/20/gradle-subproject-grouping/>Gradle에서 서브 프로젝트를 한 디렉토리에 몰아넣기/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2018/01/21/taming-maven-transitive-dependencies/>Maven의 Transitive Dependency 길들이기/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2017/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2017/11/02/kotlin-jpa-pitfalls/>Kotlin에서 JPA 사용할 때 주의할 점/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2017/07/16/redux-observable/>redux-observable 사용하기/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2017/07/10/diffmonster/>Diff Monster를 소개합니다/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2017/04/01/do-not-trust-sql-transaction/>SQL 트랜잭션 - 믿는 도끼에 발등 찍힌다/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2017/03/12/testing-restful-api-servers/>RESTful API 서버 테스트하기/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2016/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2016/12/15/react-with-rx/>RxJS로 React 컴포넌트 상태 관리하기/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2016/08/04/jersey-hk2/>Jersey 2.x에 내장된 의존성 주입 기능 사용하기/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2016/07/29/animeta-react-ssr/>애니메타의 React 서버 렌더링 아키텍쳐/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2016/03/20/react-internals-utils/>React 소스 코드 읽기 - 유틸리티들/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2016/03/17/react-internals-elements/>React 소스 코드 읽기 - ReactElement/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2016/03/09/react-internals-modules/>React 소스 코드 읽기 - 모듈 시스템/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2015/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2015/10/24/advanced-uwsgi/>uWSGI의 고급 기능들/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2015/10/07/thinking-in-graphql/>Thinking in GraphQL (번역)/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2015/09/01/graphql-rfc/>GraphQL 살펴보기/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2015/05/15/graphql-and-relay/>GraphQL과 Relay: 웹 애플리케이션 개발의 미래/a>/li>/ul>/div>div rolegroup>h3 classyearGroupHeading_rMGB>2014/h3>ul classsidebarItemList_Yudw clean-list>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2014/09/21/python-web-app-deploy/>파이썬 웹 애플리케이션 배포: 고려할 점들/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2014/08/12/upstart/>Upstart로 오래 도는 프로세스 관리하기/a>/li>li classsidebarItem__DBe>a classsidebarItemLink_mo7H href/2014/08/10/jquery-to-react/>jQuery to React/a>/li>/ul>/div>/nav>/aside>main classcol col--7>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2023/01/22/k3s/>나의 k3s 구성 둘러보기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2023-01-22T00:00:00.000Z>2023년 1월 22일/time> · !-- -->약 10분/div>/header>div classmarkdown>h2 classanchor anchorWithStickyNavbar_LWe7 id개인-서버-운영의-역사-tmi-대방출>개인 서버 운영의 역사 (TMI 대방출)a href#개인-서버-운영의-역사-tmi-대방출 classhash-link aria-label개인 서버 운영의 역사 (TMI 대방출)에 대한 직접 링크 title개인 서버 운영의 역사 (TMI 대방출)에 대한 직접 링크>/a>/h2>p>개인 서버를 운영해온지도 거의 10여년이 넘어가고 있다. 몇몇 취미로 운영하는 서비스를 돌리거나 a hrefhttps://tt-rss.org/ target_blank relnoopener noreferrer>RSS 리더/a>, Mastodon 인스턴스 등 개인적인 서비스를 띄우는 데에 사용하고 있다. 무료 크레딧을 받거나 해서 저렴하게 운영할 수 있는 옵션이 생길 때마다 여러 호스팅 서비스를 옮겨다니곤 했다. (생각나는 것만: 카페24, DigitalOcean, Linode, GCE, EC2) 지금은 a hrefhttps://aws.amazon.com/ko/lightsail/ target_blank relnoopener noreferrer>Lightsail/a>에 정착했다./p>p>이렇다보니 언제든 호스팅 서비스를 옮길 수 있게 해둘 필요를 느꼈다. 단순히 설정 방법을 기록해 두는 건 실수의 여지가 많고, 초기 설정 이후에 바꾼 내용 업데이트를 잊기 쉽다. 직접 서버에 들어가서 뭔가 수정하기보다는 형상 관리가 가능한 방식을 사용하고 싶어서 처음에는 당시에 익숙했던 a hrefhttps://github.com/dittos/ansibles target_blank relnoopener noreferrer>Ansible/a>로 관리를 했다. 하지만 시스템 전역에 설치한 패키지에 의존하도록 하다보니 OS 버전이 바뀌거나 하면 제대로 작동하지 않는 경우가 많았다./p>p>결국 환경의 영향을 덜 받게 하려면 Docker를 사용해야겠다 생각했다. 물론 Ansible로 Docker 컨테이너를 관리하는 게 불가능하지는 않다. 그렇지만 중단 없이 서비스를 재시작(롤링 업데이트)하는 등 조금 복잡한 작업을 하기는 어렵다. 당시 Kubernetes는 단일 서버에서 사용하기에는 설정이 쉽지 않았기 때문에 Docker Swarm을 잠시 시도해봤으나 여러 가지로 아쉬운 점이 많아서 대충 방치해놓고 시간이 흘렀다./p>p>그러던 중 a hrefhttps://k3s.io/ target_blank relnoopener noreferrer>k3s/a>라는 가볍고 단일 서버에 설치 가능한 Kubernetes 배포판이 나왔다는 소식을 접하고 시험해봤는데 꽤나 만족스러웠다. 설치도 간단해서 좋았다. (명령어 하나면 충분: code>curl -sfL https://get.k3s.io | sh -/code>) 대기 상태에서도 머신 자원을 생각보다 많이 사용하는 문제는 있지만 그 정도는 편리함과 타협할 수 있는 부분이라고 보고 지금까지 정착해서 사용하고 있다./p>p>물론 직접 서버를 운영하는 게 Vercel이나 fly.io 같은 PaaS를 사용하는 것에 비해 효율이 떨어지는 일인 건 맞다. 그래도 인프라 공부도 되고 취미 생활로는 나쁘지 않은 것 같다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id인그레스-컨트롤러와-tls-인증서>인그레스 컨트롤러와 TLS 인증서a href#인그레스-컨트롤러와-tls-인증서 classhash-link aria-label인그레스 컨트롤러와 TLS 인증서에 대한 직접 링크 title인그레스 컨트롤러와 TLS 인증서에 대한 직접 링크>/a>/h2>p>Kubernetes에서 웹 서비스는 일반적으로 인그레스로 외부에 노출하게 되고 인그레스 컨트롤러를 설치해야 한다. k3s에는 Traefik이 인그레스 컨트롤러로 기본 설치되는데 나는 익숙한 a hrefhttps://kubernetes.github.io/ingress-nginx/ target_blank relnoopener noreferrer>ingress-nginx/a>를 대신 사용하고 있다. Traefik을 설치하지 않으려면 k3s을 처음 설치할 때 a hrefhttps://docs.k3s.io/reference/server-config#kubernetes-components target_blank relnoopener noreferrer>code>--disable traefik/code> 옵션/a>을 줘야 한다./p>p>ingress-nginx는 code>LoadBalancer/code> 타입의 서비스를 만들고 a hrefhttps://docs.k3s.io/networking#service-load-balancer target_blank relnoopener noreferrer>k3s의 ServiceLB/a>에 의해 호스트 외부에서 접속할 수 있게 된다. 물론 Lightsail의 방화벽 설정에서도 80, 443 포트를 열어줘야 한다. Lightsail 인스턴스가 바뀌어도 IP가 그대로이도록 고정 IP를 할당하고, DNS는 직접 서버 IP를 가리키게 한다./p>p>AWS의 로드 밸런서를 사용하지 않기 때문에 TLS 인증서는 직접 발급해야 한다. a hrefhttps://cert-manager.io/ target_blank relnoopener noreferrer>cert-manager/a>를 이용해서 Let's Encrypt 인증서를 자동 발급, 자동 갱신하고 있다. 새 서브도메인에 대한 인증서가 필요하면 Ingress에 어노테이션만 달아주면 된다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id모니터링>모니터링a href#모니터링 classhash-link aria-label모니터링에 대한 직접 링크 title모니터링에 대한 직접 링크>/a>/h2>p>처음에 별 생각 없이 익숙한 Prometheus Operator를 설치했다가 리소스 낭비가 심해서 삭제하고 그냥 살고 있었는데, 최근 실행하는 서비스가 많아지니 다시 필요성을 느꼈다./p>p>회사 인프라에서 쓰는 걸 보고 알게 된 VictoriaMetrics를 한 번 깔아봤는데 가볍고 잘 작동한다! VictoriaMetrics도 오퍼레이터를 사용하는 게 권장하는 방식 같지만, 오퍼레이터 자체의 오버헤드도 줄이고 싶어서 단일 프로세스인 a hrefhttps://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-single.html target_blank relnoopener noreferrer>VictoriaMetrics Single/a>을 띄우는 방향으로 해보았다./p>p>VictoriaMetrics는 딱 Prometheus와 같이 지표 수집, 저장만 하는 역할이어서 지표를 보고 싶으면 Grafana도 설치해야 한다. 이 역시 a hrefhttps://docs.victoriametrics.com/guides/k8s-monitoring-via-vm-single.html target_blank relnoopener noreferrer>튜토리얼 문서/a>에 나온대로 하면 된다. 외부에서 Grafana에 접속하게 하려면 인증을 붙이든지 해야 하는데 귀찮아서 일단 a hrefhttps://tailscale.com/kb/1185/kubernetes/#sample-proxy target_blank relnoopener noreferrer>Tailscale 프록시/a>를 달아놓고 Tailscale VPN 내에서만 노출해 두었다./p>p>로그도 오래 보관하면 좋긴 하겠지만 당장은 필요가 없어서 그냥 두고 있다. 만약 필요하다면 a hrefhttps://grafana.com/oss/loki/ target_blank relnoopener noreferrer>Loki/a>를 써볼지도.../p>h2 classanchor anchorWithStickyNavbar_LWe7 id데이터베이스와-퍼시스턴트-볼륨>데이터베이스와 퍼시스턴트 볼륨a href#데이터베이스와-퍼시스턴트-볼륨 classhash-link aria-label데이터베이스와 퍼시스턴트 볼륨에 대한 직접 링크 title데이터베이스와 퍼시스턴트 볼륨에 대한 직접 링크>/a>/h2>p>내가 돌리는 대부분의 서비스가 PostgreSQL에 의존하고 있기 때문에 k3s에 하나를 설치해서 공유하고 있다. 파드가 재시작되어도 데이터가 날아가면 안되니 퍼시스턴트 볼륨을 마운트해야 하는데, 이 또한 k3s에서 기본 제공하는 a hrefhttps://docs.k3s.io/storage#setting-up-the-local-storage-provider target_blank relnoopener noreferrer>Local Storage Provider/a>를 사용한다. (예전에는 기본 탑재가 아니었는데 언젠가부터 기본적으로 설치된다. 여기서 또 TMI, a hrefhttps://github.com/rancher/local-path-provisioner/pull/87 target_blank relnoopener noreferrer>CPU를 많이 잡아먹는 버그/a>를 수정한 적이 있다 😎)/p>p>호스트 머신 파일시스템의 code>/opt/local-path-provisioner/code> 디렉토리 밑에 파일이 저장되는 매우 단순한 구조이므로 파드가 다른 머신으로 옮겨다닐 수 없는 문제가 있지만 어차피 노드를 한 개만 사용할 것이므로 전혀 문제가 되지 않는다. 노드를 추가하더라도 컨트롤 플레인이 있는 노드에서만 DB를 돌리면 오케이./p>p>DB 백업은 별도로 하지 않고, Lightsail의 백업 기능을 켜두었다. 매일 디스크 스냅샷을 떠주고 일주일 간 유지한다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id인프라-형상-관리>인프라 형상 관리a href#인프라-형상-관리 classhash-link aria-label인프라 형상 관리에 대한 직접 링크 title인프라 형상 관리에 대한 직접 링크>/a>/h2>p>위에서 설명한 시스템 구성 요소는 Helm 차트로 설치하고 있다. 개인적으로 Helm을 썩 좋아하진 않지만 (YAML을 템플리팅 할 미친 생각을 대체 누가 했을까?) Kubernetes 환경의 사실상 표준이기 때문에 어쩔 수 없이 타협을 해야 한다. 내가 직접 패키징해야 하는 경우는 거의 Kustomize를 사용하고 있다./p>p>아무튼 여러 개의 Helm 차트를 설치해야 하고, 매번 code>helm/code> 명령어를 입력할 수는 없기 때문에 a hrefhttps://helmfile.readthedocs.io/en/latest/ target_blank relnoopener noreferrer>Helmfile/a>로 Helm 차트의 목록과 파라미터를 관리한다. 가능하면 직접 code>helm/code>이나 code>kubectl/code>로 조작하는 건 지양하고 Helmfile만으로 관리하려고 노력한다. 모든 설정은 a hrefhttps://github.com/dittos/k8s/blob/master/helm/ target_blank relnoopener noreferrer>나의 Git 저장소/a>에 잘 올려두고 있다./p>p>아직 깔끔하게 정리하지 못한 부분이 DB 암호 등 Helmfile에 주입할 비밀 정보를 관리하는 것이다. 일단은 Git에는 커밋하지 않는 내 로컬 머신에만 있는 파일에 넣어두고 있다. 만약 내 로컬 머신이 불의의 사고로 날아가더라도 k8s Secret에는 남아있어서 복구 가능하므로 큰 문제는 없을 것으로 생각한다./p>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/kubernetes/>kubernetes/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/sysadmin/>sysadmin/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2022/03/04/gradle-convention-plugins/>Gradle Convention Plugins 삽질기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2022-03-04T00:00:00.000Z>2022년 3월 4일/time> · !-- -->약 5분/div>/header>div classmarkdown>p>오랜만에 Spring Boot 프로젝트를 멀티 모듈로 구성하려고 Gradle 문서를 읽다보니 a hrefhttps://docs.gradle.org/7.4/userguide/sharing_build_logic_between_subprojects.html#sec:convention_plugins_vs_cross_configuration target_blank relnoopener noreferrer>멀티 프로젝트에서 code>subprojects {}/code>, code>allprojects {}/code>의 사용을 더이상 권장하지 않는다/a>는 내용을 보게 되었다./p>p>위와 같은 기존 방식을 em>cross project configuration/em> 이라고 하는데, 다음과 같은 문제가 있다고 한다./p>ul>li>서브프로젝트의 빌드 스크립트만 봐서는 부모 프로젝트에서 빌드 로직이 주입된다는 것이 분명하게 드러나지 않기 때문에 로직을 파악하기 힘들다./li>li>설정 시점에 프로젝트 간에 커플링이 생기기 때문에 a hrefhttps://docs.gradle.org/7.4/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand target_blank relnoopener noreferrer>configuration-on-demand/a>와 같은 최적화가 제대로 작동하지 않는다./li>/ul>p>큰 프로젝트가 아니라면 사실 별로 상관 없다고 생각하지만, 아무튼 일리가 있다고 생각되니 권장 방식인 Convention Plugins 방식을 사용해본다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id문제-1-unknownpluginexception>문제 1: code>UnknownPluginException/code>a href#문제-1-unknownpluginexception classhash-link aria-label문제-1-unknownpluginexception에 대한 직접 링크 title문제-1-unknownpluginexception에 대한 직접 링크>/a>/h2>p>a hrefhttps://docs.gradle.org/7.4/userguide/declaring_dependencies_between_subprojects.html target_blank relnoopener noreferrer>공식 문서/a>를 Kotlin DSL 버전으로 따라해보았는데, 다음과 같은 에러가 나면서 잘 되지 않았다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>Build file '/Users/user/myproject/api/build.gradle.kts' line: 1/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Plugin id: 'myproject.java-conventions' was not found in any of the following sources:/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>* Try:/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>> Run with --info or --debug option to get more log output./span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>> Run with --scan to get full insights./span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>* Exception is:/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>org.gradle.api.plugins.UnknownPluginException: Plugin id: 'myproject.java-conventions' was not found in any of the following sources:/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>- Plugin Repositories (plugin dependency must include a version number for this source)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>.../span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>이 문제는, code>buildSrc/build.gradle.kts/code>를 다음과 같이 만들어주면 해결된다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>plugins /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> `kotlin/span>span classtoken operator stylecolor:#393A34>-/span>span classtoken plain>dsl`/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>repositories /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// for kotlin-dsl plugin/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>gradlePluginPortal/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>해당 문서화 버그는 a hrefhttps://github.com/gradle/gradle/issues/19667 target_blank relnoopener noreferrer>gradle/gradle#19667/a>로 등록되어 있다. (이 자식들 ㅠㅠ)/p>h2 classanchor anchorWithStickyNavbar_LWe7 id문제-2-플러그인-버전-지정-불가>문제 2: 플러그인 버전 지정 불가a href#문제-2-플러그인-버전-지정-불가 classhash-link aria-label문제 2: 플러그인 버전 지정 불가에 대한 직접 링크 title문제 2: 플러그인 버전 지정 불가에 대한 직접 링크>/a>/h2>p>a hrefhttps://start.spring.io/ target_blank relnoopener noreferrer>Spring Initializr/a>에서 만들어진 플러그인 설정을 그대로 사용했더니 다음과 같은 에러가 났다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>Invalid plugin request id: 'org.springframework.boot', version: '2.6.4'. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'org.springframework.boot' is an implementation dependency of project ':buildSrc'./span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Invalid plugin request id: 'io.spring.dependency-management', version: '1.0.11.RELEASE'. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'io.spring.dependency-management' is an implementation dependency of project ':buildSrc'./span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Invalid plugin request id: 'org.jetbrains.kotlin.jvm', version: '1.6.10'. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'org.jetbrains.kotlin.jvm' is an implementation dependency of project ':buildSrc'./span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Invalid plugin request id: 'org.jetbrains.kotlin.plugin.spring', version: '1.6.10'. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'org.jetbrains.kotlin.plugin.spring' is an implementation dependency of project ':buildSrc'./span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>일단 code>plugins {}/code> 블럭에서 다음과 같이 버전을 제거했다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>plugins /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>id/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"org.springframework.boot"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>id/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"io.spring.dependency-management"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>kotlin/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"jvm"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>kotlin/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"plugin.spring"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>근데 그러면 플러그인 버전을 어떻게 지정해야 할까? 답은 code>buildSrc/build.gradle.kts/code>에 다음과 같이 의존성을 추가하는 것이다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>dependencies /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>implementation/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"org.springframework.boot:spring-boot-gradle-plugin:2.6.4"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>implementation/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>implementation/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>implementation/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"org.jetbrains.kotlin:kotlin-allopen:1.6.10"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>참고로 여기에는 플러그인 ID가 아니라, Maven 좌표를 찾아서 적어주었다. Maven 좌표는 a hrefhttps://plugins.gradle.org/ target_blank relnoopener noreferrer>plugins.gradle.org/a>에서 플러그인 ID로 검색해서 찾는다./p>p>예를 들어 a hrefhttps://plugins.gradle.org/plugin/org.springframework.boot target_blank relnoopener noreferrer>code>org.springframework.boot/code>/a>를 찾아서 들어가보면 "Using legacy plugin application:" 아래에 다음과 같이 되어있다./p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>buildscript /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>.../span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> dependencies /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// v~~~~ 이 부분!/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> classpath /span>span classtoken interpolation-string string stylecolor:#e3116c>"org.springframework.boot:spring-boot-gradle-plugin:2.6.4"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>플러그인 ID만 가지고 할 수 있는 더 좋은 방법이 있을 수도 있는데 찾아보진 않았다. 끝!/p>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/gradle/>gradle/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/build-tools/>build-tools/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/kotlin/>kotlin/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/spring/>spring/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2021/05/09/nix/>‘순수 함수형’ 패키지 관리자 Nix 맛보기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2021-05-09T00:00:00.000Z>2021년 5월 9일/time> · !-- -->약 9분/div>/header>div classmarkdown>p>a hrefhttps://nixos.org/ target_blank relnoopener noreferrer>Nix/a>는 Linux와 macOS를 지원하는 패키지 관리 시스템입니다./p>p>a hrefhttps://news.ycombinator.com/ target_blank relnoopener noreferrer>해커 뉴스/a> 등에서 Nix에 대해 종종 접하게 되어서 궁금증이 생겼고, 조금 사용해보면서 파악한 내용을 정리합니다. 계속 사용할지는 아직 모르겠지만 이것저것 찾아보느라 들인 시간이 아까우니까요. 누군가에겐 도움이 되겠죠?/p>p>Nix에는 여러 특징이 있지만, 그 중에서도 같은 패키지의 여러 버전을 동시에 설치할 수 있어서 패키지를 한번 설치하면 시스템에 변화가 있더라도 계속 작동이 보장된다는 점이 유용해 보입니다. 이런 특징을 활용하면 프로젝트마다 독립된 개발 환경을 구축하는 데에 쓸 수 있습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id의존성-지옥>의존성 지옥!a href#의존성-지옥 classhash-link aria-label의존성 지옥!에 대한 직접 링크 title의존성 지옥!에 대한 직접 링크>/a>/h2>p>APT나 Homebrew 등 일반적인 패키지 관리 시스템에서는 시스템 전역에 특정 패키지 이름으로는 딱 한가지 버전만 설치할 수 있습니다. 이를 우회하기 위해 패키지 이름에 버전을 명시하기도 합니다. (예를 들면 code>python3.9/code>와 code>python3.10/code>을 별개의 패키지로 배포하는 등)/p>p>여러 패키지가 하나의 공통 패키지에 의존하는 경우 특히 문제가 있습니다. 예를 들어 OpenSSL 같은 라이브러리를 업그레이드하면 OpenSSL에 직간접적으로 의존하는 모든 패키지가 영향을 받습니다./p>p>패키지마다 호환되는 의존성의 버전을 느슨하게 정의해두고 있기는 하지만, 운이 나쁘면 이전과 똑같이 작동하지 않을 수 있습니다. 그리고 업그레이드하려는 버전이 어떤 패키지에서 요구하는 버전과 충돌하는 경우 아예 업그레이드를 못할 수도 있습니다./p>p>Nix에서는 의존성을 code>yc41q33h5xrw1zbyw5hp1y1ga0jk9hwd-openssl-1.1.1k/code>과 같이 정확한 버전과 특정 빌드로 정의합니다.따라서 패키지마다 각자 독립적으로 의존성의 버전을 선택할 수 있습니다.패키지끼리 서로 영향을 주지 않기 때문에 안전하게 패키지를 설치하거나 업그레이드할 수 있습니다. 또한 의존성이 고정되므로 패키지가 저자의 의도대로 작동할 가능성이 높아집니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 idnix-맛보기>Nix 맛보기a href#nix-맛보기 classhash-link aria-labelNix 맛보기에 대한 직접 링크 titleNix 맛보기에 대한 직접 링크>/a>/h2>p>code>nix-shell/code>을 사용하면 특정 Nix 패키지가 설치된 환경을 여러개 만들 수 있습니다. 예제로 Python과 Node가 설치된 환경을 만들어보겠습니다./p>p>먼저 a hrefhttps://nixos.org/download.html#nix-quick-install target_blank relnoopener noreferrer>Nix를 설치/a>합니다./p>p>프로젝트 디렉토리에 code>shell.nix/code> 파일을 만들고 다음 내용을 추가합니다./p>div classlanguage-nix codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-nix codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>let/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> pkgs /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>import/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34></span>span classtoken plain>nixpkgs/span>span classtoken operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>in/span>span classtoken plain> pkgs/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>mkShell /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> nativeBuildInputs /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> pkgs/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>python3/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> pkgs/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>nodejs/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그 다음 해당 디렉토리에서 code>nix-shell/code>을 실행하면 필요한 패키지를 다운로드 받고 새로운 쉘이 켜집니다.이 쉘 환경에서는 code>python/code>, code>node/code>가 code>/nix/store/code> 하위에 설치된 특정 바이너리를 가리키고 있는 것을 확인할 수 있습니다./p>div classlanguage-sh codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-sh codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>~/myproject$ nix-shell/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>these paths will be fetched (...):/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> (생략)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>nix-shell:~/myproject$ which python/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/nix/store/d44wd6n98f93hjr6q1d1phhh1hw7a17d-python3-3.8.8/bin/python/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>nix-shell:~/myproject$ which node/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/nix/store/y9ay04l5mfm255r296vhcjbxjqkjxp39-nodejs-14.16.1/bin/node/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 id패키지를-추가하자>패키지를 추가하자a href#패키지를-추가하자 classhash-link aria-label패키지를 추가하자에 대한 직접 링크 title패키지를 추가하자에 대한 직접 링크>/a>/h2>p>code>shell.nix/code>가 무언가 생소한 언어로 작성되어서 혼란스러울 것입니다. 사실 이것은 a hrefhttps://nixos.org/manual/nix/stable/#ch-expression-language target_blank relnoopener noreferrer>Nix expression language/a> 코드이지만 일단은 설명하지 않겠습니다./p>p>중요한 부분은 code>nativeBuildInput/code>입니다. code>python3/code>, code>node/code> 외에 다른 패키지는 a hrefhttps://search.nixos.org/packages target_blank relnoopener noreferrer>Nixpkgs/a>에서 검색해서 찾으면 됩니다. em>Channel을 unstable로 설정해서 찾아야 합니다./em> 그리고 macOS를 지원하지 않는 패키지가 종종 있으므로 Platforms에 code>x86_64-darwin/code>가 있는지 확인합시다./p>p>패키지를 찾았다면 code>pkgs.패키지명/code>을 추가하고, code>nix-shell/code>에서 나갔다가 (kbd>Ctrl-D/kbd> 입력) 다시 code>nix-shell/code>을 실행하면 해당 패키지가 추가된 환경으로 들어갈 수 있습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 idnixpkgs를-고정하자>Nixpkgs를 고정하자a href#nixpkgs를-고정하자 classhash-link aria-labelNixpkgs를 고정하자에 대한 직접 링크 titleNixpkgs를 고정하자에 대한 직접 링크>/a>/h2>p>2번째 줄 code>pkgs import <nixpkgs> {};/code>에서 code><nixpkgs>/code>는 시스템 전역에 설정된 a hrefhttps://nixos.wiki/wiki/Nix_channels target_blank relnoopener noreferrer>채널/a>을 따라가기 때문에 계속 변할 수 있는 값입니다. 정말 개발 환경이 항상 같으려면 Nixpkgs를 특정 버전으로 고정해야합니다./p>p>이를 위해 a hrefhttps://github.com/nmattia/niv target_blank relnoopener noreferrer>niv/a>를 사용하겠습니다. 프로젝트 디렉토리에서 다음 명령을 실행합니다./p>div classlanguage-sh codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-sh codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>nix-shell -p niv --run "niv init -b nixpkgs-unstable"/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그러면 code>nix/sources.json/code>, code>nix/sources.nix/code>가 생성됩니다. 이제 code>shell.nix/code>를 수정해서 고정된 Nixpkgs를 사용하도록 설정합니다./p>div classlanguage-nix codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-nix codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>let/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> sources /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>import/span>span classtoken plain> /span>span classtoken url stylecolor:#36acaa>./nix/sources.nix/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> pkgs /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>import/span>span classtoken plain> sources/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>nixpkgs /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>in/span>span classtoken plain> pkgs/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>mkShell /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 iddirenv를-연동하자>direnv를 연동하자a href#direnv를-연동하자 classhash-link aria-labeldirenv를 연동하자에 대한 직접 링크 titledirenv를 연동하자에 대한 직접 링크>/a>/h2>p>매번 적절한 code>nix-shell/code>을 켜는 것은 불편하므로 a hrefhttps://direnv.net/ target_blank relnoopener noreferrer>direnv/a>를 활용하면 좋습니다./p>p>direnv도 Nix를 활용해서 설치해보겠습니다. (이미 direnv가 설치되어 있다면 넘어가시면 됩니다.)/p>div classlanguage-sh codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-sh codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>nix-env -iA nixpkgs.direnv/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그리고 a hrefhttps://direnv.net/docs/hook.html target_blank relnoopener noreferrer>문서를 참고해서 direnv hook을 추가/a>한 뒤 쉘을 새로 띄웁니다./p>p>프로젝트 디렉토리 하위에 code>.envrc/code>를 만들고 code>use nix/code>를 적어줍니다. 최초 한번 code>direnv allow/code>를 실행해주어야 합니다./p>div classlanguage-sh codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-sh codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>~/myproject$ echo "use nix" > .envrc/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>direnv: error /home/ditto/myproject/.envrc is blocked. Run `direnv allow` to approve its content/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>~/myproject$ direnv allow/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>direnv: loading ~/myproject/.envrc/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>direnv: using nix/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>direnv: export +AR +AS +CC +CONFIG_SHELL +CXX +DETERMINISTIC_BUILD +HOST_PATH +IN_NIX_SHELL (...생략)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain># 와! nix-shell 안에 있어요/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>~/myproject$ which node/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/nix/store/y9ay04l5mfm255r296vhcjbxjqkjxp39-nodejs-14.16.1/bin/node/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain># 다른 디렉토리로 빠져나오면 이전 상태로 돌아옵니다/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>~/myproject$ cd/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>direnv: unloading/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>~$ which node/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/usr/bin/node/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 id더-알아보기>더 알아보기a href#더-알아보기 classhash-link aria-label더 알아보기에 대한 직접 링크 title더 알아보기에 대한 직접 링크>/a>/h2>ul>li>a hrefhttps://nixos.org/learn.html#learn-guides target_blank relnoopener noreferrer>Nix 가이드 문서/a>를 따라해봅니다./li>li>a hrefhttps://nixos.org/manual/nix/stable/#ch-expression-language target_blank relnoopener noreferrer>Nix expression language/a>를 공부해봅니다. 구글링할 때 나오는 Nix 코드 스타일이 제각각이라 언어를 대충이라도 알아야 갖다 쓰기가 편합니다. Nix의 가장 큰 진입장벽인 것 같습니다./li>li>a hrefhttps://nixos.org/manual/nixpkgs/stable/#chap-language-support target_blank relnoopener noreferrer>pip 등 언어 패키지 관리자의 기능을 Nix가 어느 정도 대체/a>할 수 있습니다./li>li>a hrefhttps://nixos.org/guides/nix-pills/our-first-derivation.html target_blank relnoopener noreferrer>Nix 패키지를 만들어보기!/a> 아직 못 해봤어요./li>li>a hrefhttps://edolstra.github.io/pubs/nspfssd-lisa2004-final.pdf target_blank relnoopener noreferrer>Nix: A Safe and Policy-Free System for Software Deployment (PDF)/a> 논문을 읽어봅니다. 🤔 실용적인 부분을 제쳐두더라도 생각보다 역사가 오래되고 흥미로운 소프트웨어인 것을 알 수 있습니다./li>/ul>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/nix/>nix/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/tools/>tools/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/package-manager/>package-manager/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2019/12/29/microservices-1/>나도 MSA 한번 해보자 (1)/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2019-12-29T00:00:00.000Z>2019년 12월 29일/time> · !-- -->약 13분/div>/header>div classmarkdown>p>마이크로서비스 아키텍처에 대한 이야기는 최소 5년 전부터 꾸준히 들려왔던 걸로 기억한다. 하지만 회사에서 하던 프로젝트가 (MSA라고 하기는 조금 뭐하지만 어쨌든) 여러 서비스의 조합으로 구성되어 있었는데, 나쁜 경험을 많이 해서 막연한 거부감이 있었다. 그래서 서비스를 어떻게 잘 나누는 것이 좋은지 가끔 생각해보긴 했어도 '웬만하면 모노리스가 낫지'라는 마음가짐으로 살아왔다. (DHH가 작성한 a hrefhttps://m.signalvnoise.com/the-majestic-monolith/ target_blank relnoopener noreferrer>The Majestic Monolith/a>라는 글의 영향도 어느 정도 있었다.)/p>figure>img src/public/img/2019-12-msa.jpg altMonolithic vs Microservices>figcaption>결국 다 똥인가…/figcaption>/figure>p>세월이 흘러 회사에서 다른 프로젝트를 하게 되었고 이전의 경험을 반면교사 삼아 이번에는 하나의 코드베이스에서 최대한 서비스를 나누지 않았다. 초기에 프로젝트를 빠르게 진행할 수 있었고 지금도 어느 정도 레거시가 쌓이긴 했지만 기능을 추가하는데 크게 무리는 없는 상태다. (다른 팀원들은 어떻게 생각하는지 모르겠다 ㅠㅠ)/p>p>하지만 사업 측 이해당사자가 많아지고 팀원도 늘어나면서 서로 다른 기능 영역의 릴리즈 스케줄이 서로 꼬이기 시작했다. 어떤 기능 영역 하나에서 발생한 성능 문제가 서비스 전체에 영향을 주는 일이 생기기도 했다. 그러다보니 서비스를 적절히 나누면 독립적인 배포가 가능할 수도 있겠다는 생각에 MSA에 대해 다시 관심이 생겼다. 또 다른 계기로는 이전보다 MSA로 전환하는 사례가 많이 보이고, 채용공고에도 'MSA 경험자' 같은 말이 등장하기 시작하는 분위기에 약간의 위기감을 느꼈던 것도 있다./p>p>그리하여 MSA 기반의 연습 프로젝트를 해봐야겠다고 마음먹었다. 가장 먼저 프로젝트 주제를 정했는데, 문제 영역이 충분히 복잡해야 한다는 생각이 있었다. 예를 들어 투두리스트 앱이라면 머리를 최대한 짜내도 현실에서는 필요하지 않을 법한 이상한 기능을 추가하지 않는 이상 서비스를 여러 개로 나누기 힘들다고 봤다. 결론은 a hrefhttps://watcha.com/ target_blank relnoopener noreferrer>왓챠/a> (왓챠 플레이 말고!)를 베끼기로 했다. 오래 해오던 사이드 프로젝트와 비슷해서 구조에 대한 아이디어가 어느 정도 있었기 때문에 내게는 꽤 자연스러운 선택이다. 프로젝트 이름은 code>microservices/code> + code>watcha/code> code>matcha/code>로 정했다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id마이크로서비스에-대한-뇌피셜>마이크로서비스에 대한 뇌피셜a href#마이크로서비스에-대한-뇌피셜 classhash-link aria-label마이크로서비스에 대한 뇌피셜에 대한 직접 링크 title마이크로서비스에 대한 뇌피셜에 대한 직접 링크>/a>/h2>p>MSA에 대해 나름대로 정의를 내려야 프로젝트의 목표가 좀 더 확실해질 것이다. 내가 생각하기에 대충 다음 조건을 만족하면 마이크로서비스라고 부르는 것 같다./p>ul>li>다른 서비스와는 네트워크로 통신: OS 프로세스를 다른 서비스와 공유하지 않는다./li>li>독립적인 배포가 가능한 단위: 다른 서비스와 의존 관계일 수는 있지만 대부분의 경우에./li>li>(논리적인) 데이터베이스를 다른 서비스와 공유하지 않음: 같은 데이터베이스 서버를 사용하더라도 Foreign Key를 걸거나 테이블을 Join하지 않는다./li>/ul>p>이러한 조건 때문에 발생할 것으로 예상되는 여러가지 어려움이 있고, 어떻게 해결할 지 아이디어가 있는 것도 있고 없는 것도 있다. (없다면 공부하게 될 영역이다.) 하나씩 살펴보자./p>h3 classanchor anchorWithStickyNavbar_LWe7 id분산-트랜잭션>분산 트랜잭션a href#분산-트랜잭션 classhash-link aria-label분산 트랜잭션에 대한 직접 링크 title분산 트랜잭션에 대한 직접 링크>/a>/h3>p>가장 골치아플 것 같은 문제는 데이터의 일관성을 보장하는 것이다. 이전에는 데이터베이스 트랜잭션이 보장해주던 원자성을 잃어버리기 때문이다. 서비스 A가 담당하는 데이터와 서비스 B가 담당하는 데이터를 함께 변경해야 한다면, A의 데이터를 변경하고 나서 B의 데이터를 변경할 것이다. 하지만,/p>ol>li>외부에서는 A의 데이터는 변경되었지만 B의 데이터는 아직 변경되지 않은 상태를 볼 수 있게 된다./li>li>A의 데이터 변경은 성공했지만 B의 장애로 B의 데이터 변경은 실패했다면 A와 B의 상태는 일관성이 깨진 채로 남아있게 된다./li>li>A의 데이터 변경은 성공했지만 B의 데이터 변경이 성립하는 제약 조건이 더이상 성립하지 않으면 A를 원래 상태로 되돌려야 한다./li>/ol>p>1번 문제는 중간 상태가 보여도 문제가 없도록 서비스 경계를 잘 나눠서 회피할 수 있다고 본다. 하지만 회피할 수 없다면?/p>p>2번 문제는 A의 데이터 변경과 함께 원자적으로 이벤트를 발행하면 될 것 같다. 이러한 이벤트를 받아서 확실히 성공할 때까지 B에 데이터 변경을 전파해주는 녀석을 만들어야 한다. 원자적인 이벤트 발행을 위해서는 DB에 이벤트를 같이 쓰는 방법(a hrefhttps://microservices.io/patterns/data/transactional-outbox.html target_blank relnoopener noreferrer>outbox 패턴/a>), DB의 데이터 변경 이벤트를 이용하는 방법(a hrefhttps://en.wikipedia.org/wiki/Change_data_capture target_blank relnoopener noreferrer>change data capture/a>), 변경 자체를 이벤트로 나타내는 방법(이벤트 소싱) 등이 있을 것이다./p>p>3번 문제는 a hrefhttps://microservices.io/patterns/data/saga.html target_blank relnoopener noreferrer>saga 패턴/a>을 적용하면 된다고 들은 적이 있는데 뭔지 잘 모르니 알아봐야 할 것 같다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id서비스-간-통신>서비스 간 통신a href#서비스-간-통신 classhash-link aria-label서비스 간 통신에 대한 직접 링크 title서비스 간 통신에 대한 직접 링크>/a>/h3>p>서비스끼리 통신하려면 잘 정의된 API가 필요하다. RESTful API를 사용할 것인지, gRPC를 사용할 것인지, 서비스 코드 사이에 프로토콜은 어떤 식으로 공유하고 프로토콜 버전 관리는 어떻게 할 것인지 등의 고민을 해야한다./p>p>서비스끼리 네트워크로 통신한다는 것은 더이상 다른 서비스를 믿을 수 없다는 말이다. 언제나 서비스 호출이 실패할 수 있다고 가정해야 한다. 여러 서비스가 의존하는 서비스가 느려지면 서비스 전체에 문제가 생길 수 있기 때문에 장애가 전파되는 것을 미리 차단할 필요가 있다. (a hrefhttps://microservices.io/patterns/reliability/circuit-breaker.html target_blank relnoopener noreferrer>circuit breaker/a>)/p>p>또한 함수를 호출하는 것과 달리 서비스 호출은 스택 트레이스가 남는 것이 아니므로 기존 도구로는 디버깅이 어려워질 수 있다. 이를 위해 여러 서비스에 걸친 작업을 추적해야 한다. (a hrefhttps://microservices.io/patterns/observability/distributed-tracing.html target_blank relnoopener noreferrer>distributed tracing/a>)/p>h3 classanchor anchorWithStickyNavbar_LWe7 idapi-통합>API 통합a href#api-통합 classhash-link aria-labelAPI 통합에 대한 직접 링크 titleAPI 통합에 대한 직접 링크>/a>/h3>p>프론트엔드에서 '화면'을 그리기 위해서는 여러 서비스에 분산되어 있는 데이터를 각각 가져와서 적절히 합쳐야 한다. 하지만 프론트엔드를 구현하기 위해 어떤 데이터가 어느 서비스에 있는지 알아야 한다면 불편할 것이다. 그리고 내부 구조가 변할 때 외부 API의 소비자가 모두 업데이트 되어야 하는 문제도 있다./p>p>이를 해결하기 위해 외부에서 오는 모든 API 요청을 받아주는 API 게이트웨이를 도입할 수 있을 것이다. 프론트엔드가 사용하기 쉬운 형태로 데이터를 통합해서 내려주는 역할이다. 가능하다면 프론트엔드에서 필요한 데이터만 가져올 수 있도록 GraphQL을 활용해보면 좋겠다./p>p>또한 모든 요청이 API 게이트웨이를 통해 들어온다면 인증 처리를 API 게이트웨이에서만 하고 내부 서비스들은 검증된 아이덴티티를 그대로 사용할 수 있을 것 같다. 보안상 좋은 구조인지는 모르겠다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id배포와-테스트>배포와 테스트a href#배포와-테스트 classhash-link aria-label배포와 테스트에 대한 직접 링크 title배포와 테스트에 대한 직접 링크>/a>/h3>p>고민할 것이 많다./p>ul>li>서비스마다 다른 소스 코드 저장소를 사용할지, 아니면 모든 서비스를 한 저장소에서 관리할 것인지/li>li>서비스 간에 코드를 공유할 것인지 말 것인지/li>li>새로운 서비스를 쉽게 추가하려면 어떻게 해야 하는지/li>li>개발할 때 여러 개의 서비스를 쉽게 띄우려면 어떻게 해야 하는지/li>li>여러 서비스에 걸친 통합 테스트를 어떻게 구성할 것인지/li>/ul>h2 classanchor anchorWithStickyNavbar_LWe7 id마치며>마치며a href#마치며 classhash-link aria-label마치며에 대한 직접 링크 title마치며에 대한 직접 링크>/a>/h2>p>혼자서 마이크로서비스 아키텍처를 구성하면 당연히 실제 프로젝트와 같은 경험을 얻지는 못할 것이다. 특히 서비스 분리 단위에 대한 비즈니스적 제약조건이 없기 때문이다. 그래도 앞서 나열한 것처럼 서비스를 분리하면 생길 기술적인 문제를 해결하는 경험은 해볼 수 있을거라고 봤다. MSA에 대한 많은 자료가 있지만 직접 코드를 작성해보지 않으면 알 수 없는 부분에 부딪혀보는 것이 목표다./p>p>다음 글에서는 프로젝트 초기에 잡은 설계에 대해 소개해 볼 예정이다./p>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/microservice/>microservice/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/architecture/>architecture/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/distributed/>distributed/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2019/06/09/docker-desktop-for-windows-home/>Windows 10 Home에 Docker Desktop 설치하기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2019-06-09T00:00:00.000Z>2019년 6월 9일/time> · !-- -->약 3분/div>/header>div classmarkdown>p>a hrefhttps://docs.docker.com/docker-for-windows/install/ target_blank relnoopener noreferrer>Docker Desktop for Windows/a>를 설치하려면 Hyper-V를 지원하는 OS가 필요합니다. Home은 여기에 포함되지 않으므로, VirtualBox 기반의 레거시 a hrefhttps://docs.docker.com/toolbox/overview/ target_blank relnoopener noreferrer>Docker Toolbox/a>를 사용하라고 친절하게 나와있습니다. 하지만 저는 최신 버전을 쓰고 싶었기에 방법이 없을까 찾아보던 중 a hrefhttps://forums.docker.com/t/installing-docker-on-windows-10-home/11722/25 target_blank relnoopener noreferrer>Docker 포럼의 한 글/a>을 발견했습니다. 따라해보니까 잘 되어서 정리해 둡니다. (어쩌면 윈도우 라이센스 위반일 수도 있지만...)/p>h2 classanchor anchorWithStickyNavbar_LWe7 id1단계-hyper-v-설치>1단계: Hyper-V 설치a href#1단계-hyper-v-설치 classhash-link aria-label1단계: Hyper-V 설치에 대한 직접 링크 title1단계: Hyper-V 설치에 대한 직접 링크>/a>/h2>p>다음 스크립트를 code>.bat/code> 확장자의 파일로 저장한 다음 관리자 권한으로 실행합니다./p>div classlanguage-bat codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-bat codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>pushd "%~dp0"/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>for /f %%i in ('findstr /i . hyper-v.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i"/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>del hyper-v.txt/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Dism /online /enable-feature /featurename:Microsoft-Hyper-V -All /LimitAccess /ALL/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>pause/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>약간의 시간이 지나면 설치가 완료되고 재부팅 하라고 나옵니다. 재부팅을 합시다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id2단계-docker-인스톨러의-윈도우-에디션-체크-우회>2단계: Docker 인스톨러의 윈도우 에디션 체크 우회a href#2단계-docker-인스톨러의-윈도우-에디션-체크-우회 classhash-link aria-label2단계: Docker 인스톨러의 윈도우 에디션 체크 우회에 대한 직접 링크 title2단계: Docker 인스톨러의 윈도우 에디션 체크 우회에 대한 직접 링크>/a>/h2>p>Hyper-V를 켜도 Docker 인스톨러가 지원하는 윈도우 버전인지 확인하기 때문에 우회가 필요합니다./p>p>레지스트리 편집기(code>regedit/code>)을 켜고 code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion/code>에 가서 code>EditionID/code>을 code>Professional/code>로 변경합니다./p>p>이제 인스톨러를 실행하면 설치가 잘 될 것입니다. strong>설치가 끝난 다음 해당 레지스트리 값을 원래대로 돌려놓으세요!/strong>/p>h2 classanchor anchorWithStickyNavbar_LWe7 id3단계-설치가-잘-되었는지-확인>3단계: 설치가 잘 되었는지 확인a href#3단계-설치가-잘-되었는지-확인 classhash-link aria-label3단계: 설치가 잘 되었는지 확인에 대한 직접 링크 title3단계: 설치가 잘 되었는지 확인에 대한 직접 링크>/a>/h2>p>Docker Desktop을 실행하고, 명령 프롬프트에서 다음 명령을 입력해서 잘 되는지 확인해 봅니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>docker run hello-world/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/docker/>docker/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/windows/>windows/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2018/08/26/kotlin-jpa-pitfalls-embeddable/>Kotlin에서 JPA 사용할 때 주의할 점 (2) - Embeddable, IdClass/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2018-08-26T00:00:00.000Z>2018년 8월 26일/time> · !-- -->약 10분/div>/header>div classmarkdown>p>a href/2017/11/02/kotlin-jpa-pitfalls/>Kotlin에서 JPA 사용할 때 주의할 점/a>을 쓴 이후로 직장에서 하는 프로젝트에도 Kotlin + JPA를 사용하게 되었습니다. 그러다보니 좀 더 고급 기능을 사용하게 되고 또 여러가지 새로운 어려움에 부딪혔습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 idembeddable>Embeddablea href#embeddable classhash-link aria-labelEmbeddable에 대한 직접 링크 titleEmbeddable에 대한 직접 링크>/a>/h2>p>기간(시작 날짜, 끝 날짜)이나 좌표(X, Y) 등 항상 같이 다니는 값들을 객체로 묶어서 Entity의 속성으로 지정할 수 있습니다./p>p>이러한 객체의 클래스에 code>@Embeddable/code> 어노테이션을 붙여서 선언하고, Entity에서 code>@Embedded/code> 어노테이션을 붙여서 사용합니다. 예제 코드를 보면,/p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Embeddable/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Coordinate/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> x/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> y/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>와 같이 선언하고/p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Marker/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> id/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Embedded/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> coordinate/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Coordinate/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>처럼 가져다 쓸 수 있습니다./p>p>데이터베이스 스키마에서는 code>Coordinate/code>에 대한 새로운 테이블이 생기지 않고, code>Marker/code> 테이블에 code>x/code>, code>y/code> 컬럼이 추가됩니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id주의사항>주의사항a href#주의사항 classhash-link aria-label주의사항에 대한 직접 링크 title주의사항에 대한 직접 링크>/a>/h3>p>Embeddable 클래스도 a href/2017/11/02/kotlin-jpa-pitfalls/>이전 글/a>에서 설명한 Entity 클래스와 마찬가지로 다음 속성을 만족해야 합니다./p>ul>li>기본 생성자(인자 없는 생성자)가 있어야 한다. → a hrefhttp://kotlinlang.org/docs/reference/compiler-plugins.html#jpa-support target_blank relnoopener noreferrer>kotlin-jpa 컴파일러 플러그인/a> 추가/li>li>code>final/code>이면 안된다. → a hrefhttp://kotlinlang.org/docs/reference/compiler-plugins.html#all-open-compiler-plugin target_blank relnoopener noreferrer>kotlin-allopen 컴파일러 플러그인/a>에 code>javax.persistence.Embeddable/code> 추가/li>li>프로퍼티에는 getter와 setter가 존재해야 한다. → code>val/code>이 아니라 strong>code>var/code>로 선언/strong>/li>/ul>h3 classanchor anchorWithStickyNavbar_LWe7 idembedded와-null>Embedded와 nulla href#embedded와-null classhash-link aria-labelEmbedded와 null에 대한 직접 링크 titleEmbedded와 null에 대한 직접 링크>/a>/h3>p>a hrefhttps://github.com/javaee/jpa-spec/issues/42 target_blank relnoopener noreferrer>JPA 스펙에 의하면 Embedded 속성은 null이 될 수 없습니다./a> 하지만 Hibernate 같은 구현체들은 null을 지원합니다./p>p>당연히 일단 Kotlin에서 nullable 타입으로 수정해야 null을 넣을 수 있습니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Marker/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> id/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Embedded/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> coordinate/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Coordinate/span>span classtoken operator stylecolor:#393A34>?/span>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// <- nullable 타입으로 수정/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그리고 실제 데이터베이스 스키마에서도 컬럼을 nullable하게 만들어야 합니다./p>p>그런데 Hibernate에서 자동으로 테이블을 생성하는 경우(code>hbm2ddl.auto/code> 사용시), Embeddable에 속한 컬럼은 무조건 not null 컬럼이 되는 문제가 있습니다. 그런 경우 다음과 같이 수정하면 nullable 컬럼이 생성되게 할 수 있습니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Embeddable/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Coordinate/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Column/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> x/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Column/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> y/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>참고로, Embeddable 클래스의 어노테이션 위치 (필드 vs 프로퍼티)는 포함된 Entity 클래스의 어노테이션 위치를 따라갑니다. 앞의 예제에서 code>Marker/code>는 프로퍼티(getter)에 어노테이션을 달았기 때문에 Embeddable에서도 getter에 달아야 인식이 됩니다./p>p>strong>Kotlin에서 JPA 관련 어노테이션은 무조건 code>@get:/code>으로 달아야 한다/strong>고 기억해두면 혼란이 적은 것 같습니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id같은-타입의-embedded-속성을-여러-개-선언하기>같은 타입의 Embedded 속성을 여러 개 선언하기a href#같은-타입의-embedded-속성을-여러-개-선언하기 classhash-link aria-label같은 타입의 Embedded 속성을 여러 개 선언하기에 대한 직접 링크 title같은 타입의 Embedded 속성을 여러 개 선언하기에 대한 직접 링크>/a>/h3>p>Kotlin과는 무관하지만 알아두면 좋은 내용입니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Line/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> id/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Embedded/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> start/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Coordinate/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Embedded/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> end/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Coordinate/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>위와 같이 선언하면 code>start/code>와 code>end/code>가 동일한 컬럼 code>x/code>, code>y/code>를 가지려고 해서 다음과 같은 오류가 발생합니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>org.hibernate.MappingException: Repeated column in mapping for entity: org.sapzil.jpa.Line column: x (should be mapped with insert"false" update"false")/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>정석 해결 방법은 a hrefhttps://docs.oracle.com/javaee/6/api/javax/persistence/AttributeOverride.html target_blank relnoopener noreferrer>@AttributeOverride/a>를 사용하는 것이지만 이런 속성이 많아지면 일일히 달기는 귀찮습니다. 이 때 code>ImplicitNamingStrategy/code>를 이용하면 자동으로 code>start_x/code>, code>end_x/code>와 같이 prefix 붙은 컬럼을 지정할 수 있습니다./p>p>Spring에서는 code>spring.jpa.hibernate.naming.implicit-strategy/code> 프로퍼티를 code>org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl/code>로 지정하거나, code>ImplicitNamingStrategyComponentPathImpl/code>을 Bean으로 주입하여 설정하면 됩니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 ididclass>IdClassa href#idclass classhash-link aria-labelIdClass에 대한 직접 링크 titleIdClass에 대한 직접 링크>/a>/h2>p>JPA에서 복합 기본키(composite primary key)를 매핑하기 위해서는 code>@IdClass/code> 어노테이션을 사용합니다. 다음과 같이 PK가 될 속성에 모두 code>@Id/code>를 붙이고, PK의 속성을 모두 가진 클래스를 만들어서 code>@IdClass/code> 어노테이션으로 지정합니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@IdClass/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>Name/span>span classtoken operator stylecolor:#393A34>::/span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Person/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> firstName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> lastName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> phoneNumber/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>어떤 클래스를 IdClass로 사용하려면 Serializable 인터페이스를 구현해야 돼서 처음에 다음과 같이 선언해 봤습니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Name/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> firstName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> lastName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Serializable/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그랬더니 객체를 저장하려고 할 때 이런 오류가 발생했습니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>java.lang.IllegalArgumentException: No argument provided for a required parameter: parameter #0 firstName of fun <init>(kotlin.String, kotlin.String): org.sapzil.jpa.Name/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod(KCallableImpl.kt:138)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:110)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> at org.springframework.beans.BeanUtils$KotlinDelegate.instantiateClass(BeanUtils.java:765)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:170)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:124)/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> .../span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>메시지를 보니 아무래도 Spring이 생성자에 인자를 넘기지 않고 객체를 만들려고 해서 그런 것 같습니다. 그래서 인자를 받지 않는 생성자를 추가해봤습니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Name/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> firstName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> lastName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Serializable /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>constructor/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>this/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>""/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>""/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그랬는데도 같은 오류가 발생했습니다. 스택 트레이스에 나타난 a hrefhttps://github.com/spring-projects/spring-framework/blob/d553ddc5b3a657adebad04d9f3c7d466fbdd7b05/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java#L122-L124 target_blank relnoopener noreferrer>BeanUtils의 코드/a>를 보면.../p>div classlanguage-java codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-java codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken class-name>Constructor/span>span classtoken generics punctuation stylecolor:#393A34></span>span classtoken generics class-name>T/span>span classtoken generics punctuation stylecolor:#393A34>>/span>span classtoken plain> ctor /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken class-name>KotlinDetector/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>isKotlinType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>clazz/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>?/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken class-name>KotlinDelegate/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>getPrimaryConstructor/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>clazz/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> clazz/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>getDeclaredConstructor/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>return/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>instantiateClass/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>ctor/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>Kotlin 클래스일 경우 Primary Constructor를 찾게 되어있습니다. a hrefhttps://kotlinlang.org/docs/reference/classes.html#constructors target_blank relnoopener noreferrer>Primary Constructor/a>는 클래스 선언 헤더에 같이 선언되는 생성자를 말합니다. 방금 추가한 인자 없는 생성자는 Secondary Constructor이기 때문에 인식되지 않은 것입니다./p>p>그래서 data class를 포기해야 하나... 생각하고 있었는데, 구글링하던 중 a hrefhttps://gist.github.com/mchlstckl/4f9602b5d776878f48f0 target_blank relnoopener noreferrer>Gist/a>를 하나 발견했습니다. 결론은 생성자의 모든 파라미터에 기본값을 지정하면 된다는 것입니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Name/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> firstName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>""/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> lastName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>""/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Serializable/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>이렇게 하면 Primary Constructor를 인자 없이 호출할 수 있게 되어서 정상적으로 저장이 됩니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 ididclass와-implicitnamingstrategy>IdClass와 ImplicitNamingStrategya href#idclass와-implicitnamingstrategy classhash-link aria-labelIdClass와 ImplicitNamingStrategy에 대한 직접 링크 titleIdClass와 ImplicitNamingStrategy에 대한 직접 링크>/a>/h3>p>(Kotlin과는 무관한 내용이지만) 앞서 설명한대로 ImplicitNamingStrategy를 변경하면 IdClass를 사용할 때 문제가 생길 수 있습니다./p>p>code>ImplicitNamingStrategyComponentPathImpl/code>을 사용할 때, 위의 예제대로 모델을 선언한 후 저장하려고 하면 이렇게 됩니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>org.h2.jdbc.JdbcSQLException: NULL not allowed for column "_IDENTIFIER_MAPPER_FIRST_NAME"; SQL statement:/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>insert into person (phone_number, id_first_name, id_last_name) values (?, ?, ?) 23502-197/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>먼저 컬럼 이름 앞에 code>id_/code>가 붙은 것도 이상하고, code>_identifier_mapper_first_name/code>이라는 이상한 컬럼이 생긴 것도 문제입니다./p>p>IdClass를 지정하면 Hibernate는 내부적으로 code>id/code>, code>_identifierMapper/code>라는 숨은 속성을 생성합니다. 즉 code>id.firstName/code>, code>_identifierMapper.firstName/code> 같은 속성이 생기는 건데요. 기본 ImplicitNamingStrategy에서는 속성 경로의 마지막 부분만 취하기 때문에 문제가 없지만, 우리는 속성 경로를 모두 나타내는 전략으로 변경하였기 때문에 이런 이상한 일이 벌어진 겁니다. (결국은 a hrefhttps://hibernate.atlassian.net/browse/HHH-11427 target_blank relnoopener noreferrer>HHH-11427 버그/a> 때문입니다.)/p>p>이것을 제대로 해결하려면 ImplicitNamingStrategy의 구현을 수정해야겠지만, 간단하게는 컬럼명을 직접 지정해서 해결할 수 있습니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@IdClass/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>Name/span>span classtoken operator stylecolor:#393A34>::/span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Person/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Column/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>"first_name"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// <- 컬럼명 지정/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> firstName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:Column/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>"last_name"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// <- 컬럼명 지정/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> lastName/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> phoneNumber/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 id정리>정리a href#정리 classhash-link aria-label정리에 대한 직접 링크 title정리에 대한 직접 링크>/a>/h2>ul>li>a hrefhttp://kotlinlang.org/docs/reference/compiler-plugins.html#jpa-support target_blank relnoopener noreferrer>kotlin-jpa 컴파일러 플러그인/a> 사용하자./li>li>a hrefhttp://kotlinlang.org/docs/reference/compiler-plugins.html#all-open-compiler-plugin target_blank relnoopener noreferrer>kotlin-allopen 컴파일러 플러그인/a> 사용하자. (code>javax.persistence.Embeddable/code> 추가해주자.)/li>li>code>Embeddable/code>의 속성은 code>var/code>로 선언하자./li>li>속성에 어노테이션 붙일 때는 getter에 붙이자. (code>@get:/code>)/li>li>code>IdClass/code>의 생성자에는 모두 기본값을 달아주자./li>li>code>ImplicitNamingStrategyComponentPathImpl/code> 쓰면 편리하다./li>li>code>ImplicitNamingStrategyComponentPathImpl/code>과 code>IdClass/code> 같이 쓰려면 컬럼명을 지정해주자./li>/ul>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/database/>database/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/jpa/>jpa/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/kotlin/>kotlin/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/java/>java/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/spring/>spring/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/backend/>backend/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2018/06/20/gradle-subproject-grouping/>Gradle에서 서브 프로젝트를 한 디렉토리에 몰아넣기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2018-06-20T00:00:00.000Z>2018년 6월 20일/time> · !-- -->약 4분/div>/header>div classmarkdown>p>한 문장으로 요약이 잘 안돼서 제목이 이상한데, 읽어보시면 뭔지 알 수 있습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 idgradle-멀티프로젝트-기초>Gradle 멀티프로젝트 기초a href#gradle-멀티프로젝트-기초 classhash-link aria-labelGradle 멀티프로젝트 기초에 대한 직접 링크 titleGradle 멀티프로젝트 기초에 대한 직접 링크>/a>/h2>p>일단 간단하게 Gradle에서 멀티프로젝트 설정 방법을 알아봅시다. code>model/code>, code>server/code>, code>util/code> 세 가지 서브프로젝트로 나눈다고 하면 디렉토리 구조는 다음과 같습니다./p>ul>li>code>model//code>ul>li>code>build.gradle/code>/li>/ul>/li>li>code>server//code>ul>li>code>build.gradle/code>/li>/ul>/li>li>code>util//code>ul>li>code>build.gradle/code>/li>/ul>/li>li>code>build.gradle/code>/li>li>code>settings.gradle/code>/li>/ul>p>여기서 code>settings.gradle/code>에 모든 서브프로젝트를 선언해줍니다./p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>rootProject/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken interpolation-string string stylecolor:#e3116c>"example"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"model"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"server"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"util"/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>그리고 만약 code>server/code>에서 code>model/code>을 참조하고자 한다면 code>server/build.gradle/code>에서/p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>dependencies /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>compile/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken function stylecolor:#d73a49>project/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>":model"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>과 같이 할 수 있습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id서브-프로젝트를-한-디렉토리에-몰아넣기>서브 프로젝트를 한 디렉토리에 몰아넣기a href#서브-프로젝트를-한-디렉토리에-몰아넣기 classhash-link aria-label서브 프로젝트를 한 디렉토리에 몰아넣기에 대한 직접 링크 title서브 프로젝트를 한 디렉토리에 몰아넣기에 대한 직접 링크>/a>/h2>p>보통 Git 저장소에는 코드만 있는게 아니라 다른 것들도 있습니다. 예를 들면 스크립트를 모아둔 code>scripts/code> 디렉토리 같은 것인데, 이게 Gradle 프로젝트 디렉토리와 섞여있으면 기분이 나쁩니다. (개인 취향)/p>p>그래서 만약 디렉토리 구조를 다음과 같이 변경하고 싶다면,/p>ul>li>code>scripts//code>/li>li>strong>code>subprojects//code>/strong>ul>li>code>model//code>ul>li>code>build.gradle/code>/li>/ul>/li>li>code>server//code>ul>li>code>build.gradle/code>/li>/ul>/li>li>code>util//code>ul>li>code>build.gradle/code>/li>/ul>/li>/ul>/li>li>code>build.gradle/code>/li>li>code>settings.gradle/code>/li>/ul>p>code>settings.gradle/code>은 다음과 같이 바뀌고.../p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>rootProject/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken interpolation-string string stylecolor:#e3116c>"example"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"subprojects:model"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"subprojects:server"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"subprojects:util"/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>다른 프로젝트를 참조할때도 code>project(":subprojects:model")/code>과 같이 code>subprojects:/code>를 꼭 붙여줘야 합니다./p>p>다행히도 Gradle은 논리적인 프로젝트 경로와 실제 프로젝트 디렉토리를 다르게 설정할 수 있습니다. 따라서 code>settings.gradle/code>를 다음과 같이 구성하면 됩니다./p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>rootProject/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken interpolation-string string stylecolor:#e3116c>"example"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"model"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"server"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>include/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"util"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>for/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>project /span>span classtoken keyword stylecolor:#00009f>in/span>span classtoken plain> rootProject/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>children/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> project/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>projectDir /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>file/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken interpolation-string string stylecolor:#e3116c>"subprojects//span>span classtoken interpolation-string interpolation interpolation-punctuation punctuation stylecolor:#393A34>${/span>span classtoken interpolation-string interpolation expression>project/span>span classtoken interpolation-string interpolation expression punctuation stylecolor:#393A34>./span>span classtoken interpolation-string interpolation expression>name/span>span classtoken interpolation-string interpolation interpolation-punctuation punctuation stylecolor:#393A34>}/span>span classtoken interpolation-string string stylecolor:#e3116c>"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>이렇게 하면 서브프로젝트끼리 참조할 때도 code>project(":model")/code>처럼 해주면 됩니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id자세한-작동-원리>자세한 작동 원리a href#자세한-작동-원리 classhash-link aria-label자세한 작동 원리에 대한 직접 링크 title자세한 작동 원리에 대한 직접 링크>/a>/h2>p>일단 저렇게 해서 잘 돌아가는 것은 확인했는데 어떻게 해서 되는 것인지 좀 더 알아보았습니다./p>ul>li>code>settings.gradle/code>에서 제공되는 변수, 함수는 a hrefhttps://docs.gradle.org/current/dsl/org.gradle.api.initialization.Settings.html target_blank relnoopener noreferrer>Settings/a> 객체에 대응됩니다./li>li>code>rootProject/code>는 a hrefhttps://docs.gradle.org/current/javadoc/org/gradle/api/initialization/ProjectDescriptor.html target_blank relnoopener noreferrer>ProjectDescriptor/a> 객체입니다./li>li>a hrefhttps://docs.gradle.org/current/dsl/org.gradle.api.initialization.Settings.html#org.gradle.api.initialization.Settings:include(java.lang.String%5B%5D) target_blank relnoopener noreferrer>code>include(String...)/code>/a>을 호출하면 해당 이름의 프로젝트가 code>rootProject/code>에 자식으로 추가되고 추가된 프로젝트의 code>projectDir/code>은 프로젝트 이름과 같게 됩니다./li>li>그러므로, 먼저 code>include/code>를 한 다음 code>rootProject.children/code>으로 전체 code>ProjectDescriptor/code>를 받아올 수 있습니다. (서브 프로젝트가 여러 계층이라면 프로젝트 계층을 탐색해야겠군요.)/li>li>각 code>ProjectDescriptor/code>의 code>projectDir/code>을 실제 원하는 디렉토리로 설정해주면 목적을 달성할 수 있습니다. (code>projectDir/code>이 code>java.io.File/code> 타입이므로 code>file(...)/code> 함수를 사용해야 합니다.)/li>/ul>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/gradle/>gradle/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/build-tools/>build-tools/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2018/01/21/taming-maven-transitive-dependencies/>Maven의 Transitive Dependency 길들이기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2018-01-21T00:00:00.000Z>2018년 1월 21일/time> · !-- -->약 6분/div>/header>div classmarkdown>p>Maven으로 의존성을 관리하다보면 라이브러리 버전이 꼬이는 경우가 종종 있습니다. 그동안은 주먹구구식으로 해결하곤 했는데 한번쯤 확실히 알아둬야겠다고 생각해서 정리해 보았습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 idtransitive-dependency란>Transitive Dependency란?a href#transitive-dependency란 classhash-link aria-labelTransitive Dependency란?에 대한 직접 링크 titleTransitive Dependency란?에 대한 직접 링크>/a>/h2>p>어떤 아티팩트를 의존성으로 추가하면, 그 아티팩트가 가지고 있는 의존성이 함께 딸려옵니다. 그렇게 '딸려온' 의존성을 Transitive Dependency라고 합니다./p>p>아래 의존 관계 트리에서 MyProject ← A이고 A ← X이므로 MyProject ← X의 의존 관계가 생겼습니다./p>ul>li>MyProject!-- -->ul>li>A!-- -->ul>li>X/li>/ul>/li>/ul>/li>/ul>h2 classanchor anchorWithStickyNavbar_LWe7 id참고사항-의존-관계-디버그>(참고사항) 의존 관계 디버그a href#참고사항-의존-관계-디버그 classhash-link aria-label(참고사항) 의존 관계 디버그에 대한 직접 링크 title(참고사항) 의존 관계 디버그에 대한 직접 링크>/a>/h2>p>a hrefhttps://maven.apache.org/plugins/maven-dependency-plugin/ target_blank relnoopener noreferrer>Maven Dependency Plugin/a>을 사용하면 의존 관계 트리를 찍어볼 수 있습니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>$ mvn dependency:tree/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>INFO dependency:tree/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>INFO org.apache.maven.plugins:maven-dependency-plugin:maven-plugin:2.0-alpha-5-SNAPSHOT/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>INFO \- org.apache.maven.doxia:doxia-site-renderer:jar:1.0-alpha-8:compile/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>INFO \- org.codehaus.plexus:plexus-velocity:jar:1.1.3:compile/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>INFO \- velocity:velocity:jar:1.4:compile/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>또는 IntelliJ에 있는 기능을 사용할 수도 있습니다. (근데 Ultimate Edition에서만 되는 듯 합니다.)/p>h2 classanchor anchorWithStickyNavbar_LWe7 id의존-관계-중재-dependency-mediation>의존 관계 중재 (Dependency Mediation)a href#의존-관계-중재-dependency-mediation classhash-link aria-label의존 관계 중재 (Dependency Mediation)에 대한 직접 링크 title의존 관계 중재 (Dependency Mediation)에 대한 직접 링크>/a>/h2>p>의존 관계 트리에 한 아티팩트의 여러 버전이 있으면 어떤 버전이 선택될까요? 가장 가까운 정의가 선택됩니다./p>ul>li>MyProject!-- -->ul>li>A!-- -->ul>li>strong>X 1.0/strong>/li>/ul>/li>li>B!-- -->ul>li>C!-- -->ul>li>X 2.0/li>/ul>/li>/ul>/li>/ul>/li>/ul>p>위의 트리에서, MyProject 기준으로 X 1.0이 X 2.0보다 가까이 있습니다. 따라서 X 1.0이 선택됩니다./p>p>만약 거리(깊이)가 같으면 어떻게 될까요?/p>ul>li>MyProject!-- -->ul>li>A!-- -->ul>li>strong>X 1.0/strong>/li>/ul>/li>li>C!-- -->ul>li>X 2.0/li>/ul>/li>/ul>/li>/ul>p>이 때는 strong>먼저/strong> 선언된 쪽이 이깁니다. A가 C보다 먼저 선언되었으므로 X 1.0이 선택됩니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id원하는-버전으로-고정하기>원하는 버전으로 고정하기a href#원하는-버전으로-고정하기 classhash-link aria-label원하는 버전으로 고정하기에 대한 직접 링크 title원하는 버전으로 고정하기에 대한 직접 링크>/a>/h2>p>의존성 사이에 충돌이 일어났을 때 어떤 알고리즘으로 중재되는지 살펴봤습니다. 이제 충돌을 우리가 원하는 버전으로 해결하는 방법을 알아보겠습니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id방법-1-직접-의존성으로-포함>방법 1: 직접 의존성으로 포함a href#방법-1-직접-의존성으로-포함 classhash-link aria-label방법 1: 직접 의존성으로 포함에 대한 직접 링크 title방법 1: 직접 의존성으로 포함에 대한 직접 링크>/a>/h3>p>MyProject에 원하는 버전의 아티팩트를 직접 포함시키면, 이것이 가장 가까운 의존성이 되므로 항상 선택됩니다./p>ul>li>MyProject!-- -->ul>li>A!-- -->ul>li>X 1.0/li>/ul>/li>li>B!-- -->ul>li>C!-- -->ul>li>X 2.0/li>/ul>/li>/ul>/li>li>strong>X 2.0/strong>/li>/ul>/li>/ul>p>하지만 MyProject의 코드에서 X를 직접 사용하지 않는다면 불필요한 의존성을 추가한 것이므로 좋은 방법이 아닐 수 있습니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id방법-2-원하지-않는-의존성-제외>방법 2: 원하지 않는 의존성 제외a href#방법-2-원하지-않는-의존성-제외 classhash-link aria-label방법 2: 원하지 않는 의존성 제외에 대한 직접 링크 title방법 2: 원하지 않는 의존성 제외에 대한 직접 링크>/a>/h3>p>code><exclusion>/code> 설정을 이용하면 원하지 않는 의존성을 제외할 수 있습니다. 다음과 같이 A에서 X를 제외하면 X 2.0이 가장 가까운 의존성이 되어 선택됩니다./p>ul>li>MyProject!-- -->ul>li>A!-- -->ul>li>del>X 1.0/del>/li>/ul>/li>li>B!-- -->ul>li>C!-- -->ul>li>strong>X 2.0/strong>/li>/ul>/li>/ul>/li>/ul>/li>/ul>p>이 방법의 단점은 X 1.0에 의존하는 아티팩트가 여러개라면 일일히 제외시켜줘야 한다는 것입니다. 그리고 의존성 버전을 바꾸게 될 때마다 기존에 의도한 버전이 계속 선택되고 있는지 확인해야 합니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 id방법-3-dependency-management-설정-사용>방법 3: Dependency Management 설정 사용a href#방법-3-dependency-management-설정-사용 classhash-link aria-label방법 3: Dependency Management 설정 사용에 대한 직접 링크 title방법 3: Dependency Management 설정 사용에 대한 직접 링크>/a>/h3>p>code><dependencyManagement>/code> 설정으로 특정 아티팩트의 딸려온 의존성을 포함한 모든 의존성의 버전을 고정할 수 있습니다./p>p>POM에 다음 내용을 추가하면 모든 X가 기존에 설정된 버전을 무시하고 2.0 버전으로 고정됩니다./p>div classlanguage-xml codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-xml codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>dependencyManagement/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>dependencies/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>dependency/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>groupId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>com.example/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>groupId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>artifactId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>X/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>artifactId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>version/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>2.0/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>version/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>dependency/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>dependencies/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>dependencyManagement/span>span classtoken tag punctuation stylecolor:#393A34>>/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>ul>li>MyProject!-- -->ul>li>A!-- -->ul>li>del>X 1.0/del> strong>X 2.0/strong>/li>/ul>/li>li>B!-- -->ul>li>C!-- -->ul>li>X 2.0/li>/ul>/li>/ul>/li>/ul>/li>/ul>h2 classanchor anchorWithStickyNavbar_LWe7 iddependency-management-활용-bom>Dependency Management 활용: BOMa href#dependency-management-활용-bom classhash-link aria-labelDependency Management 활용: BOM에 대한 직접 링크 titleDependency Management 활용: BOM에 대한 직접 링크>/a>/h2>p>규모가 큰 라이브러리는 여러 모듈로 쪼개져서 배포되는 경우가 있습니다. 예를 들어 Jackson은 code>jackson-core/code>, code>jackson-databind/code>, code>jackson-dataformat-yaml/code> 등의 모듈로 나눠져 있습니다./p>p>보통은 문제가 안되지만, 이렇게 나눠진 모듈끼리 버전이 안 맞으면 공포의 code>ClassNotFoundException/code>을 유발하는 원인이 됩니다. 예를 들어 code>jackson-core/code>는 2.8인데 code>jackson-databind/code>는 2.6이라거나요./p>p>그래서 이렇게 쪼개진 라이브러리들은 대부분 "bill of materials" (BOM)을 함께 배포합니다. BOM을 임포트하면 해당 라이브러리의 모든 모듈을 특정 버전으로 고정할 수 있습니다./p>p>다음 내용을 POM에 추가하면 모든 Jackson의 모듈이 2.9.0 버전으로 강제됩니다./p>div classlanguage-xml codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-xml codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>dependencyManagement/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>dependencies/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>dependency/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>groupId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>com.fasterxml.jackson/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>groupId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>artifactId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>jackson-bom/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>artifactId/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>version/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>2.9.0/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>version/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>scope/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>import/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>scope/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34></span>span classtoken tag stylecolor:#00009f>type/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>pom/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>type/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>dependency/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>dependencies/span>span classtoken tag punctuation stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken tag punctuation stylecolor:#393A34><//span>span classtoken tag stylecolor:#00009f>dependencyManagement/span>span classtoken tag punctuation stylecolor:#393A34>>/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 id레퍼런스>레퍼런스a href#레퍼런스 classhash-link aria-label레퍼런스에 대한 직접 링크 title레퍼런스에 대한 직접 링크>/a>/h2>ul>li>a hrefhttp://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html target_blank relnoopener noreferrer>Maven – Introduction to the Dependency Mechanism/a>/li>/ul>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/maven/>maven/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/build-tools/>build-tools/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2017/11/02/kotlin-jpa-pitfalls/>Kotlin에서 JPA 사용할 때 주의할 점/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2017-11-02T00:00:00.000Z>2017년 11월 2일/time> · !-- -->약 9분/div>/header>div classmarkdown>p>Kotlin에서 JPA를 사용해봅시다! Java에서 쓸 때와 별로 다를 것은 없습니다. 하지만 엔티티 클래스를 a hrefhttp://kotlinlang.org/docs/reference/data-classes.html target_blank relnoopener noreferrer>데이터 클래스/a>로 선언하였을 때 런타임 프록시 객체를 사용하는 Hibernate/JPA의 기능들이 잘 작동하지 않을 수 있어 주의가 필요합니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id프로젝트-세팅>프로젝트 세팅a href#프로젝트-세팅 classhash-link aria-label프로젝트 세팅에 대한 직접 링크 title프로젝트 세팅에 대한 직접 링크>/a>/h2>p>예제 프로젝트는 Spring Boot를 사용하겠습니다. 하지만 다른 프레임워크에도 마찬가지로 적용되는 내용입니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 idbuildgradle-applicationkt>code>build.gradle/code>, code>Application.kt/code>a href#buildgradle-applicationkt classhash-link aria-labelbuildgradle-applicationkt에 대한 직접 링크 titlebuildgradle-applicationkt에 대한 직접 링크>/a>/h3>p>크게 중요하지 않아서 a hrefhttps://gist.github.com/dittos/9e94540705d39f60521f437415f20eeb target_blank relnoopener noreferrer>Gist 링크로 대체합니다./a>/p>h3 classanchor anchorWithStickyNavbar_LWe7 iduserkt>code>User.kt/code>a href#userkt classhash-link aria-labeluserkt에 대한 직접 링크 titleuserkt에 대한 직접 링크>/a>/h3>p>엔티티 클래스입니다./p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>package/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>sapzil/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> javax/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>persistence/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken operator stylecolor:#393A34>*/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>User/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken annotation builtin>@Id/span>span classtoken plain> /span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@GeneratedValue/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>strategy /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> GenerationType/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>AUTO/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> id/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken operator stylecolor:#393A34>?/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>null/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> name/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 iduserrepositorykt>code>UserRepository.kt/code>a href#userrepositorykt classhash-link aria-labeluserrepositorykt에 대한 직접 링크 titleuserrepositorykt에 대한 직접 링크>/a>/h3>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>package/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>sapzil/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>springframework/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>data/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>repository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>CrudRepository/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>interface/span>span classtoken plain> UserRepository /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> CrudRepository/span>span classtoken operator stylecolor:#393A34></span>span classtoken plain>User/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> Int/span>span classtoken operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>fun/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>findByName/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>name/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> User/span>span classtoken operator stylecolor:#393A34>?/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 iddemokt>code>Demo.kt/code>a href#demokt classhash-link aria-labeldemokt에 대한 직접 링크 titledemokt에 대한 직접 링크>/a>/h3>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>package/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>sapzil/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>junit/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>Before/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>junit/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>Test/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>junit/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>runner/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>RunWith/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>springframework/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>beans/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>factory/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>annotation/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>Autowired/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>springframework/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>boot/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>test/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>autoconfigure/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>orm/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>jpa/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>DataJpaTest/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>springframework/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>test/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>context/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>junit4/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>SpringRunner/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> javax/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>persistence/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>EntityManager/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@RunWith/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>SpringRunner/span>span classtoken operator stylecolor:#393A34>::/span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@DataJpaTest/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>showSql /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken boolean stylecolor:#36acaa>true/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>open/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> Demo /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@Autowired/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>lateinit/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> userRepository/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> UserRepository/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@Autowired/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>lateinit/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> entityManager/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> EntityManager/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@Before/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>fun/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>setup/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> userRepository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>save/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken function stylecolor:#d73a49>User/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>"alice"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> userRepository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>save/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken function stylecolor:#d73a49>User/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>"bob"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> entityManager/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>clear/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@Test/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>fun/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>simple/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>println/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>userRepository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>findByName/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"bob"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>테스트 코드를 실행해보면 오류가 발생합니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>org.springframework.orm.jpa.JpaSystemException: No default constructor for entity: : org.sapzil.User; nested exception is org.hibernate.InstantiationException: No default constructor for entity: : org.sapzil.User/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>기본 생성자가 없다고 합니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id기본-생성자를-만들자>기본 생성자를 만들자a href#기본-생성자를-만들자 classhash-link aria-label기본 생성자를 만들자에 대한 직접 링크 title기본 생성자를 만들자에 대한 직접 링크>/a>/h2>p>사실, 위의 엔티티 선언에는 문제가 있습니다. JPA 엔티티 클래스에는 기본 생성자(다른 말로는, 인자 없는 생성자)가 반드시 필요합니다./p>p>당연히, 기본 생성자를 추가해주면 됩니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 iduserkt-수정---예시>code>User.kt/code> (수정 - 예시)a href#userkt-수정---예시 classhash-link aria-labeluserkt-수정---예시에 대한 직접 링크 titleuserkt-수정---예시에 대한 직접 링크>/a>/h3>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>User/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken operator stylecolor:#393A34>../span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>constructor/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>this/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken keyword stylecolor:#00009f>null/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>""/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>하지만 모든 필드에 기본값을 채워줘야 하니 귀찮습니다. 어차피 JPA가 객체를 생성한 다음에 알아서 값을 채워줄텐데요./p>p>a hrefhttp://kotlinlang.org/docs/reference/compiler-plugins.html#jpa-support target_blank relnoopener noreferrer>kotlin-jpa 컴파일러 플러그인/a>을 쓰면 code>@Entity/code> 등의 어노테이션이 붙은 클래스에 자동으로 기본 생성자를 만들도록 할 수 있습니다. code>build.gradle/code>에 다음 내용을 추가합니다./p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>buildscript /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> dependencies /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> classpath /span>span classtoken interpolation-string string stylecolor:#e3116c>"org.jetbrains.kotlin:kotlin-noarg:/span>span classtoken interpolation-string interpolation interpolation-punctuation punctuation stylecolor:#393A34>$/span>span classtoken interpolation-string interpolation expression>kotlin_version/span>span classtoken interpolation-string string stylecolor:#e3116c>"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>apply plugin/span>span classtoken punctuation stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken interpolation-string string stylecolor:#e3116c>"kotlin-jpa"/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>실제로 만들어지는지 바이트코드를 까서 확인해봅시다. IntelliJ에서요. Gradle 임포트 후 code>Build > Rebuild Project/code> 한 다음 code>User.kt/code>에서 code>Tools > Kotlin > Show Kotlin Bytecode/code>를 실행하면.../p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain> // access flags 0x1/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> public <init>()V/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> L0/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> ALOAD 0/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> INVOKESPECIAL java/lang/Object.<init> ()V/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> RETURN/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> L1/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> LOCALVARIABLE this Lkotlin/Unit; L0 L1 0/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> MAXSTACK 1/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> MAXLOCALS 1/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>기본 생성자가 추가된 것을 알 수 있습니다. 이제는 테스트도 통과합니다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_ from user user0_ where user0_.name?/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>User(id2, namebob)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>데이터 클래스로 선언했으니 code>toString()/code>도 자동으로 구현된 것을 알 수 있습니다. 매우 편리하네요./p>h2 classanchor anchorWithStickyNavbar_LWe7 idmanytoone과-지연-로딩-문제>code>@ManyToOne/code>과 지연 로딩 문제a href#manytoone과-지연-로딩-문제 classhash-link aria-labelmanytoone과-지연-로딩-문제에 대한 직접 링크 titlemanytoone과-지연-로딩-문제에 대한 직접 링크>/a>/h2>p>이제 새로운 code>Post/code> 엔티티를 추가해봅시다./p>h3 classanchor anchorWithStickyNavbar_LWe7 idpostkt>code>Post.kt/code>a href#postkt classhash-link aria-labelpostkt에 대한 직접 링크 titlepostkt에 대한 직접 링크>/a>/h3>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>package/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>sapzil/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> javax/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>persistence/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken operator stylecolor:#393A34>*/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>Post/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken annotation builtin>@Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@GeneratedValue/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>strategy /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> GenerationType/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>AUTO/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> id/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken operator stylecolor:#393A34>?/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>null/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@ManyToOne/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>fetch /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> FetchType/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>LAZY/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@JoinColumn/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>name /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>"user_id"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> user/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> User/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> content/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 idpostrepositorykt>code>PostRepository.kt/code>a href#postrepositorykt classhash-link aria-labelpostrepositorykt에 대한 직접 링크 titlepostrepositorykt에 대한 직접 링크>/a>/h3>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>package/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>sapzil/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>import/span>span classtoken plain> org/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>springframework/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>data/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>repository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>CrudRepository/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>interface/span>span classtoken plain> PostRepository /span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> CrudRepository/span>span classtoken operator stylecolor:#393A34></span>span classtoken plain>Post/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> Int/span>span classtoken operator stylecolor:#393A34>>/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 iddemokt-코드-추가>code>Demo.kt/code> (코드 추가)a href#demokt-코드-추가 classhash-link aria-labeldemokt-코드-추가에 대한 직접 링크 titledemokt-코드-추가에 대한 직접 링크>/a>/h3>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Autowired/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>lateinit/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> postRepository/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> PostRepository/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken annotation builtin>@Test/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>fun/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>lazy/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>val/span>span classtoken plain> bob /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> userRepository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>findByName/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"bob"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken operator stylecolor:#393A34>!!/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>val/span>span classtoken plain> postId /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> postRepository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>save/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken function stylecolor:#d73a49>Post/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>user /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> bob/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> content /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken string-literal singleline string stylecolor:#e3116c>"Hello world"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>id/span>span classtoken operator stylecolor:#393A34>!!/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> entityManager/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>clear/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>println/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"*** EntityManager cleared"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>val/span>span classtoken plain> post /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> postRepository/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken function stylecolor:#d73a49>findOne/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>postId/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>println/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"... Accessing post.user.id"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>println/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>post/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>user/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>id/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>println/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string-literal singleline string stylecolor:#e3116c>"... Accessing post.user.name"/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>println/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>post/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>user/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>name/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>code>Post/code>를 가져온 뒤, 연관된 code>User/code>에 접근하는 테스트 코드입니다. 실행해봅시다./p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>*** EntityManager cleared/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select post0_.id as id1_0_0_, post0_.content as content2_0_0_, post0_.user_id as user_id3_0_0_ from post post0_ where post0_.id?/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select user0_.id as id1_1_0_, user0_.name as name2_1_0_ from user user0_ where user0_.id? <- ???/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>... Accessing post.user.id/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>4/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>... Accessing post.user.name/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>bob/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>쿼리 로그에서 알 수 있듯이 code>Post/code>를 가져올 때 code>User/code>까지 동시에 가져와져 버렸습니다. 즉 지연 로딩이 작동하지 않은 것입니다./p>p>지연 로딩을 하려면 프록시 객체를 만들어야 하는데, Kotlin의 모든 클래스는 final이라 상속을 받을 수 없습니다. 일반 클래스는 code>open/code>할 수 있지만 데이터 클래스는 불가능합니다./p>p>사실, JPA 표준에서는 엔티티 클래스가 final이면 안됩니다. 하지만 이 예제에서는 Hibernate를 JPA 구현체로 사용하기 때문에 어떻게든 작동하긴 하는 것 같네요./p>p>아무튼 이번에도 a hrefhttp://kotlinlang.org/docs/reference/compiler-plugins.html#all-open-compiler-plugin target_blank relnoopener noreferrer>컴파일러 플러그인/a>의 도움을 받아서 엔티티 클래스를 확장할 수 있도록 만듭니다. code>build.gradle/code>에 다음 내용을 추가합니다./p>div classlanguage-groovy codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-groovy codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>buildscript /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> dependencies /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> classpath /span>span classtoken interpolation-string string stylecolor:#e3116c>"org.jetbrains.kotlin:kotlin-allopen:/span>span classtoken interpolation-string interpolation interpolation-punctuation punctuation stylecolor:#393A34>$/span>span classtoken interpolation-string interpolation expression>kotlin_version/span>span classtoken interpolation-string string stylecolor:#e3116c>"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>apply plugin/span>span classtoken punctuation stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken interpolation-string string stylecolor:#e3116c>"kotlin-allopen"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>allOpen /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> annotation /span>span classtoken interpolation-string string stylecolor:#e3116c>"javax.persistence.Entity"/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>Gradle 임포트와 Rebuild를 한 다음 테스트를 다시 실행해보면.../p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>*** EntityManager cleared/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select post0_.id as id1_0_0_, post0_.content as content2_0_0_, post0_.user_id as user_id3_0_0_ from post post0_ where post0_.id?/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>... Accessing post.user.id/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select user0_.id as id1_1_0_, user0_.name as name2_1_0_ from user user0_ where user0_.id?/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>4/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>... Accessing post.user.name/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>bob/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>code>post.user/code>에 접근할 때가 돼서야 code>User/code>를 불러왔습니다. 이번에는 제대로 지연 로딩이 작동한 것을 확인할 수 있습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id연관-객체의-id에-쿼리-없이-접근>연관 객체의 ID에 쿼리 없이 접근a href#연관-객체의-id에-쿼리-없이-접근 classhash-link aria-label연관 객체의 ID에 쿼리 없이 접근에 대한 직접 링크 title연관 객체의 ID에 쿼리 없이 접근에 대한 직접 링크>/a>/h2>p>strong>아래 내용은 이제 사실이 아닙니다. a hrefhttps://hibernate.atlassian.net/browse/HHH-12096 target_blank relnoopener noreferrer>Hibernate 5.2.13/5.3/a>에서 문제가 수정되어 필드 접근 모드에서도 ID 접근시 엔티티가 로드되지 않습니다. 읽을 때 참고 바랍니다./strong>/p>p>code>post.user.id/code>는 사실 code>User/code>를 쿼리해보지 않아도 code>post.user_id/code> 컬럼으로 알아낼 수 있습니다. 알고보면 Hibernate에서 원래 지원하는 기능입니다. 하지만 위의 예제 코드에서는 작동하지 않았죠. 왜일까요?/p>p>데이터 클래스의 필드에 어노테이션을 달면, getter 메소드가 아니라 JVM 필드에 어노테이션이 달립니다. 그러면 프로퍼티 접근 모드가 아니라 a hrefhttp://blog.xebia.com/jpa-implementation-patterns-field-access-vs-property-access/ target_blank relnoopener noreferrer>필드 접근 모드/a>가 되고, 이 경우 a hrefhttps://hibernate.atlassian.net/browse/HHH-3718 target_blank relnoopener noreferrer>Hibernate는 지연 로딩을 지원하지 않습니다./a>/p>p>어쨌든 getter에 어노테이션이 붙도록 수정하면 됩니다./p>p>code>User.kt/code> (수정)/p>div classlanguage-kotlin codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-kotlin codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken annotation builtin>@Entity/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>data/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>class/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>User/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken annotation builtin>@get:Id/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken annotation builtin>@get:GeneratedValue/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>strategy /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> GenerationType/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken plain>AUTO/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> id/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> Int/span>span classtoken operator stylecolor:#393A34>?/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>null/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>var/span>span classtoken plain> name/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> String/span>span classtoken punctuation stylecolor:#393A34>)/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>Rebuild 후 테스트를 다시 실행해보면.../p>div classcodeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-text codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken plain>*** EntityManager cleared/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select post0_.id as id1_0_0_, post0_.content as content2_0_0_, post0_.user_id as user_id3_0_0_ from post post0_ where post0_.id?/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>... Accessing post.user.id <- 이때는 쿼리가 안날아감/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>4/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>... Accessing post.user.name/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>Hibernate: select user0_.id as id1_1_0_, user0_.name as name2_1_0_ from user user0_ where user0_.id?/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>bob/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>code>post.user.name/code>에 접근할 때 code>User/code>를 가져오는 것을 확인할 수 있습니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id세-줄-요약>세 줄 요약a href#세-줄-요약 classhash-link aria-label세 줄 요약에 대한 직접 링크 title세 줄 요약에 대한 직접 링크>/a>/h2>ul>li>code>kotlin-jpa/code> 컴파일러 플러그인을 써서 기본 생성자를 자동으로 추가하자./li>li>code>kotlin-allopen/code> 컴파일러 플러그인을 써서 엔티티 클래스를 상속 가능하게 만들자./li>li>JPA 어노테이션은 getter에 달자./li>/ul>h2 classanchor anchorWithStickyNavbar_LWe7 id고민해볼-점>고민해볼 점a href#고민해볼-점 classhash-link aria-label고민해볼 점에 대한 직접 링크 title고민해볼 점에 대한 직접 링크>/a>/h2>ul>li>데이터 클래스를 쓸 때도 a hrefhttps://developer.jboss.org/wiki/EqualsAndHashCode target_blank relnoopener noreferrer>code>equals()/code>와 code>hashCode()/code>를 오버라이드 해야하나?/a> code>toString()/code>은?/li>li>모든 필드를 데이터 클래스의 생성자에 선언해야 하나? code>@OneToMany/code> 붙은 콜렉션은?/li>li>이쯤되면 JPA가 문제인 것 같다. strong>굳이 JPA를 써야하나? 😂/strong>/li>/ul>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/database/>database/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/jpa/>jpa/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/kotlin/>kotlin/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/java/>java/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/spring/>spring/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/backend/>backend/a>/li>/ul>/div>/footer>/article>article classmargin-bottom--xl>header>h2 classtitle_f1Hy>a href/2017/07/16/redux-observable/>redux-observable 사용하기/a>/h2>div classcontainer_mt6G margin-vert--md>time datetime2017-07-16T00:00:00.000Z>2017년 7월 16일/time> · !-- -->약 6분/div>/header>div classmarkdown>p>a hrefhttps://redux-observable.js.org/ target_blank relnoopener noreferrer>redux-observable/a>은 RxJS로 Redux에서 비동기 액션을 처리할 수 있게 해줍니다./p>h2 classanchor anchorWithStickyNavbar_LWe7 id기초>기초a href#기초 classhash-link aria-label기초에 대한 직접 링크 title기초에 대한 직접 링크>/a>/h2>h3 classanchor anchorWithStickyNavbar_LWe7 id액션을-observable로-다루기>액션을 Observable로 다루기a href#액션을-observable로-다루기 classhash-link aria-label액션을 Observable로 다루기에 대한 직접 링크 title액션을 Observable로 다루기에 대한 직접 링크>/a>/h3>p>redux-observable에서는 Redux 스토어에 들어오는 액션들을 code>Observable/code>로 다룰 수 있게 해줍니다. code>dispatch/code>가 호출되면, 액션이 스토어에서 처리된 후에 Observable에 액션이 출력됩니다./p>p>실제로는 code>Observable/code>을 확장한 code>ActionsObservable/code>을 얻을 수 있는데 여기에는 특정 종류의 액션만 걸러낼 수 있는 code>ofType/code> 연산자가 추가로 제공됩니다. code>.ofType('ACTION_TYPE')/code>은 code>.filter(action > action.type 'ACTION_TYPE')/code>와 동일합니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 idepic>Epica href#epic classhash-link aria-labelEpic에 대한 직접 링크 titleEpic에 대한 직접 링크>/a>/h3>p>redux-observable에서는 액션이 들어오는 이벤트를 받아서 em>추가적인/em> 액션을 발생시킬 수 있습니다. (이미 들어온 액션을 바꾸거나 없앨 수는 없습니다.) 이렇게 액션의 Observable을 추가로 발생시킬 액션의 Observable로 바꿔주는 함수를 strong>Epic/strong>이라고 부릅니다. 그림으로 보면 다음과 같습니다./p>p>img decodingasync loadinglazy alt그림 src/assets/images/2017-07-redux-observable-diagram-3c98fcbb8c39d6094297315b1d0d0261.png width1068 height818 classimg_ev3q>/p>p>Epic은 '서사시'라는 뜻인데 Epic이 실행되는 동안 발생하는 액션을 어떻게 처리할지에 대한 이야기이기 때문에 그런 이름이 된 것이 아닐까 생각합니다./p>p>code>PING/code> 액션을 받아서 code>PONG/code> 액션을 발생시키는 가장 간단한 Epic을 생각해볼 수 있습니다. (별로 쓸모는 없지만)/p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>function/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>pingEpic/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action$/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> action$/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>ofType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'PING'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>map/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>type/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'PONG'/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>실제로도 유용할 것 같은, 액션을 받아서 비동기 API를 호출하고 성공 액션을 발생시키는 가장 기본적인 Epic은 다음과 같이 생겼습니다./p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>function/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>fetchPostsEpic/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action$/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> action$/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>ofType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>mergeMap/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>getPosts/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>map/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>posts/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>type/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_SUCCESS'/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>payload/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> posts /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken keyword control-flow stylecolor:#00009f>catch/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>err/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken maybe-class-name>Observable/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>of/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>type/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_ERROR'/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>payload/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> err/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>error/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken boolean stylecolor:#36acaa>true/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>p>리듀서는 이런 식으로 만들 수 있을겁니다./p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>function/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>reducer/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>state /span>span classtoken parameter operator stylecolor:#393A34>/span>span classtoken parameter> /span>span classtoken parameter punctuation stylecolor:#393A34>{/span>span classtoken parameter punctuation stylecolor:#393A34>}/span>span classtoken parameter punctuation stylecolor:#393A34>,/span>span classtoken parameter> action/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>switch/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>action/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>type/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>case/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS'/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// Epic과 무관하게 FETCH_POSTS는 리듀서로 들어옵니다!/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>isLoading/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken boolean stylecolor:#36acaa>true/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>case/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_SUCCESS'/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>isLoading/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken boolean stylecolor:#36acaa>false/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>posts/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> action/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>payload/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>case/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_ERROR'/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>isLoading/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken boolean stylecolor:#36acaa>false/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>error/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> action/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>payload/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword module stylecolor:#00009f>default/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> state/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 id여러-epic-합성하기>여러 Epic 합성하기a href#여러-epic-합성하기 classhash-link aria-label여러 Epic 합성하기에 대한 직접 링크 title여러 Epic 합성하기에 대한 직접 링크>/a>/h3>p>일반적으로 처리하는 액션 타입에 따라 여러 개의 Epic을 만들어서 합성하여 사용하게 됩니다. 합성은 code>combineEpics/code> 함수를 사용하고, 이렇게 합쳐져서 최종적으로 만들어진 Epic을 strong>Root Epic/strong>이라고 합니다. (리듀서를 code>combineReducers/code>로 합쳐서 루트 리듀서를 만드는 것과 비슷합니다)/p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword module stylecolor:#00009f>import/span>span classtoken plain> /span>span classtoken imports punctuation stylecolor:#393A34>{/span>span classtoken imports> combineEpics /span>span classtoken imports punctuation stylecolor:#393A34>}/span>span classtoken plain> /span>span classtoken keyword module stylecolor:#00009f>from/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'redux-observable'/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>const/span>span classtoken plain> rootEpic /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>combineEpics/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> pingEpic/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> fetchPostsEpic/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 id적용>적용a href#적용 classhash-link aria-label적용에 대한 직접 링크 title적용에 대한 직접 링크>/a>/h2>h3 classanchor anchorWithStickyNavbar_LWe7 id의존성-설치>의존성 설치a href#의존성-설치 classhash-link aria-label의존성 설치에 대한 직접 링크 title의존성 설치에 대한 직접 링크>/a>/h3>p>npm으로 code>rxjs/code>와 code>redux-observable/code>을 설치합니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 idepic-middleware-추가하기>Epic Middleware 추가하기a href#epic-middleware-추가하기 classhash-link aria-labelEpic Middleware 추가하기에 대한 직접 링크 titleEpic Middleware 추가하기에 대한 직접 링크>/a>/h3>p>Epic을 실제로 적용하려면 미들웨어를 통해서 Redux 스토어에 붙입니다./p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword module stylecolor:#00009f>import/span>span classtoken plain> /span>span classtoken imports punctuation stylecolor:#393A34>{/span>span classtoken imports> createStore/span>span classtoken imports punctuation stylecolor:#393A34>,/span>span classtoken imports> applyMiddleware /span>span classtoken imports punctuation stylecolor:#393A34>}/span>span classtoken plain> /span>span classtoken keyword module stylecolor:#00009f>from/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'redux'/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword module stylecolor:#00009f>import/span>span classtoken plain> /span>span classtoken imports punctuation stylecolor:#393A34>{/span>span classtoken imports> createEpicMiddleware /span>span classtoken imports punctuation stylecolor:#393A34>}/span>span classtoken plain> /span>span classtoken keyword module stylecolor:#00009f>from/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'redux-observable'/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>const/span>span classtoken plain> epicMiddleware /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>createEpicMiddleware/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>rootEpic/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken keyword stylecolor:#00009f>const/span>span classtoken plain> store /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>createStore/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> rootReducer/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>applyMiddleware/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> epicMiddleware/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken spread operator stylecolor:#393A34>.../span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h2 classanchor anchorWithStickyNavbar_LWe7 id실전-팁>실전 팁a href#실전-팁 classhash-link aria-label실전 팁에 대한 직접 링크 title실전 팁에 대한 직접 링크>/a>/h2>h3 classanchor anchorWithStickyNavbar_LWe7 id프로젝트-구조>프로젝트 구조a href#프로젝트-구조 classhash-link aria-label프로젝트 구조에 대한 직접 링크 title프로젝트 구조에 대한 직접 링크>/a>/h3>p>공식 문서에서는 a hrefhttps://github.com/erikras/ducks-modular-redux target_blank relnoopener noreferrer>Ducks 패턴/a>을 추천하고 있습니다. Ducks 패턴은 연관된 액션 타입, 액션 크리에이터와 리듀서를 하나의 모듈로 묶는 방식인데 여기에 Epic이 추가되는 겁니다./p>h3 classanchor anchorWithStickyNavbar_LWe7 idepic에서-스토어-상태-가져오기>Epic에서 스토어 상태 가져오기a href#epic에서-스토어-상태-가져오기 classhash-link aria-labelEpic에서 스토어 상태 가져오기에 대한 직접 링크 titleEpic에서 스토어 상태 가져오기에 대한 직접 링크>/a>/h3>p>사실 Epic의 두번째 파라미터로는 Redux 스토어가 들어옵니다. 따라서 필요할 때 code>getState()/code>를 호출하여 스토어 상태에 따라 액션을 처리할 수 있습니다./p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>function/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>addCommentEpic/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action$/span>span classtoken parameter punctuation stylecolor:#393A34>,/span>span classtoken parameter> store/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> action$/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>ofType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'ADD_COMMENT'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>mergeMap/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword stylecolor:#00009f>const/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> currentUser /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain> /span>span classtoken operator stylecolor:#393A34>/span>span classtoken plain> store/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>getState/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>addComment/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>currentUser/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> action/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>body/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>map/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken spread operator stylecolor:#393A34>.../span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 id비동기-요청-취소하기>비동기 요청 취소하기a href#비동기-요청-취소하기 classhash-link aria-label비동기 요청 취소하기에 대한 직접 링크 title비동기 요청 취소하기에 대한 직접 링크>/a>/h3>p>RxJS의 a hrefhttp://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-takeUntil target_blank relnoopener noreferrer>code>takeUntil/code>/a> 연산자를 적용하면 특정 액션이 들어올 때 동작을 취소할 수 있습니다./p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>function/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>fetchPostsEpic/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action$/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> action$/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>ofType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>mergeMap/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>getPosts/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>map/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>posts/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>type/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_SUCCESS'/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>payload/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> posts /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// FETCH_POSTS_CANCEL 액션이 들어오면 구독 취소/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>takeUntil/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>action$/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>ofType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_CANCEL'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>h3 classanchor anchorWithStickyNavbar_LWe7 id액션-종료-시에-알림-받기>액션 종료 시에 알림 받기a href#액션-종료-시에-알림-받기 classhash-link aria-label액션 종료 시에 알림 받기에 대한 직접 링크 title액션 종료 시에 알림 받기에 대한 직접 링크>/a>/h3>p>Epic의 구조상 액션을 dispatch하는 곳에서 액션 처리가 완료된 것을 알기 어렵습니다. 모든 것을 Redux에서 관리하는 것이 최선이긴 하지만 때로는 탈출구가 필요하기도 합니다./p>p>어쩔 수 없을 때는 a hrefhttps://github.com/redux-observable/redux-observable/issues/90#issuecomment-237331721 target_blank relnoopener noreferrer>redux-observable에 올라온 이슈/a>에서 힌트를 얻어서 액션에 콜백을 같이 넘기는 방법을 사용해볼 수 있습니다. (콜백보다는 Promise나 RxJS의 Subject를 사용하면 약간 더 깔끔합니다.)/p>div classlanguage-js codeBlockContainer_Ckt0 theme-code-block style--prism-color:#393A34;--prism-background-color:#f6f8fa>div classcodeBlockContent_biex>pre tabindex0 classprism-code language-js codeBlock_bY9V thin-scrollbar stylecolor:#393A34;background-color:#f6f8fa>code classcodeBlockLines_e6Vv>span classtoken-line stylecolor:#393A34>span classtoken keyword stylecolor:#00009f>function/span>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>fetchPostsEpic/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action$/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>return/span>span classtoken plain> action$/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>ofType/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>mergeMap/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>action/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken function stylecolor:#d73a49>getPosts/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken keyword control-flow stylecolor:#00009f>do/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>posts/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken keyword control-flow stylecolor:#00009f>if/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>action/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>meta/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>callback/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> action/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken property-access>meta/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>callback/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken plain>posts/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain> /span>span classtoken comment stylecolor:#999988;font-style:italic>// 밖에 알려주기/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>map/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken parameter>posts/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>type/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS_SUCCESS'/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>payload/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> posts /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain styledisplay:inline-block>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken function stylecolor:#d73a49>dispatch/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>type/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken string stylecolor:#e3116c>'FETCH_POSTS'/span>span classtoken punctuation stylecolor:#393A34>,/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain> /span>span classtoken literal-property property stylecolor:#36acaa>meta/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>{/span>span classtoken plain> /span>span classtoken function-variable function stylecolor:#d73a49>callback/span>span classtoken operator stylecolor:#393A34>:/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken arrow operator stylecolor:#393A34>>/span>span classtoken plain> /span>span classtoken console class-name>console/span>span classtoken punctuation stylecolor:#393A34>./span>span classtoken method function property-access stylecolor:#d73a49>log/span>span classtoken punctuation stylecolor:#393A34>(/span>span classtoken string stylecolor:#e3116c>'done!'/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken plain> /span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken plain>/span>br>/span>span classtoken-line stylecolor:#393A34>span classtoken plain>/span>span classtoken punctuation stylecolor:#393A34>}/span>span classtoken punctuation stylecolor:#393A34>)/span>span classtoken punctuation stylecolor:#393A34>;/span>br>/span>/code>/pre>div classbuttonGroup__atx>button typebutton aria-label클립보드에 코드 복사 title복사 classclean-btn>span classcopyButtonIcons_eSgA aria-hiddentrue>svg viewBox0 0 24 24 classcopyButtonIcon_y97N>path fillcurrentColor dM19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z>/path>/svg>svg viewBox0 0 24 24 classcopyButtonSuccessIcon_LjdS>path fillcurrentColor dM21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z>/path>/svg>/span>/button>/div>/div>/div>/div>footer classrow docusaurus-mt-lg>div classcol>b>태그:/b>ul classtags_jXut padding--none margin-left--sm>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/redux/>redux/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/react/>react/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/javascript/>javascript/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/rxjs/>rxjs/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/reactive/>reactive/a>/li>li classtag_QGVx>a classtag_zVej tagRegular_sFm0 href/tags/web/>web/a>/li>/ul>/div>/footer>/article>nav classpagination-nav aria-label블로그 게시물 목록 탐색>a classpagination-nav__link pagination-nav__link--next href/page/2/>div classpagination-nav__label>다음 페이지/div>/a>/nav>/main>/div>/div>/div>footer classfooter>div classcontainer container-fluid>div classfooter__bottom text--center>div classfooter__copyright>© 2024. All rights reserved./div>/div>/div>/footer>/div>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]