[{"data":1,"prerenderedAt":5353},["ShallowReactive",2],{"navigation_docs_ko":3,"-ko-note-spring-record-and-version-concurrency":267,"-ko-note-spring-record-and-version-concurrency-surround":5348},[4,130,253],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":33},"Note (개발 지식)","emojione-v1:note-pad","\u002Fko\u002Fnote","ko\u002F1.note",[10,34,48,74,84,94,110,120],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":33},"Spring","logos:spring-icon","\u002Fko\u002Fnote\u002Fspring","ko\u002F1.note\u002F0.spring",[16,21,25,29],{"title":17,"path":18,"stem":19,"icon":20},"Spring AI 핵심 개념과 LLM 애플리케이션 구조 이해","\u002Fko\u002Fnote\u002Fspring\u002Fspring-ai","ko\u002F1.note\u002F0.spring\u002F1.spring-ai","lucide:file-text",{"title":22,"path":23,"stem":24,"icon":20},"[Java] Record와 @Version으로 이해하는 불변 데이터와 동시성 제어","\u002Fko\u002Fnote\u002Fspring\u002Frecord-and-version-concurrency","ko\u002F1.note\u002F0.spring\u002F2.record-and-version-concurrency",{"title":26,"path":27,"stem":28,"icon":20},"[JPA] @PersistenceContext와 EntityManager로 이해하는 영속성 컨텍스트","\u002Fko\u002Fnote\u002Fspring\u002Fspring-persistence-context","ko\u002F1.note\u002F0.spring\u002F3.spring-persistence-context",{"title":30,"path":31,"stem":32,"icon":20},"[Java] YearMonth로 월 단위 날짜를 다루는 방법","\u002Fko\u002Fnote\u002Fspring\u002Fjava-year-month","ko\u002F1.note\u002F0.spring\u002F4.java-year-month",false,{"title":35,"icon":36,"path":37,"stem":38,"children":39,"page":33},"Javascript","skill-icons:javascript","\u002Fko\u002Fnote\u002Fjavascript","ko\u002F1.note\u002F1.javascript",[40,44],{"title":41,"path":42,"stem":43,"icon":20},"Promise.all과 구조 분해 할당을 활용한 데이터 처리 패턴","\u002Fko\u002Fnote\u002Fjavascript\u002Fdestructuring","ko\u002F1.note\u002F1.javascript\u002F1.destructuring",{"title":45,"path":46,"stem":47,"icon":20},"JavaScript 배열 고차 함수와 데이터 처리 패턴 이해","\u002Fko\u002Fnote\u002Fjavascript\u002Ffilter","ko\u002F1.note\u002F1.javascript\u002F2.filter",{"title":49,"icon":50,"path":51,"stem":52,"children":53,"page":33},"Nuxt4","logos:nuxt-icon","\u002Fko\u002Fnote\u002Fnuxt4","ko\u002F1.note\u002F2.nuxt4",[54,58,62,66,70],{"title":55,"path":56,"stem":57,"icon":20},"Nuxt 4를 선택한 이유와 아키텍처 및 실무 가이드","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-info","ko\u002F1.note\u002F2.nuxt4\u002F1.nuxt-info",{"title":59,"path":60,"stem":61,"icon":20},"Nuxt useAsyncData 동작 원리와 SSR 데이터 페칭 최적화","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-use_async_data","ko\u002F1.note\u002F2.nuxt4\u002F2.nuxt-use_async_data",{"title":63,"path":64,"stem":65,"icon":20},"Nuxt Content v3 데이터 모델과 Query 시스템 이해","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-content-v3","ko\u002F1.note\u002F2.nuxt4\u002F3.nuxt-content-v3",{"title":67,"path":68,"stem":69,"icon":20},"Nuxt Auto-imports 내부 동작과 컴파일 과정 분석","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-auto-import","ko\u002F1.note\u002F2.nuxt4\u002F4.nuxt-auto-import",{"title":71,"path":72,"stem":73,"icon":20},"Nuxt 렌더링 방식 이해: SSR, SSG, ISR, CSR 구현 방법","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-rendering-strategy","ko\u002F1.note\u002F2.nuxt4\u002F5.nuxt-rendering-strategy",{"title":75,"icon":76,"path":77,"stem":78,"children":79,"page":33},"Nitro","unjs:nitro","\u002Fko\u002Fnote\u002Fnitro","ko\u002F1.note\u002F3.nitro",[80],{"title":81,"path":82,"stem":83,"icon":20},"Nitro 아키텍처와 서버 엔진 구조","\u002Fko\u002Fnote\u002Fnitro\u002Fnitro","ko\u002F1.note\u002F3.nitro\u002Fnitro",{"title":85,"icon":86,"path":87,"stem":88,"children":89,"page":33},"Devops","vscode-icons:file-type-pipeline","\u002Fko\u002Fnote\u002Fdevops","ko\u002F1.note\u002F4.devops",[90],{"title":91,"path":92,"stem":93,"icon":20},"서버사이드 WebAssembly(Wasm)와 차세대 실행 환경","\u002Fko\u002Fnote\u002Fdevops\u002Fwebassembly","ko\u002F1.note\u002F4.devops\u002F1.webAssembly",{"title":95,"icon":96,"path":97,"stem":98,"children":99,"page":33},"Tooling","lucide:wrench","\u002Fko\u002Fnote\u002Ftooling","ko\u002F1.note\u002F4.tooling",[100],{"title":101,"icon":102,"path":103,"stem":104,"children":105,"page":33},"Package Manager","devicon:npm","\u002Fko\u002Fnote\u002Ftooling\u002Fpackage-manager","ko\u002F1.note\u002F4.tooling\u002F1.package-manager",[106],{"title":107,"path":108,"stem":109,"icon":20},"Package Lock 파일의 역할과 의존성 관리 원리","\u002Fko\u002Fnote\u002Ftooling\u002Fpackage-manager\u002Fpackage-lock","ko\u002F1.note\u002F4.tooling\u002F1.package-manager\u002F1.package-lock",{"title":111,"icon":112,"path":113,"stem":114,"children":115,"page":33},"Web","streamline-plump:web","\u002Fko\u002Fnote\u002Fweb","ko\u002F1.note\u002F4.web",[116],{"title":117,"path":118,"stem":119,"icon":20},"웹 애플리케이션 렌더링 아키텍처 이해","\u002Fko\u002Fnote\u002Fweb\u002Frendering-modes","ko\u002F1.note\u002F4.web\u002F2.rendering-modes",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":33},"SEO","icon-park-solid:seo","\u002Fko\u002Fnote\u002Fseo","ko\u002F1.note\u002F6.seo",[126],{"title":127,"path":128,"stem":129,"icon":20},"[SEO] 개발 블로그 검색 노출을 위한 robots.txt와 SEO 설정 가이드","\u002Fko\u002Fnote\u002Fseo\u002Fseo","ko\u002F1.note\u002F6.seo\u002F1.seo",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":33},"Project (개발일지)","icon-park-solid:branch-two","\u002Fko\u002Fproject","ko\u002F2.project",[136,187],{"title":137,"icon":138,"path":139,"stem":140,"children":141,"page":33},"Brolog","custom:symbol","\u002Fko\u002Fproject\u002Fbrolog","ko\u002F2.project\u002F1.brolog",[142,146,150,154,158,162,166,170,174,179,183],{"title":143,"path":144,"stem":145,"icon":20},"[Brolog] 프로젝트 개요","\u002Fko\u002Fproject\u002Fbrolog\u002Foutline","ko\u002F2.project\u002F1.brolog\u002F1.outline",{"title":147,"path":148,"stem":149,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (3) - Runtime 기능 문제 분석과 AI Assistant 전환","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy3-ai-integration","ko\u002F2.project\u002F1.brolog\u002F10.vercel-deploy3-ai-integration",{"title":151,"path":152,"stem":153,"icon":20},"[Brolog] AI Assistant 적용 과정 - Agent 구조와 API 사용량 관리 시 고려사항","\u002Fko\u002Fproject\u002Fbrolog\u002Fai-assistant-application-process","ko\u002F2.project\u002F1.brolog\u002F11.ai-assistant-application-process",{"title":155,"path":156,"stem":157,"icon":20},"[Brolog] 검색 엔진 노출을 위한 SEO 적용 과정","\u002Fko\u002Fproject\u002Fbrolog\u002Fbrolog-seo","ko\u002F2.project\u002F1.brolog\u002F12.brolog-seo",{"title":159,"path":160,"stem":161,"icon":20},"[Brolog] 프로젝트 구조","\u002Fko\u002Fproject\u002Fbrolog\u002Fstructure","ko\u002F2.project\u002F1.brolog\u002F2.structure",{"title":163,"path":164,"stem":165,"icon":20},"[Brolog] Nuxt Content 네비게이션 아이콘 커스텀 빌드","\u002Fko\u002Fproject\u002Fbrolog\u002Fcustom-icon","ko\u002F2.project\u002F1.brolog\u002F3.custom-icon",{"title":167,"path":168,"stem":169,"icon":20},"[Brolog] Nuxt4 Auto-imports 동작 원리와 자동 import 시스템 이해","\u002Fko\u002Fproject\u002Fbrolog\u002Fnuxt-auto-import","ko\u002F2.project\u002F1.brolog\u002F4.nuxt-auto-import",{"title":171,"path":172,"stem":173,"icon":20},"[Brolog] 개발 블로그 메인 페이지 구현","\u002Fko\u002Fproject\u002Fbrolog\u002Fdevelop-main","ko\u002F2.project\u002F1.brolog\u002F5.develop-main",{"title":175,"path":176,"stem":177,"icon":178},"[Brolog] 브랜드 아이덴티티 적용 - Custom 404 페이지와 Default No Image 구현","\u002Fko\u002Fproject\u002Fbrolog\u002Fcustom-error-and-default-thumbnail","ko\u002F2.project\u002F1.brolog\u002F6.custom-error-and-default-thumbnail","lucide:image",{"title":180,"path":181,"stem":182,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (1)","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy1","ko\u002F2.project\u002F1.brolog\u002F8.vercel-deploy1",{"title":184,"path":185,"stem":186,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (2) - Build Pipeline 오류 분석과 Nuxt 실행 구조 이해","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy2-error-analyze","ko\u002F2.project\u002F1.brolog\u002F9.vercel-deploy2-error-analyze",{"title":188,"icon":189,"path":190,"stem":191,"children":192,"page":33},"Brobean","custom:brobean","\u002Fko\u002Fproject\u002Fbrobean","ko\u002F2.project\u002F2.brobean",[193,197,201,205,209,213,217,221,225,229,233,237,241,245,249],{"title":194,"path":195,"stem":196,"icon":20},"[BroBean] 연인과 함께 사용하는 가계부: BroBean - 프로젝트 기획과 이름의 탄생","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-01","ko\u002F2.project\u002F2.brobean\u002F1.brobean-dev-log-01",{"title":198,"path":199,"stem":200,"icon":20},"[BroBean] 연인과 함께 사용하는 가계부: BroBean 프로젝트 시작 - Nuxt 4 기반 프론트 개발 환경 구축","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-02","ko\u002F2.project\u002F2.brobean\u002F2.brobean-dev-log-02",{"title":202,"path":203,"stem":204,"icon":20},"[BroBean] BroBean 프로젝트 #3 - 모바일 앱 전환을 고려한 공통 레이아웃 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-03","ko\u002F2.project\u002F2.brobean\u002F3.brobean-dev-log-03",{"title":206,"path":207,"stem":208,"icon":20},"[BroBean] BroBean 프로젝트 #4 - 홈 화면(Dashboard) 설계와 Mock 데이터 구성","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-04","ko\u002F2.project\u002F2.brobean\u002F4.brobean-dev-log-04",{"title":210,"path":211,"stem":212,"icon":20},"[BroBean] BroBean 프로젝트 #5 돈을 모으면 캐릭터도 성장한다 - 브로빈 성장 시스템 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-05","ko\u002F2.project\u002F2.brobean\u002F5.brobean-dev-log-05",{"title":214,"path":215,"stem":216,"icon":20},"[BroBean] BroBean 프로젝트 #6 씨앗 심기와 밭 가꾸기 기능 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-06","ko\u002F2.project\u002F2.brobean\u002F6.brobean-dev-log-06",{"title":218,"path":219,"stem":220,"icon":20},"[BroBean] BroBean 프로젝트 #7 성장 일지와 나무 키우기 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-07","ko\u002F2.project\u002F2.brobean\u002F7.brobean-dev-log-07",{"title":222,"path":223,"stem":224,"icon":20},"[BroBean] BroBean 프로젝트 #8 수확 정산으로 한눈에 보는 소비 흐름 - 소비 리포트 & 정산 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-08","ko\u002F2.project\u002F2.brobean\u002F8.brobean-dev-log-08",{"title":226,"path":227,"stem":228,"icon":20},"[BroBean] BroBean 프로젝트 #9 알림부터 마이페이지까지 - 사용자 편의 기능과 개인 농장 설정 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-09","ko\u002F2.project\u002F2.brobean\u002F9.brobean-dev-log-09",{"title":230,"path":231,"stem":232,"icon":20},"[BroBean] BroBean 프로젝트 #10 로그인부터 파트너 초대까지 - 계정 인증과 커플 연결 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-10","ko\u002F2.project\u002F2.brobean\u002F910.brobean-dev-log-10",{"title":234,"path":235,"stem":236,"icon":20},"[BroBean] BroBean 프로젝트 #11 개인과 커플을 모두 위한 모드 분리 - Pinia 기반 개인\u002F커플 모드 전환 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-11","ko\u002F2.project\u002F2.brobean\u002F911.brobean-dev-log-11",{"title":238,"path":239,"stem":240,"icon":20},"[BroBean] BroBean 프로젝트 #12 인증 상태 복구와 Nuxt Plugin 실행 순서 제어","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-12","ko\u002F2.project\u002F2.brobean\u002F912.brobean-dev-log-12",{"title":242,"path":243,"stem":244,"icon":20},"[BroBean] BroBean 프로젝트 #13 개인·커플 모드와 가계부 API 구현 - 쿼리 성능을 다시 돌아보다","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-13","ko\u002F2.project\u002F2.brobean\u002F913.brobean-dev-log-13",{"title":246,"path":247,"stem":248,"icon":20},"[BroBean] BroBean 프로젝트 #14 순환 참조 오류를 ObjectProvider로 해결하기","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-14","ko\u002F2.project\u002F2.brobean\u002F914.brobean-dev-log-14",{"title":250,"path":251,"stem":252,"icon":20},"[BroBean] BroBean 프로젝트 #15 JPA 페이지네이션 적용, Page와 Slice의 차이","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-15","ko\u002F2.project\u002F2.brobean\u002F915.brobean-dev-log-15",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":33},"Error (트러블슈팅)","emojione:stop-sign","\u002Fko\u002Ftrouble-shooting","ko\u002F3.trouble-shooting",[259],{"title":137,"icon":138,"path":260,"stem":261,"children":262,"page":33},"\u002Fko\u002Ftrouble-shooting\u002Fbrolog","ko\u002F3.trouble-shooting\u002F1.brolog",[263],{"title":264,"path":265,"stem":266,"icon":20},"[Nuxt Content] Nuxt Icon 오류 해결과 Custom Collection 기반 오프라인 아이콘 구성","\u002Fko\u002Ftrouble-shooting\u002Fbrolog\u002Fnuxt-icon-restricted-network-troubleshooting","ko\u002F3.trouble-shooting\u002F1.brolog\u002F1.nuxt-icon-restricted-network-troubleshooting",{"id":268,"title":22,"body":269,"date":5329,"description":5330,"extension":5331,"links":5332,"meta":5333,"navigation":5343,"path":23,"seo":5344,"stem":24,"__hash__":5347},"docs_ko\u002Fko\u002F1.note\u002F0.spring\u002F2.record-and-version-concurrency.md",{"type":270,"value":271,"toc":5324},"minimark",[272,5320],[273,274,276,281,285,293,300,303,587,590,593,604,615,618,655,658,661,665,673,676,729,732,735,949,952,998,1001,1004,1012,1014,1018,1021,1024,1042,1045,1084,1087,1117,1120,1123,1126,1129,1137,1140,1143,1145,1149,1152,1200,1203,1262,1269,1310,1313,1329,1332,1347,1350,1352,1356,1359,1399,1402,1408,1415,1418,1420,1440,1443,1450,1452,1456,1459,1462,1500,1507,1510,1566,1569,1572,1574,1578,1585,1588,1611,1614,1617,1637,1640,1643,1646,1649,1651,1655,1661,1664,1730,1742,1745,1751,1754,1757,1759,1763,1766,1769,1828,1831,1839,1842,1864,1870,1873,1875,1879,1885,1941,1944,1950,1953,1955,1959,1962,1965,2043,2046,2105,2111,2118,2120,2124,2127,2130,2242,2245,2248,2271,2274,2276,2280,2283,2403,2406,2412,2414,2418,2421,2447,2450,2457,2460,2505,2508,2510,2514,2517,2520,2525,2528,2532,2535,2539,2542,2556,2559,2562,2568,2571,2573,2577,2580,2583,2623,2626,2672,2675,2678,2680,2684,2687,2725,2728,2864,2867,2869,2873,2876,2922,2924,3058,3061,3063,3067,3074,3077,3079,3180,3183,3222,3225,3228,3234,3237,3239,3243,3250,3253,3364,3370,3399,3402,3407,3410,3412,3416,3419,3422,3425,3431,3434,3440,3443,3449,3452,3458,3461,3467,3470,3476,3479,3482,3488,3490,3493,3495,3499,3505,3508,3511,3514,3520,3523,3529,3532,3535,3537,3541,3544,3547,3553,3556,3558,3562,3565,3570,3573,3576,3579,3606,3608,3611,3614,3617,3631,3634,3636,3640,3643,3648,3651,3654,3660,3663,3668,3670,3674,3680,3682,3771,3773,3794,3801,3804,3810,3813,3816,3822,3825,3828,3834,3836,3838,3842,3845,3848,3854,3857,3860,3880,3883,3886,3925,3932,3935,3941,3944,3959,3962,3964,3968,3971,3974,3980,3983,3988,3991,3996,3999,4002,4039,4042,4045,4051,4054,4057,4062,4064,4066,4100,4103,4106,4112,4114,4117,4123,4125,4128,4133,4136,4138,4142,4145,4151,4154,4157,4163,4166,4172,4174,4178,4181,4187,4190,4196,4198,4200,4204,4207,4227,4230,4239,4241,4247,4250,4252,4256,4262,4264,4284,4287,4309,4312,4318,4324,4326,4359,4362,4364,4368,4373,4376,4406,4409,4415,4418,4420,4424,4433,4436,4438,4544,4547,4568,4571,4574,4580,4582,4586,4591,4594,4690,4692,4698,4701,4707,4709,4713,4716,4721,4724,4727,4730,4736,4739,4742,4877,4880,4886,4889,4891,4895,4898,4901,4904,4910,4913,4916,4922,4925,4928,4931,4933,4937,4940,4942,4948,4951,4957,4960,4966,4969,4972,4990,4993,4999,5002,5009,5011,5015,5021,5024,5030,5033,5082,5088,5094,5096,5175,5178,5184,5187,5193,5196,5198,5202,5207,5212,5215,5221,5230,5233,5239,5242,5252,5255,5261,5267,5273,5276,5282,5285,5288,5294,5297,5300,5306,5309],"u-page-card",{":spotlight":275},"true",[277,278,280],"h1",{"id":279},"java-record와-version으로-이해하는-불변-데이터와-동시성-제어","📖 Java Record와 @Version으로 이해하는 불변 데이터와 동시성 제어",[282,283,284],"p",{},"Java로 백엔드 애플리케이션을 개발하다 보면 반복적으로 등장하는 두 가지 문제가 있다.",[282,286,287,288,292],{},"하나는 데이터를 전달하기 위한 객체를 만들면서 생기는 ",[289,290,291],"strong",{},"불필요한 코드의 반복","이고,",[282,294,295,296,299],{},"다른 하나는 여러 사용자가 동시에 같은 데이터를 수정하면서 발생하는 ",[289,297,298],{},"동시성 문제","다.",[282,301,302],{},"예를 들어 API 응답을 전달하기 위해 다음과 같은 클래스를 만든다고 생각해보자.",[304,305,310],"pre",{"className":306,"code":307,"language":308,"meta":309,"style":309},"language-java shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","public class UserResponse {\n\n    private final Long id;\n    private final String name;\n    private final String email;\n\n    public UserResponse(Long id, String name, String email) {\n        this.id = id;\n        this.name = name;\n        this.email = email;\n    }\n\n    public Long getId() {\n        return id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n}\n","java","",[311,312,313,333,340,359,374,388,393,429,445,459,473,479,484,499,510,515,520,534,543,548,553,567,576,581],"code",{"__ignoreMap":309},[314,315,318,322,325,329],"span",{"class":316,"line":317},"line",1,[314,319,321],{"class":320},"spNyl","public",[314,323,324],{"class":320}," class",[314,326,328],{"class":327},"sBMFI"," UserResponse",[314,330,332],{"class":331},"sMK4o"," {\n",[314,334,336],{"class":316,"line":335},2,[314,337,339],{"emptyLinePlaceholder":338},true,"\n",[314,341,343,346,349,352,356],{"class":316,"line":342},3,[314,344,345],{"class":320},"    private",[314,347,348],{"class":320}," final",[314,350,351],{"class":320}," Long",[314,353,355],{"class":354},"sTEyZ"," id",[314,357,358],{"class":331},";\n",[314,360,362,364,366,369,372],{"class":316,"line":361},4,[314,363,345],{"class":320},[314,365,348],{"class":320},[314,367,368],{"class":320}," String",[314,370,371],{"class":354}," name",[314,373,358],{"class":331},[314,375,377,379,381,383,386],{"class":316,"line":376},5,[314,378,345],{"class":320},[314,380,348],{"class":320},[314,382,368],{"class":320},[314,384,385],{"class":354}," email",[314,387,358],{"class":331},[314,389,391],{"class":316,"line":390},6,[314,392,339],{"emptyLinePlaceholder":338},[314,394,396,399,402,405,408,411,414,416,418,420,422,424,427],{"class":316,"line":395},7,[314,397,398],{"class":320},"    public",[314,400,328],{"class":401},"s2Zo4",[314,403,404],{"class":331},"(",[314,406,407],{"class":320},"Long",[314,409,355],{"class":410},"sHdIc",[314,412,413],{"class":331},",",[314,415,368],{"class":320},[314,417,371],{"class":410},[314,419,413],{"class":331},[314,421,368],{"class":320},[314,423,385],{"class":410},[314,425,426],{"class":331},")",[314,428,332],{"class":331},[314,430,432,435,438,441,443],{"class":316,"line":431},8,[314,433,434],{"class":331},"        this.",[314,436,437],{"class":354},"id ",[314,439,440],{"class":331},"=",[314,442,355],{"class":354},[314,444,358],{"class":331},[314,446,448,450,453,455,457],{"class":316,"line":447},9,[314,449,434],{"class":331},[314,451,452],{"class":354},"name ",[314,454,440],{"class":331},[314,456,371],{"class":354},[314,458,358],{"class":331},[314,460,462,464,467,469,471],{"class":316,"line":461},10,[314,463,434],{"class":331},[314,465,466],{"class":354},"email ",[314,468,440],{"class":331},[314,470,385],{"class":354},[314,472,358],{"class":331},[314,474,476],{"class":316,"line":475},11,[314,477,478],{"class":331},"    }\n",[314,480,482],{"class":316,"line":481},12,[314,483,339],{"emptyLinePlaceholder":338},[314,485,487,489,491,494,497],{"class":316,"line":486},13,[314,488,398],{"class":320},[314,490,351],{"class":320},[314,492,493],{"class":401}," getId",[314,495,496],{"class":331},"()",[314,498,332],{"class":331},[314,500,502,506,508],{"class":316,"line":501},14,[314,503,505],{"class":504},"s7zQu","        return",[314,507,355],{"class":354},[314,509,358],{"class":331},[314,511,513],{"class":316,"line":512},15,[314,514,478],{"class":331},[314,516,518],{"class":316,"line":517},16,[314,519,339],{"emptyLinePlaceholder":338},[314,521,523,525,527,530,532],{"class":316,"line":522},17,[314,524,398],{"class":320},[314,526,368],{"class":320},[314,528,529],{"class":401}," getName",[314,531,496],{"class":331},[314,533,332],{"class":331},[314,535,537,539,541],{"class":316,"line":536},18,[314,538,505],{"class":504},[314,540,371],{"class":354},[314,542,358],{"class":331},[314,544,546],{"class":316,"line":545},19,[314,547,478],{"class":331},[314,549,551],{"class":316,"line":550},20,[314,552,339],{"emptyLinePlaceholder":338},[314,554,556,558,560,563,565],{"class":316,"line":555},21,[314,557,398],{"class":320},[314,559,368],{"class":320},[314,561,562],{"class":401}," getEmail",[314,564,496],{"class":331},[314,566,332],{"class":331},[314,568,570,572,574],{"class":316,"line":569},22,[314,571,505],{"class":504},[314,573,385],{"class":354},[314,575,358],{"class":331},[314,577,579],{"class":316,"line":578},23,[314,580,478],{"class":331},[314,582,584],{"class":316,"line":583},24,[314,585,586],{"class":331},"}\n",[282,588,589],{},"데이터 자체는 단순하다.",[282,591,592],{},"하지만 필드 선언부터 생성자, getter까지 반복해서 작성해야 한다.",[282,594,595,596,599,600,603],{},"Java의 ",[311,597,598],{},"record","는 이런 ",[289,601,602],{},"데이터 중심 객체를 간결하게 표현하기 위해 등장한 문법","이다.",[282,605,606,607,610,611,614],{},"반면 데이터베이스에서 여러 사용자가 동시에 같은 데이터를 수정하는 문제는 ",[311,608,609],{},"@Version","과 같은 ",[289,612,613],{},"낙관적 락(Optimistic Lock)"," 개념으로 해결할 수 있다.",[282,616,617],{},"이번 글에서는 두 개념을 단순히 문법 수준에서 끝내지 않고,",[619,620,621,625,628,631,634,637,640,646,649,652],"ul",{},[622,623,624],"li",{},"Java Record란 무엇인지",[622,626,627],{},"왜 사용하는지",[622,629,630],{},"일반 클래스와 어떤 차이가 있는지",[622,632,633],{},"불변 객체와 어떤 관계가 있는지",[622,635,636],{},"DTO로 어떻게 활용하는지",[622,638,639],{},"Record의 장단점은 무엇인지",[622,641,642,643,645],{},"JPA의 ",[311,644,609],{},"은 어떤 역할을 하는지",[622,647,648],{},"낙관적 락은 어떻게 동작하는지",[622,650,651],{},"동시에 수정했을 때 어떤 일이 발생하는지",[622,653,654],{},"실제 서비스에서는 어떻게 적용해야 하는지",[282,656,657],{},"까지 하나의 흐름으로 정리해본다.",[659,660],"hr",{},[277,662,664],{"id":663},"_1-java-record란-무엇인가","🔎 1. Java Record란 무엇인가?",[282,666,667,669,670,603],{},[311,668,598],{},"는 Java 16부터 정식으로 사용할 수 있게 된 ",[289,671,672],{},"데이터 전달 객체를 간결하게 표현하기 위한 클래스 선언 방식",[282,674,675],{},"간단한 예를 보면 다음과 같다.",[304,677,679],{"className":306,"code":678,"language":308,"meta":309,"style":309},"public record UserResponse(\n    Long id,\n    String name,\n    String email\n) {\n}\n",[311,680,681,693,703,712,719,725],{"__ignoreMap":309},[314,682,683,685,688,690],{"class":316,"line":317},[314,684,321],{"class":320},[314,686,687],{"class":320}," record",[314,689,328],{"class":327},[314,691,692],{"class":331},"(\n",[314,694,695,698,700],{"class":316,"line":335},[314,696,697],{"class":320},"    Long",[314,699,355],{"class":354},[314,701,702],{"class":331},",\n",[314,704,705,708,710],{"class":316,"line":342},[314,706,707],{"class":320},"    String",[314,709,371],{"class":354},[314,711,702],{"class":331},[314,713,714,716],{"class":316,"line":361},[314,715,707],{"class":320},[314,717,718],{"class":354}," email\n",[314,720,721,723],{"class":316,"line":376},[314,722,426],{"class":331},[314,724,332],{"class":331},[314,726,727],{"class":316,"line":390},[314,728,586],{"class":331},[282,730,731],{},"이렇게 선언하면 Java가 데이터 객체에 필요한 기본적인 구조를 제공한다.",[282,733,734],{},"일반 클래스에서는 다음과 같이 작성해야 했던 코드가:",[304,736,737],{"className":306,"code":307,"language":308,"meta":309,"style":309},[311,738,739,749,753,765,777,789,793,821,833,845,857,861,865,877,885,889,893,905,913,917,921,933,941,945],{"__ignoreMap":309},[314,740,741,743,745,747],{"class":316,"line":317},[314,742,321],{"class":320},[314,744,324],{"class":320},[314,746,328],{"class":327},[314,748,332],{"class":331},[314,750,751],{"class":316,"line":335},[314,752,339],{"emptyLinePlaceholder":338},[314,754,755,757,759,761,763],{"class":316,"line":342},[314,756,345],{"class":320},[314,758,348],{"class":320},[314,760,351],{"class":320},[314,762,355],{"class":354},[314,764,358],{"class":331},[314,766,767,769,771,773,775],{"class":316,"line":361},[314,768,345],{"class":320},[314,770,348],{"class":320},[314,772,368],{"class":320},[314,774,371],{"class":354},[314,776,358],{"class":331},[314,778,779,781,783,785,787],{"class":316,"line":376},[314,780,345],{"class":320},[314,782,348],{"class":320},[314,784,368],{"class":320},[314,786,385],{"class":354},[314,788,358],{"class":331},[314,790,791],{"class":316,"line":390},[314,792,339],{"emptyLinePlaceholder":338},[314,794,795,797,799,801,803,805,807,809,811,813,815,817,819],{"class":316,"line":395},[314,796,398],{"class":320},[314,798,328],{"class":401},[314,800,404],{"class":331},[314,802,407],{"class":320},[314,804,355],{"class":410},[314,806,413],{"class":331},[314,808,368],{"class":320},[314,810,371],{"class":410},[314,812,413],{"class":331},[314,814,368],{"class":320},[314,816,385],{"class":410},[314,818,426],{"class":331},[314,820,332],{"class":331},[314,822,823,825,827,829,831],{"class":316,"line":431},[314,824,434],{"class":331},[314,826,437],{"class":354},[314,828,440],{"class":331},[314,830,355],{"class":354},[314,832,358],{"class":331},[314,834,835,837,839,841,843],{"class":316,"line":447},[314,836,434],{"class":331},[314,838,452],{"class":354},[314,840,440],{"class":331},[314,842,371],{"class":354},[314,844,358],{"class":331},[314,846,847,849,851,853,855],{"class":316,"line":461},[314,848,434],{"class":331},[314,850,466],{"class":354},[314,852,440],{"class":331},[314,854,385],{"class":354},[314,856,358],{"class":331},[314,858,859],{"class":316,"line":475},[314,860,478],{"class":331},[314,862,863],{"class":316,"line":481},[314,864,339],{"emptyLinePlaceholder":338},[314,866,867,869,871,873,875],{"class":316,"line":486},[314,868,398],{"class":320},[314,870,351],{"class":320},[314,872,493],{"class":401},[314,874,496],{"class":331},[314,876,332],{"class":331},[314,878,879,881,883],{"class":316,"line":501},[314,880,505],{"class":504},[314,882,355],{"class":354},[314,884,358],{"class":331},[314,886,887],{"class":316,"line":512},[314,888,478],{"class":331},[314,890,891],{"class":316,"line":517},[314,892,339],{"emptyLinePlaceholder":338},[314,894,895,897,899,901,903],{"class":316,"line":522},[314,896,398],{"class":320},[314,898,368],{"class":320},[314,900,529],{"class":401},[314,902,496],{"class":331},[314,904,332],{"class":331},[314,906,907,909,911],{"class":316,"line":536},[314,908,505],{"class":504},[314,910,371],{"class":354},[314,912,358],{"class":331},[314,914,915],{"class":316,"line":545},[314,916,478],{"class":331},[314,918,919],{"class":316,"line":550},[314,920,339],{"emptyLinePlaceholder":338},[314,922,923,925,927,929,931],{"class":316,"line":555},[314,924,398],{"class":320},[314,926,368],{"class":320},[314,928,562],{"class":401},[314,930,496],{"class":331},[314,932,332],{"class":331},[314,934,935,937,939],{"class":316,"line":569},[314,936,505],{"class":504},[314,938,385],{"class":354},[314,940,358],{"class":331},[314,942,943],{"class":316,"line":578},[314,944,478],{"class":331},[314,946,947],{"class":316,"line":583},[314,948,586],{"class":331},[282,950,951],{},"Record에서는:",[304,953,954],{"className":306,"code":678,"language":308,"meta":309,"style":309},[311,955,956,966,974,982,988,994],{"__ignoreMap":309},[314,957,958,960,962,964],{"class":316,"line":317},[314,959,321],{"class":320},[314,961,687],{"class":320},[314,963,328],{"class":327},[314,965,692],{"class":331},[314,967,968,970,972],{"class":316,"line":335},[314,969,697],{"class":320},[314,971,355],{"class":354},[314,973,702],{"class":331},[314,975,976,978,980],{"class":316,"line":342},[314,977,707],{"class":320},[314,979,371],{"class":354},[314,981,702],{"class":331},[314,983,984,986],{"class":316,"line":361},[314,985,707],{"class":320},[314,987,718],{"class":354},[314,989,990,992],{"class":316,"line":376},[314,991,426],{"class":331},[314,993,332],{"class":331},[314,995,996],{"class":316,"line":390},[314,997,586],{"class":331},[282,999,1000],{},"로 줄어든다.",[282,1002,1003],{},"즉 Record의 핵심 목적은 단순하다.",[1005,1006,1007],"blockquote",{},[282,1008,1009],{},[289,1010,1011],{},"데이터를 담는 객체를 더 간결하고 명확하게 표현하는 것",[659,1013],{},[277,1015,1017],{"id":1016},"_2-record가-등장한-이유","🧩 2. Record가 등장한 이유",[282,1019,1020],{},"기존 Java에서는 데이터를 전달하기 위한 객체를 만들 때 반복되는 코드가 많았다.",[282,1022,1023],{},"대표적으로:",[304,1025,1027],{"className":306,"code":1026,"language":308,"meta":309,"style":309},"private final String name;\n",[311,1028,1029],{"__ignoreMap":309},[314,1030,1031,1034,1036,1038,1040],{"class":316,"line":317},[314,1032,1033],{"class":320},"private",[314,1035,348],{"class":320},[314,1037,368],{"class":320},[314,1039,371],{"class":354},[314,1041,358],{"class":331},[282,1043,1044],{},"필드를 선언하고,",[304,1046,1048],{"className":306,"code":1047,"language":308,"meta":309,"style":309},"public UserResponse(String name) {\n    this.name = name;\n}\n",[311,1049,1050,1067,1080],{"__ignoreMap":309},[314,1051,1052,1054,1056,1058,1061,1063,1065],{"class":316,"line":317},[314,1053,321],{"class":320},[314,1055,328],{"class":401},[314,1057,404],{"class":331},[314,1059,1060],{"class":320},"String",[314,1062,371],{"class":354},[314,1064,426],{"class":331},[314,1066,332],{"class":331},[314,1068,1069,1072,1074,1076,1078],{"class":316,"line":335},[314,1070,1071],{"class":331},"    this.",[314,1073,452],{"class":354},[314,1075,440],{"class":331},[314,1077,371],{"class":354},[314,1079,358],{"class":331},[314,1081,1082],{"class":316,"line":342},[314,1083,586],{"class":331},[282,1085,1086],{},"생성자를 만들고,",[304,1088,1090],{"className":306,"code":1089,"language":308,"meta":309,"style":309},"public String getName() {\n    return name;\n}\n",[311,1091,1092,1104,1113],{"__ignoreMap":309},[314,1093,1094,1096,1098,1100,1102],{"class":316,"line":317},[314,1095,321],{"class":320},[314,1097,368],{"class":320},[314,1099,529],{"class":401},[314,1101,496],{"class":331},[314,1103,332],{"class":331},[314,1105,1106,1109,1111],{"class":316,"line":335},[314,1107,1108],{"class":504},"    return",[314,1110,371],{"class":354},[314,1112,358],{"class":331},[314,1114,1115],{"class":316,"line":342},[314,1116,586],{"class":331},[282,1118,1119],{},"getter까지 만들어야 했다.",[282,1121,1122],{},"필드가 10개라면 getter도 계속 늘어난다.",[282,1124,1125],{},"이런 객체는 대부분 특별한 비즈니스 로직을 가지고 있지 않고 단순히 데이터를 전달하는 역할만 한다.",[282,1127,1128],{},"예를 들어 API Response라면:",[304,1130,1135],{"className":1131,"code":1133,"language":1134,"meta":309},[1132],"language-text","UserResponse\n\nid\nname\nemail\ncreatedAt\n","text",[311,1136,1133],{"__ignoreMap":309},[282,1138,1139],{},"정도의 데이터를 전달하는 것이 목적이다.",[282,1141,1142],{},"Record는 이런 **데이터 중심 객체(Data Carrier)**를 표현하기 위한 문법이다.",[659,1144],{},[277,1146,1148],{"id":1147},"️-3-record-기본-사용법","🛠️ 3. Record 기본 사용법",[282,1150,1151],{},"가장 기본적인 형태는 다음과 같다.",[304,1153,1155],{"className":306,"code":1154,"language":308,"meta":309,"style":309},"public record User(\n    Long id,\n    String name,\n    String email\n) {\n}\n",[311,1156,1157,1168,1176,1184,1190,1196],{"__ignoreMap":309},[314,1158,1159,1161,1163,1166],{"class":316,"line":317},[314,1160,321],{"class":320},[314,1162,687],{"class":320},[314,1164,1165],{"class":327}," User",[314,1167,692],{"class":331},[314,1169,1170,1172,1174],{"class":316,"line":335},[314,1171,697],{"class":320},[314,1173,355],{"class":354},[314,1175,702],{"class":331},[314,1177,1178,1180,1182],{"class":316,"line":342},[314,1179,707],{"class":320},[314,1181,371],{"class":354},[314,1183,702],{"class":331},[314,1185,1186,1188],{"class":316,"line":361},[314,1187,707],{"class":320},[314,1189,718],{"class":354},[314,1191,1192,1194],{"class":316,"line":376},[314,1193,426],{"class":331},[314,1195,332],{"class":331},[314,1197,1198],{"class":316,"line":390},[314,1199,586],{"class":331},[282,1201,1202],{},"객체를 생성할 때는 일반 클래스와 비슷하다.",[304,1204,1206],{"className":306,"code":1205,"language":308,"meta":309,"style":309},"User user = new User(\n    1L,\n    \"콩이\",\n    \"bean@example.com\"\n);\n",[311,1207,1208,1225,1233,1247,1257],{"__ignoreMap":309},[314,1209,1210,1213,1216,1218,1221,1223],{"class":316,"line":317},[314,1211,1212],{"class":320},"User",[314,1214,1215],{"class":354}," user ",[314,1217,440],{"class":331},[314,1219,1220],{"class":504}," new",[314,1222,1165],{"class":401},[314,1224,692],{"class":331},[314,1226,1227,1231],{"class":316,"line":335},[314,1228,1230],{"class":1229},"sbssI","    1L",[314,1232,702],{"class":331},[314,1234,1235,1238,1242,1245],{"class":316,"line":342},[314,1236,1237],{"class":331},"    \"",[314,1239,1241],{"class":1240},"sfazB","콩이",[314,1243,1244],{"class":331},"\"",[314,1246,702],{"class":331},[314,1248,1249,1251,1254],{"class":316,"line":361},[314,1250,1237],{"class":331},[314,1252,1253],{"class":1240},"bean@example.com",[314,1255,1256],{"class":331},"\"\n",[314,1258,1259],{"class":316,"line":376},[314,1260,1261],{"class":331},");\n",[282,1263,1264,1265,1268],{},"값을 가져올 때는 getter 대신 ",[289,1266,1267],{},"컴포넌트 이름과 동일한 accessor method","를 사용한다.",[304,1270,1272],{"className":306,"code":1271,"language":308,"meta":309,"style":309},"user.id();\nuser.name();\nuser.email();\n",[311,1273,1274,1288,1299],{"__ignoreMap":309},[314,1275,1276,1279,1282,1285],{"class":316,"line":317},[314,1277,1278],{"class":354},"user",[314,1280,1281],{"class":331},".",[314,1283,1284],{"class":401},"id",[314,1286,1287],{"class":331},"();\n",[314,1289,1290,1292,1294,1297],{"class":316,"line":335},[314,1291,1278],{"class":354},[314,1293,1281],{"class":331},[314,1295,1296],{"class":401},"name",[314,1298,1287],{"class":331},[314,1300,1301,1303,1305,1308],{"class":316,"line":342},[314,1302,1278],{"class":354},[314,1304,1281],{"class":331},[314,1306,1307],{"class":401},"email",[314,1309,1287],{"class":331},[282,1311,1312],{},"즉 일반 클래스의:",[304,1314,1316],{"className":306,"code":1315,"language":308,"meta":309,"style":309},"user.getName();\n",[311,1317,1318],{"__ignoreMap":309},[314,1319,1320,1322,1324,1327],{"class":316,"line":317},[314,1321,1278],{"class":354},[314,1323,1281],{"class":331},[314,1325,1326],{"class":401},"getName",[314,1328,1287],{"class":331},[282,1330,1331],{},"대신:",[304,1333,1335],{"className":306,"code":1334,"language":308,"meta":309,"style":309},"user.name();\n",[311,1336,1337],{"__ignoreMap":309},[314,1338,1339,1341,1343,1345],{"class":316,"line":317},[314,1340,1278],{"class":354},[314,1342,1281],{"class":331},[314,1344,1296],{"class":401},[314,1346,1287],{"class":331},[282,1348,1349],{},"을 사용한다.",[659,1351],{},[277,1353,1355],{"id":1354},"_4-record의-컴포넌트란","🔎 4. Record의 컴포넌트란?",[282,1357,1358],{},"다음 코드를 보자.",[304,1360,1362],{"className":306,"code":1361,"language":308,"meta":309,"style":309},"public record User(\n    Long id,\n    String name\n) {\n}\n",[311,1363,1364,1374,1382,1389,1395],{"__ignoreMap":309},[314,1365,1366,1368,1370,1372],{"class":316,"line":317},[314,1367,321],{"class":320},[314,1369,687],{"class":320},[314,1371,1165],{"class":327},[314,1373,692],{"class":331},[314,1375,1376,1378,1380],{"class":316,"line":335},[314,1377,697],{"class":320},[314,1379,355],{"class":354},[314,1381,702],{"class":331},[314,1383,1384,1386],{"class":316,"line":342},[314,1385,707],{"class":320},[314,1387,1388],{"class":354}," name\n",[314,1390,1391,1393],{"class":316,"line":361},[314,1392,426],{"class":331},[314,1394,332],{"class":331},[314,1396,1397],{"class":316,"line":376},[314,1398,586],{"class":331},[282,1400,1401],{},"여기서:",[304,1403,1406],{"className":1404,"code":1405,"language":1134,"meta":309},[1132],"Long id\nString name\n",[311,1407,1405],{"__ignoreMap":309},[282,1409,1410,1411,1414],{},"을 Record의 ",[289,1412,1413],{},"component","라고 한다.",[282,1416,1417],{},"이 컴포넌트를 기준으로 Java가 여러 기능을 자동으로 제공한다.",[282,1419,1023],{},[619,1421,1422,1425,1428,1431,1434,1437],{},[622,1423,1424],{},"private final 필드",[622,1426,1427],{},"canonical constructor",[622,1429,1430],{},"accessor method",[622,1432,1433],{},"equals()",[622,1435,1436],{},"hashCode()",[622,1438,1439],{},"toString()",[282,1441,1442],{},"등이 만들어진다.",[282,1444,1445,1446,1449],{},"따라서 Record는 단순히 코드를 줄여주는 문법이 아니라 ",[289,1447,1448],{},"데이터 객체의 의도를 명확하게 표현하는 타입","이라고 보는 것이 좋다.",[659,1451],{},[277,1453,1455],{"id":1454},"_5-record는-기본적으로-불변-객체다","🧱 5. Record는 기본적으로 불변 객체다",[282,1457,1458],{},"Record에서 중요한 특징 중 하나가 **불변성(Immutable)**이다.",[282,1460,1461],{},"다음과 같이 선언했다고 생각해보자.",[304,1463,1464],{"className":306,"code":1361,"language":308,"meta":309,"style":309},[311,1465,1466,1476,1484,1490,1496],{"__ignoreMap":309},[314,1467,1468,1470,1472,1474],{"class":316,"line":317},[314,1469,321],{"class":320},[314,1471,687],{"class":320},[314,1473,1165],{"class":327},[314,1475,692],{"class":331},[314,1477,1478,1480,1482],{"class":316,"line":335},[314,1479,697],{"class":320},[314,1481,355],{"class":354},[314,1483,702],{"class":331},[314,1485,1486,1488],{"class":316,"line":342},[314,1487,707],{"class":320},[314,1489,1388],{"class":354},[314,1491,1492,1494],{"class":316,"line":361},[314,1493,426],{"class":331},[314,1495,332],{"class":331},[314,1497,1498],{"class":316,"line":376},[314,1499,586],{"class":331},[282,1501,1502,1503,1506],{},"Record의 컴포넌트는 기본적으로 ",[311,1504,1505],{},"final"," 필드로 관리된다.",[282,1508,1509],{},"따라서 다음과 같이 값을 변경할 수 없다.",[304,1511,1513],{"className":306,"code":1512,"language":308,"meta":309,"style":309},"User user = new User(1L, \"콩이\");\n\nuser.name = \"두부\";\n",[311,1514,1515,1543,1547],{"__ignoreMap":309},[314,1516,1517,1519,1521,1523,1525,1527,1529,1532,1534,1537,1539,1541],{"class":316,"line":317},[314,1518,1212],{"class":320},[314,1520,1215],{"class":354},[314,1522,440],{"class":331},[314,1524,1220],{"class":504},[314,1526,1165],{"class":401},[314,1528,404],{"class":331},[314,1530,1531],{"class":1229},"1L",[314,1533,413],{"class":331},[314,1535,1536],{"class":331}," \"",[314,1538,1241],{"class":1240},[314,1540,1244],{"class":331},[314,1542,1261],{"class":331},[314,1544,1545],{"class":316,"line":335},[314,1546,339],{"emptyLinePlaceholder":338},[314,1548,1549,1551,1553,1555,1557,1559,1562,1564],{"class":316,"line":342},[314,1550,1278],{"class":354},[314,1552,1281],{"class":331},[314,1554,452],{"class":354},[314,1556,440],{"class":331},[314,1558,1536],{"class":331},[314,1560,1561],{"class":1240},"두부",[314,1563,1244],{"class":331},[314,1565,358],{"class":331},[282,1567,1568],{},"위 코드는 컴파일되지 않는다.",[282,1570,1571],{},"Record는 객체가 생성된 이후 자신의 컴포넌트를 직접 변경할 수 없도록 설계되어 있다.",[659,1573],{},[277,1575,1577],{"id":1576},"_6-불변-객체란-무엇인가","🔐 6. 불변 객체란 무엇인가?",[282,1579,1580,1581,1584],{},"불변 객체(Immutable Object)는 ",[289,1582,1583],{},"객체가 생성된 이후 내부 상태를 변경할 수 없는 객체","를 의미한다.",[282,1586,1587],{},"예를 들어:",[304,1589,1591],{"className":306,"code":1590,"language":308,"meta":309,"style":309},"String name = \"BroBean\";\n",[311,1592,1593],{"__ignoreMap":309},[314,1594,1595,1597,1600,1602,1604,1607,1609],{"class":316,"line":317},[314,1596,1060],{"class":320},[314,1598,1599],{"class":354}," name ",[314,1601,440],{"class":331},[314,1603,1536],{"class":331},[314,1605,1606],{"class":1240},"BroBean",[314,1608,1244],{"class":331},[314,1610,358],{"class":331},[282,1612,1613],{},"이라는 문자열이 있다고 하자.",[282,1615,1616],{},"다음 코드:",[304,1618,1620],{"className":306,"code":1619,"language":308,"meta":309,"style":309},"name = \"Java\";\n",[311,1621,1622],{"__ignoreMap":309},[314,1623,1624,1626,1628,1630,1633,1635],{"class":316,"line":317},[314,1625,452],{"class":354},[314,1627,440],{"class":331},[314,1629,1536],{"class":331},[314,1631,1632],{"class":1240},"Java",[314,1634,1244],{"class":331},[314,1636,358],{"class":331},[282,1638,1639],{},"는 기존 String 객체의 값을 변경하는 것이 아니다.",[282,1641,1642],{},"새로운 문자열을 가리키도록 참조가 변경되는 것이다.",[282,1644,1645],{},"이처럼 불변 객체는 객체 자체의 상태가 생성 이후 변경되지 않는다.",[282,1647,1648],{},"Record 역시 이러한 데이터 객체 설계를 쉽게 만들 수 있다.",[659,1650],{},[277,1652,1654],{"id":1653},"_7-불변-객체를-사용하는-이유","🌱 7. 불변 객체를 사용하는 이유",[282,1656,1657,1658,603],{},"불변 객체를 사용하는 가장 큰 이유 중 하나는 ",[289,1659,1660],{},"상태 변경으로 인한 예측하지 못한 사이드 이펙트를 줄이는 것",[282,1662,1663],{},"예를 들어 여러 메서드가 동일한 객체를 참조하고 있다고 하자.",[304,1665,1667],{"className":306,"code":1666,"language":308,"meta":309,"style":309},"User user = ...;\n\nserviceA.process(user);\nserviceB.process(user);\nserviceC.process(user);\n",[311,1668,1669,1680,1684,1700,1715],{"__ignoreMap":309},[314,1670,1671,1673,1675,1677],{"class":316,"line":317},[314,1672,1212],{"class":320},[314,1674,1215],{"class":354},[314,1676,440],{"class":331},[314,1678,1679],{"class":331}," ...;\n",[314,1681,1682],{"class":316,"line":335},[314,1683,339],{"emptyLinePlaceholder":338},[314,1685,1686,1689,1691,1694,1696,1698],{"class":316,"line":342},[314,1687,1688],{"class":354},"serviceA",[314,1690,1281],{"class":331},[314,1692,1693],{"class":401},"process",[314,1695,404],{"class":331},[314,1697,1278],{"class":354},[314,1699,1261],{"class":331},[314,1701,1702,1705,1707,1709,1711,1713],{"class":316,"line":361},[314,1703,1704],{"class":354},"serviceB",[314,1706,1281],{"class":331},[314,1708,1693],{"class":401},[314,1710,404],{"class":331},[314,1712,1278],{"class":354},[314,1714,1261],{"class":331},[314,1716,1717,1720,1722,1724,1726,1728],{"class":316,"line":376},[314,1718,1719],{"class":354},"serviceC",[314,1721,1281],{"class":331},[314,1723,1693],{"class":401},[314,1725,404],{"class":331},[314,1727,1278],{"class":354},[314,1729,1261],{"class":331},[282,1731,1732,1733,1735,1736,1738,1739,1741],{},"만약 ",[311,1734,1688],{},"가 객체 내부 상태를 변경한다면 ",[311,1737,1704],{},"와 ",[311,1740,1719],{},"의 결과도 달라질 수 있다.",[282,1743,1744],{},"반면 불변 객체라면:",[304,1746,1749],{"className":1747,"code":1748,"language":1134,"meta":309},[1132],"생성\n\n↓\n\n값 고정\n\n↓\n\n여러 곳에서 공유\n\n↓\n\n상태 변경 없음\n",[311,1750,1748],{"__ignoreMap":309},[282,1752,1753],{},"이라는 구조가 된다.",[282,1755,1756],{},"따라서 객체의 상태를 추적하기 쉬워진다.",[659,1758],{},[277,1760,1762],{"id":1761},"_8-record와-equals-hashcode","🧠 8. Record와 equals(), hashCode()",[282,1764,1765],{},"Record는 데이터 객체로 사용되는 경우가 많기 때문에 값 기반 비교가 중요하다.",[282,1767,1768],{},"예:",[304,1770,1772],{"className":306,"code":1771,"language":308,"meta":309,"style":309},"User user1 = new User(1L, \"콩이\");\nUser user2 = new User(1L, \"콩이\");\n",[311,1773,1774,1801],{"__ignoreMap":309},[314,1775,1776,1778,1781,1783,1785,1787,1789,1791,1793,1795,1797,1799],{"class":316,"line":317},[314,1777,1212],{"class":320},[314,1779,1780],{"class":354}," user1 ",[314,1782,440],{"class":331},[314,1784,1220],{"class":504},[314,1786,1165],{"class":401},[314,1788,404],{"class":331},[314,1790,1531],{"class":1229},[314,1792,413],{"class":331},[314,1794,1536],{"class":331},[314,1796,1241],{"class":1240},[314,1798,1244],{"class":331},[314,1800,1261],{"class":331},[314,1802,1803,1805,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826],{"class":316,"line":335},[314,1804,1212],{"class":320},[314,1806,1807],{"class":354}," user2 ",[314,1809,440],{"class":331},[314,1811,1220],{"class":504},[314,1813,1165],{"class":401},[314,1815,404],{"class":331},[314,1817,1531],{"class":1229},[314,1819,413],{"class":331},[314,1821,1536],{"class":331},[314,1823,1241],{"class":1240},[314,1825,1244],{"class":331},[314,1827,1261],{"class":331},[282,1829,1830],{},"두 객체는 서로 다른 객체이지만 데이터가 동일하다.",[282,1832,1833,1834,1738,1836,1838],{},"Record는 컴포넌트를 기준으로 ",[311,1835,1433],{},[311,1837,1436],{},"를 생성한다.",[282,1840,1841],{},"따라서:",[304,1843,1845],{"className":306,"code":1844,"language":308,"meta":309,"style":309},"user1.equals(user2);\n",[311,1846,1847],{"__ignoreMap":309},[314,1848,1849,1852,1854,1857,1859,1862],{"class":316,"line":317},[314,1850,1851],{"class":354},"user1",[314,1853,1281],{"class":331},[314,1855,1856],{"class":401},"equals",[314,1858,404],{"class":331},[314,1860,1861],{"class":354},"user2",[314,1863,1261],{"class":331},[282,1865,1866,1867,1869],{},"는 ",[311,1868,275],{},"가 된다.",[282,1871,1872],{},"일반 클래스에서는 직접 구현하거나 Lombok 등을 사용해야 하는 경우가 많지만 Record에서는 기본적으로 제공된다.",[659,1874],{},[277,1876,1878],{"id":1877},"_9-record의-tostring","📝 9. Record의 toString()",[282,1880,1881,1882,1884],{},"Record는 ",[311,1883,1439],{}," 역시 자동으로 생성한다.",[304,1886,1888],{"className":306,"code":1887,"language":308,"meta":309,"style":309},"User user = new User(1L, \"콩이\");\n\nSystem.out.println(user);\n",[311,1889,1890,1916,1920],{"__ignoreMap":309},[314,1891,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914],{"class":316,"line":317},[314,1893,1212],{"class":320},[314,1895,1215],{"class":354},[314,1897,440],{"class":331},[314,1899,1220],{"class":504},[314,1901,1165],{"class":401},[314,1903,404],{"class":331},[314,1905,1531],{"class":1229},[314,1907,413],{"class":331},[314,1909,1536],{"class":331},[314,1911,1241],{"class":1240},[314,1913,1244],{"class":331},[314,1915,1261],{"class":331},[314,1917,1918],{"class":316,"line":335},[314,1919,339],{"emptyLinePlaceholder":338},[314,1921,1922,1925,1927,1930,1932,1935,1937,1939],{"class":316,"line":342},[314,1923,1924],{"class":354},"System",[314,1926,1281],{"class":331},[314,1928,1929],{"class":354},"out",[314,1931,1281],{"class":331},[314,1933,1934],{"class":401},"println",[314,1936,404],{"class":331},[314,1938,1278],{"class":354},[314,1940,1261],{"class":331},[282,1942,1943],{},"결과는 대략 다음과 같은 형태가 된다.",[304,1945,1948],{"className":1946,"code":1947,"language":1134,"meta":309},[1132],"User[id=1, name=콩이]\n",[311,1949,1947],{"__ignoreMap":309},[282,1951,1952],{},"따라서 디버깅할 때도 객체 내부 값을 쉽게 확인할 수 있다.",[659,1954],{},[277,1956,1958],{"id":1957},"️-10-record에서도-메서드를-만들-수-있을까","🛠️ 10. Record에서도 메서드를 만들 수 있을까?",[282,1960,1961],{},"Record가 단순한 데이터만 담을 수 있는 것은 아니다.",[282,1963,1964],{},"필요하다면 메서드를 추가할 수 있다.",[304,1966,1968],{"className":306,"code":1967,"language":308,"meta":309,"style":309},"public record User(\n    Long id,\n    String name\n) {\n\n    public String displayName() {\n        return \"농부 \" + name;\n    }\n}\n",[311,1969,1970,1980,1988,1994,2000,2004,2017,2035,2039],{"__ignoreMap":309},[314,1971,1972,1974,1976,1978],{"class":316,"line":317},[314,1973,321],{"class":320},[314,1975,687],{"class":320},[314,1977,1165],{"class":327},[314,1979,692],{"class":331},[314,1981,1982,1984,1986],{"class":316,"line":335},[314,1983,697],{"class":320},[314,1985,355],{"class":354},[314,1987,702],{"class":331},[314,1989,1990,1992],{"class":316,"line":342},[314,1991,707],{"class":320},[314,1993,1388],{"class":354},[314,1995,1996,1998],{"class":316,"line":361},[314,1997,426],{"class":331},[314,1999,332],{"class":331},[314,2001,2002],{"class":316,"line":376},[314,2003,339],{"emptyLinePlaceholder":338},[314,2005,2006,2008,2010,2013,2015],{"class":316,"line":390},[314,2007,398],{"class":320},[314,2009,368],{"class":320},[314,2011,2012],{"class":401}," displayName",[314,2014,496],{"class":331},[314,2016,332],{"class":331},[314,2018,2019,2021,2023,2026,2028,2031,2033],{"class":316,"line":395},[314,2020,505],{"class":504},[314,2022,1536],{"class":331},[314,2024,2025],{"class":1240},"농부 ",[314,2027,1244],{"class":331},[314,2029,2030],{"class":331}," +",[314,2032,371],{"class":354},[314,2034,358],{"class":331},[314,2036,2037],{"class":316,"line":431},[314,2038,478],{"class":331},[314,2040,2041],{"class":316,"line":447},[314,2042,586],{"class":331},[282,2044,2045],{},"사용:",[304,2047,2049],{"className":306,"code":2048,"language":308,"meta":309,"style":309},"User user = new User(1L, \"콩이\");\n\nSystem.out.println(user.displayName());\n",[311,2050,2051,2077,2081],{"__ignoreMap":309},[314,2052,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073,2075],{"class":316,"line":317},[314,2054,1212],{"class":320},[314,2056,1215],{"class":354},[314,2058,440],{"class":331},[314,2060,1220],{"class":504},[314,2062,1165],{"class":401},[314,2064,404],{"class":331},[314,2066,1531],{"class":1229},[314,2068,413],{"class":331},[314,2070,1536],{"class":331},[314,2072,1241],{"class":1240},[314,2074,1244],{"class":331},[314,2076,1261],{"class":331},[314,2078,2079],{"class":316,"line":335},[314,2080,339],{"emptyLinePlaceholder":338},[314,2082,2083,2085,2087,2089,2091,2093,2095,2097,2099,2102],{"class":316,"line":342},[314,2084,1924],{"class":354},[314,2086,1281],{"class":331},[314,2088,1929],{"class":354},[314,2090,1281],{"class":331},[314,2092,1934],{"class":401},[314,2094,404],{"class":331},[314,2096,1278],{"class":354},[314,2098,1281],{"class":331},[314,2100,2101],{"class":401},"displayName",[314,2103,2104],{"class":331},"());\n",[282,2106,2107,2108,603],{},"즉 Record는 단순한 구조체가 아니라 ",[289,2109,2110],{},"일반적인 Java 타입",[282,2112,2113,2114,2117],{},"다만 Record의 목적을 생각하면 데이터의 상태를 복잡하게 관리하는 객체보다는 ",[289,2115,2116],{},"값을 표현하고 전달하는 객체","에 사용하는 것이 적합하다.",[659,2119],{},[277,2121,2123],{"id":2122},"_11-record에서-생성자를-커스터마이징할-수-있을까","🔎 11. Record에서 생성자를 커스터마이징할 수 있을까?",[282,2125,2126],{},"가능하다.",[282,2128,2129],{},"예를 들어 입력값을 검증하고 싶다면 compact constructor를 사용할 수 있다.",[304,2131,2133],{"className":306,"code":2132,"language":308,"meta":309,"style":309},"public record User(\n    Long id,\n    String name\n) {\n\n    public User {\n        if (name == null || name.isBlank()) {\n            throw new IllegalArgumentException(\"이름은 필수입니다.\");\n        }\n    }\n}\n",[311,2134,2135,2145,2153,2159,2165,2169,2177,2208,2229,2234,2238],{"__ignoreMap":309},[314,2136,2137,2139,2141,2143],{"class":316,"line":317},[314,2138,321],{"class":320},[314,2140,687],{"class":320},[314,2142,1165],{"class":327},[314,2144,692],{"class":331},[314,2146,2147,2149,2151],{"class":316,"line":335},[314,2148,697],{"class":320},[314,2150,355],{"class":354},[314,2152,702],{"class":331},[314,2154,2155,2157],{"class":316,"line":342},[314,2156,707],{"class":320},[314,2158,1388],{"class":354},[314,2160,2161,2163],{"class":316,"line":361},[314,2162,426],{"class":331},[314,2164,332],{"class":331},[314,2166,2167],{"class":316,"line":376},[314,2168,339],{"emptyLinePlaceholder":338},[314,2170,2171,2173,2175],{"class":316,"line":390},[314,2172,398],{"class":320},[314,2174,1165],{"class":401},[314,2176,332],{"class":331},[314,2178,2179,2182,2185,2187,2190,2193,2196,2198,2200,2203,2206],{"class":316,"line":395},[314,2180,2181],{"class":504},"        if",[314,2183,2184],{"class":331}," (",[314,2186,452],{"class":354},[314,2188,2189],{"class":331},"==",[314,2191,2192],{"class":331}," null",[314,2194,2195],{"class":331}," ||",[314,2197,371],{"class":354},[314,2199,1281],{"class":331},[314,2201,2202],{"class":401},"isBlank",[314,2204,2205],{"class":331},"())",[314,2207,332],{"class":331},[314,2209,2210,2213,2215,2218,2220,2222,2225,2227],{"class":316,"line":431},[314,2211,2212],{"class":504},"            throw",[314,2214,1220],{"class":504},[314,2216,2217],{"class":401}," IllegalArgumentException",[314,2219,404],{"class":331},[314,2221,1244],{"class":331},[314,2223,2224],{"class":1240},"이름은 필수입니다.",[314,2226,1244],{"class":331},[314,2228,1261],{"class":331},[314,2230,2231],{"class":316,"line":447},[314,2232,2233],{"class":331},"        }\n",[314,2235,2236],{"class":316,"line":461},[314,2237,478],{"class":331},[314,2239,2240],{"class":316,"line":475},[314,2241,586],{"class":331},[282,2243,2244],{},"여기서는 Record의 기본 생성자 형태를 유지하면서 검증 로직만 추가했다.",[282,2246,2247],{},"객체 생성 시:",[304,2249,2251],{"className":306,"code":2250,"language":308,"meta":309,"style":309},"new User(1L, \"\");\n",[311,2252,2253],{"__ignoreMap":309},[314,2254,2255,2258,2260,2262,2264,2266,2269],{"class":316,"line":317},[314,2256,2257],{"class":504},"new",[314,2259,1165],{"class":401},[314,2261,404],{"class":331},[314,2263,1531],{"class":1229},[314,2265,413],{"class":331},[314,2267,2268],{"class":331}," \"\"",[314,2270,1261],{"class":331},[282,2272,2273],{},"을 실행하면 예외가 발생한다.",[659,2275],{},[277,2277,2279],{"id":2278},"_12-record와-일반-클래스의-차이","🧩 12. Record와 일반 클래스의 차이",[282,2281,2282],{},"둘의 차이를 정리하면 다음과 같다.",[2284,2285,2286,2302],"table",{},[2287,2288,2289],"thead",{},[2290,2291,2292,2296,2299],"tr",{},[2293,2294,2295],"th",{},"항목",[2293,2297,2298],{},"일반 Class",[2293,2300,2301],{},"Record",[2303,2304,2305,2317,2328,2342,2352,2361,2370,2381,2392],"tbody",{},[2290,2306,2307,2311,2314],{},[2308,2309,2310],"td",{},"필드",[2308,2312,2313],{},"직접 선언",[2308,2315,2316],{},"Component로 선언",[2290,2318,2319,2322,2325],{},[2308,2320,2321],{},"생성자",[2308,2323,2324],{},"직접 작성 가능",[2308,2326,2327],{},"Canonical Constructor 자동 생성",[2290,2329,2330,2333,2336],{},[2308,2331,2332],{},"getter",[2308,2334,2335],{},"직접 작성",[2308,2337,2338,2341],{},[311,2339,2340],{},"name()"," 형태 자동 생성",[2290,2343,2344,2346,2349],{},[2308,2345,1856],{},[2308,2347,2348],{},"직접 구현 가능",[2308,2350,2351],{},"자동 생성",[2290,2353,2354,2357,2359],{},[2308,2355,2356],{},"hashCode",[2308,2358,2348],{},[2308,2360,2351],{},[2290,2362,2363,2366,2368],{},[2308,2364,2365],{},"toString",[2308,2367,2348],{},[2308,2369,2351],{},[2290,2371,2372,2375,2378],{},[2308,2373,2374],{},"불변성",[2308,2376,2377],{},"직접 설계",[2308,2379,2380],{},"Component가 final",[2290,2382,2383,2386,2389],{},[2308,2384,2385],{},"상속",[2308,2387,2388],{},"가능",[2308,2390,2391],{},"다른 클래스를 상속할 수 없음",[2290,2393,2394,2397,2400],{},[2308,2395,2396],{},"목적",[2308,2398,2399],{},"다양한 객체 모델링",[2308,2401,2402],{},"데이터 중심 객체",[282,2404,2405],{},"따라서 Record는 일반 클래스를 완전히 대체하는 것이 아니다.",[282,2407,2408,2411],{},[289,2409,2410],{},"데이터 전달이나 값 표현에 특화된 별도의 도구","라고 이해하는 것이 좋다.",[659,2413],{},[277,2415,2417],{"id":2416},"_13-record는-상속할-수-없다","🚫 13. Record는 상속할 수 없다",[282,2419,2420],{},"Record는 다른 클래스를 상속할 수 없다.",[304,2422,2424],{"className":306,"code":2423,"language":308,"meta":309,"style":309},"public record User(...) extends BaseUser {\n}\n",[311,2425,2426,2443],{"__ignoreMap":309},[314,2427,2428,2430,2432,2434,2437,2440],{"class":316,"line":317},[314,2429,321],{"class":320},[314,2431,687],{"class":320},[314,2433,1165],{"class":327},[314,2435,2436],{"class":331},"(...)",[314,2438,2439],{"class":354}," extends BaseUser ",[314,2441,2442],{"class":331},"{\n",[314,2444,2445],{"class":316,"line":335},[314,2446,586],{"class":331},[282,2448,2449],{},"이런 형태는 사용할 수 없다.",[282,2451,2452,2453,2456],{},"Record 자체가 이미 ",[311,2454,2455],{},"java.lang.Record","를 상속하고 있기 때문이다.",[282,2458,2459],{},"다만 interface는 구현할 수 있다.",[304,2461,2463],{"className":306,"code":2462,"language":308,"meta":309,"style":309},"public record User(\n    Long id,\n    String name\n) implements UserInfo {\n}\n",[311,2464,2465,2475,2483,2489,2501],{"__ignoreMap":309},[314,2466,2467,2469,2471,2473],{"class":316,"line":317},[314,2468,321],{"class":320},[314,2470,687],{"class":320},[314,2472,1165],{"class":327},[314,2474,692],{"class":331},[314,2476,2477,2479,2481],{"class":316,"line":335},[314,2478,697],{"class":320},[314,2480,355],{"class":354},[314,2482,702],{"class":331},[314,2484,2485,2487],{"class":316,"line":342},[314,2486,707],{"class":320},[314,2488,1388],{"class":354},[314,2490,2491,2493,2496,2499],{"class":316,"line":361},[314,2492,426],{"class":331},[314,2494,2495],{"class":320}," implements",[314,2497,2498],{"class":327}," UserInfo",[314,2500,332],{"class":331},[314,2502,2503],{"class":316,"line":376},[314,2504,586],{"class":331},[282,2506,2507],{},"따라서 공통 동작을 interface로 추상화하는 방식은 사용할 수 있다.",[659,2509],{},[277,2511,2513],{"id":2512},"️-14-record의-단점","⚠️ 14. Record의 단점",[282,2515,2516],{},"Record가 항상 좋은 것은 아니다.",[282,2518,2519],{},"대표적인 단점은 다음과 같다.",[2521,2522,2524],"h3",{"id":2523},"_1-상속-제한","1. 상속 제한",[282,2526,2527],{},"클래스를 상속할 수 없다.",[2521,2529,2531],{"id":2530},"_2-상태-변경이-어렵다","2. 상태 변경이 어렵다",[282,2533,2534],{},"불변 객체를 만드는 것이 목적이기 때문에 변경 가능한 객체가 필요한 경우 적합하지 않다.",[2521,2536,2538],{"id":2537},"_3-jpa-entity로-사용하기-어렵다","3. JPA Entity로 사용하기 어렵다",[282,2540,2541],{},"JPA Entity는 일반적으로 다음과 같은 특성을 필요로 한다.",[619,2543,2544,2547,2550,2553],{},[622,2545,2546],{},"기본 생성자",[622,2548,2549],{},"프록시 생성",[622,2551,2552],{},"변경 감지",[622,2554,2555],{},"엔티티 상태 관리",[282,2557,2558],{},"Record의 설계 목적과 JPA Entity의 요구사항은 서로 다르다.",[282,2560,2561],{},"따라서 일반적으로:",[304,2563,2566],{"className":2564,"code":2565,"language":1134,"meta":309},[1132],"Entity = class\n\nDTO = record\n",[311,2567,2565],{"__ignoreMap":309},[282,2569,2570],{},"구조를 많이 사용한다.",[659,2572],{},[277,2574,2576],{"id":2575},"_15-record를-dto로-사용하는-이유","📦 15. Record를 DTO로 사용하는 이유",[282,2578,2579],{},"Record가 가장 자주 사용되는 영역 중 하나가 **DTO(Data Transfer Object)**다.",[282,2581,2582],{},"예를 들어 API 요청:",[304,2584,2586],{"className":306,"code":2585,"language":308,"meta":309,"style":309},"public record CreateUserRequest(\n    String name,\n    String email\n) {\n}\n",[311,2587,2588,2599,2607,2613,2619],{"__ignoreMap":309},[314,2589,2590,2592,2594,2597],{"class":316,"line":317},[314,2591,321],{"class":320},[314,2593,687],{"class":320},[314,2595,2596],{"class":327}," CreateUserRequest",[314,2598,692],{"class":331},[314,2600,2601,2603,2605],{"class":316,"line":335},[314,2602,707],{"class":320},[314,2604,371],{"class":354},[314,2606,702],{"class":331},[314,2608,2609,2611],{"class":316,"line":342},[314,2610,707],{"class":320},[314,2612,718],{"class":354},[314,2614,2615,2617],{"class":316,"line":361},[314,2616,426],{"class":331},[314,2618,332],{"class":331},[314,2620,2621],{"class":316,"line":376},[314,2622,586],{"class":331},[282,2624,2625],{},"API 응답:",[304,2627,2628],{"className":306,"code":678,"language":308,"meta":309,"style":309},[311,2629,2630,2640,2648,2656,2662,2668],{"__ignoreMap":309},[314,2631,2632,2634,2636,2638],{"class":316,"line":317},[314,2633,321],{"class":320},[314,2635,687],{"class":320},[314,2637,328],{"class":327},[314,2639,692],{"class":331},[314,2641,2642,2644,2646],{"class":316,"line":335},[314,2643,697],{"class":320},[314,2645,355],{"class":354},[314,2647,702],{"class":331},[314,2649,2650,2652,2654],{"class":316,"line":342},[314,2651,707],{"class":320},[314,2653,371],{"class":354},[314,2655,702],{"class":331},[314,2657,2658,2660],{"class":316,"line":361},[314,2659,707],{"class":320},[314,2661,718],{"class":354},[314,2663,2664,2666],{"class":316,"line":376},[314,2665,426],{"class":331},[314,2667,332],{"class":331},[314,2669,2670],{"class":316,"line":390},[314,2671,586],{"class":331},[282,2673,2674],{},"처럼 사용할 수 있다.",[282,2676,2677],{},"DTO는 기본적으로 데이터를 전달하는 역할을 하기 때문에 Record의 목적과 잘 맞는다.",[659,2679],{},[277,2681,2683],{"id":2682},"_16-spring-boot에서-request-dto로-사용하기","🌐 16. Spring Boot에서 Request DTO로 사용하기",[282,2685,2686],{},"Spring Boot에서는 다음과 같이 사용할 수 있다.",[304,2688,2689],{"className":306,"code":2585,"language":308,"meta":309,"style":309},[311,2690,2691,2701,2709,2715,2721],{"__ignoreMap":309},[314,2692,2693,2695,2697,2699],{"class":316,"line":317},[314,2694,321],{"class":320},[314,2696,687],{"class":320},[314,2698,2596],{"class":327},[314,2700,692],{"class":331},[314,2702,2703,2705,2707],{"class":316,"line":335},[314,2704,707],{"class":320},[314,2706,371],{"class":354},[314,2708,702],{"class":331},[314,2710,2711,2713],{"class":316,"line":342},[314,2712,707],{"class":320},[314,2714,718],{"class":354},[314,2716,2717,2719],{"class":316,"line":361},[314,2718,426],{"class":331},[314,2720,332],{"class":331},[314,2722,2723],{"class":316,"line":376},[314,2724,586],{"class":331},[282,2726,2727],{},"Controller:",[304,2729,2731],{"className":306,"code":2730,"language":308,"meta":309,"style":309},"@PostMapping(\"\u002Fusers\")\npublic ResponseEntity\u003CVoid> create(\n    @RequestBody CreateUserRequest request\n) {\n\n    userService.create(\n        request.name(),\n        request.email()\n    );\n\n    return ResponseEntity.ok().build();\n}\n",[311,2732,2733,2753,2774,2787,2793,2797,2809,2821,2832,2837,2841,2860],{"__ignoreMap":309},[314,2734,2735,2738,2741,2743,2745,2748,2750],{"class":316,"line":317},[314,2736,2737],{"class":331},"@",[314,2739,2740],{"class":320},"PostMapping",[314,2742,404],{"class":331},[314,2744,1244],{"class":331},[314,2746,2747],{"class":1240},"\u002Fusers",[314,2749,1244],{"class":331},[314,2751,2752],{"class":331},")\n",[314,2754,2755,2757,2760,2763,2766,2769,2772],{"class":316,"line":335},[314,2756,321],{"class":320},[314,2758,2759],{"class":320}," ResponseEntity",[314,2761,2762],{"class":331},"\u003C",[314,2764,2765],{"class":354},"Void",[314,2767,2768],{"class":331},">",[314,2770,2771],{"class":401}," create",[314,2773,692],{"class":331},[314,2775,2776,2779,2782,2784],{"class":316,"line":342},[314,2777,2778],{"class":331},"    @",[314,2780,2781],{"class":320},"RequestBody",[314,2783,2596],{"class":320},[314,2785,2786],{"class":354}," request\n",[314,2788,2789,2791],{"class":316,"line":361},[314,2790,426],{"class":331},[314,2792,332],{"class":331},[314,2794,2795],{"class":316,"line":376},[314,2796,339],{"emptyLinePlaceholder":338},[314,2798,2799,2802,2804,2807],{"class":316,"line":390},[314,2800,2801],{"class":354},"    userService",[314,2803,1281],{"class":331},[314,2805,2806],{"class":401},"create",[314,2808,692],{"class":331},[314,2810,2811,2814,2816,2818],{"class":316,"line":395},[314,2812,2813],{"class":354},"        request",[314,2815,1281],{"class":331},[314,2817,1296],{"class":401},[314,2819,2820],{"class":331},"(),\n",[314,2822,2823,2825,2827,2829],{"class":316,"line":431},[314,2824,2813],{"class":354},[314,2826,1281],{"class":331},[314,2828,1307],{"class":401},[314,2830,2831],{"class":331},"()\n",[314,2833,2834],{"class":316,"line":447},[314,2835,2836],{"class":331},"    );\n",[314,2838,2839],{"class":316,"line":461},[314,2840,339],{"emptyLinePlaceholder":338},[314,2842,2843,2845,2847,2849,2852,2855,2858],{"class":316,"line":475},[314,2844,1108],{"class":504},[314,2846,2759],{"class":354},[314,2848,1281],{"class":331},[314,2850,2851],{"class":401},"ok",[314,2853,2854],{"class":331},"().",[314,2856,2857],{"class":401},"build",[314,2859,1287],{"class":331},[314,2861,2862],{"class":316,"line":481},[314,2863,586],{"class":331},[282,2865,2866],{},"Lombok 없이도 간결한 요청 객체를 만들 수 있다.",[659,2868],{},[277,2870,2872],{"id":2871},"_17-response-dto로-사용하기","📤 17. Response DTO로 사용하기",[282,2874,2875],{},"응답 객체 역시 Record와 잘 맞는다.",[304,2877,2878],{"className":306,"code":678,"language":308,"meta":309,"style":309},[311,2879,2880,2890,2898,2906,2912,2918],{"__ignoreMap":309},[314,2881,2882,2884,2886,2888],{"class":316,"line":317},[314,2883,321],{"class":320},[314,2885,687],{"class":320},[314,2887,328],{"class":327},[314,2889,692],{"class":331},[314,2891,2892,2894,2896],{"class":316,"line":335},[314,2893,697],{"class":320},[314,2895,355],{"class":354},[314,2897,702],{"class":331},[314,2899,2900,2902,2904],{"class":316,"line":342},[314,2901,707],{"class":320},[314,2903,371],{"class":354},[314,2905,702],{"class":331},[314,2907,2908,2910],{"class":316,"line":361},[314,2909,707],{"class":320},[314,2911,718],{"class":354},[314,2913,2914,2916],{"class":316,"line":376},[314,2915,426],{"class":331},[314,2917,332],{"class":331},[314,2919,2920],{"class":316,"line":390},[314,2921,586],{"class":331},[282,2923,2727],{},[304,2925,2927],{"className":306,"code":2926,"language":308,"meta":309,"style":309},"@GetMapping(\"\u002Fusers\u002F{id}\")\npublic UserResponse getUser(\n    @PathVariable Long id\n) {\n\n    User user = userService.findById(id);\n\n    return new UserResponse(\n        user.getId(),\n        user.getName(),\n        user.getEmail()\n    );\n}\n",[311,2928,2929,2947,2958,2970,2976,2980,3003,3007,3017,3029,3039,3050,3054],{"__ignoreMap":309},[314,2930,2931,2933,2936,2938,2940,2943,2945],{"class":316,"line":317},[314,2932,2737],{"class":331},[314,2934,2935],{"class":320},"GetMapping",[314,2937,404],{"class":331},[314,2939,1244],{"class":331},[314,2941,2942],{"class":1240},"\u002Fusers\u002F{id}",[314,2944,1244],{"class":331},[314,2946,2752],{"class":331},[314,2948,2949,2951,2953,2956],{"class":316,"line":335},[314,2950,321],{"class":320},[314,2952,328],{"class":320},[314,2954,2955],{"class":401}," getUser",[314,2957,692],{"class":331},[314,2959,2960,2962,2965,2967],{"class":316,"line":342},[314,2961,2778],{"class":331},[314,2963,2964],{"class":320},"PathVariable",[314,2966,351],{"class":320},[314,2968,2969],{"class":354}," id\n",[314,2971,2972,2974],{"class":316,"line":361},[314,2973,426],{"class":331},[314,2975,332],{"class":331},[314,2977,2978],{"class":316,"line":376},[314,2979,339],{"emptyLinePlaceholder":338},[314,2981,2982,2985,2987,2989,2992,2994,2997,2999,3001],{"class":316,"line":390},[314,2983,2984],{"class":320},"    User",[314,2986,1215],{"class":354},[314,2988,440],{"class":331},[314,2990,2991],{"class":354}," userService",[314,2993,1281],{"class":331},[314,2995,2996],{"class":401},"findById",[314,2998,404],{"class":331},[314,3000,1284],{"class":354},[314,3002,1261],{"class":331},[314,3004,3005],{"class":316,"line":395},[314,3006,339],{"emptyLinePlaceholder":338},[314,3008,3009,3011,3013,3015],{"class":316,"line":431},[314,3010,1108],{"class":504},[314,3012,1220],{"class":504},[314,3014,328],{"class":401},[314,3016,692],{"class":331},[314,3018,3019,3022,3024,3027],{"class":316,"line":447},[314,3020,3021],{"class":354},"        user",[314,3023,1281],{"class":331},[314,3025,3026],{"class":401},"getId",[314,3028,2820],{"class":331},[314,3030,3031,3033,3035,3037],{"class":316,"line":461},[314,3032,3021],{"class":354},[314,3034,1281],{"class":331},[314,3036,1326],{"class":401},[314,3038,2820],{"class":331},[314,3040,3041,3043,3045,3048],{"class":316,"line":475},[314,3042,3021],{"class":354},[314,3044,1281],{"class":331},[314,3046,3047],{"class":401},"getEmail",[314,3049,2831],{"class":331},[314,3051,3052],{"class":316,"line":481},[314,3053,2836],{"class":331},[314,3055,3056],{"class":316,"line":486},[314,3057,586],{"class":331},[282,3059,3060],{},"응답 객체는 외부에 데이터를 전달하는 것이 목적이므로 굳이 변경 가능한 상태를 가질 필요가 없다.",[659,3062],{},[277,3064,3066],{"id":3065},"_18-record를-entity로-사용하면-안-될까","🧠 18. Record를 Entity로 사용하면 안 될까?",[282,3068,3069,3070,3073],{},"무조건 \"절대 안 된다\"라고 생각하기보다는 ",[289,3071,3072],{},"역할이 다르다","고 이해하는 것이 좋다.",[282,3075,3076],{},"JPA Entity는 데이터베이스와 연결되어 있고 생명주기와 상태 변경을 관리해야 한다.",[282,3078,1768],{},[304,3080,3082],{"className":306,"code":3081,"language":308,"meta":309,"style":309},"@Entity\npublic class User {\n\n    @Id\n    private Long id;\n\n    private String name;\n\n    public void changeName(String name) {\n        this.name = name;\n    }\n}\n",[311,3083,3084,3091,3101,3105,3112,3122,3126,3136,3140,3160,3172,3176],{"__ignoreMap":309},[314,3085,3086,3088],{"class":316,"line":317},[314,3087,2737],{"class":331},[314,3089,3090],{"class":320},"Entity\n",[314,3092,3093,3095,3097,3099],{"class":316,"line":335},[314,3094,321],{"class":320},[314,3096,324],{"class":320},[314,3098,1165],{"class":327},[314,3100,332],{"class":331},[314,3102,3103],{"class":316,"line":342},[314,3104,339],{"emptyLinePlaceholder":338},[314,3106,3107,3109],{"class":316,"line":361},[314,3108,2778],{"class":331},[314,3110,3111],{"class":320},"Id\n",[314,3113,3114,3116,3118,3120],{"class":316,"line":376},[314,3115,345],{"class":320},[314,3117,351],{"class":320},[314,3119,355],{"class":354},[314,3121,358],{"class":331},[314,3123,3124],{"class":316,"line":390},[314,3125,339],{"emptyLinePlaceholder":338},[314,3127,3128,3130,3132,3134],{"class":316,"line":395},[314,3129,345],{"class":320},[314,3131,368],{"class":320},[314,3133,371],{"class":354},[314,3135,358],{"class":331},[314,3137,3138],{"class":316,"line":431},[314,3139,339],{"emptyLinePlaceholder":338},[314,3141,3142,3144,3147,3150,3152,3154,3156,3158],{"class":316,"line":447},[314,3143,398],{"class":320},[314,3145,3146],{"class":320}," void",[314,3148,3149],{"class":401}," changeName",[314,3151,404],{"class":331},[314,3153,1060],{"class":320},[314,3155,371],{"class":410},[314,3157,426],{"class":331},[314,3159,332],{"class":331},[314,3161,3162,3164,3166,3168,3170],{"class":316,"line":461},[314,3163,434],{"class":331},[314,3165,452],{"class":354},[314,3167,440],{"class":331},[314,3169,371],{"class":354},[314,3171,358],{"class":331},[314,3173,3174],{"class":316,"line":475},[314,3175,478],{"class":331},[314,3177,3178],{"class":316,"line":481},[314,3179,586],{"class":331},[282,3181,3182],{},"반면 Record는:",[304,3184,3186],{"className":306,"code":3185,"language":308,"meta":309,"style":309},"public record UserResponse(\n    Long id,\n    String name\n) {\n}\n",[311,3187,3188,3198,3206,3212,3218],{"__ignoreMap":309},[314,3189,3190,3192,3194,3196],{"class":316,"line":317},[314,3191,321],{"class":320},[314,3193,687],{"class":320},[314,3195,328],{"class":327},[314,3197,692],{"class":331},[314,3199,3200,3202,3204],{"class":316,"line":335},[314,3201,697],{"class":320},[314,3203,355],{"class":354},[314,3205,702],{"class":331},[314,3207,3208,3210],{"class":316,"line":342},[314,3209,707],{"class":320},[314,3211,1388],{"class":354},[314,3213,3214,3216],{"class":316,"line":361},[314,3215,426],{"class":331},[314,3217,332],{"class":331},[314,3219,3220],{"class":316,"line":376},[314,3221,586],{"class":331},[282,3223,3224],{},"처럼 현재 데이터를 표현하고 전달하는 역할에 적합하다.",[282,3226,3227],{},"따라서 일반적인 구조는:",[304,3229,3232],{"className":3230,"code":3231,"language":1134,"meta":309},[1132],"Database\n   ↓\nEntity\n   ↓\nService\n   ↓\nDTO \u002F Record\n   ↓\nController\n   ↓\nClient\n",[311,3233,3231],{"__ignoreMap":309},[282,3235,3236],{},"와 같은 형태가 된다.",[659,3238],{},[277,3240,3242],{"id":3241},"_19-record와-value-object","🌱 19. Record와 Value Object",[282,3244,3245,3246,3249],{},"Record는 DTO뿐만 아니라 ",[289,3247,3248],{},"Value Object","를 표현하는 데에도 활용할 수 있다.",[282,3251,3252],{},"예를 들어 이메일 주소를 하나의 값 객체로 만들 수 있다.",[304,3254,3256],{"className":306,"code":3255,"language":308,"meta":309,"style":309},"public record Email(\n    String value\n) {\n\n    public Email {\n        if (value == null || !value.contains(\"@\")) {\n            throw new IllegalArgumentException(\"올바른 이메일이 아닙니다.\");\n        }\n    }\n}\n",[311,3257,3258,3269,3276,3282,3286,3294,3333,3352,3356,3360],{"__ignoreMap":309},[314,3259,3260,3262,3264,3267],{"class":316,"line":317},[314,3261,321],{"class":320},[314,3263,687],{"class":320},[314,3265,3266],{"class":327}," Email",[314,3268,692],{"class":331},[314,3270,3271,3273],{"class":316,"line":335},[314,3272,707],{"class":320},[314,3274,3275],{"class":354}," value\n",[314,3277,3278,3280],{"class":316,"line":342},[314,3279,426],{"class":331},[314,3281,332],{"class":331},[314,3283,3284],{"class":316,"line":361},[314,3285,339],{"emptyLinePlaceholder":338},[314,3287,3288,3290,3292],{"class":316,"line":376},[314,3289,398],{"class":320},[314,3291,3266],{"class":401},[314,3293,332],{"class":331},[314,3295,3296,3298,3300,3303,3305,3307,3309,3312,3315,3317,3320,3322,3324,3326,3328,3331],{"class":316,"line":390},[314,3297,2181],{"class":504},[314,3299,2184],{"class":331},[314,3301,3302],{"class":354},"value ",[314,3304,2189],{"class":331},[314,3306,2192],{"class":331},[314,3308,2195],{"class":331},[314,3310,3311],{"class":331}," !",[314,3313,3314],{"class":354},"value",[314,3316,1281],{"class":331},[314,3318,3319],{"class":401},"contains",[314,3321,404],{"class":331},[314,3323,1244],{"class":331},[314,3325,2737],{"class":1240},[314,3327,1244],{"class":331},[314,3329,3330],{"class":331},"))",[314,3332,332],{"class":331},[314,3334,3335,3337,3339,3341,3343,3345,3348,3350],{"class":316,"line":395},[314,3336,2212],{"class":504},[314,3338,1220],{"class":504},[314,3340,2217],{"class":401},[314,3342,404],{"class":331},[314,3344,1244],{"class":331},[314,3346,3347],{"class":1240},"올바른 이메일이 아닙니다.",[314,3349,1244],{"class":331},[314,3351,1261],{"class":331},[314,3353,3354],{"class":316,"line":431},[314,3355,2233],{"class":331},[314,3357,3358],{"class":316,"line":447},[314,3359,478],{"class":331},[314,3361,3362],{"class":316,"line":461},[314,3363,586],{"class":331},[282,3365,3366,3367,3369],{},"이렇게 하면 단순한 ",[311,3368,1060],{},"보다 의미가 명확해진다.",[304,3371,3373],{"className":306,"code":3372,"language":308,"meta":309,"style":309},"Email email = new Email(\"bean@example.com\");\n",[311,3374,3375],{"__ignoreMap":309},[314,3376,3377,3380,3383,3385,3387,3389,3391,3393,3395,3397],{"class":316,"line":317},[314,3378,3379],{"class":320},"Email",[314,3381,3382],{"class":354}," email ",[314,3384,440],{"class":331},[314,3386,1220],{"class":504},[314,3388,3266],{"class":401},[314,3390,404],{"class":331},[314,3392,1244],{"class":331},[314,3394,1253],{"class":1240},[314,3396,1244],{"class":331},[314,3398,1261],{"class":331},[282,3400,3401],{},"이 객체는 단순 문자열이 아니라:",[1005,3403,3404],{},[282,3405,3406],{},"\"유효한 이메일 주소라는 의미를 가진 값\"",[282,3408,3409],{},"을 표현한다.",[659,3411],{},[277,3413,3415],{"id":3414},"_20-이제-동시성-문제를-생각해보자","🔎 20. 이제 동시성 문제를 생각해보자",[282,3417,3418],{},"Record가 데이터 객체의 구조와 불변성에 관한 이야기라면,",[282,3420,3421],{},"이번에는 여러 사용자가 동시에 데이터를 수정하는 문제를 생각해보자.",[282,3423,3424],{},"예를 들어 농장에 다음과 같은 자산이 있다고 하자.",[304,3426,3429],{"className":3427,"code":3428,"language":1134,"meta":309},[1132],"현재 자산 = 100,000원\n",[311,3430,3428],{"__ignoreMap":309},[282,3432,3433],{},"사용자 A가 조회한다.",[304,3435,3438],{"className":3436,"code":3437,"language":1134,"meta":309},[1132],"A → 100,000원\n",[311,3439,3437],{"__ignoreMap":309},[282,3441,3442],{},"동시에 사용자 B도 조회한다.",[304,3444,3447],{"className":3445,"code":3446,"language":1134,"meta":309},[1132],"B → 100,000원\n",[311,3448,3446],{"__ignoreMap":309},[282,3450,3451],{},"A가 20,000원을 사용한다.",[304,3453,3456],{"className":3454,"code":3455,"language":1134,"meta":309},[1132],"100,000 → 80,000\n",[311,3457,3455],{"__ignoreMap":309},[282,3459,3460],{},"B가 30,000원을 사용한다.",[304,3462,3465],{"className":3463,"code":3464,"language":1134,"meta":309},[1132],"100,000 → 70,000\n",[311,3466,3464],{"__ignoreMap":309},[282,3468,3469],{},"만약 두 요청이 단순히 자신의 조회 결과를 기준으로 저장한다면 최종 결과는:",[304,3471,3474],{"className":3472,"code":3473,"language":1134,"meta":309},[1132],"70,000원\n",[311,3475,3473],{"__ignoreMap":309},[282,3477,3478],{},"이 될 수 있다.",[282,3480,3481],{},"하지만 실제로 기대한 값은:",[304,3483,3486],{"className":3484,"code":3485,"language":1134,"meta":309},[1132],"100,000 - 20,000 - 30,000\n\n= 50,000원\n",[311,3487,3485],{"__ignoreMap":309},[282,3489,603],{},[282,3491,3492],{},"이런 문제가 바로 **동시성 문제(Concurrency Problem)**다.",[659,3494],{},[277,3496,3498],{"id":3497},"️-21-lost-update란","⚠️ 21. Lost Update란?",[282,3500,3501,3502,1414],{},"위와 같은 문제를 ",[289,3503,3504],{},"Lost Update",[282,3506,3507],{},"두 사용자가 같은 데이터를 조회한 후 각각 수정하고 저장하면서,",[282,3509,3510],{},"먼저 저장된 변경사항이 나중 저장된 값에 의해 덮어써지는 문제다.",[282,3512,3513],{},"흐름을 보면:",[304,3515,3518],{"className":3516,"code":3517,"language":1134,"meta":309},[1132],"현재 값: 100\n\nA 조회 → 100\nB 조회 → 100\n\nA 수정 → 80\nB 수정 → 70\n\nA 저장 → 80\nB 저장 → 70\n",[311,3519,3517],{"__ignoreMap":309},[282,3521,3522],{},"최종 값:",[304,3524,3527],{"className":3525,"code":3526,"language":1134,"meta":309},[1132],"70\n",[311,3528,3526],{"__ignoreMap":309},[282,3530,3531],{},"A의 변경사항이 사라졌다.",[282,3533,3534],{},"이것이 Lost Update다.",[659,3536],{},[277,3538,3540],{"id":3539},"_22-동시성-제어란","🔐 22. 동시성 제어란?",[282,3542,3543],{},"동시성 제어는 여러 요청이 동시에 같은 데이터를 수정할 때 데이터의 일관성을 유지하기 위한 방법이다.",[282,3545,3546],{},"대표적인 방식은 크게 두 가지다.",[304,3548,3551],{"className":3549,"code":3550,"language":1134,"meta":309},[1132],"비관적 락\nPessimistic Lock\n\n낙관적 락\nOptimistic Lock\n",[311,3552,3550],{"__ignoreMap":309},[282,3554,3555],{},"두 방식은 접근 방법 자체가 다르다.",[659,3557],{},[277,3559,3561],{"id":3560},"_23-비관적-락이란","🔒 23. 비관적 락이란?",[282,3563,3564],{},"비관적 락은:",[1005,3566,3567],{},[282,3568,3569],{},"\"동시에 수정할 가능성이 높으니 일단 잠그고 보자.\"",[282,3571,3572],{},"라는 방식이다.",[282,3574,3575],{},"데이터를 조회할 때부터 다른 트랜잭션이 수정하지 못하도록 잠근다.",[282,3577,3578],{},"대표적인 SQL 개념은:",[304,3580,3584],{"className":3581,"code":3582,"language":3583,"meta":309,"style":309},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT *\nFROM user\nWHERE id = ?\nFOR UPDATE;\n","sql",[311,3585,3586,3591,3596,3601],{"__ignoreMap":309},[314,3587,3588],{"class":316,"line":317},[314,3589,3590],{},"SELECT *\n",[314,3592,3593],{"class":316,"line":335},[314,3594,3595],{},"FROM user\n",[314,3597,3598],{"class":316,"line":342},[314,3599,3600],{},"WHERE id = ?\n",[314,3602,3603],{"class":316,"line":361},[314,3604,3605],{},"FOR UPDATE;\n",[282,3607,603],{},[282,3609,3610],{},"이 경우 해당 행을 다른 트랜잭션이 동시에 수정하기 어렵게 만든다.",[282,3612,3613],{},"장점은 충돌 자체를 줄일 수 있다는 것이다.",[282,3615,3616],{},"하지만 단점도 있다.",[619,3618,3619,3622,3625,3628],{},[622,3620,3621],{},"락을 오래 유지하면 대기 발생",[622,3623,3624],{},"트래픽 증가 시 병목 가능",[622,3626,3627],{},"데드락 가능성",[622,3629,3630],{},"DB 리소스 사용 증가",[282,3632,3633],{},"따라서 항상 비관적 락이 정답은 아니다.",[659,3635],{},[277,3637,3639],{"id":3638},"_24-낙관적-락이란","🌱 24. 낙관적 락이란?",[282,3641,3642],{},"낙관적 락은 반대로 생각한다.",[1005,3644,3645],{},[282,3646,3647],{},"\"대부분 충돌하지 않을 것이다. 일단 작업하고 충돌이 발생했을 때 감지하자.\"",[282,3649,3650],{},"즉 데이터를 조회할 때는 락을 걸지 않는다.",[282,3652,3653],{},"대신 데이터가 수정되는 순간:",[304,3655,3658],{"className":3656,"code":3657,"language":1134,"meta":309},[1132],"내가 조회했던 데이터가\n아직 그대로인가?\n",[311,3659,3657],{"__ignoreMap":309},[282,3661,3662],{},"를 확인한다.",[282,3664,3665,3666,603],{},"이때 사용하는 대표적인 방법이 ",[311,3667,609],{},[659,3669],{},[277,3671,3673],{"id":3672},"️-25-version이란","🏷️ 25. @Version이란?",[282,3675,3676,3677,3679],{},"JPA에서는 ",[311,3678,609],{},"을 사용해 엔티티의 버전을 관리할 수 있다.",[282,3681,1768],{},[304,3683,3685],{"className":306,"code":3684,"language":308,"meta":309,"style":309},"@Entity\npublic class Farm {\n\n    @Id\n    @GeneratedValue\n    private Long id;\n\n    private String name;\n\n    @Version\n    private Long version;\n}\n",[311,3686,3687,3693,3704,3708,3714,3721,3731,3735,3745,3749,3756,3767],{"__ignoreMap":309},[314,3688,3689,3691],{"class":316,"line":317},[314,3690,2737],{"class":331},[314,3692,3090],{"class":320},[314,3694,3695,3697,3699,3702],{"class":316,"line":335},[314,3696,321],{"class":320},[314,3698,324],{"class":320},[314,3700,3701],{"class":327}," Farm",[314,3703,332],{"class":331},[314,3705,3706],{"class":316,"line":342},[314,3707,339],{"emptyLinePlaceholder":338},[314,3709,3710,3712],{"class":316,"line":361},[314,3711,2778],{"class":331},[314,3713,3111],{"class":320},[314,3715,3716,3718],{"class":316,"line":376},[314,3717,2778],{"class":331},[314,3719,3720],{"class":320},"GeneratedValue\n",[314,3722,3723,3725,3727,3729],{"class":316,"line":390},[314,3724,345],{"class":320},[314,3726,351],{"class":320},[314,3728,355],{"class":354},[314,3730,358],{"class":331},[314,3732,3733],{"class":316,"line":395},[314,3734,339],{"emptyLinePlaceholder":338},[314,3736,3737,3739,3741,3743],{"class":316,"line":431},[314,3738,345],{"class":320},[314,3740,368],{"class":320},[314,3742,371],{"class":354},[314,3744,358],{"class":331},[314,3746,3747],{"class":316,"line":447},[314,3748,339],{"emptyLinePlaceholder":338},[314,3750,3751,3753],{"class":316,"line":461},[314,3752,2778],{"class":331},[314,3754,3755],{"class":320},"Version\n",[314,3757,3758,3760,3762,3765],{"class":316,"line":475},[314,3759,345],{"class":320},[314,3761,351],{"class":320},[314,3763,3764],{"class":354}," version",[314,3766,358],{"class":331},[314,3768,3769],{"class":316,"line":481},[314,3770,586],{"class":331},[282,3772,1401],{},[304,3774,3776],{"className":306,"code":3775,"language":308,"meta":309,"style":309},"@Version\nprivate Long version;\n",[311,3777,3778,3784],{"__ignoreMap":309},[314,3779,3780,3782],{"class":316,"line":317},[314,3781,2737],{"class":331},[314,3783,3755],{"class":320},[314,3785,3786,3788,3790,3792],{"class":316,"line":335},[314,3787,1033],{"class":320},[314,3789,351],{"class":320},[314,3791,3764],{"class":354},[314,3793,358],{"class":331},[282,3795,3796,3797,3800],{},"이 필드가 해당 엔티티의 ",[289,3798,3799],{},"버전 번호"," 역할을 한다.",[282,3802,3803],{},"데이터가 처음 생성되었다고 하면:",[304,3805,3808],{"className":3806,"code":3807,"language":1134,"meta":309},[1132],"id = 1\nname = \"알콩달콩 농장\"\nversion = 0\n",[311,3809,3807],{"__ignoreMap":309},[282,3811,3812],{},"일 수 있다.",[282,3814,3815],{},"수정이 성공하면:",[304,3817,3820],{"className":3818,"code":3819,"language":1134,"meta":309},[1132],"version = 1\n",[311,3821,3819],{"__ignoreMap":309},[282,3823,3824],{},"로 증가한다.",[282,3826,3827],{},"다시 수정하면:",[304,3829,3832],{"className":3830,"code":3831,"language":1134,"meta":309},[1132],"version = 2\n",[311,3833,3831],{"__ignoreMap":309},[282,3835,1869],{},[659,3837],{},[277,3839,3841],{"id":3840},"_26-version은-어떻게-동작할까","🔎 26. @Version은 어떻게 동작할까?",[282,3843,3844],{},"핵심은 UPDATE SQL에 있다.",[282,3846,3847],{},"처음 조회했을 때:",[304,3849,3852],{"className":3850,"code":3851,"language":1134,"meta":309},[1132],"id = 1\nversion = 3\n",[311,3853,3851],{"__ignoreMap":309},[282,3855,3856],{},"이었다고 하자.",[282,3858,3859],{},"수정할 때 JPA는 단순히:",[304,3861,3863],{"className":3581,"code":3862,"language":3583,"meta":309,"style":309},"UPDATE farm\nSET name = ?\nWHERE id = ?;\n",[311,3864,3865,3870,3875],{"__ignoreMap":309},[314,3866,3867],{"class":316,"line":317},[314,3868,3869],{},"UPDATE farm\n",[314,3871,3872],{"class":316,"line":335},[314,3873,3874],{},"SET name = ?\n",[314,3876,3877],{"class":316,"line":342},[314,3878,3879],{},"WHERE id = ?;\n",[282,3881,3882],{},"만 실행하는 것이 아니다.",[282,3884,3885],{},"대략 다음과 같은 조건이 포함된다.",[304,3887,3889],{"className":3581,"code":3888,"language":3583,"meta":309,"style":309},"UPDATE farm\nSET\n    name = ?,\n    version = version + 1\nWHERE\n    id = ?\n    AND version = ?;\n",[311,3890,3891,3895,3900,3905,3910,3915,3920],{"__ignoreMap":309},[314,3892,3893],{"class":316,"line":317},[314,3894,3869],{},[314,3896,3897],{"class":316,"line":335},[314,3898,3899],{},"SET\n",[314,3901,3902],{"class":316,"line":342},[314,3903,3904],{},"    name = ?,\n",[314,3906,3907],{"class":316,"line":361},[314,3908,3909],{},"    version = version + 1\n",[314,3911,3912],{"class":316,"line":376},[314,3913,3914],{},"WHERE\n",[314,3916,3917],{"class":316,"line":390},[314,3918,3919],{},"    id = ?\n",[314,3921,3922],{"class":316,"line":395},[314,3923,3924],{},"    AND version = ?;\n",[282,3926,3927,3928,3931],{},"여기서 마지막 ",[311,3929,3930],{},"version = ?","가 핵심이다.",[282,3933,3934],{},"내가 조회했을 당시 버전이:",[304,3936,3939],{"className":3937,"code":3938,"language":1134,"meta":309},[1132],"3\n",[311,3940,3938],{"__ignoreMap":309},[282,3942,3943],{},"이었다면:",[304,3945,3947],{"className":3581,"code":3946,"language":3583,"meta":309,"style":309},"WHERE id = 1\nAND version = 3\n",[311,3948,3949,3954],{"__ignoreMap":309},[314,3950,3951],{"class":316,"line":317},[314,3952,3953],{},"WHERE id = 1\n",[314,3955,3956],{"class":316,"line":335},[314,3957,3958],{},"AND version = 3\n",[282,3960,3961],{},"이라는 조건으로 UPDATE를 실행한다.",[659,3963],{},[277,3965,3967],{"id":3966},"_27-왜-version-조건이-필요한가","🧠 27. 왜 version 조건이 필요한가?",[282,3969,3970],{},"두 사용자가 동시에 조회했다고 하자.",[282,3972,3973],{},"현재 데이터:",[304,3975,3978],{"className":3976,"code":3977,"language":1134,"meta":309},[1132],"version = 3\n",[311,3979,3977],{"__ignoreMap":309},[282,3981,3982],{},"A:",[304,3984,3986],{"className":3985,"code":3851,"language":1134,"meta":309},[1132],[311,3987,3851],{"__ignoreMap":309},[282,3989,3990],{},"B:",[304,3992,3994],{"className":3993,"code":3851,"language":1134,"meta":309},[1132],[311,3995,3851],{"__ignoreMap":309},[282,3997,3998],{},"둘 다 같은 버전을 가지고 있다.",[282,4000,4001],{},"먼저 A가 수정한다.",[304,4003,4005],{"className":3581,"code":4004,"language":3583,"meta":309,"style":309},"UPDATE farm\nSET\n    name = 'A',\n    version = 4\nWHERE\n    id = 1\n    AND version = 3;\n",[311,4006,4007,4011,4015,4020,4025,4029,4034],{"__ignoreMap":309},[314,4008,4009],{"class":316,"line":317},[314,4010,3869],{},[314,4012,4013],{"class":316,"line":335},[314,4014,3899],{},[314,4016,4017],{"class":316,"line":342},[314,4018,4019],{},"    name = 'A',\n",[314,4021,4022],{"class":316,"line":361},[314,4023,4024],{},"    version = 4\n",[314,4026,4027],{"class":316,"line":376},[314,4028,3914],{},[314,4030,4031],{"class":316,"line":390},[314,4032,4033],{},"    id = 1\n",[314,4035,4036],{"class":316,"line":395},[314,4037,4038],{},"    AND version = 3;\n",[282,4040,4041],{},"정상적으로 수정된다.",[282,4043,4044],{},"현재 DB:",[304,4046,4049],{"className":4047,"code":4048,"language":1134,"meta":309},[1132],"version = 4\n",[311,4050,4048],{"__ignoreMap":309},[282,4052,4053],{},"이제 B가 수정하려고 한다.",[282,4055,4056],{},"B가 가지고 있는 버전은 여전히:",[304,4058,4060],{"className":4059,"code":3977,"language":1134,"meta":309},[1132],[311,4061,3977],{"__ignoreMap":309},[282,4063,603],{},[282,4065,1841],{},[304,4067,4069],{"className":3581,"code":4068,"language":3583,"meta":309,"style":309},"UPDATE farm\nSET\n    name = 'B',\n    version = 4\nWHERE\n    id = 1\n    AND version = 3;\n",[311,4070,4071,4075,4079,4084,4088,4092,4096],{"__ignoreMap":309},[314,4072,4073],{"class":316,"line":317},[314,4074,3869],{},[314,4076,4077],{"class":316,"line":335},[314,4078,3899],{},[314,4080,4081],{"class":316,"line":342},[314,4082,4083],{},"    name = 'B',\n",[314,4085,4086],{"class":316,"line":361},[314,4087,4024],{},[314,4089,4090],{"class":316,"line":376},[314,4091,3914],{},[314,4093,4094],{"class":316,"line":390},[314,4095,4033],{},[314,4097,4098],{"class":316,"line":395},[314,4099,4038],{},[282,4101,4102],{},"를 실행한다.",[282,4104,4105],{},"하지만 현재 DB의 version은 이미:",[304,4107,4110],{"className":4108,"code":4109,"language":1134,"meta":309},[1132],"4\n",[311,4111,4109],{"__ignoreMap":309},[282,4113,603],{},[282,4115,4116],{},"따라서 조건에 맞는 행이 없다.",[304,4118,4121],{"className":4119,"code":4120,"language":1134,"meta":309},[1132],"UPDATE 대상 행 = 0개\n",[311,4122,4120],{"__ignoreMap":309},[282,4124,1869],{},[282,4126,4127],{},"이것을 통해 JPA는:",[1005,4129,4130],{},[282,4131,4132],{},"\"내가 조회했던 데이터가 다른 사람에 의해 이미 변경되었다.\"",[282,4134,4135],{},"는 사실을 감지할 수 있다.",[659,4137],{},[277,4139,4141],{"id":4140},"_28-optimisticlockexception","🚨 28. OptimisticLockException",[282,4143,4144],{},"이렇게 버전 충돌이 발생하면 JPA는 일반적으로:",[304,4146,4149],{"className":4147,"code":4148,"language":1134,"meta":309},[1132],"OptimisticLockException\n",[311,4150,4148],{"__ignoreMap":309},[282,4152,4153],{},"을 발생시킨다.",[282,4155,4156],{},"즉:",[304,4158,4161],{"className":4159,"code":4160,"language":1134,"meta":309},[1132],"A 조회\nB 조회\n\nA 수정 성공\nversion 3 → 4\n\nB 수정 시도\n\nversion 3 조건 불일치\n\n↓\n\nOptimisticLockException\n",[311,4162,4160],{"__ignoreMap":309},[282,4164,4165],{},"이라는 흐름이 된다.",[282,4167,4168,4169,4171],{},"이것이 ",[311,4170,609],{},"을 이용한 낙관적 락의 핵심 원리다.",[659,4173],{},[277,4175,4177],{"id":4176},"_29-version의-전체-흐름","🔄 29. @Version의 전체 흐름",[282,4179,4180],{},"전체 과정을 정리하면 다음과 같다.",[304,4182,4185],{"className":4183,"code":4184,"language":1134,"meta":309},[1132],"1. 데이터 조회\n\n        ↓\n\n2. 현재 version 확인\n\n        ↓\n\n3. 애플리케이션에서 데이터 수정\n\n        ↓\n\n4. UPDATE 실행\n\n        ↓\n\n5. WHERE id = ? AND version = ?\n\n        ↓\n\n6. 수정 성공\n\n        ↓\n\n7. version 증가\n",[311,4186,4184],{"__ignoreMap":309},[282,4188,4189],{},"만약 수정 대상이 없다면:",[304,4191,4194],{"className":4192,"code":4193,"language":1134,"meta":309},[1132],"version 충돌\n\n↓\n\nOptimisticLockException\n\n↓\n\n트랜잭션 실패\n",[311,4195,4193],{"__ignoreMap":309},[282,4197,1869],{},[659,4199],{},[277,4201,4203],{"id":4202},"_30-version은-락을-거는-것이-아니다","🧩 30. @Version은 락을 거는 것이 아니다",[282,4205,4206],{},"여기서 자주 하는 오해가 있다.",[304,4208,4209],{"className":306,"code":3775,"language":308,"meta":309,"style":309},[311,4210,4211,4217],{"__ignoreMap":309},[314,4212,4213,4215],{"class":316,"line":317},[314,4214,2737],{"class":331},[314,4216,3755],{"class":320},[314,4218,4219,4221,4223,4225],{"class":316,"line":335},[314,4220,1033],{"class":320},[314,4222,351],{"class":320},[314,4224,3764],{"class":354},[314,4226,358],{"class":331},[282,4228,4229],{},"을 사용하면 DB Row에 물리적인 Lock이 걸린다고 생각하는 경우가 있다.",[282,4231,4232,4233,4235,4236,603],{},"하지만 ",[311,4234,609],{},"의 핵심은 ",[289,4237,4238],{},"락을 미리 걸어두는 것이 아니라 변경 충돌을 감지하는 것",[282,4240,4156],{},[304,4242,4245],{"className":4243,"code":4244,"language":1134,"meta":309},[1132],"비관적 락\n\n\"먼저 잠근다.\"\n\n낙관적 락\n\n\"일단 처리하고 충돌을 확인한다.\"\n",[311,4246,4244],{"__ignoreMap":309},[282,4248,4249],{},"라고 이해하면 된다.",[659,4251],{},[277,4253,4255],{"id":4254},"️-31-version-필드-타입","⚙️ 31. @Version 필드 타입",[282,4257,4258,4259,4261],{},"JPA에서 ",[311,4260,609],{},"은 여러 타입으로 사용할 수 있다.",[282,4263,1023],{},[304,4265,4266],{"className":306,"code":3775,"language":308,"meta":309,"style":309},[311,4267,4268,4274],{"__ignoreMap":309},[314,4269,4270,4272],{"class":316,"line":317},[314,4271,2737],{"class":331},[314,4273,3755],{"class":320},[314,4275,4276,4278,4280,4282],{"class":316,"line":335},[314,4277,1033],{"class":320},[314,4279,351],{"class":320},[314,4281,3764],{"class":354},[314,4283,358],{"class":331},[282,4285,4286],{},"또는:",[304,4288,4290],{"className":306,"code":4289,"language":308,"meta":309,"style":309},"@Version\nprivate Integer version;\n",[311,4291,4292,4298],{"__ignoreMap":309},[314,4293,4294,4296],{"class":316,"line":317},[314,4295,2737],{"class":331},[314,4297,3755],{"class":320},[314,4299,4300,4302,4305,4307],{"class":316,"line":335},[314,4301,1033],{"class":320},[314,4303,4304],{"class":320}," Integer",[314,4306,3764],{"class":354},[314,4308,358],{"class":331},[282,4310,4311],{},"등을 사용할 수 있다.",[282,4313,4314,4315,4317],{},"일반적인 서비스에서는 ",[311,4316,407],{},"을 사용하는 경우가 많다.",[282,4319,4320,4321,603],{},"중요한 것은 개발자가 직접 version을 증가시키는 것이 아니라 ",[289,4322,4323],{},"JPA가 관리하도록 맡기는 것",[282,4325,2561],{},[304,4327,4329],{"className":306,"code":4328,"language":308,"meta":309,"style":309},"entity.setVersion(entity.getVersion() + 1);\n",[311,4330,4331],{"__ignoreMap":309},[314,4332,4333,4336,4338,4341,4343,4345,4347,4350,4352,4354,4357],{"class":316,"line":317},[314,4334,4335],{"class":354},"entity",[314,4337,1281],{"class":331},[314,4339,4340],{"class":401},"setVersion",[314,4342,404],{"class":331},[314,4344,4335],{"class":354},[314,4346,1281],{"class":331},[314,4348,4349],{"class":401},"getVersion",[314,4351,496],{"class":331},[314,4353,2030],{"class":331},[314,4355,4356],{"class":1229}," 1",[314,4358,1261],{"class":331},[282,4360,4361],{},"처럼 직접 수정하지 않는다.",[659,4363],{},[277,4365,4367],{"id":4366},"_32-version-값을-직접-수정하면-안-되는-이유","🚫 32. Version 값을 직접 수정하면 안 되는 이유",[282,4369,4370,4372],{},[311,4371,609],{},"은 JPA가 엔티티 변경 여부를 추적하는 핵심 값이다.",[282,4374,4375],{},"따라서 다음처럼 직접 변경하는 코드는 피해야 한다.",[304,4377,4379],{"className":306,"code":4378,"language":308,"meta":309,"style":309},"farm.setVersion(farm.getVersion() + 1);\n",[311,4380,4381],{"__ignoreMap":309},[314,4382,4383,4386,4388,4390,4392,4394,4396,4398,4400,4402,4404],{"class":316,"line":317},[314,4384,4385],{"class":354},"farm",[314,4387,1281],{"class":331},[314,4389,4340],{"class":401},[314,4391,404],{"class":331},[314,4393,4385],{"class":354},[314,4395,1281],{"class":331},[314,4397,4349],{"class":401},[314,4399,496],{"class":331},[314,4401,2030],{"class":331},[314,4403,4356],{"class":1229},[314,4405,1261],{"class":331},[282,4407,4408],{},"Version은:",[304,4410,4413],{"className":4411,"code":4412,"language":1134,"meta":309},[1132],"조회\n\n↓\n\n수정 감지\n\n↓\n\nUPDATE\n\n↓\n\nVersion 증가\n",[311,4414,4412],{"__ignoreMap":309},[282,4416,4417],{},"과정을 JPA가 관리하도록 두는 것이 기본적인 사용법이다.",[659,4419],{},[277,4421,4423],{"id":4422},"_33-version과-dirty-checking","🔎 33. @Version과 Dirty Checking",[282,4425,4426,4428,4429,4432],{},[311,4427,609],{},"을 이해하려면 JPA의 ",[289,4430,4431],{},"Dirty Checking","도 함께 알아야 한다.",[282,4434,4435],{},"JPA는 트랜잭션 안에서 관리 중인 엔티티의 상태가 변경되었는지 확인한다.",[282,4437,1768],{},[304,4439,4441],{"className":306,"code":4440,"language":308,"meta":309,"style":309},"@Transactional\npublic void changeFarmName(\n    Long farmId,\n    String name\n) {\n\n    Farm farm = farmRepository.findById(farmId)\n        .orElseThrow();\n\n    farm.changeName(name);\n}\n",[311,4442,4443,4450,4461,4470,4476,4482,4486,4510,4520,4524,4540],{"__ignoreMap":309},[314,4444,4445,4447],{"class":316,"line":317},[314,4446,2737],{"class":331},[314,4448,4449],{"class":320},"Transactional\n",[314,4451,4452,4454,4456,4459],{"class":316,"line":335},[314,4453,321],{"class":320},[314,4455,3146],{"class":320},[314,4457,4458],{"class":401}," changeFarmName",[314,4460,692],{"class":331},[314,4462,4463,4465,4468],{"class":316,"line":342},[314,4464,697],{"class":320},[314,4466,4467],{"class":354}," farmId",[314,4469,702],{"class":331},[314,4471,4472,4474],{"class":316,"line":361},[314,4473,707],{"class":320},[314,4475,1388],{"class":354},[314,4477,4478,4480],{"class":316,"line":376},[314,4479,426],{"class":331},[314,4481,332],{"class":331},[314,4483,4484],{"class":316,"line":390},[314,4485,339],{"emptyLinePlaceholder":338},[314,4487,4488,4491,4494,4496,4499,4501,4503,4505,4508],{"class":316,"line":395},[314,4489,4490],{"class":320},"    Farm",[314,4492,4493],{"class":354}," farm ",[314,4495,440],{"class":331},[314,4497,4498],{"class":354}," farmRepository",[314,4500,1281],{"class":331},[314,4502,2996],{"class":401},[314,4504,404],{"class":331},[314,4506,4507],{"class":354},"farmId",[314,4509,2752],{"class":331},[314,4511,4512,4515,4518],{"class":316,"line":431},[314,4513,4514],{"class":331},"        .",[314,4516,4517],{"class":401},"orElseThrow",[314,4519,1287],{"class":331},[314,4521,4522],{"class":316,"line":447},[314,4523,339],{"emptyLinePlaceholder":338},[314,4525,4526,4529,4531,4534,4536,4538],{"class":316,"line":461},[314,4527,4528],{"class":354},"    farm",[314,4530,1281],{"class":331},[314,4532,4533],{"class":401},"changeName",[314,4535,404],{"class":331},[314,4537,1296],{"class":354},[314,4539,1261],{"class":331},[314,4541,4542],{"class":316,"line":475},[314,4543,586],{"class":331},[282,4545,4546],{},"여기서는 명시적인:",[304,4548,4550],{"className":306,"code":4549,"language":308,"meta":309,"style":309},"farmRepository.save(farm);\n",[311,4551,4552],{"__ignoreMap":309},[314,4553,4554,4557,4559,4562,4564,4566],{"class":316,"line":317},[314,4555,4556],{"class":354},"farmRepository",[314,4558,1281],{"class":331},[314,4560,4561],{"class":401},"save",[314,4563,404],{"class":331},[314,4565,4385],{"class":354},[314,4567,1261],{"class":331},[282,4569,4570],{},"가 없어도 변경사항이 반영될 수 있다.",[282,4572,4573],{},"트랜잭션이 종료될 때 JPA가 변경된 상태를 감지하고 UPDATE를 실행하기 때문이다.",[282,4575,4576,4577,4579],{},"이때 ",[311,4578,609],{},"이 있다면 UPDATE에 version 조건이 포함된다.",[659,4581],{},[277,4583,4585],{"id":4584},"_34-version과-트랜잭션은-함께-이해해야-한다","🧠 34. @Version과 트랜잭션은 함께 이해해야 한다",[282,4587,4588,4590],{},[311,4589,609],{},"만 선언했다고 모든 동시성 문제가 자동으로 해결되는 것은 아니다.",[282,4592,4593],{},"보통 다음과 같은 구조에서 사용한다.",[304,4595,4597],{"className":306,"code":4596,"language":308,"meta":309,"style":309},"@Transactional\npublic void updateFarm(\n    Long farmId,\n    String name\n) {\n\n    Farm farm = farmRepository.findById(farmId)\n        .orElseThrow();\n\n    farm.changeName(name);\n}\n",[311,4598,4599,4605,4616,4624,4630,4636,4640,4660,4668,4672,4686],{"__ignoreMap":309},[314,4600,4601,4603],{"class":316,"line":317},[314,4602,2737],{"class":331},[314,4604,4449],{"class":320},[314,4606,4607,4609,4611,4614],{"class":316,"line":335},[314,4608,321],{"class":320},[314,4610,3146],{"class":320},[314,4612,4613],{"class":401}," updateFarm",[314,4615,692],{"class":331},[314,4617,4618,4620,4622],{"class":316,"line":342},[314,4619,697],{"class":320},[314,4621,4467],{"class":354},[314,4623,702],{"class":331},[314,4625,4626,4628],{"class":316,"line":361},[314,4627,707],{"class":320},[314,4629,1388],{"class":354},[314,4631,4632,4634],{"class":316,"line":376},[314,4633,426],{"class":331},[314,4635,332],{"class":331},[314,4637,4638],{"class":316,"line":390},[314,4639,339],{"emptyLinePlaceholder":338},[314,4641,4642,4644,4646,4648,4650,4652,4654,4656,4658],{"class":316,"line":395},[314,4643,4490],{"class":320},[314,4645,4493],{"class":354},[314,4647,440],{"class":331},[314,4649,4498],{"class":354},[314,4651,1281],{"class":331},[314,4653,2996],{"class":401},[314,4655,404],{"class":331},[314,4657,4507],{"class":354},[314,4659,2752],{"class":331},[314,4661,4662,4664,4666],{"class":316,"line":431},[314,4663,4514],{"class":331},[314,4665,4517],{"class":401},[314,4667,1287],{"class":331},[314,4669,4670],{"class":316,"line":447},[314,4671,339],{"emptyLinePlaceholder":338},[314,4673,4674,4676,4678,4680,4682,4684],{"class":316,"line":461},[314,4675,4528],{"class":354},[314,4677,1281],{"class":331},[314,4679,4533],{"class":401},[314,4681,404],{"class":331},[314,4683,1296],{"class":354},[314,4685,1261],{"class":331},[314,4687,4688],{"class":316,"line":475},[314,4689,586],{"class":331},[282,4691,1401],{},[304,4693,4696],{"className":4694,"code":4695,"language":1134,"meta":309},[1132],"@Transactional\n\n+\n\nJPA Entity\n\n+\n\n@Version\n\n+\n\nDirty Checking\n",[311,4697,4695],{"__ignoreMap":309},[282,4699,4700],{},"이 서로 연결된다.",[282,4702,4703,4704,4706],{},"따라서 ",[311,4705,609],{},"을 제대로 이해하려면 JPA 영속성 컨텍스트와 트랜잭션에 대한 기본적인 이해도 필요하다.",[659,4708],{},[277,4710,4712],{"id":4711},"_35-실무에서는-충돌을-어떻게-처리할까","🔥 35. 실무에서는 충돌을 어떻게 처리할까?",[282,4714,4715],{},"낙관적 락의 중요한 특징은:",[1005,4717,4718],{},[282,4719,4720],{},"충돌을 막는 것이 아니라 충돌을 발견한다.",[282,4722,4723],{},"는 것이다.",[282,4725,4726],{},"따라서 충돌이 발생하면 애플리케이션에서 적절한 처리를 해야 한다.",[282,4728,4729],{},"예를 들어 사용자에게:",[304,4731,4734],{"className":4732,"code":4733,"language":1134,"meta":309},[1132],"다른 사용자가 먼저 정보를 수정했습니다.\n\n최신 정보를 다시 확인한 후\n다시 시도해주세요.\n",[311,4735,4733],{"__ignoreMap":309},[282,4737,4738],{},"라고 안내할 수 있다.",[282,4740,4741],{},"Spring에서는 예외를 적절한 응답으로 변환할 수 있다.",[304,4743,4745],{"className":306,"code":4744,"language":308,"meta":309,"style":309},"@RestControllerAdvice\npublic class GlobalExceptionHandler {\n\n    @ExceptionHandler(OptimisticLockException.class)\n    public ResponseEntity\u003CString> handleOptimisticLock(\n        OptimisticLockException e\n    ) {\n\n        return ResponseEntity\n            .status(HttpStatus.CONFLICT)\n            .body(\"다른 사용자가 먼저 데이터를 수정했습니다.\");\n    }\n}\n",[311,4746,4747,4754,4765,4769,4788,4805,4813,4820,4824,4831,4851,4869,4873],{"__ignoreMap":309},[314,4748,4749,4751],{"class":316,"line":317},[314,4750,2737],{"class":331},[314,4752,4753],{"class":320},"RestControllerAdvice\n",[314,4755,4756,4758,4760,4763],{"class":316,"line":335},[314,4757,321],{"class":320},[314,4759,324],{"class":320},[314,4761,4762],{"class":327}," GlobalExceptionHandler",[314,4764,332],{"class":331},[314,4766,4767],{"class":316,"line":342},[314,4768,339],{"emptyLinePlaceholder":338},[314,4770,4771,4773,4776,4778,4781,4783,4786],{"class":316,"line":361},[314,4772,2778],{"class":331},[314,4774,4775],{"class":320},"ExceptionHandler",[314,4777,404],{"class":331},[314,4779,4780],{"class":354},"OptimisticLockException",[314,4782,1281],{"class":331},[314,4784,4785],{"class":354},"class",[314,4787,2752],{"class":331},[314,4789,4790,4792,4794,4796,4798,4800,4803],{"class":316,"line":376},[314,4791,398],{"class":320},[314,4793,2759],{"class":320},[314,4795,2762],{"class":331},[314,4797,1060],{"class":320},[314,4799,2768],{"class":331},[314,4801,4802],{"class":401}," handleOptimisticLock",[314,4804,692],{"class":331},[314,4806,4807,4810],{"class":316,"line":390},[314,4808,4809],{"class":320},"        OptimisticLockException",[314,4811,4812],{"class":410}," e\n",[314,4814,4815,4818],{"class":316,"line":395},[314,4816,4817],{"class":331},"    )",[314,4819,332],{"class":331},[314,4821,4822],{"class":316,"line":431},[314,4823,339],{"emptyLinePlaceholder":338},[314,4825,4826,4828],{"class":316,"line":447},[314,4827,505],{"class":504},[314,4829,4830],{"class":320}," ResponseEntity\n",[314,4832,4833,4836,4839,4841,4844,4846,4849],{"class":316,"line":461},[314,4834,4835],{"class":331},"            .",[314,4837,4838],{"class":401},"status",[314,4840,404],{"class":331},[314,4842,4843],{"class":354},"HttpStatus",[314,4845,1281],{"class":331},[314,4847,4848],{"class":354},"CONFLICT",[314,4850,2752],{"class":331},[314,4852,4853,4855,4858,4860,4862,4865,4867],{"class":316,"line":475},[314,4854,4835],{"class":331},[314,4856,4857],{"class":401},"body",[314,4859,404],{"class":331},[314,4861,1244],{"class":331},[314,4863,4864],{"class":1240},"다른 사용자가 먼저 데이터를 수정했습니다.",[314,4866,1244],{"class":331},[314,4868,1261],{"class":331},[314,4870,4871],{"class":316,"line":481},[314,4872,478],{"class":331},[314,4874,4875],{"class":316,"line":486},[314,4876,586],{"class":331},[282,4878,4879],{},"HTTP 상태 코드로는 일반적으로:",[304,4881,4884],{"className":4882,"code":4883,"language":1134,"meta":309},[1132],"409 Conflict\n",[311,4885,4883],{"__ignoreMap":309},[282,4887,4888],{},"를 사용하는 것을 고려할 수 있다.",[659,4890],{},[277,4892,4894],{"id":4893},"_36-충돌이-발생하면-무조건-실패시켜야-할까","🔄 36. 충돌이 발생하면 무조건 실패시켜야 할까?",[282,4896,4897],{},"항상 그런 것은 아니다.",[282,4899,4900],{},"데이터의 성격에 따라 처리 전략을 선택할 수 있다.",[282,4902,4903],{},"예를 들어 단순한 프로필 수정이라면:",[304,4905,4908],{"className":4906,"code":4907,"language":1134,"meta":309},[1132],"충돌 발생\n\n↓\n\n최신 데이터 다시 조회\n\n↓\n\n사용자에게 다시 입력 요청\n",[311,4909,4907],{"__ignoreMap":309},[282,4911,4912],{},"하는 방식이 적합할 수 있다.",[282,4914,4915],{},"반면 자동으로 합칠 수 있는 데이터라면:",[304,4917,4920],{"className":4918,"code":4919,"language":1134,"meta":309},[1132],"A의 변경\n\n+\n\nB의 변경\n\n↓\n\nMerge\n\n↓\n\n최종 데이터\n",[311,4921,4919],{"__ignoreMap":309},[282,4923,4924],{},"방식을 사용할 수도 있다.",[282,4926,4927],{},"하지만 모든 데이터를 자동으로 Merge하는 것은 위험하다.",[282,4929,4930],{},"특히 금액, 재고, 수량처럼 비즈니스 규칙이 중요한 데이터는 충돌 발생 시 명확하게 처리하는 것이 좋다.",[659,4932],{},[277,4934,4936],{"id":4935},"_37-금액-데이터와-동시성-제어","💰 37. 금액 데이터와 동시성 제어",[282,4938,4939],{},"가계부나 금융 서비스에서는 동시성 문제가 특히 중요하다.",[282,4941,1587],{},[304,4943,4946],{"className":4944,"code":4945,"language":1134,"meta":309},[1132],"현재 잔액 = 100,000원\n",[311,4947,4945],{"__ignoreMap":309},[282,4949,4950],{},"A가:",[304,4952,4955],{"className":4953,"code":4954,"language":1134,"meta":309},[1132],"20,000원 사용\n",[311,4956,4954],{"__ignoreMap":309},[282,4958,4959],{},"B가:",[304,4961,4964],{"className":4962,"code":4963,"language":1134,"meta":309},[1132],"30,000원 사용\n",[311,4965,4963],{"__ignoreMap":309},[282,4967,4968],{},"하는 상황에서 단순 조회 후 저장을 사용하면 Lost Update가 발생할 수 있다.",[282,4970,4971],{},"따라서 단순히:",[304,4973,4975],{"className":306,"code":4974,"language":308,"meta":309,"style":309},"balance -= amount;\n",[311,4976,4977],{"__ignoreMap":309},[314,4978,4979,4982,4985,4988],{"class":316,"line":317},[314,4980,4981],{"class":354},"balance ",[314,4983,4984],{"class":331},"-=",[314,4986,4987],{"class":354}," amount",[314,4989,358],{"class":331},[282,4991,4992],{},"만 생각할 것이 아니라,",[304,4994,4997],{"className":4995,"code":4996,"language":1134,"meta":309},[1132],"동시에 변경되는가?\n\n↓\n\n변경 충돌을 어떻게 감지할 것인가?\n\n↓\n\n충돌하면 어떻게 처리할 것인가?\n",[311,4998,4996],{"__ignoreMap":309},[282,5000,5001],{},"까지 함께 설계해야 한다.",[282,5003,5004,5005,5008],{},"특히 금융 데이터처럼 ",[289,5006,5007],{},"절대 유실되면 안 되는 데이터","라면 낙관적 락뿐만 아니라 원자적 UPDATE, 비관적 락, 데이터베이스 제약조건 등 여러 방법을 함께 검토해야 한다.",[659,5010],{},[277,5012,5014],{"id":5013},"_38-record와-version을-함께-정리하면","🧩 38. Record와 @Version을 함께 정리하면",[282,5016,5017,5018,5020],{},"Record와 ",[311,5019,609],{},"은 서로 전혀 다른 기능처럼 보이지만 백엔드 애플리케이션을 설계할 때 함께 이해하면 좋은 개념이다.",[282,5022,5023],{},"Record는:",[304,5025,5028],{"className":5026,"code":5027,"language":1134,"meta":309},[1132],"데이터를 어떻게 표현하고 전달할 것인가?\n",[311,5029,5027],{"__ignoreMap":309},[282,5031,5032],{},"에 대한 문제를 해결한다.",[304,5034,5036],{"className":306,"code":5035,"language":308,"meta":309,"style":309},"public record FarmResponse(\n    Long id,\n    String name,\n    Long level\n) {\n}\n",[311,5037,5038,5049,5057,5065,5072,5078],{"__ignoreMap":309},[314,5039,5040,5042,5044,5047],{"class":316,"line":317},[314,5041,321],{"class":320},[314,5043,687],{"class":320},[314,5045,5046],{"class":327}," FarmResponse",[314,5048,692],{"class":331},[314,5050,5051,5053,5055],{"class":316,"line":335},[314,5052,697],{"class":320},[314,5054,355],{"class":354},[314,5056,702],{"class":331},[314,5058,5059,5061,5063],{"class":316,"line":342},[314,5060,707],{"class":320},[314,5062,371],{"class":354},[314,5064,702],{"class":331},[314,5066,5067,5069],{"class":316,"line":361},[314,5068,697],{"class":320},[314,5070,5071],{"class":354}," level\n",[314,5073,5074,5076],{"class":316,"line":376},[314,5075,426],{"class":331},[314,5077,332],{"class":331},[314,5079,5080],{"class":316,"line":390},[314,5081,586],{"class":331},[282,5083,5084,5085,5087],{},"반면 ",[311,5086,609],{},"은:",[304,5089,5092],{"className":5090,"code":5091,"language":1134,"meta":309},[1132],"여러 요청이 동시에 데이터를 변경할 때\n어떻게 충돌을 감지할 것인가?\n",[311,5093,5091],{"__ignoreMap":309},[282,5095,5032],{},[304,5097,5099],{"className":306,"code":5098,"language":308,"meta":309,"style":309},"@Entity\npublic class Farm {\n\n    @Id\n    private Long id;\n\n    private String name;\n\n    @Version\n    private Long version;\n}\n",[311,5100,5101,5107,5117,5121,5127,5137,5141,5151,5155,5161,5171],{"__ignoreMap":309},[314,5102,5103,5105],{"class":316,"line":317},[314,5104,2737],{"class":331},[314,5106,3090],{"class":320},[314,5108,5109,5111,5113,5115],{"class":316,"line":335},[314,5110,321],{"class":320},[314,5112,324],{"class":320},[314,5114,3701],{"class":327},[314,5116,332],{"class":331},[314,5118,5119],{"class":316,"line":342},[314,5120,339],{"emptyLinePlaceholder":338},[314,5122,5123,5125],{"class":316,"line":361},[314,5124,2778],{"class":331},[314,5126,3111],{"class":320},[314,5128,5129,5131,5133,5135],{"class":316,"line":376},[314,5130,345],{"class":320},[314,5132,351],{"class":320},[314,5134,355],{"class":354},[314,5136,358],{"class":331},[314,5138,5139],{"class":316,"line":390},[314,5140,339],{"emptyLinePlaceholder":338},[314,5142,5143,5145,5147,5149],{"class":316,"line":395},[314,5144,345],{"class":320},[314,5146,368],{"class":320},[314,5148,371],{"class":354},[314,5150,358],{"class":331},[314,5152,5153],{"class":316,"line":431},[314,5154,339],{"emptyLinePlaceholder":338},[314,5156,5157,5159],{"class":316,"line":447},[314,5158,2778],{"class":331},[314,5160,3755],{"class":320},[314,5162,5163,5165,5167,5169],{"class":316,"line":461},[314,5164,345],{"class":320},[314,5166,351],{"class":320},[314,5168,3764],{"class":354},[314,5170,358],{"class":331},[314,5172,5173],{"class":316,"line":475},[314,5174,586],{"class":331},[282,5176,5177],{},"전체 구조를 보면 다음과 같이 이해할 수 있다.",[304,5179,5182],{"className":5180,"code":5181,"language":1134,"meta":309},[1132],"                    Database\n                       │\n                       ▼\n                 JPA Entity\n                       │\n                @Version 관리\n                       │\n                       ▼\n                    Service\n                       │\n              비즈니스 로직 수행\n                       │\n                       ▼\n                 Record DTO\n                       │\n                       ▼\n                   Controller\n                       │\n                       ▼\n                    Client\n",[311,5183,5181],{"__ignoreMap":309},[282,5185,5186],{},"즉,",[304,5188,5191],{"className":5189,"code":5190,"language":1134,"meta":309},[1132],"Entity\n\n→ 데이터베이스와 상태를 관리\n\nRecord\n\n→ 데이터를 안전하고 간결하게 전달\n\n@Version\n\n→ 동시에 수정되는 데이터의 충돌을 감지\n",[311,5192,5190],{"__ignoreMap":309},[282,5194,5195],{},"라는 역할 차이가 있다.",[659,5197],{},[277,5199,5201],{"id":5200},"마무리","🎯 마무리",[282,5203,595,5204,5206],{},[311,5205,598],{},"는 단순히 \"코드를 줄여주는 문법\"으로만 이해하면 아쉽다.",[282,5208,1881,5209,603],{},[289,5210,5211],{},"데이터를 표현하는 객체의 의도를 명확하게 만들고, 불변 데이터 구조를 간결하게 작성할 수 있도록 해주는 Java의 기능",[282,5213,5214],{},"특히 다음과 같은 객체에서 활용하기 좋다.",[304,5216,5219],{"className":5217,"code":5218,"language":1134,"meta":309},[1132],"Request DTO\n\nResponse DTO\n\nValue Object\n\nAPI 데이터 전달 객체\n\n읽기 전용 데이터 객체\n",[311,5220,5218],{"__ignoreMap":309},[282,5222,5223,5224,5226,5227,603],{},"반면 JPA의 ",[311,5225,609],{},"은 ",[289,5228,5229],{},"여러 사용자가 같은 데이터를 동시에 수정하는 상황에서 변경 충돌을 감지하기 위한 기능",[282,5231,5232],{},"핵심 동작은 다음과 같다.",[304,5234,5237],{"className":5235,"code":5236,"language":1134,"meta":309},[1132],"조회 당시 version = 3\n\n        ↓\n\n다른 사용자가 수정\n\n        ↓\n\nDB version = 4\n\n        ↓\n\n내가 version = 3 조건으로 UPDATE\n\n        ↓\n\n수정 대상 없음\n\n        ↓\n\nOptimisticLockException\n",[311,5238,5236],{"__ignoreMap":309},[282,5240,5241],{},"결국 두 개념을 한 문장으로 정리하면 다음과 같다.",[1005,5243,5244],{},[282,5245,5246],{},[289,5247,5248,5249,5251],{},"Record는 데이터를 어떻게 안전하고 명확하게 표현할 것인가에 대한 도구이고, ",[311,5250,609],{},"은 여러 요청이 같은 데이터를 수정할 때 변경 충돌을 어떻게 감지할 것인가에 대한 도구다.",[282,5253,5254],{},"그리고 이 둘을 제대로 이해하기 위해서는 함께 알아두면 좋은 개념들이 있다.",[304,5256,5259],{"className":5257,"code":5258,"language":1134,"meta":309},[1132],"Record\n ↓\n불변 객체\n ↓\nValue Object\n ↓\nDTO\n ↓\nJPA Entity\n ↓\n영속성 컨텍스트\n ↓\nDirty Checking\n ↓\nTransaction\n ↓\n동시성 문제\n ↓\nLost Update\n ↓\n비관적 락\n ↓\n낙관적 락\n ↓\n@Version\n ↓\nOptimisticLockException\n",[311,5260,5258],{"__ignoreMap":309},[282,5262,5263,5264,5266],{},"특히 실무에서는 단순히 ",[311,5265,609],{},"을 붙이는 것에서 끝내지 않고,",[304,5268,5271],{"className":5269,"code":5270,"language":1134,"meta":309},[1132],"충돌을 어떻게 감지할 것인가?\n\n↓\n\n충돌이 발생하면 어떤 예외를 반환할 것인가?\n\n↓\n\n사용자에게 재시도를 요구할 것인가?\n\n↓\n\n자동으로 재시도할 수 있는 데이터인가?\n\n↓\n\n비관적 락이 더 적합한가?\n\n↓\n\nDB의 원자적 연산으로 해결할 수 있는가?\n",[311,5272,5270],{"__ignoreMap":309},[282,5274,5275],{},"까지 함께 고민해야 한다.",[282,5277,5278,5279,299],{},"결국 동시성 제어는 특정 어노테이션 하나를 사용하는 문제가 아니라 ",[289,5280,5281],{},"데이터의 특성과 비즈니스 규칙에 맞는 일관성 전략을 선택하는 문제",[282,5283,5284],{},"Record 역시 마찬가지다.",[282,5286,5287],{},"모든 클래스를 Record로 바꾸는 것이 목적이 아니라,",[304,5289,5292],{"className":5290,"code":5291,"language":1134,"meta":309},[1132],"상태를 변경해야 하는 Entity\n        VS\n값을 표현하고 전달하는 DTO\n",[311,5293,5291],{"__ignoreMap":309},[282,5295,5296],{},"를 구분하고 각각의 역할에 맞는 객체를 사용하는 것이 중요하다.",[282,5298,5299],{},"Java 백엔드에서 객체 설계를 할 때는 결국:",[304,5301,5304],{"className":5302,"code":5303,"language":1134,"meta":309},[1132],"\"이 객체는 무엇을 표현하는가?\"\n\n\"이 객체의 상태는 변경되어야 하는가?\"\n\n\"이 데이터는 어디까지 전달되는가?\"\n\n\"동시에 여러 사용자가 변경할 수 있는가?\"\n\n\"변경 충돌이 발생하면 어떻게 처리해야 하는가?\"\n",[311,5305,5303],{"__ignoreMap":309},[282,5307,5308],{},"라는 질문을 먼저 생각하는 것이 중요하다.",[282,5310,5311,1738,5313,5315,5316,5319],{},[311,5312,598],{},[311,5314,609],{},"은 서로 다른 문제를 해결하지만, 이러한 ",[289,5317,5318],{},"데이터의 역할과 상태, 그리고 동시성에 대한 사고방식","을 익히는 데 좋은 출발점이 된다.",[5321,5322,5323],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":309,"searchDepth":335,"depth":335,"links":5325},[5326,5327,5328],{"id":2523,"depth":342,"text":2524},{"id":2530,"depth":342,"text":2531},{"id":2537,"depth":342,"text":2538},"2026-08-10","Java의 record 객체가 등장한 이유와 불변 데이터 객체의 특징, 장단점, DTO 활용법부터 JPA의 @Version을 이용한 낙관적 락과 동시성 제어 원리까지 정리합니다.","md",null,{"tags":5334,"image":5342},[1632,2301,5335,5336,5337,609,5338,5339,5340,5341],"불변 객체","DTO","JPA","낙관적 락","동시성 제어","Optimistic Lock","Spring Boot","\u002Fimages\u002FjavaSpring-thumbnail.png",{"icon":20},{"title":5345,"description":5346},"Java Record와 @Version 완벽 정리 - 불변 객체와 낙관적 락 이해하기","Java record의 특징과 사용 이유, 일반 클래스와의 차이, DTO 활용법, JPA @Version을 이용한 낙관적 락과 동시성 제어 원리를 예제와 함께 정리합니다.","dHoM9cgPsVpzdxFq_v4Z7o2_SgOCGPTFaNxA6xz8cvk",[5349,5351],{"title":17,"path":18,"stem":19,"description":5350,"icon":20,"children":-1},"Spring AI가 제공하는 AI 서비스 추상화 구조와 LLM, Vector DB 연동 방식, 실무에서 활용되는 주요 개발 패턴 정리",{"title":26,"path":27,"stem":28,"description":5352,"icon":20,"children":-1},"JPA의 @PersistenceContext가 무엇을 의미하는지부터 EntityManager와 영속성 컨텍스트의 관계, 엔티티 생명주기, Dirty Checking, Flush, Transaction과의 관계까지 정리합니다.",1789947615958]