<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>/usr/cs/study.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 22 Dec 2025 06:33:21 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>/usr/cs/study.log</title>
            <url>https://velog.velcdn.com/images/my_sql/profile/8d1ae4d3-86c2-4c8a-8f16-9d0b8d5af372/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. /usr/cs/study.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/my_sql" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Transactional readOnly는 뭐하는 역할일까]]></title>
            <link>https://velog.io/@my_sql/spring-study3</link>
            <guid>https://velog.io/@my_sql/spring-study3</guid>
            <pubDate>Mon, 22 Dec 2025 06:33:21 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-발생">문제 발생</h1>
<p>프로젝트를 진행하는 과정에서 H2 데이터베이스 환경에서 해당 코드를 작성했을 때 정상적으로 쓰기 작업이 수행되었다</p>
<pre><code class="language-java">@Transactional(readOnly = true)
public WalkResponse getWalk(Member member) {
  Walk walk = walkRepository.findByMember(member)
    .orElseThrow(() -&gt; new DataNotFoundException(&quot;해당되는 id의 산책 경로가 존재하지 않습니다.&quot;));

  updateSlopes(walk);

  return new WalkResponse(
    walk.getId(),
    walk.getMaxSlope(),
    walk.getAvgOfSlope(),
    walk.getUpdateDateTime(),
    walk.getWalk()
  );
}

@Transactional
public void updateSlopes(Walk walk) {
  MapResponse mapResponse = mapService.getFitness(walk.getWalk().coordinates());

  walk.updateSlopes(
    mapResponse.maxSlope(),
    mapResponse.avgOfSlope(),
    LocalDateTime.now()
  );
}</code></pre>
<p>알고 있는 정보에 따르면 <code>@Transactional(readOnly = true)</code> 로 설정을 하고 그 내부에서 메서드를 호출했을 때에는 <code>readOnly = true</code> 속성이 전파된다.</p>
<p>그렇기 때문에 flush가 되지 않고 쓰기 작업은 이루어질 수 없다.</p>
<h1 id="왜-그럴까">왜 그럴까</h1>
<p><img src="https://velog.velcdn.com/images/my_sql/post/b7477560-f88e-420d-a0a2-65e117247172/image.png" alt=""></p>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html">공식문서 내용</a>이다. readOnly 자체는 쓰기 작업을 막는 역할이 아니라 단지 hint를 제공하는 것이라는 내용이다.</p>
<p>그럼 이렇게 힌트를 던져줬을 때 다양한 DB 벤더사들은 이걸 어떻게 처리하느냐가 다를 수 있다는 것이다.</p>
<p>h2 database의 <a href="https://github.com/h2database/h2database/blob/master/h2/src/main/org/h2/jdbc/JdbcConnection.java">구현 내용</a>이다.</p>
<pre><code class="language-java">/**
 * According to the JDBC specs, this setting is only a hint to the database
 * to enable optimizations - it does not cause writes to be prohibited.
 *
 * @param readOnly ignored
 * @throws SQLException if the connection is closed
 */
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
    try {
        if (isDebugEnabled()) {
            debugCode(&quot;setReadOnly(&quot; + readOnly + &#39;)&#39;);
        }
        checkClosed();
    } catch (Exception e) {
        throw logAndConvert(e);
    }
}</code></pre>
<p>readOnly 힌트를 무시한다고 되어있다.</p>
<p>그래서 코드를 조금 수정을 해보았다.</p>
<pre><code class="language-java">@Transactional(readOnly = true)
public WalkResponse getWalk(Member member) {
  Walk walk = walkRepository.findByMember(member)
    .orElseThrow(() -&gt; new DataNotFoundException(&quot;해당되는 id의 산책 경로가 존재하지 않습니다.&quot;));

  updateSlopes(walk);

  return new WalkResponse(
    walk.getId(),
    walk.getMaxSlope(),
    walk.getAvgOfSlope(),
    walk.getUpdateDateTime(),
    walk.getWalk()
  );
}

private void updateSlopes(Walk walk) {
  MapResponse mapResponse = mapService.getFitness(walk.getWalk().coordinates());

  walk.updateSlopes(
    mapResponse.maxSlope(),
    mapResponse.avgOfSlope(),
    LocalDateTime.now()
  );
}</code></pre>
<p>이런 식으로 코드를 고쳐봤을 때에도 API를 호출할 때마다 업데이트가 정상적으로 이루어졌다.</p>
<h1 id="정리하자면">정리하자면,</h1>
<p><code>@Transactional</code> 의 <code>readOnly</code> 옵션은 쓰기 작업을 막는 역할이 아니라 힌트를 제공하는 역할인데, 벤더사마다 해당 힌트를 처리하는 방식이 다르다.</p>
<p>현재 개발 및 테스트 환경에서 사용하는 h2 데이터베이스의 경우에는 해당 힌트를 무시한다고 적혀있다.</p>
<p>테스트 할 때에는 업데이트 간격을 제거하고 업데이트가 정상적으로 작동하는지만 보는데, 간격을 설정하고 배포를 했을 때에는 어떻게 될 지 모르는 일이었다.</p>
<p>특히나 로컬에서는 h2를 사용하고 배포 환경에서는 MySQL을 사용하기 때문에 이러한 문제는 더더욱 조심해야겠다고 생각했다.</p>
<p>+) 실제로 실행을 해보지는 않았지만, 찾아본 바로는 MySQL 환경에서는 해당 작업이 <strong>실패</strong>한다고 한다.</p>
<p>+) 이 부분에 대해서 멘토님께 조언을 구해보았는데, 개발 환경이나 운영 환경이나 같은 DB 벤더사를 사용하는 것이 최선이라고 하셨다. RDS를 사용하는데 public으로 돌린 경우에 비용문제가 발생해서 운영 환경에서는 MySQL, 개발 환경에서는 H2를 사용하게 된게 화근이었던 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오테크캠퍼스 3기 회고]]></title>
            <link>https://velog.io/@my_sql/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%85%8C%ED%81%AC%EC%BA%A0%ED%8D%BC%EC%8A%A4-3%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@my_sql/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%85%8C%ED%81%AC%EC%BA%A0%ED%8D%BC%EC%8A%A4-3%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 22 Dec 2025 06:29:21 GMT</pubDate>
            <description><![CDATA[<p>4월부터 11월까지 장정 7개월 정도의 노력이 끝을 맺었다.
자소서를 써보고 코딩테스트를 본 것은 이번이 처음이기 때문에 시작부터 조금 떨렸는데, 무사히 마쳐서 다행이라는 생각이 든다...
한 달이 지난 시점에서 더 늦어지기 전에 회고를 작성해보고자 한다.</p>
<h1 id="지원">지원</h1>
<p>카테캠에 대해서 알게 된 것은 3학년 초였다. 교내 엘레베이터 옆이나 남부운동장 쪽 현수막이나... 카테캠 지원에 대한 정보가 눈에 많이 보였다. 이 때는 사실 코테나 프로젝트나 준비해둔 것이 많이 없었기 때문에 자신이 없어서 지원을 하지는 못했다. 내년에도 기회가 있지 않을까? 라는 생각을 하면서 여름방학 때 팀 프로젝트를 진행했었고, 4학년이 되었을 때 카테캠에 지원을 하게 되었다.</p>
<p>서류 마감 1-2주 전에 설명회를 했다. 커리큘럼에 관한 소개도 있었고 서류 관련된 팁도 있었나? 그랬던 것 같다.</p>
<p>지원하기에 앞서 자소서 문항부터 조금 막혔다. 대학도 정시로 왔었고, 자소서를 써본 것이라고는 고등학교 진학을 위해 중학교때 써본 것이 전부였다... <del>취업을 하려면 슬슬 써야될텐데</del>
여기서 설명회를 듣길 잘했다는 생각도 들었고 무엇보다도 팀 프로젝트 경험이 빛을 발했던 것 같았다. 프로젝트를 진행하면서 팀원들과 의견이 맞지 않았거나, 중간에 팀원 몇명이 도망갔던 경험들, 그로 인해서 소통이 중요하다는 것을 느꼈음을 최대한 자소서에 녹여냈다.</p>
<p><img src="https://velog.velcdn.com/images/my_sql/post/5c4e6992-a300-42b7-a10f-86b3b717316d/image.png" alt=""></p>
<p>서류는 통과했다. 에타를 보니까 1차는 대부분 합격을 한 것 같았다. 그렇기 때문에 코딩테스트는 조금 더 부담이 됐다.</p>
<p>코딩테스트는 늘 자신이 없었기 때문에 어쩔 수 없이 며칠 동안 프로그래머스 lv1 위주로 풀어봤는데 조금 도움이 되었던 것 같다. 기억으로는 앨리스에서 코딩테스트를 봤다. 가끔가끔 풀던 PS는 백준, 프로그래머스로 IDE에 따로 코드를 쳐서 옮겼기 때문에 자동완성에 익숙해져있었는데, 코딩테스트를 보는 환경은 자동완성이 지원되지 않았다. 그렇기 때문에 거의 메모장 코딩으로 연습을 했던 것 같다.</p>
<p>4문제가 나왔고 첫 번째 문제는 엄청 쉬웠다.
두 번째 문제와 세 번째 문제는 뭔가 비슷한 느낌이었는데, 두 번째 문제는 풀고 세 번째 문제는 테스트케이스 두 개에서 시간초과가 났었다. 네 번째 문제는 사실 손도 못댔다. 코딩테스트를 평소에도 열심히 준비할걸 이라는 후회가 조금 있었다...</p>
<p>코테 결과에는 조금 자신이 없었기 때문에 기대 반 긴장 반으로 며칠을 보냈다.</p>
<p>결과 발표가 4월 1일이었던 것 같은데, 학교 가는 지하철을 기다리는 중에 결과가 나왔다는 문자가 왔다.</p>
<p><img src="https://velog.velcdn.com/images/my_sql/post/cd0b1e18-dc79-49c6-b328-eea0974fe5ca/image.png" alt=""></p>
<p>메일함을 보니 합격이었다...! 4학년이고 슬슬 취업을 앞둔 상황이기 때문에 이렇게 좋은 기회가 찾아왔다는 것에 크게 감사했다.</p>
<h1 id="1단계">1단계</h1>
<p>1단계는 기본적인 강의 위주로 진행되었다. 각 트랙(FE, BE)마다 강의가 지급됐고 매주 일정 분량 만큼의 강의를 듣고 공부 내용을 정리해야됐다. 중간 점검용으로 과제가 두 번 나왔다. 첫 번째 과제는 Java를 이용해서 계산기를 만드는 과제였고, 두 번째 과제는 Spring을 이용해서 API를 만드는 과제였다.
기본 과제는 크게 어렵지 않았다. 심화 과제는 난이도가 조금 있었는데, 강의를 잘 들었으면 충분히 풀 수 있는 난이도였다.</p>
<p><img src="https://velog.velcdn.com/images/my_sql/post/53f3cd2d-0fd5-473b-8e92-673fb3954981/image.png" alt=""></p>
<p>노션 스페이스에 개인별로 페이지가 나뉘고 거기에 공부하는 내용을 정리해야했다. 매 주마다 질문 같은 것들이 생겨서 적어놓으면 멘토님께서 답변을 달아주셨다.</p>
<p>1단계가 사실 제일 힘들었던 것 같았다. 1학기에는 15학점을 들었는데, 매 주 많은 강의를 듣고 공부한 내용을 정리하는 것이 생각보다 많이 빡셌다. <del>사실 2단계, 3단계를 진행하다보니까 왜 이렇게 1단계를 설계하셨는지 이해하게 됐다...</del></p>
<p>머리에 집어넣어야 하는 내용도 조금 많기 때문에, SQL, Java 정도는 미리 조금이라도 공부해놓고 가는 것을 추천한다.</p>
<p>1단계가 거의 마무리 됐을 시점에는 점검용으로 테스트를 봤다. 얼마나 강의를 성실하게 들었는지 확인하는 용도라고 하셨는데, 매주 강의를 전부 듣는 것이 벅찼던 입장으로서는 조금 쫄렸다. 근데 문제를 보니까 진짜 강의를 듣기만 했으면 못 풀 수가 없는 문제였기 때문에 큰 문제 없이 테스트에 통과했다.</p>
<p>중간중간 라이브 특강도 진행됐다. 특강은 4개가 있었는데, 깃과 같은 기술적인 부분 뿐만 아니라 테크니컬 라이팅, 그리고 앞서 카테캠을 수료하신 개발자 분께서 하시는 동기부여 강의, 졸업생이신 선배가 질문에 답변해주시는 네트워킹 시간 등으로 구성됐다.</p>
<p>특강 전부 lms에 따로 업로드를 해주시기 때문에 다시 듣고 싶은 강의라면 언제든 다시 들을 수 있었다! <del>사실 네트워킹 시간은 졸프 구현을 하다가 리마인드 알람 확인을 못해서 제때 참여를 못하고 lms로 뒤늦게 수강했다.</del></p>
<h1 id="2단계">2단계</h1>
<p>2단계는 클론 코딩으로 진행했다. 백엔드는 카카오 선물하기 클론코딩을 진행했는데, 매주 월요일, 목요일에는 실시간 강의가 있고, 해당 강의에 참여를 해야한다. 백엔드 멘토님께서 외국에 계셔서 시차 때문인지 강의 시간은 아침으로 고정이 되어있었다. 방학 때 아침에 일어나는 것은 많이 힘든 일이었지만 그래도 일찍 일어나니 하루가 길어지는 느낌이라 좋았다...
처음에는 기본적인 API 설계에 관해서 얘기를 해주셨고 중후반 부터는 테스트 코드에 대해서 많이 다뤄주셨다.</p>
<p>정해진 날짜까지 과제 구현을 하고, PR을 올려서 담당 멘토님께 코드 리뷰를 받아야 했다. 구현 자체는 1단계 강의를 잘 수강했다면 어렵지 않게 해낼 수 있는 정도다. 다만, JPA 위주로 써왔기 때문에 JDBC로 구현하는 것은 조금 어려웠다...ㅎㅎ</p>
<p>미션은 크게 4개로 나눠져있었고, 각각 3-4단계로 나눠져있어 각 단계마다 PR을 올리고, 크게 나뉜 미션마다 담당 멘토님이 바뀌었다. 멘토님들마다 관점이 다르시기 때문에 여러 멘토님들의 의견을 들어보고 어떻게 판단할지 스스로 결정하는 힘을 기를 수 있었다.</p>
<p>멘토님들 모두 다 친절하셨고, 생각해보지도 못했던 부분에서 질문을 남겨주시거나 수정 요청을 주셔서 이 때 진짜 크게 도움이 되었다. 기계적으로 코드를 짜는 경향이 있었는데, 이런 습관이 꼬집히는 기분이었다. 덕분에 3단계나 새로 프로젝트를 시작한 지금에서도 코드 짤 때 어떤 식으로 짜는 것이 좋을지 먼저 생각해보는 태도를 가질 수 있게 되었다.</p>
<p>2단계 때도 어김없이 특강은 있었는데, 1단계 만큼 많지는 않았고 AI 관련 특강과 aws 강의가 지급되었다. aws 강의는 2단계 과정 중 배포를 위해 지급을 해주신 걸로 기억을 한다.</p>
<h3 id="팀빌딩-워크샵">팀빌딩 워크샵</h3>
<p>그리고 가장 재밌었던 워크샵!</p>
<p>경북대, 부산대, 전남대는 여수로, 강원대와 충남대는 용인으로 갔다. 이렇게 나뉜 이유는 아마 거리 때문이지 않을까 하는 추측이다.
1박 2일로 용인 카카오 AI 캠퍼스에서 지냈는데, 무거운 날은 아니었고, 특강 두 개에 나머지는 거의 팀활동이었다. 우선 건물 자체도 너무 좋았고 무엇보다도 밥이 진짜 맛있었다...! 중간에 빵도 간식으로 나왔었고 우유나 커피는 마시고 싶을 때 가져다가 마시는 식이었다. 식당 안에는 카페도 있었는데 약간 얼그레이 맛 나는 라떼같은걸 마신 기억이 있는데 이게 진짜 맛있었다.</p>
<p><img src="https://velog.velcdn.com/images/my_sql/post/86331efd-91db-41a6-9892-04f647c2422a/image.png" alt=""></p>
<p>이렇게 생긴 웰컴 키트도 받았다.</p>
<p><img src="https://velog.velcdn.com/images/my_sql/post/3a76f310-5fdf-455f-a864-54b60b278a7d/image.png" alt=""></p>
<p>안에 펜, 스티커, 노트, 보조배터리, 에코백, 텀블러가 들어있었다. 특히 저 텀블러는 매니저님이 디자인하신 걸로 기억하는데 정말 예쁘게 디자인해주셔서 감사했다.
웰컴 키트 외에도 집에서 입기 편한 단체 티셔츠를 주셨고, 그걸로 환복한 뒤에 활동을 진행했다.</p>
<p>우리 학교가 워낙 크고 학과도 한 학년에 100명 넘게 있어 큰 편이라 다같이 친해질 기회가 많이 없었는데, 학교 별로 팀을 짜주신 덕에 이 기회에 많이 소통을 할 수 있게 돼서 좋았다.</p>
<p>이렇게 소중한 기회를 만들어주신 운영진 분들께 다시 한 번 감사의 말씀을 드리고 싶다.</p>
<h1 id="3단계">3단계</h1>
<p>3단계는 진짜 팀프로젝트의 시작이었다. 기획 강의도 있었고, 피그마 UI/UX 강의도 있었고... 그리고 기획 단계기 때문에 팀원들과 회의시간도 자주 있었고 길기도 했다.</p>
<h3 id="아이디어-톤">아이디어 톤</h3>
<p>8월 초에는 아이디어톤이 있었다. 이 때는 모든 학교가 용인으로 모였고, 무려 무박 2일이었다.
이 때도 똑같이 단체 티셔츠가 배부되었고, 냉방 때문에 작은 무릎 담요도 나눠주셨다. 어김없이 밥은 맛있었다. 중간에 23시인가 0시 쯤에는 야식으로 햄버거도 나눠주셨다.</p>
<p>이 때는 워크샵과는 다르게 기획에 집중을 해야했기 때문에 거의 하루 종일 주제 구체화를 하고, 발표자료를 만드느라 바빴다. 오후에서 저녁쯤에는 실제로 2단계 멘토를 담당하셨던 몇몇 분들이 오셔서 팀들에게 기술적이나 기획적인 리뷰를 남겨주시기도 했다.</p>
<p>새벽 3시쯤에는 옆 테이블 팀과 피어리뷰를 진행했고, 아침 8시인가에 발표를 하고 마무리 지었던 것 같았다. 토너먼트 형식으로 각 그룹마다 발표를 하고 거기서 1등을 한 팀이 최종 발표를 진행하는 형식이었다.</p>
<p>살면서 처음 해보는 아이디어톤이었지만 팀원들과 운영진 분들이 잘 이끌어주신 덕분에 무사히 마무리 지은 것 같다. </p>
<h3 id="팀-프로젝트">팀 프로젝트</h3>
<p>스프린트 식으로 진행됐다. 스프린트 0에는 기능 구체화, 스프린트 1부터 본격적인 개발을 시작하고 스프린트 3까지 있었다.</p>
<p>스프린트 1에는 주요 기능을 구현하고 스프린트 2에는 배포를 진행했다. 그리고 매주 금요일까지 develop 브랜치에서 main으로 PR을 올리고, 멘토님이 코드 리뷰를 남겨주신다. 그리고 주말에는 코드 리뷰를 토대로 주말 동안 수정을 한다. 그리고 매 스프린트 마다 멘토님과 기술 멘토링을 진행한다. 팀원 각자 사전에 질문을 정해오고 멘토님께서 멘토링 시간에 답변해주시는 방식이다.</p>
<p>스프린트 0은 기획 단계였기 때문에 기술적으로 크게 질문할 부분은 없었고, 협업 위주로 질문을 드렸던 것 같다. 그리고 멘토링 때마다 매번 질문한 것 외에도 자잘한 팁이나, 취업 쪽과 관련해서도 답변을 해주셔서 정말 큰 도움이 되었다.</p>
<p>프로젝트 주제는 사실 팀 별로 천차만별이기 때문에 어떤 프로젝트를 진행할지, 어떤 기능을 구현할지에 따라서도 구현 난이도나 기술 난이도도 달랐다. 우리 팀은 백엔드 측에서는 크게 어렵게 구현할 내용은 없었기 때문에 코드 리뷰를 받을 때 기본적인 부분이나 코드 스타일에 대해 리뷰를 많이 받았다. 기초를 단단히 하는 것에 있어서는 큰 도움이 되었지만, 사실 지금 학생 신분에서 현업에 종사하고 계신 분의 리뷰를 받는 흔치 않은 기회인데 조금 더 난이도있는 기술을 필요로 하는 주제를 채택해서 구현했더라면 어땠을까 하는 아쉬움도 있다.</p>
<p>이런 아쉬움이 있다고 하더라도 과정을 진행하면서 얻은게 엄청 많다고 생각하기 때문에 후회는 안한다.</p>
<p>특히, 협업에 있어서도 크게 배웠다. 2단계까지는 개인 과제로 진행되다가 3단계부터는 협업을 하기 때문에 어쩔 수 없이 팀과 어떻게 소통을 해야하는지 배울 수 있었던 것 같다. 프론트 쪽에 대한 지식은 전무하기 때문에 입장 차이가 있을 수 밖에 없는데, 그 부분에서 어떻게 조율을 해야하는지, 오류가 발생하면 양쪽에서 어떻게 해결해야하는지 이런 부분은 협업을 해보지 않는 이상 경험할 수 없는 것들인데, 다양한 경험을 할 수 있어서 의미있었다.</p>
<h1 id="마치며">마치며</h1>
<p>이전에 프로젝트라고는 불특정한 사람들과 모여서 진행했던 것밖에 없었다. 그 당시에는 스프링에 대한 지식도 없는 채로 맨땅에 헤딩을 하면서 구현을 했기 때문에 많이 부족했었다. 이 과정을 진행하면서 처음에는 양질의 강의를 제공해서 기초 지식부터 쌓고, 그 다음에는 강의에서 배운 것들을 활용한 클론 코딩 프로젝트, 그리고  최종적으로는 협업까지 진행하다보니 크게 성장할 수 있는 계기가 됐던 것 같다.</p>
<p>만약 내가 스프링에 대한 지식도 충분하고 다양한 기술을 활용할 수 있다면 이 과정은 크게 도움이 될 거라고 장담은 못한다. 지원을 함에 있어서도 프로젝트 경험을 크게 보는 것 같지도 않고, 오히려 이 과정을 끝까지 수행할 수 있는 의지가 있는지? 이걸 크게 보는 것 같다.</p>
<p>내가 프론트엔드나 백엔드에 관심이있는데, 어떻게 공부를 시작해야하지? 라는 생각을 가진 학생들이라면 진짜 큰 도움이 될 것이라고 생각한다.</p>
<p>회고를 처음써봐서 이렇게 쓰는게 맞나?싶긴 하지만 다음 기수 지원을 고민하는 사람이 봤을 때 도움이 됐으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭션이 너무 어렵다]]></title>
            <link>https://velog.io/@my_sql/spring-study2</link>
            <guid>https://velog.io/@my_sql/spring-study2</guid>
            <pubDate>Thu, 04 Sep 2025 08:02:49 GMT</pubDate>
            <description><![CDATA[<h2 id="과제-내용">과제 내용</h2>
<p>지난번 포스트랑 동일한 맥락이다.
상품을 주문하는데, 주문이 완료되면 회원의 위시리스트에서 해당 상품 옵션을 제거하는 코드다.</p>
<h3 id="작성했던-코드">작성했던 코드</h3>
<pre><code class="language-java">@TransactionalEventListener
public void handleOrderCreateEvent(OrderCreateEvent event) {
  wishlistRepository.deleteByProductIdAndMemberId(
    event.getProduct().getId(),
    event.getMember().getId()
  );
}</code></pre>
<p>사실 위시리스트에서 지우는건 실패하더라도 주문하기라는 기능에 크게 영향을 미치지 않는다. 그래서 <code>AFTER_COMMIT</code>으로 해당 메서드를 작성해주었다.</p>
<h3 id="문제-발생">문제 발생</h3>
<p>실행된 쿼리들을 보면 이 메서드에서 SELECT만 하고 있고, DELETE를 하지 않는다. DELETE를 하기 전에 SELECT를 한다는 것은 알고 있는데, 이게 SELECT는 되고 왜 DELETE가 안되는지 알 수가 없었다.</p>
<p>더더욱 어려웠던건 뭐냐면, 기존에 간단한 CRUD를 작성할 때 까먹고 <code>@Transactional</code>을 붙이지 않아도 정상적으로 DELETE가 작동했었다. 이거 때문에 더 헷갈린 것 같다.</p>
<h3 id="해결">해결</h3>
<p><code>@Transactional(propagation = Propagation.REQUIRES_NEW)</code>를 달아주니까 해결이 되긴 했다.</p>
<h3 id="">???</h3>
<p>일단 이전 트랜잭션은 커밋이 된 상태다. 즉, 트랜잭션이 커밋이 된 건 알겠는데 예전에 <code>@Transactional</code>을 달지 않은 간단한 CURD 메서드를 실행했을 땐 왜 성공했고, 지금은 왜 실패하는 건지가 너무 이해하기 힘들었다.</p>
<p>우선, SELECT는 트랜잭션이 생성되지 않아도 커넥션만 있으면 실행이 된다. 그리고 기존 DELETE 메서드도 Repository에 <code>@Transactional</code>이 달려 있어서 트랜잭션이 만들어지고 DELETE를 수행한다고 한다.</p>
<p>또 여기서 생긴 궁금증은 결국 저 메서드 안에서도 repository의 delete 메서드를 호출하는 거니까 <code>@Transactional</code>이 달려있는거 아닌가? 근데 왜 얘는 안되고 쟤는 되냐고? 였다.</p>
<p>결론부터 말하자면 그냥 단순히 <code>@Transcational</code>을 달고 있지 않은 메서드는 새로운 트랜잭션을 열지만, <code>@TranscationalEventListener</code>가 달려있는 메서드는 이미 커밋된 트랜잭션에 참여하기 때문에 이런 차이가 생긴 거였다.</p>
<h3 id="결론">결론</h3>
<p>커밋된다고 트랜잭션이 바로 사라지지는 않는다. 그래서 저 코드는 커밋이 된 트랜잭션에 참여만 하지, 커밋되지는 않아서 DB에 반영이 안되는 것이었다.</p>
<p>그래서 명시적으로 트랜잭션을 열어줘야 하는 것이었다. 아니면 BEFORE_COMMIT을 사용해서 커밋 이전에 참여시키면 되지만... 위시리스트에서 삭제하는 부분이 그렇게 중요한 로직은 아니라고 판단해서 그냥 명시적으로 트랜잭션을 열어주는 방법으로 해결을 했다.</p>
<p>며칠동안 이해하기 너무 힘들었다. 사실 AI의 앞뒤 안맞는 설명도 한 몫했지만... 그냥 구글링하고 docs를 보니까 바로 이해가 됐다 😡</p>
<p>영속성 컨텍스트랑 트랜잭션에 대해서는 조금 더 공부를 해봐야겠다. 데이터베이스 강의를 수강하면서 트랜잭션에 관련된 지식은 조금 있었지만 이런 식으로 맞닥뜨리니 머리 아팠고, 영속성 컨텍스트에 대해서 조금만 더 공부를 해봤으면 잘 해결이 될 수 있었을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EventListener와 TransactionalEventListener]]></title>
            <link>https://velog.io/@my_sql/spring-study1</link>
            <guid>https://velog.io/@my_sql/spring-study1</guid>
            <pubDate>Thu, 04 Sep 2025 07:26:34 GMT</pubDate>
            <description><![CDATA[<h2 id="과제-내용">과제 내용</h2>
<p>상품을 주문하는 코드를 작성한다. 이 때 상품이 주문되면 저장되어있던 수량이 차감되고, 만약 주문하는 수량이 저장된 수량보다 크다면 주문에 실패하게 된다.</p>
<h3 id="작성했던-코드">작성했던 코드</h3>
<pre><code class="language-java">@EventListener
public synchronized void handleOrderCreateEvent(OrderCreateEvent event) {
  Option option = optionRepository.findById(event.getOptionId())
    .orElseThrow(() -&gt; new FailedToFindException(&quot;해당 옵션이 존재하지 않습니다.&quot;));

  if (option.getQuantity() &lt; event.getQuantity()) {
    throw new OutOfStockException(&quot;주문할 수 있는 수량을 초과하였습니다.&quot;);
  }
}</code></pre>
<p>상품을 주문했을때 option에 저장되어 있는 수량을 차감하는 코드다.
이 과제를 해결하는 데에 있어서 수량은 중요한 요소라고 생각했다.</p>
<p>상품 주문 요청 -&gt; 수량 확인 -&gt; 주문 진행 </p>
<p>이런 식으로 플로우를 구성하면 주문보다 수량 확인이 먼저 되기 때문에 수량이 부족한 경우에는 주문 자체가 진행되지 않으므로 더 효율적이라고 생각했다.</p>
<h3 id="문제-발생">문제 발생</h3>
<p>만약 <code>@EventListener</code>가 붙은 메서드에서 수량이 초과해 예외가 던져지게 되면 기존 트랜잭션에는 어떤 영향을 미칠 것 같냐는 멘토님의 피드백이 있었다.</p>
<p>찾아보니 <code>@EventListener</code>로 처리를 하게 되면 여기서 예외가 던져졌을 때 기존 트랜잭션에는 영향을 미치지 않는다. 기존 트랜잭션에 영향을 주려면 <code>@TransactionalEventListener</code>를 사용해야 된다는 것이다.</p>
<h3 id="해결">해결</h3>
<p>그래서 이 부분은 <code>@TransactionalEventListener</code>로 리팩토링해주었다.
그런데 또 중요한건, 해당 로직은 주문에 있어서 중요한 로직이기 때문에,<code>@TrasactionalEventListener</code>의 기본 설정인 <code>AFTER_COMMIT</code>이 아닌, <code>BEFORE_COMMIT</code>으로 설정을 변경해주었다.</p>
<p>두 설정의 차이는 간단하게 설명해보자면, 기존 트랜잭션이 커밋된 이후에 실행되냐 커밋되기 이전에 실행되냐의 차이다.</p>
<p>커밋되고 나서 실행이 되면 이미 주문이 처리가 되는 것이기 때문에 그때 옵션을 체크해도 의미가 없다. 그래서 커밋 이전에 옵션을 체크하고, 옵션 수량이 부족하다면 롤백을 하게끔 코드를 다시 작성했다.</p>
<h3 id="">+@</h3>
<p>그냥 멀티 스레드 환경을 가정하고 <code>synchronized</code> 키워드도 붙였었는데, 이 부분에 대해서도 피드백을 받았다.
<code>synchronized</code> 키워드는 멀티 프로세스 환경에서는 의미가 없고, 멀티 스레드로 가정한다고 하더라도 모니터락에 의해서 성능에 영향을 준다고 한다.</p>
<p>여기서 멘토님이 제안해주신 방식은 <code>ConcurrentHashMap</code>을 활용해서 객체에 락을 걸어주는 방식인데, 다른 과제를 수행하는 데에도 시간이 조금 촉박했고 중간에 빌드 문제도 발생했기 때문에 해당 방식을 도입해보지는 못했다.</p>
<p>추후에 동시성에 대해서 공부를 해야된다면 그때 다시 공부를 해보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[영속성 관리 - 1]]></title>
            <link>https://velog.io/@my_sql/pm-1</link>
            <guid>https://velog.io/@my_sql/pm-1</guid>
            <pubDate>Mon, 14 Jul 2025 07:38:06 GMT</pubDate>
            <description><![CDATA[<h1 id="entitymanagerfactory와-entitymanager">EntityManagerFactory와 EntityManager</h1>
<p>데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 하나만 생성합니다.</p>
<pre><code class="language-java">EntityManagerFactory emf =
    Persistence.createEntityManagerFactory(&quot;example&quot;);</code></pre>
<p><code>Persistence.createEntityManagerFactory(&quot;example&quot;);</code>를 호출하게 되면 <code>META-INF/persistence.xml</code>에 있는 정보를 바탕으로 EntityManagerFactory를 생성합니다.</p>
<p>EntityManagerFactory를 만들었다면 이제 필요할 때마다 만들어둔 EntityManagerFactory에서 EntityManager를 생성하면 됩니다.</p>
<pre><code class="language-java">EntityManager em = emf.createEntityManager();</code></pre>
<p>EntityManagerFactory를 만드는 비용은 상당히 큽니다. 따라서 한 개만 만들고 애플리케이션 전체에서 공유하도록 설계가 되어있습니다.
반면 EntityManagerFactory에서 EntityManager를 찍어내는 것은 비용이 거의 들지 않습니다.</p>
<p>또한, EntityManagerFactory는 여러 스레드가 동시에 접근해도 안전합니다. 그렇기 때문에 서로 다른 스레드 간 공유해도 상관이 없지만, EntityManagersms 여러 스레드가 동시에 접근하게 되면 <strong>동시성 문제</strong>가 발생할 수 있기 때문에 스레드 간 절대로 공유하면 안됩니다.</p>
<p><img src="https://velog.velcdn.com/images/my_sql/post/c9d56939-6a59-4f77-ad85-f63d743a0af9/image.jpeg" alt=""></p>
<p>위 그림을 보면 하나의 EntityManagerFactory에서 다수의 EntityManager를 생성합니다.</p>
<p>EntityManger는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않습니다. 그렇기 때문에 EntityManager2와 달리 EntityManager1은 아직 데이터베이스 커넥션을 사용하지 않고 있습니다.</p>
<p>EntityManager2는 커넥션을 사용 중인데, 보통 <strong>트랜잭션</strong>을 시작할 때 커넥션을 획득합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 8-2: CI/CD]]></title>
            <link>https://velog.io/@my_sql/WEEK8-CICD-2</link>
            <guid>https://velog.io/@my_sql/WEEK8-CICD-2</guid>
            <pubDate>Sun, 01 Jun 2025 12:57:15 GMT</pubDate>
            <description><![CDATA[<h1 id="cicd">CI/CD</h1>
<h2 id="cicontinuous-integration">CI(Continuous Integration)</h2>
<p>개발자가 코드를 자주 통합하고 자동 빌드 및 테스트를 통해 코드 품질을 유지하며 문제를 조기에 발견하는 과정을 의미합니다.</p>
<h2 id="cdcontinuous-deliverydeployment">CD(Continuous Delivery/Deployment)</h2>
<p>CI 결과물을 자동으로 스테이징 혹은 운영 환경에 배포하는 과정입니다. 승인 없이 바로 배포하는 것은 Continuous Deployment라고 부릅니다.</p>
<h2 id="대표적인-도구">대표적인 도구</h2>
<ul>
<li>GitHub Actions</li>
<li>Jenkins</li>
<li>GitLab CI</li>
</ul>
<h1 id="amazon-ecs">Amazon ECS</h1>
<p>AWS에서 제공하는 완전 관리형 컨테이너 오케스트레이션 서비스입니다.
Docker 컨테이너의 배포와 운영을 쉽게 해줍니다.</p>
<h2 id="구성-요소">구성 요소</h2>
<ul>
<li>ECR: Docker 이미지 저장소</li>
<li>ECS Cluster: 컨테이너 실행을 위한 클러스터</li>
<li>ECS Service: 실행 그룹</li>
<li>ECS Task: 실제 실행되는 컨테이너 집합</li>
<li>Task Definitaion: 어떤 이미지를 몇 개, 어떤 포트로 실행할 지 정의</li>
</ul>
<h1 id="github-actions에서의-자동-배포">GitHub Actions에서의 자동 배포</h1>
<p>WorkFlow 파일에 빌드, ECR 푸시, ECS 배포 단계를 정의합니다. 이 때, WorkFlow 템플릿은 제공하는 템플릿을 유용하게 사용할 수 있습니다.</p>
<p>AWS 자격증명이 필요한 경우에는 GitHub Secrets로 관리합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 8-1: Docker]]></title>
            <link>https://velog.io/@my_sql/WEEK8-DOCKER-1</link>
            <guid>https://velog.io/@my_sql/WEEK8-DOCKER-1</guid>
            <pubDate>Sun, 01 Jun 2025 12:50:42 GMT</pubDate>
            <description><![CDATA[<h1 id="docker">Docker</h1>
<p>Docker는 어플리케이션을 쉽고 빠르게 만들고, 테스트하며, 배포할 수 있는 소프트웨어 플랫폼입니다. 컨테이너라는 개념으로 가볍고 이식성 높은 패키지로 어플리케이션을 실행시킬 수 있습니다.</p>
<h2 id="image">Image</h2>
<p>docker의 image는 실행에 필요한 모든 것들(코드, 라이브러리 등)을 포함합니다. 컨테이너는 image를 실제로 실행시킨 인스턴스이고, 격리된 환경에서 작동합니다.</p>
<h2 id="특징">특징</h2>
<ul>
<li>컨테이너화: 어플리케이션과 필요한 모든 것들을 하나의 패키지로 묶어서 어디서든 실행 가능하게 합니다.</li>
<li>경량성: 운영체제 커널을 공유해 가상머신보다 훨씬 가볍고 빠릅니다.</li>
<li>이식성: 개발, 테스트, 운영 환경이 달라도 동일하게 실행됩니다.</li>
<li>확장성: 여러 컨테이너를 효율적으로 관리하고 쉽게 확장 가능합니다.</li>
<li>격리성: 컨테이너마다 독립된 환경을 제공하며 보안과 안정성을 확보합니다.</li>
</ul>
<h2 id="네트워크">네트워크</h2>
<ul>
<li>Bridge Network: 기본 네트워크입니다. 같은 브리지 네트워크에 연결된 컨테이너끼리 통신이 가능합니다. 단일 호스트에서 여러 컨테이너 연결에 사용합니다.</li>
<li>Host Network: 컨테이너가 호스트의 네트워크를 직접 사용합니다. 네트워크의 격리가 없지만 성능이 뛰어납니다.</li>
<li>Overlay Network: 여러 Docker 호스트에 걸쳐 있는 컨테이너 연결에 사용합니다.</li>
</ul>
<h2 id="docker-vs-vm">Docker vs. VM</h2>
<table>
<thead>
<tr>
<th>특징</th>
<th>Docker</th>
<th>VM</th>
</tr>
</thead>
<tbody><tr>
<td>실행 속도</td>
<td>빠름</td>
<td>느림</td>
</tr>
<tr>
<td>리소스 사용</td>
<td>가벼움, 오버헤드가 작음</td>
<td>무거움, 오버헤드가 큼</td>
</tr>
<tr>
<td>이식성</td>
<td>어디서나 동일하게 실행</td>
<td>환경마다 다름</td>
</tr>
<tr>
<td>격리 수준</td>
<td>낮음</td>
<td>높음</td>
</tr>
<tr>
<td>OS 다양성</td>
<td>linux에서 최적화</td>
<td>다양한 OS 실행 가능</td>
</tr>
<tr>
<td>보안</td>
<td>커널 공유로 인해 보안에 약함</td>
<td>OS 단위 격리라 보안에 강함</td>
</tr>
</tbody></table>
<h2 id="어디서-사용하는-것이-좋은가">어디서 사용하는 것이 좋은가?</h2>
<ul>
<li>일관된 개발 환경이 필요한 경우</li>
<li>빠른 배포와 확장이 필요한 경우</li>
<li>마이크로서비스 아키텍처가 필요한 경우</li>
<li>CI/CD 파이프라인 구축이 필요한 경우</li>
<li>리소스 효율화가 필요한 경우</li>
<li>어플리케이션 격리가 필요한 경우</li>
</ul>
<h2 id="주요-명령어">주요 명령어</h2>
<ul>
<li>이미지 빌드: <code>docker build -t myapp:latest .</code></li>
<li>이미지 가져오기: <code>docker pull myapp</code></li>
<li>컨테이너 실행: <code>docker run -d -p 8080:80 myapp:latest</code></li>
<li>컨테이너 내부 접속: <code>docker exec -it container_name /bin/bash</code></li>
</ul>
<h2 id="docker-compose">Docker Compose</h2>
<p>여러 컨테이너를 한 번에 관리할 수 있는 도구입니다.
docker-compose.yml파일에 여러 서비스, 네트워크, 볼륨 설정을 정의합니다.
docker compose up -d 명령어로 모든 서비스를 일괄 실행하거나 일괄 중지할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-6: Git]]></title>
            <link>https://velog.io/@my_sql/WEEK7-GIT-6</link>
            <guid>https://velog.io/@my_sql/WEEK7-GIT-6</guid>
            <pubDate>Sun, 25 May 2025 17:18:41 GMT</pubDate>
            <description><![CDATA[<h1 id="충돌">충돌</h1>
<p>여러 명이 동시에 같은 파일의 같은 부분을 수정하면, Git은 어떤 변경을 적용해야 할 지 스스로 결정하지 못해 충돌이 발생하게 됩니다.</p>
<p>PR 과정 뿐만 아니라, git pull 등으로 코드를 병합할 때에도 발생할 수 있습니다.</p>
<h2 id="해결">해결</h2>
<ol>
<li>충돌이 발생하면 Git은 CONFLICT 메세지와 함께 충돌된 파일의 위치를 표시합니다.</li>
<li>충돌을 해결한 뒤 <code>git add 파일명</code>으로 변경을 스테이징합니다.</li>
<li><code>git rebase --continue</code>를 실행해 rebase 과정을 이어갑니다.</li>
<li>충돌이 여러 번 발생하면 이 과정을 반복합니다. </li>
</ol>
<h2 id="해결-후-상태-확인">해결 후 상태 확인</h2>
<p>git log를 확인해보면, rebase로 인해 기존 커밋의 해시가 달라지고, history가 일직선으로 정리됩니다. rebase는 커밋을 다시 쌓기 때문에, 같은 내용이라고 커밋 해시가 새로 생성됩니다.</p>
<h1 id="히스토리-관리">히스토리 관리</h1>
<ul>
<li>commit --amend: 가장 최근 커밋의 메세지나 파일을 수정할 때 사용합니다. 커밋을 새로 만드는 대신 기존 커밋을 덮어씁니다.</li>
<li>rebase -i (interactive): 커밋을 쪼개거나, 여러 개를 하나로 합치기 등 커밋 히스토리를 세밀하게 재구성할 수 있습니다.</li>
</ul>
<p>이미 원격 저장소에 푸시된 커밋에 대해 해당 히스토리 변경 작업을 수행하면 협업에 심각한 문제가 발생할 수 있습니다. 그렇기 때문에 로컬 작업 또는 공유 전 브랜치에서만 사용해야 합니다.</p>
<h1 id="tag">Tag</h1>
<p>특정 커밋을 스냅샷처럼 고정해 버전으로 명명하는 기능입니다.
브랜치가 작업 흐름을 관리한다면, 태그는 중요한 시점을 영구적으로 표시합니다.
태그는 한 번 지정하면 위치가 변하지 않습니다. 그렇기 때문에 주로 릴리즈나 롤백 시점 관리에 사용합니다.</p>
<h1 id="release">Release</h1>
<p>태그를 기반으로 실제 사용자에게 배포되는 버전과 그에 대한 설명을 기록하는 Github 공식 기능입니다. Release note에는 주요 변경점, 버전, 업데이트 방법, 알려진 이슈, 기여자 등의 정보를 포함해 팀원 및 사용자에게 중요한 정보를 제공합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-5: Git]]></title>
            <link>https://velog.io/@my_sql/WEEK7-GIT-5</link>
            <guid>https://velog.io/@my_sql/WEEK7-GIT-5</guid>
            <pubDate>Sun, 25 May 2025 17:10:26 GMT</pubDate>
            <description><![CDATA[<h1 id="git-브랜치-전략">Git 브랜치 전략</h1>
<p>여러 명이 협업을 하거나, 프로젝트의 규모가 커질 때 코드의 안정성과 효율성을 높이기 위해 브랜치를 어떻게 나누고 merge를 하는 지에 대한 정책입니다.</p>
<h2 id="dev-stage-prod-branch">Dev, Stage, Prod branch</h2>
<ul>
<li>Dev: 새로운 기능 개발과 버그 수정 등 모든 작업의 기반 브랜치 입니다. 실험적인 코드가 포함될 수 있고 안정적이지 않을 수 있으며 개발 완료 후에는 Stage로 병합합니다.</li>
<li>Stage: 배포 전 최종 테스트 환경입니다. Dev에서 충분히 검증된 코드를 통합 테스트 및 QA 후 Prod로 병합합니다. 실제 운영 환경과 유사한 설정으로 문제를 사전에 발견할 수 있습니다.</li>
<li>Prod: 실제 사용자에게 제공되는 운영 환경 브랜치입니다. 반드시 안정적이어야 하며, 긴급 수정은 hotfix 브랜치에서 작업 후 바로 병합이 가능합니다.</li>
</ul>
<h2 id="위험과-대안">위험과 대안</h2>
<ul>
<li>Risky: Staging에서 충분히 검증되지 않은 코드를 Prod에 반영하면 예상치 못한 장애나 사용자 경험 문제가 발생할 수 있습니다. 철저한 QA와 점진적 배포가 중요합니다.</li>
<li>Slow &amp; Fat Release: 변경 사항이 많을수록 테스트 및 배포하는 시간이 늘어나게 되고, 대규모 릴리스는 문제 발생 시 원인 파악과 복구가 어렵습니다. 작은 단위로 자주 배포하는 small release 전략이 권장됩니다.</li>
</ul>
<h2 id="브랜치-명칭별-역할">브랜치 명칭별 역할</h2>
<table>
<thead>
<tr>
<th>브랜치 명칭</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>main</td>
<td>최종 배포용 브랜치입니다. 최신 안정 코드를 유지합니다.</td>
</tr>
<tr>
<td>feature</td>
<td>새로운 기능 개발용 브랜치입니다. develop에서 분기 후 작업 완료 시 develop에서 병합합니다.</td>
</tr>
<tr>
<td>release</td>
<td>배포 전 최종 QA 및 버그 수정, develop에서 분기, 완료 후 master와 develop에 병합합니다.</td>
</tr>
<tr>
<td>hotfix</td>
<td>운영 중 긴급 버그 수정, master에서 분기, 완료 후 master와 develop에 병합합니다.</td>
</tr>
</tbody></table>
<p>브랜치의 이름을 지을 때는, 목적이 명확하게 드러나도록 지어야합니다.</p>
<h2 id="버전-관리">버전 관리</h2>
<p>x.y.z로 표현이 되며, 각 자리에 대해서는</p>
<p>x: 주 버전으로, 호환성이 깨지는 큰 변화가 일어났을 때
y: 부 버전으로, 호환성을 유지하며 기능을 추가했을 때
z: 패치로, 버그 수정 등 작은 변경이 일어났을 때</p>
<p>를 의미합니다.</p>
<h1 id="git-flow">Git Flow</h1>
<p>다양한 브랜치를 명확히 구분해 체계적으로 관리합니다.
대규모 서비스, 안정성이 중요한 프로젝트에 적합합니다.</p>
<h1 id="github-flow">Github Flow</h1>
<p>메인 브랜치와 기능 브랜치만을 사용해 단순하고 빠른 개발 및 배포가 가능합니다.
스타트업, 소규모 프로젝트, 빠른 피드백이 필요한 환경에 적합합니다.</p>
<h1 id="trunk-based-developmenttbd">Trunk-based Development(TBD)</h1>
<p>오직 하나의 브랜치를 중심으로 개발합니다.
아주 작은 단위로 브랜치를 만들어 빠르게 main에 병합합니다.
대규모 CI/CD, DevOps 환경에서 많이 사용합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-4: Git]]></title>
            <link>https://velog.io/@my_sql/WEEK7-GIT-4-fvlkhe1k</link>
            <guid>https://velog.io/@my_sql/WEEK7-GIT-4-fvlkhe1k</guid>
            <pubDate>Sun, 25 May 2025 16:58:51 GMT</pubDate>
            <description><![CDATA[<h1 id="github-issue">Github Issue</h1>
<p>이슈는 단순한 버그 신고 뿐만이 아니라, 작업 계획, 토론, 진행 상황 추적 등 모든 개발과 관련된 논의를 기록하는 핵심 도구입니다.</p>
<p>문제를 세분화해 프로젝트 구조를 만들고, 팀원 간 빠른 커뮤니케이션과 명확한 작업 분배를 가능하게 합니다. 이슈를 등록할 때에는 제목, 요약, 환경, 재현 방법, 예상 및 실제 결과, 첨부파일, 관련 이슈, 태그 등을 체계적으로 기록해야 합니다.</p>
<p>태그와 멘션 기능으로 이슈를 분류하고, 다른 이슈와 PR과 연동해 관리할 수 있습니다.</p>
<h1 id="github-project">Github Project</h1>
<p>칸반 보드(Backlog, Ready, In Progress, In Review, Done 등) 형태로 작업의 상태와 일정을 시각적으로 관리합니다.</p>
<p>이슈를 프로젝트 보드에 등록하여 우선순위, 담당자, 진행 상황을 한눈에 파악할 수 있습니다. 이슈가 완료된다면 Close 처리를 하고 Done으로 이동합니다. 이로 프로젝트의 전체 흐름을 쉽게 추적할 수 있습니다.</p>
<h1 id="github-wiki">Github Wiki</h1>
<p>프로젝트 내에서 공식 문서, 개발 가이드, 명세서 등을 마크다운으로 체계적으로 관리할 수 있는 공간입니다.</p>
<p>목차 구조로, 긴 설명이나 가이드 및 명세서를 정리할 때 유용합니다.</p>
<p>정보의 중복과 최신ㅇ화 문제 때문에 README, Issue, PR 설명 등과 역할의 분담이 필요합니다.</p>
<h1 id="grepapp">grep.app</h1>
<p>Github 전체에서 원하는 코드 스니펫, 파일, 함수 등을 빠르게 검색할 수 있는 외부 도구입니다. 구글링으로 찾기 어려운 코드 예제, 자동화 명세 등을 바로 찾아서 활용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-4: Git]]></title>
            <link>https://velog.io/@my_sql/WEEK7-GIT-4</link>
            <guid>https://velog.io/@my_sql/WEEK7-GIT-4</guid>
            <pubDate>Sun, 25 May 2025 16:52:20 GMT</pubDate>
            <description><![CDATA[<h1 id="github-원격-저장소-연결">Github 원격 저장소 연결</h1>
<ol>
<li>Github 저장소 생성: github 로그인 후 Create repository 버튼을 클릭하여 새로운 저장소를 만듭니다. </li>
<li>로컬 저장소와 원격 저장소 연동: 이미 로컬에서 git init, add, commit을 마친 상태라면 <code>git remote add origin 저장소주소</code> 명령어로 원격 저장소를 등록합니다.</li>
<li><code>git push -u origin main</code> 명령어로 코드를 업로드합니다.</li>
</ol>
<p>여기서 push할 때는 Github 계정 인증이 필요합니다.
아이디로는 Github 닉네임을 사용하고
비밀번호로는 Github 로그인시 사용하는 비밀번호가 아닌, Access Token을 따로 발급받아 사용합니다.</p>
<pre><code class="language-bash">git config --global user.name &quot;name&quot;
git config --global user.email &quot;email&quot;
git config --global credential.helper store</code></pre>
<p>위와 같은 명령어를 입력하면, 반복 인증을 생략할 수 있습니다.</p>
<h1 id="fork-vs-clone">Fork vs. Clone</h1>
<h2 id="fork">Fork</h2>
<p>다른 사람의 Github 저장소를 내 계정의 저장소로 복제하는 작업입니다. 계정에는 새로운 저장소가 생성되지만, 로컬로 코드를 가져오지는 않습니다.</p>
<p>보통 오픈소스에 기여하거나 기존 프로젝트를 자신만의 방식으로 발전시키고 싶을 때 사용합니다.</p>
<h2 id="clone">Clone</h2>
<p>Github에 있는 저장소를 로컬로 복제하는 작업입니다. 여기서 저장소는 자신의 것이든, 남의 것이든 상관없습니다. 만약 남의 것을 clone한다고 하면, 저장소가 따로 추가되지는 않습니다.</p>
<h1 id="fork-clone-후-pr까지의-흐름">Fork, Clone 후 PR까지의 흐름</h1>
<ol>
<li>Fork</li>
<li>Clone</li>
<li>새 브랜치 생성 및 작업</li>
<li>commit 및 push</li>
<li>Pull Request 작성</li>
<li>리뷰 및 Merge</li>
<li>pull을 통해 로컬 저장소 동기화</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-3: Git]]></title>
            <link>https://velog.io/@my_sql/WEEK7-GIT-3</link>
            <guid>https://velog.io/@my_sql/WEEK7-GIT-3</guid>
            <pubDate>Sun, 25 May 2025 16:32:23 GMT</pubDate>
            <description><![CDATA[<h1 id="주요-git-명령어">주요 git 명령어</h1>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>git init</td>
<td>현재 디렉토리를 git이 추적하는 저장소로 초기화합니다. <code>.git</code> 폴더를 생성하고 빈 저장소의 상태를 확인합니다.</td>
</tr>
<tr>
<td>git add</td>
<td>변경된 파일을 Staging Area에 올립니다. 파일별 또는 전체로 추가가 가능합니다.</td>
</tr>
<tr>
<td>git status</td>
<td>현재 브랜치, 커밋 상태, 스테이징된 파일과 추적되지 않은 파일을 구분해서 확인할 수 있습니다.</td>
</tr>
<tr>
<td>git commit</td>
<td>stage에 올린 변경사항을 저장합니다. 메세지 작성 규칙이 존재하며 이를 지키는게 중요합니다.</td>
</tr>
<tr>
<td>git commit -m</td>
<td>간단한 메세지로 바로 커밋이 가능합니다. 작은 변경이나 테스트에 활용됩니다.</td>
</tr>
<tr>
<td>git diff</td>
<td>커밋 전후, 브랜치 간, 커밋 간 변경 사항을 비교합니다.</td>
</tr>
<tr>
<td>git log</td>
<td>커밋 이력, 작성자, 시간, 메세지 등을 확인합니다.</td>
</tr>
</tbody></table>
<h2 id="commit-메세지-작성-규칙">commit 메세지 작성 규칙</h2>
<ul>
<li>제목은 50자 이내</li>
<li>본문은 변경의 이유와 내용을 담아 72자 이내</li>
<li>꼬리말(이슈 트래커, BREAKING CHANGE 등)</li>
</ul>
<p>으로 구성됩니다.</p>
<h1 id="commit-되돌리기">commit 되돌리기</h1>
<h2 id="reset-restore-revert">reset, restore, revert</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>동작 방식</th>
<th>사용하는 상황</th>
</tr>
</thead>
<tbody><tr>
<td>reset</td>
<td>커밋 이력 자체를 삭제합니다. 브랜치의 포인터를 과거 커밋으로 이동시키고 옵션에 따라서는 스테이징/작업 디렉토리에 영향을 주기도 합니다.</td>
<td>혼자 작업을 하는 경우, 커밋의 정리가 필요한 경우</td>
</tr>
<tr>
<td>restore</td>
<td>파일 단위로 변경 사항을 복구합니다. 커밋의 이력은 그대로 두며 워킹/스테이징 파일만 복구합니다.</td>
<td>파일만 되돌릴 때, 커밋 전 수정을 취소할 때</td>
</tr>
<tr>
<td>revert</td>
<td>특정 커밋의 변경 사항을 취소하는 새 커밋을 생성합니다. 커밋의 이력은 보존합니다.</td>
<td>협업 시, 안전하게 커밋 취소</td>
</tr>
</tbody></table>
<h3 id="reset">reset</h3>
<ul>
<li>--soft: 커밋만 되돌립니다. 스테이징의 상태는 그대로 둡니다.</li>
<li>--mixed: 커밋과 스테이징 모두를 되돌리고 작업 디렉토리는 유지합니다. 기본값입니다.</li>
<li>--hard: 커밋, 스테이징, 작업 디렉토리까지 모두 과거 상태로 초기화 합니다.</li>
</ul>
<h3 id="restore">restore</h3>
<ul>
<li>git restore &lt;file&gt;: 해당 파일을 가장 최근 커밋 상태로 복구합니다.</li>
<li>git restore --staged &lt;file&gt;: 스테이징을 해제합니다.</li>
</ul>
<h3 id="revert">revert</h3>
<ul>
<li>git rever &lt;commit&gt;: 해당 커밋의 변경 사항을 취소하는 새 커밋을 생성합니다.</li>
<li>git revert HEAD: 가장 최근 커밋을 취소하는 새 커밋을 생성합니다.</li>
<li>git revert -n &lt;commit&gt;:취소만 하고 커밋은 하지 않습니다. 여러 작업을 합쳐 커밋할 때 유용합니다.</li>
</ul>
<h1 id="git-브랜치">Git 브랜치</h1>
<p>브랜치는 git에서 나뭇가지처럼 독립적인 작업 공간을 만들 수 있는 기능입니다. 기본적으로는 main 브랜치에서 작업을 하는데, 새로운 기능 개발, 테스트, 배포 전 점검 등 목적에 따라서 여러 브랜치를 만들고 코드를 격리하여 관리합니다.</p>
<p>브랜치를 사용하면 각 환경에 맞추어 코드를 분리 및 관리할 수 있으며 규모가 커질 수록 필수적인 전략입니다.</p>
<h2 id="브랜치를-사용하는-이유">브랜치를 사용하는 이유?</h2>
<ul>
<li>기능별, 환경별로 코드를 안전하게 격리시킬 수 있습니다.</li>
<li>여러 명이 동시에 개발할 때 충돌을 줄일 수 있으며, 각자 작업을 독립적으로 진행할 수 있습니다.</li>
<li>배포, 테스트, 긴급 수정 등 다양한 상황에 맞추어 유연하게 코드 관리를 할 수 있습니다.</li>
</ul>
<h2 id="브랜치의-생성과-관리">브랜치의 생성과 관리</h2>
<ul>
<li>현재 브랜치 확인: <code>git branch</code></li>
<li>새 브랜치 생성:<code>git branch 브랜치이름</code></li>
<li>브랜치 이동: <code>git checkout 브랜치이름</code></li>
</ul>
<p>브랜치를 생성하면, 현재 위치한 최신 커밋을 기준으로 새 브랜치가 만들어지게 됩니다. 브랜치마다 커밋 이력이 독립적으로 쌓이며 나중에 병합할 수도 있습니다.</p>
<h1 id="cherry-pick">Cherry-pick</h1>
<p>한 브랜치의 특정 커밋만 선택적으로 다른 브랜치에 복사해오는 Git 명령어입니다. 긴급 버그 수정이나 특정 기능만 빠르게 반영해야 할 때 유용합니다.</p>
<ol>
<li>반영할 브랜치로 이동</li>
<li>가져올 커밋의 해시값 확인</li>
<li><code>git cherry-pick 커밋해시</code> 명령어 실행</li>
</ol>
<p>cherry-pick을 하면 커밋 이력이 복사되어 새로운 커밋으로 남기 때문에 브랜치 간 직접적인 연결고리는 생기지 않습니다.</p>
<h2 id="주의점">주의점</h2>
<p>커밋 이력이 단절되므로 어디서 왔는지 추적이 어려울 수 있습니다. 또한 미래 커밋과 충돌이 발생할 수 있고 이러한 경우 직접 충돌을 해결해야 합니다.</p>
<h2 id="merget-pull-request">Merget, Pull Request</h2>
<p>일반적으로는 cherry-pick보다는 브랜치 전체를 merge하는 방식이 더 많이 사용됩니다.
Github 등에서는 Pull Request를 통해 코드 변경을 공유하고, 합의 후 merge로 코드를 통합니다. 여기서 PR은 협업시 검토와 조율을 하기 위해 필수적인 절차입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-2: 리눅스 명령어]]></title>
            <link>https://velog.io/@my_sql/WEEK7-LINUX-2</link>
            <guid>https://velog.io/@my_sql/WEEK7-LINUX-2</guid>
            <pubDate>Sun, 25 May 2025 16:06:21 GMT</pubDate>
            <description><![CDATA[<h1 id="linux">Linux</h1>
<p>전 세계적으로 사용되는 <strong>오픈소스 운영체제</strong>로, 서버, 개발환경, IoT 기기 등 다양한 곳에서 활용이 됩니다. 무료로 사용할 수 있고 사용자가 직접 커스터마이즈 할 수 있으며, 다양한 배포판이 존재합니다.</p>
<p>Unix 철학과 구조를 계승한 OS입니다. Unix와 비슷한 기능과 구조를 가지지만, 오픈소스라는 점에서 누구나 개발에 참여할 수 있습니다.</p>
<h2 id="왜-개발에서-linux를-사용하는가">왜 개발에서 Linux를 사용하는가?</h2>
<p>대부분 상업용 서비스 배포 환경이 linux 기반입니다. 그렇기 때문에 개발자들은 linux 환경에 익숙해질 필요가 있습니다. 다른 운영체제로도 개발은 가능하지만, 실제 배포 환경이나 협업을 위한 것으로는 linux가 표준으로 자리 잡았습니다.</p>
<p>linux는 서버 운영, 자동화, 대규모 시스템 관리에 최적화 되어 있습니다.</p>
<h1 id="linux-명령어">Linux 명령어</h1>
<p>linux 명령어는 보통 <code>명령 [option] [argument]</code> 형태로 입력합니다.
옵션의 경우 대시로 시작하며, 여러 옵션을 결합하여 사용할 수 있습니다. 대소문자 또한 구분하기 때문에 이에 유의해야 합니다.</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ls</td>
<td>현재 폴더의 파일 및 디렉토리 목록 출력</td>
</tr>
<tr>
<td>cd</td>
<td>디렉토리 이동</td>
</tr>
<tr>
<td>pwd</td>
<td>현재 위치한 디렉토리의 절대 경로</td>
</tr>
<tr>
<td>mkdir</td>
<td>새로운 디렉토리 생성</td>
</tr>
<tr>
<td>touch</td>
<td>새로운 파일 생성</td>
</tr>
<tr>
<td>rm</td>
<td>파일 및 디렉토리 삭제</td>
</tr>
<tr>
<td>mv</td>
<td>파일 및 디렉토리 이동 또는 이름 변경</td>
</tr>
<tr>
<td>cp</td>
<td>파일 및 디렉토리 복사</td>
</tr>
<tr>
<td>cat</td>
<td>파일 내용 출력</td>
</tr>
<tr>
<td>tail</td>
<td>파일 뒷부분 출력</td>
</tr>
<tr>
<td>echo</td>
<td>텍스트 및 변수 값 출력</td>
</tr>
<tr>
<td>df -h</td>
<td>디스크 용량 확인</td>
</tr>
<tr>
<td>du -sh</td>
<td>디렉토리별 사용 용량 확인</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 7-1: git]]></title>
            <link>https://velog.io/@my_sql/WEEK7-GIT-1</link>
            <guid>https://velog.io/@my_sql/WEEK7-GIT-1</guid>
            <pubDate>Sun, 25 May 2025 15:42:16 GMT</pubDate>
            <description><![CDATA[<h1 id="git">Git</h1>
<p>코드와 파일의 변경 이력을 저장 및 관리하는 <strong>분산형 버전 관리 시스템</strong>입니다. 여러 사람이 동시에 작업할 때 수정 내역을 쉽게 추적할 수 있고 협업할 수 있게 도와줍니다.</p>
<h1 id="github">Github</h1>
<p>Git으로 관리한 코드를 인터넷에 저장 및 공유할 수 있는 웹 서비스입니다. 협업과 공개 프로젝트 관리에 유용합니다.</p>
<h1 id="버전-관리가-왜-필요한가">버전 관리가 왜 필요한가?</h1>
<p>협업하는 사람들 등과 파일을 공유할 때 직접 압축하여 공유하는 방식은 비효율적입니다. 이에 반해 Git은 변경 내역을 체계적으로 관리하고 쉽게 복원할 수 있습니다.</p>
<h1 id="분산형-버전-관리란">분산형 버전 관리란?</h1>
<p>중앙 서버뿐만 아니라 각 사용자의 컴퓨터에도 전체 이력과 데이터를 복사해서 저장합니다. 만약 서버에 장애 또는 치명적 오류가 일어나 데이터 손실이 발생한다고 해도 안전하게 코드를 관리할 수 있습니다.</p>
<h1 id="git-핵심-용어">Git 핵심 용어</h1>
<h2 id="git의-기본-구조와-용어">Git의 기본 구조와 용어</h2>
<ul>
<li>Working Tree: 사용자의 컴퓨터에서 실제로 작업이 이루어지는 전체 영역을 의미합니다. Git이 관리하는 파일들과 그렇지 않은 파일들이 모두 존재하고 코드를 작성 및 수정하는 활동들이 여기서 이루어집니다.</li>
<li>Working Directory: 실제 소스코드를 작성하고 편집하는 공간입니다. 아직 git이 변경 사항을 기록하지 않은 상태의 파일들이 위치하게 됩니다.</li>
<li>Staging Area: 변경된 파일 중 Git으로 추적할 파일을 임시로 저장하는 공간입니다. 여러 작업을 분리해서 관리할 수도 있고, 원격 저장소로 올릴 파일을 선택적으로 관리할 수도 있습니다.</li>
<li>Local Directory(.git): Staging Area에 올린 변경 사항을 commit하면 저장되는 공간입니다. commit된 내역은 원격 저장소로 업로드되기 전 단계이고 Git에서 핵심 데이터베이스 역할을 합니다.</li>
</ul>
<h2 id="주요-커맨드">주요 커맨드</h2>
<ul>
<li>commit: Staging Area에 있는 변경 사항들을 스냅샷으로 기록하는 작업입니다. commit이 없다면 원격 저장소로 코드를 업로드할 수 없으며, 각 commit은 고유한 해시 값으로 관리됩니다.</li>
<li>snapshot: commit 시점의 프로젝트 전체 상태를 저장한 것입니다. 특정 시점으로 돌아갈 수 있게 해주는 Git의 핵심 기능입니다.</li>
<li>head: 현재 작업중인 브랜치의 가장 최신 commit을 가리키는 포인터입니다.</li>
<li>branch: 여러 개발자가 각자 기능을 분리하여 작업을 할 때 사용하는 독립적인 작업 공간입니다. 다양한 목적에 필수적으로 사용이 됩니다.</li>
<li>merge: 서로 다른 브랜치에서 작업한 내용을 하나로 합치는 과정입니다. merge할 때 충돌이 발생할 수 있으며, 이를 해결하는 것도 git의 주요 기능 중 하나입니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 6-14: Spring Data JPA]]></title>
            <link>https://velog.io/@my_sql/WEEK6-SPRING-14</link>
            <guid>https://velog.io/@my_sql/WEEK6-SPRING-14</guid>
            <pubDate>Sun, 18 May 2025 16:39:30 GMT</pubDate>
            <description><![CDATA[<h1 id="spring-boot와-jpa">Spring Boot와 JPA</h1>
<p>Spring Boot에서는 JPA의 설정(EntityManagerFactory, TransactionManager 등)을 자동으로 구성합니다. DB 연결 정보는 application.properties(또는 application.yml)에서 간단히 지정하며 <code>@PersistenceContext</code>로 EntityManager를 주입받아 사용합니다.</p>
<p>Spring이 직접 EntityManager를 관리하기 때문에 직접 close할 필요는 없고, 각 요청마다 별도의 EntityManager 인스턴스를 제공합니다.</p>
<h2 id="기존-jpa-vs-spring-boot-jpa">기존 JPA vs. Spring Boot JPA</h2>
<p>기존 JPA는 직접 EntityManagerFactory/EntityManager를 생성해야 했습니다. 거기다가 Transaction 및 종료까지 수동으로 관리했습니다.</p>
<p>Spring Boot에서는 의존성 추가만으로 자동 설정을 해주고, 간편한 EntityManager를 사용하게 해줍니다.</p>
<pre><code class="language-java">@Repository
public class TutorRepository {
    @PersistenceContext
    private EntityManager em;

    public void save(Tutor tutor) { em.persist(tutor); }
    public Tutor findById(Long id) { return em.find(Tutor.class, id); }
    public List&lt;Tutor&gt; findAll() { return em.createQuery(&quot;SELECT t FROM Tutor t&quot;, Tutor.class).getResultList(); }
    public void delete(Tutor tutor) { em.remove(tutor); }
}</code></pre>
<blockquote>
<h4 id="entitymanager">EntityManager</h4>
<p>EntityManager는 동시성 문제 방지를 위해서 싱글톤으로 등록되지 않습니다. Spring Boot는 프록시를 싱글톤으로 등록해 요청마다 별도의 EntityManager 인스턴스를 제공하여 각 요청은 독립적으로 EntityManager를 사용하여 안전하게 데이터베이스 작업을 처리할 수 있습니다.</p>
</blockquote>
<h1 id="spring-data-jpa">Spring Data JPA</h1>
<p>Spring에서 JPA를 더 쉽게 사용할 수 있도록 지원하는 모듈입니다. 반복적인 CRUD 코드를 줄이고, 인터페이스만으로 데이터 접근 계층을 구현할 수 있습니다.</p>
<h2 id="jpa-vs-spring-data-jpa">JPA vs. Spring Data JPA</h2>
<ul>
<li>JPA: 직접 EntityManager로 CRUD 메서드 작성이 필요합니다.</li>
<li>Spring Data JPA: JpaRepository 인터페이스를 상속하는 것만으로 기본 CRUD를 제공합니다.</li>
</ul>
<pre><code class="language-java">public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    // 추가 쿼리 메서드 선언 가능
}</code></pre>
<h2 id="simplejparepository">SimpleJpaRepository</h2>
<p>JpaRepository의 기본 구현체입니다. save, findAll, delete 등 다양한 메서드를 기본 제공하며, Paging, Sorting 등 부가 기능까지 지원합니다.</p>
<h1 id="query-methods">Query Methods</h1>
<p>메서드 이름 규칙만 맞추면 SQL없이 자동적으로 쿼리를 생성합니다. <code>findBy{Field1}And{Field2}</code> 등으로 조건 쿼리 작성이 가능합니다. </p>
<pre><code class="language-java">public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    Member findByNameAndAddress(String name, String address);
}</code></pre>
<p>위 메서드는 <code>SELECT * FROM member WHERE name = ? AND address = ?</code> 쿼리를 자동으로 생성합니다.</p>
<h1 id="jpa-auditing">JPA Auditing</h1>
<p>엔티티의 생성 및 수정 시간을 자동으로 관리하는 기능입니다. 모든 엔티티에 createdAt, updatedAt 필드를 반복적으로 작성할 필요가 없습니다.</p>
<h2 id="순수-jpa-auditing">순수 JPA Auditing</h2>
<p><code>@MappedSuperclass</code>로 공통 부모 엔티티를 생성합니다. <code>@PrePersist</code>, <code>@PreUpdate</code>로 생성 및 수정 시각을 자동으로 기록합니다.</p>
<pre><code class="language-java">@MappedSuperclass
public class BaseEntity {
    @Column(updatable = false)
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    @PrePersist
    public void prePersist() {
        LocalDateTime now = LocalDateTime.now();
        createdAt = now;
        updatedAt = now;
    }
    @PreUpdate
    public void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
}</code></pre>
<h1 id="spring-data-jpa-auditing">Spring Data JPA Auditing</h1>
<p>적용방법</p>
<ol>
<li><code>@EnableJpaAuditing</code>: Application 클래스에 선언합니다.</li>
<li><code>@MappedSuperclass</code> + <code>@EntityListeners(AuditingEntityListener.class)</code>로 공통 엔티티를 작성합니다.</li>
<li><code>@CreatedDate</code>, <code>LastModifiedDate</code>등 annotation으로 생성 및 수정 시각을 자동으로 기록합니다.</li>
</ol>
<pre><code class="language-java">@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime modifiedAt;
}</code></pre>
<p>이런 식으로 엔티티에서 상속을 받으면 생성 및 수정 시각 필드가 자동으로 관리됩니다.</p>
<p>자료 및 코드 출처: 스파르타 코딩클럽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 6-13: Spring Entity]]></title>
            <link>https://velog.io/@my_sql/WEEK6-SPRING-13</link>
            <guid>https://velog.io/@my_sql/WEEK6-SPRING-13</guid>
            <pubDate>Sun, 18 May 2025 16:26:42 GMT</pubDate>
            <description><![CDATA[<h1 id="entity와-table">@Entity와 @Table</h1>
<h2 id="entity">@Entity</h2>
<p>클래스에 @Entity를 선언하면 JPA가 관리하는 엔티티로 인식하게 됩니다.</p>
<p><strong>필수 조건</strong></p>
<ul>
<li>PK(@Id) 필드가 반드시 존재해야 함</li>
<li>기본 생성자 필요</li>
<li>final, enum, interface, inner class에는 사용이 불가능</li>
<li>필드에 final 사용 불가능</li>
</ul>
<p>name 속성으로 엔티티 이름 지정이 가능합니다.</p>
<h2 id="table">@Table</h2>
<p>엔티티와 매핑할 테이블 이름, 스키마, 유니크 제약 조건들을 지정합니다.
생략하면 엔티티의 이름이 테이블 이름이 됩니다.</p>
<h2 id="예시">예시</h2>
<pre><code class="language-java">@Entity
@Table(name = &quot;tutor&quot;)
public class Tutor {
    @Id
    private Long id;
    private String name;
    public Tutor() {}
    public Tutor(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}</code></pre>
<h1 id="ddl-자동-생성-옵션hibernatehbm2ddlauto">DDL 자동 생성 옵션(hibernate.hbm2ddl.auto)</h1>
<table>
<thead>
<tr>
<th>값</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>create</td>
<td>기존 테이블 DROP 후 CREATE</td>
</tr>
<tr>
<td>create-drop</td>
<td>시작 시 DROP + CREATE, 종료시 DROP</td>
</tr>
<tr>
<td>update</td>
<td>변경 사항만 ALTER, 기존 데이터 유지</td>
</tr>
<tr>
<td>validate</td>
<td>엔티티와 테이블 매핑만 검증, 불일치 시 제외</td>
</tr>
<tr>
<td>none</td>
<td>자동 DDL 생성 또는 검증 없음</td>
</tr>
</tbody></table>
<p>실무에서는 validate, none 사용을 권장하며, 개발 단계에서는 create, create-drop, update를 상황에 맞게 사용합니다.</p>
<h1 id="entity-필드-매핑과-주요-어노테이션">Entity 필드 매핑과 주요 어노테이션</h1>
<ul>
<li>@Column: 컬럼명, 길이, null 허용, 유니크 등 제약조건 설정</li>
<li>@Enumerated: enum 타입 매핑(ORDINAL: 숫자, STRING: 이름)</li>
<li>@Temporal: 날짜 및 시간 타입 지정(DATE, TIME, TIMESTAMP)</li>
<li>@Transient: DB 컬럼과 매핑하지 않음(임시 필드)</li>
<li>@Lob: 대용량 데이터 매핑</li>
</ul>
<pre><code class="language-java">@Column(unique = true, length = 20, nullable = false)
private String name;

@Enumerated(EnumType.STRING)
private BoardType boardType;

@Column(columnDefinition = &quot;longtext&quot;)
private String contents;

@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;

@Transient
private int count;</code></pre>
<blockquote>
<h4 id="timestamp에-관해서">Timestamp에 관해서</h4>
<p>최신 버전 Hibernate에서 LocalDate, LocalDateTime은 @Temporal 생략이 가능합니다.</p>
</blockquote>
<h1 id="pk-생성-전략">PK 생성 전략</h1>
<ul>
<li><p>@Id: 수동 지정</p>
</li>
<li><p>@GeneratedValue: 자동 생성</p>
<ul>
<li>GenerationType.IDENTITY: MMYSQL, PostgreSQL(AutoIncrement)</li>
<li>GenerationType.SEQUENCE: Oracle(sequence)</li>
<li>GenerationType.TABLE: 키 생성용 테이블 사용</li>
<li>GenerationType.AUTO: DB 방언에 따라 자동 선택, 권장하지는 않음</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;</code></pre>
<h1 id="제약-조건-설정">제약 조건 설정</h1>
<ul>
<li>@Column: unique, nullable, length 등의 속성으로 DDL 제약 조건 설정</li>
<li>@Table: uniqueConstraints로 복합 유니크 제약조건 설정</li>
</ul>
<pre><code class="language-java">@Column(unique = true, length = 20, nullable = false)
private String name;

@Table(uniqueConstraints = {
    @UniqueConstraint(name = &quot;name_unique&quot;, columnNames = {&quot;name&quot;})
})</code></pre>
<h1 id="단방향-연관관계">단방향 연관관계</h1>
<p>한 객체만 다른 객체를 참조하는 관계를 의미합니다.
예를 들어서, Tutor가 Company만 참조하는 경우가 있습니다. 이때, Company는 Tutor를 알지 못합니다.</p>
<h2 id="특징">특징</h2>
<ul>
<li>설정이 단순하고 유지보수가 쉽습니다.</li>
<li>불필요한 데이터의 접근을 방지할 수 있습니다.</li>
<li>데이터베이스에서는 FK만으로 양쪽 조인이 가능하지만, 객체에서는 <strong>참조 필드가 있는 쪽만 다른 객체에 접근이 가능</strong>합니다.</li>
</ul>
<h2 id="예시-1">예시</h2>
<pre><code class="language-java">@Entity
@Table(name = &quot;tutor&quot;)
public class Tutor {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToOne
    @JoinColumn(name = &quot;company_id&quot;)
    private Company company;
    // ...
}</code></pre>
<p>Tutor만 Company를 참조할 수 있습니다. 즉, tutor.getCompany()는 가능하지만, company.getTutors()는 불가능합니다.</p>
<h1 id="양방향-연관관계">양방향 연관관계</h1>
<p>양쪽 객체가 서로를 참조하는 관계입니다. 예를 들면, Tutor와 Company가 서로를 참조하는 경우가 있습니다.</p>
<h2 id="특징-1">특징</h2>
<ul>
<li>두 객체 모두 상대 객체에 접근이 가능합니다.</li>
<li>객체 그래프 탐색이 자유롭습니다.</li>
<li>테이블에는 변화가 없고, 객체 차원에서만 방향이 생깁니다.</li>
</ul>
<h2 id="예시-2">예시</h2>
<pre><code class="language-java">@Entity
@Table(name = &quot;company&quot;)
public class Company {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy = &quot;company&quot;)
    private List&lt;Tutor&gt; tutors = new ArrayList&lt;&gt;();
    // ...
}</code></pre>
<p>Company에서 Tutor 리스트를 참조할 수 있습니다.
mappedBy = &quot;company&quot;로 Tutor의 company필드가 연관관계의 주인임을 명시합니다.</p>
<h1 id="mappedby">mappedBy</h1>
<h2 id="연관관계의-주인">연관관계의 주인</h2>
<p>두 객체가 <strong>양방향 관계</strong>일 때, 실제로 외래키를 관리하는 쪽을 의미합니다. 즉, 외래키가 있는 엔티티가 주인이 되는 것입니다.</p>
<p>주인은 mappedBy 속성을 사용하지 않고, 주인이 아닌 쪽만 mappedBy를 사용합니다.</p>
<h2 id="주인과-비주인의-구분">주인과 비주인의 구분</h2>
<p>주인은 외래키 필드가 있는 엔티티이며 등록, 수정, 삭제 등 관계의 제어가 가능합니다.
비주인은 mappedBy로 주인 필드명을 지정, 조회만 가능합니다.</p>
<pre><code class="language-java">// Tutor (주인)
@ManyToOne
@JoinColumn(name = &quot;company_id&quot;)
private Company company;

// Company (비주인)
@OneToMany(mappedBy = &quot;company&quot;)
private List&lt;Tutor&gt; tutors;</code></pre>
<h2 id="규칙">규칙</h2>
<ul>
<li>두 엔티티 중 하나를 반드시 연관관계의 주인으로 지정해야 합니다.</li>
<li>주인은 mappedBy를 사용하지 않습니다.</li>
<li>비주인은 mappedBy를 사용해 주인을 명시합니다.</li>
<li>주인만 외래키를 관리할 수 있습니다.</li>
<li>비주인은 조회만 가능합니다.</li>
</ul>
<p>자료 및 코드 출처: 스파르타 코딩클럽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 6-12: Spring 영속성 컨텍스트]]></title>
            <link>https://velog.io/@my_sql/WEEK6-SPRING-12</link>
            <guid>https://velog.io/@my_sql/WEEK6-SPRING-12</guid>
            <pubDate>Sun, 18 May 2025 15:49:39 GMT</pubDate>
            <description><![CDATA[<h1 id="영속성-컨텍스트persistence-context">영속성 컨텍스트(Persistence Context)</h1>
<p>JPA에서 엔티티 객체를 영구적으로 저장하고 관리하는 논리적인 메모리 공간입니다. 쉽게 말하자면, application과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스의 역할을 합니다.</p>
<h2 id="역할">역할</h2>
<p>엔티티를 저장, 조회, 삭제 하는 등의 모든 작업은 먼저 영속성 컨텍스트를 거칩니다. Entity Manager를 통해 접근하며, 같은 Transaction 내에서는 동일한 객체를 반환합니다.</p>
<h2 id="entity-상태">Entity 상태</h2>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>비영속(new)</td>
<td>영속성 컨텍스트와 전혀 관련이 없는, 새로 생성된 객체</td>
<td>new Member()</td>
</tr>
<tr>
<td>영속(managed)</td>
<td>영속성 컨텍스트에 저장되어 관리 중인 상태</td>
<td>em.persist(member)</td>
</tr>
<tr>
<td>준영속(detached)</td>
<td>한 번 영속되었다가 영속성 컨텍스트에서 분리된 상태</td>
<td>em.detach(member)</td>
</tr>
<tr>
<td>삭제(removed)</td>
<td>영속성 컨텍스트와 DB에서 삭제될 예정인 상태</td>
<td>em.remove(member)</td>
</tr>
</tbody></table>
<h2 id="1차-캐시">1차 캐시</h2>
<p>엔티티를 영속성 컨텍스트에 저장할 때 생성되는 메모리 내의 캐시입니다. 엔티티는 먼저 1차 캐시에 저장이 되고, 같은 엔티티를 다시 요청하면 DB를 조회하지 않고 1차 캐시에서 반환합니다.</p>
<h3 id="동작">동작</h3>
<p><code>em.persist(entity)</code> 호출 시 1차 캐시에 저장
<code>em.find()</code>로 엔티티 조회 시 1차 캐시에서 먼저 찾고, 없으면 DB 조회 후 1차 캐시에 저장
transaction이 종료되면 1차 캐시도 함께 삭제</p>
<pre><code class="language-java">Tutor tutor = new Tutor(1L, &quot;wonuk&quot;, 100); // 비영속
em.persist(tutor); // 영속, 1차 캐시에 저장
Tutor findTutor = em.find(Tutor.class, 1L); // 1차 캐시에서 조회</code></pre>
<p>같은 트랜잭션 내에서 동일 엔티티를 조회할 시 SQL이 한 번만 실행된다는 효과를 가집니다. 이로 인해 성능이 최적화될 수 있고, 불필요한 DB에 접근을 방지할 수 있습니다.</p>
<h2 id="동일성-보장">동일성 보장</h2>
<p>동일 트랜잭션 내에서 같은 엔티티를 여러 번 조회해도 항상 같은 객체 인스턴스를 반환합니다.</p>
<h3 id="동작-1">동작</h3>
<p>1차 캐시 덕분에 중복 조회 시 항상 같은 객체 참조를 반환합니다. Java Collection에서 객체를 꺼내는 것처럼 일관성 있게 동작합니다.</p>
<pre><code class="language-java">Tutor findTutor1 = em.find(Tutor.class, 1L);
Tutor findTutor2 = em.find(Tutor.class, 1L);
System.out.println(findTutor1 == findTutor2); // true</code></pre>
<p>Transaction 격리 수준 중 Repeatable Read를 Application 레벨에서 제공합니다.</p>
<h2 id="쓰기-지연transactional-write-behind">쓰기 지연(Transactional Write-Behind)</h2>
<p>엔티티 객체의 변경 사항을 DB에 바로 반영하지 않고, transaction의 commit 시점에 한 번에 반영하는 방식입니다.</p>
<h3 id="동작-2">동작</h3>
<p>em.persist(entity) 호출 시 즉시 INSERT SQL을 실행하지 않고, 쓰기 지연 저장소에 쿼리를 모아둡니다. 그리고 transaction.commit() 시점에 모아둔 쿼리들을 한 번에 DB에 반영합니다.</p>
<pre><code class="language-java">em.persist(tutor1);
em.persist(tutor2);
transaction.commit(); // 이때 INSERT SQL이 실행됨</code></pre>
<p>네트워크 및 DB의 부하가 감소되며, 성능이 최적화됩니다. 또한 JDBC batch 옵션을 활용하면, 여러 쿼리를 묶어 전송이 가능합니다.</p>
<h2 id="변경-감지dirty-checking">변경 감지(Dirty Checking)</h2>
<p>영속성 컨텍스트가 엔티티의 초기 상태(Sanpshot)를 저장하고, transaction commit 시점에 현재 상태와 비교해 변경 사항이 있으면 UPDATE SQL을 자동으로 생성하는 기능입니다.</p>
<h3 id="동작-3">동작</h3>
<p>엔티티를 조회하고 필드를 변경하면, commit 시점에 JPA가 변경 여부를 감지합니다. 그리고 변경된 부분만 UPDATE 쿼리로 반영합니다.</p>
<pre><code class="language-java">Tutor tutor = em.find(Tutor.class, 1L);
tutor.setName(&quot;수정된 이름&quot;);
transaction.commit(); // UPDATE SQL 자동 실행</code></pre>
<p>객체를 수정한 뒤 별도의 저장(persist) 호출 없이 DB에 반영합니다.</p>
<h2 id="flush">Flush</h2>
<p>영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 기능입니다.</p>
<h3 id="동작-4">동작</h3>
<ul>
<li><strong>자동 호출</strong>: transaction commit 시 자동으로 실행</li>
<li><strong>수동 호출</strong>: em.flush()로 원하는 시점에 직접 호출이 가능</li>
</ul>
<pre><code class="language-java">em.persist(tutor);
em.flush(); // 이 시점에 SQL 실행
transaction.commit();</code></pre>
<p>transaction 내에서 특정 시점에 DB 동기화가 필요할 때 활용할 수 있습니다.</p>
<p>자료 및 코드 출처: 스파르타 코딩클럽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 6-11: Spring JPA]]></title>
            <link>https://velog.io/@my_sql/WEEK6-SPRING-11</link>
            <guid>https://velog.io/@my_sql/WEEK6-SPRING-11</guid>
            <pubDate>Sun, 18 May 2025 15:35:58 GMT</pubDate>
            <description><![CDATA[<h1 id="jpa">JPA</h1>
<p><strong>JPA(Java Persistence API)</strong>는 자바 객체와 관계형 데이터베이스 간의 <strong>패러다임 불일치 문제</strong>를 해결하기 위해 만들어진 Java ORM(Object-Relational Mapping) 기술 표준입니다.</p>
<p>인터페이스만 제공하며, 실제 구현체로는 Hibernate, EclipseLink, OpenJPA 등이 있습니다.</p>
<p>이를 사용하면 Java 객체를 DataBase 테이블에 매핑하고, 객체를 조작하는 것만으로 DB 작업이 가능해집니다.</p>
<h2 id="동작-원리와-orm">동작 원리와 ORM</h2>
<p>ORM은 Java 클래스와 DB 테이블을 자동으로 매핑하여 객체 지향적으로 데이터베이스를 다룰 수 있게 해주는 것을 의미합니다.</p>
<p>JPA는 개발자가 직접 SQL을 작성하지 않고, Java 객체의 CRUD 작업을 메서드 호출로 처리할 수 있게 해줍니다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>반복적인 SQL, JDBC 코드 작성 없이 객체 조작만으로 CRUD 작업이 가능해 <strong>생산성</strong>이 높아집니다.</li>
<li>객체 필드가 변경되어도 SQL을 직접 수정할 필요 없이 JPA가 자동으로 처리하여 <strong>유지보수성</strong>이 높아집니다.</li>
<li>상속, 연관관계, 객체 그래프 탐색 등 객체 지향의 패턴을 데이터베이스에 자연스럽게 적용하여 <strong>패러다임 불일치 문제를 해결</strong>합니다.</li>
<li>1차 캐시, 쓰기 지연, 지연 로딩 및 즉시 로딩 등 성능 최적화 기능이 내장되어 있어 <strong>성능</strong> 측면에도 좋습니다.</li>
<li>특정 DB에만 종속되지 않고, 설정만 바꾸면 다른 DB로 쉽게 전환이 가능해 <strong>데이터베이스 독립성</strong>이라는 특징도 가지고 있습니다.</li>
</ul>
<h2 id="주요-개념">주요 개념</h2>
<ul>
<li>Entity
: 데이터베이스 테이블에 매핑되는 Java 클래스를 의미합니다.</li>
<li>Repository
: 엔티티의 저장, 조회, 삭제 등 DB 작업을 담당하는 인터페이스입니다. Spring Data JPA 에서는 <code>JpaRepository</code>를 상속받아 사용합니다.</li>
<li>EntityManager
: JPA의 핵심 객체로, 엔티티의 생명주기를 관리하고 쿼리를 실행하며 트랜잭션 처리를 담당합니다.</li>
<li>JPQL
: 객체지향 쿼리 언어입니다. SQL과 유사하지만, 엔티티 객체를 대상으로 쿼리를 작성할 수 있습니다.</li>
</ul>
<h2 id="jpa와-hibernate">JPA와 Hibernate</h2>
<p>JPA는 표준 명세이고, Hibernate는 그 구현체 중 하나입니다.
JPA를 사용하면 구현체를 갈아끼울 수 있어 <strong>벤더 종속성</strong>이 낮아집니다.</p>
<h2 id="hibernatedialect">hibernate.dialect</h2>
<p>Hibernate가 사용하는 데이터베이스 방언을 지정하는 설정입니다.</p>
<p>각 데이터 베이스는 SQL 표준을 지키지 않는 고유 기능이 존재해 Hibernate가 적합한 SQL을 생성하려면 dialect 지정이 필요합니다.</p>
<p>자료 및 코드 출처: 스파르타 코딩클럽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 6-10: Spring Servlet Filter]]></title>
            <link>https://velog.io/@my_sql/WEEK6-SPRING-10</link>
            <guid>https://velog.io/@my_sql/WEEK6-SPRING-10</guid>
            <pubDate>Sun, 18 May 2025 15:19:28 GMT</pubDate>
            <description><![CDATA[<h1 id="공통-관심사cross-cutting-concern">공통 관심사(Cross-Cutting Concern)</h1>
<p><strong>공통 관심사</strong>라는 것은, 여러 위치에서 반복적으로 사용되는 부가 기능(인증, 로깅, 보안 등)을 의미합니다. 비즈니스 로직과 별개로 동작하고 코드 중복과 유지보수 문제를 일으킬 수 있습니다.</p>
<p>대표적인 예시로는, 로그인이 되어있는 유저만 특정 API를 사용 가능한 것이 있습니다. 모든 컨트롤러 또는 메서드에 로그인 체크 로직을 넣게 된다면 코드가 지저분해지고, 변경이 될 경우에는 일괄로 수정하기가 힘들어집니다.</p>
<h1 id="filter의-필요성">Filter의 필요성</h1>
<p>위에서 이야기했던 공통 관심사를 한 곳에서 처리할 수 있는 것이 필요합니다. 모든 컨트롤러와 메서드에 로직을 넣으면 유지보수가 힘들어지는데, 한 곳에서 처리하게 된다면 코드의 중복도 줄어들게 되고, 유지보수성이 향상될 것입니다.</p>
<p>또한, 비즈니스 로직과 분리되어서 핵심 로직은 컨트롤러에 집중하고 부가적인 로직은 필터 또는 인터셉터에 위임할 수 있습니다.</p>
<h1 id="servlet-filter">Servlet Filter</h1>
<p>Web Application의 모든 HTTP 요청 또는 응답을 가로채 공통된 작업을 처리할 수 있는 표준 Servlet Component입니다.</p>
<h2 id="동작-순서">동작 순서</h2>
<p>클라이언트 요청 -&gt; Filter -&gt; DispatcherServlet -&gt; Interceptor -&gt; Controller</p>
<p>여기서 주목해야 할 점은 Filter가 DispatcherServlet보다 더 앞단에서 동작한다는 것입니다. Spring Security는 인증 및 인가 등의 보안 관련 처리를 가장 앞단에서 수행하며, 이를 통해 컨트롤러로 불필요한 요청을 차단해서 보안성과 성능을 모두 확보할 수 있습니다.</p>
<h2 id="특징">특징</h2>
<ul>
<li>모든 요청과 응답을 중앙 집중적으로 처리할 수 있습니다.</li>
<li>URL 패턴별로 적용이 가능합니다.</li>
<li>여러 개의 필터를 Filter Chain으로 순차 적용이 가능합니다.</li>
<li>doFilter() 메서드에서 전/후 처리를 하고 다음 필터로의 제어가 가능합니다.</li>
</ul>
<h2 id="구조와-동작">구조와 동작</h2>
<pre><code class="language-java">public class CustomFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 요청 전 처리
        chain.doFilter(request, response); // 다음 필터로 요청 전달
        // 응답 후 처리
    }
}</code></pre>
<ul>
<li>init(): 필터 초기화</li>
<li>doFilter(): 실제 요청 및 응답을 처리</li>
<li>destroy(): 필터 종료</li>
</ul>
<h2 id="filter-vs-interceptor">Filter vs. Interceptor</h2>
<table>
<thead>
<tr>
<th>특징</th>
<th>Filter</th>
<th>Interceptor</th>
</tr>
</thead>
<tbody><tr>
<td>관리 주체</td>
<td>웹 컨테이너</td>
<td>스프링 컨테이너</td>
</tr>
<tr>
<td>동작 위치</td>
<td>DispatcherServlet 이전</td>
<td>DispatcherServlet 이후</td>
</tr>
<tr>
<td>적용 대상</td>
<td>모든 HTTP 요청 및 응답</td>
<td>스프링 MVC Controller에 한정</td>
</tr>
<tr>
<td>용도</td>
<td>인코딩, 인증 및 인가, 로깅, 보안 등</td>
<td>인증 및 인가, 로깅, 권한 체크 등</td>
</tr>
<tr>
<td>객체 조작</td>
<td>Request/Response를 직접 조작 가능</td>
<td>직접 조작이 불가능, 데이터 가공 및 흐름 제어</td>
</tr>
</tbody></table>
<p>간단하게 말하면 Filter는 저수준, 전역적, 스프링과 무관한 기능에 적합하며, Interceptor는 스프링 MVC와 연동되는 세부로직에 적합합니다.</p>
<h1 id="filter-interface">Filter Interface</h1>
<p>Java Servlet에서 HTTP 요청과 응답을 가로채고, 이를 기반으로 하여 다양한 처리 작업을 수행하는 데에 사용되는 인터페이스입니다.</p>
<h2 id="구현-예시">구현 예시</h2>
<pre><code class="language-java">@Slf4j
public class CustomFilter implements Filter {
    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {
        // 주로 HttpServletRequest로 다운캐스팅하여 사용
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        log.info(&quot;request URI={}&quot;, requestURI);

        // 다음 필터(혹은 서블릿)로 제어를 넘김
        chain.doFilter(request, response);
    }
}</code></pre>
<p>만약 chain.doFilter()를 호출하지 않으면 다음 필터나 서블릿이 실행되지 않습니다. 모든 필터의 doFilter()가 끝나면 DispatcherServlet이 호출됩니다.</p>
<h2 id="등록-및-순서-지정">등록 및 순서 지정</h2>
<h3 id="spring-boot에서-filter-등록-방법">Spring Boot에서 Filter 등록 방법</h3>
<pre><code class="language-java">@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean&lt;Filter&gt; customFilter() {
        FilterRegistrationBean&lt;Filter&gt; filterRegistrationBean = new FilterRegistrationBean&lt;&gt;();
        filterRegistrationBean.setFilter(new CustomFilter());
        filterRegistrationBean.setOrder(1); // 숫자가 낮을수록 우선순위 높음
        filterRegistrationBean.addUrlPatterns(&quot;/*&quot;); // 전체 URL에 적용
        return filterRegistrationBean;
    }
}</code></pre>
<p>setOrder()로 Filter Chain 내 실행 순서를 지정할 수 있습니다. 여러 필터를 등록할 시 숫자가 작은 것부터 실행이 됩니다.</p>
<p>addUrlPatterns()로 필터를 적용할 URL 패턴을 지정합니다.</p>
<h3 id="여러-필터-등록-예시">여러 필터 등록 예시</h3>
<pre><code class="language-java">@Bean
public FilterRegistrationBean&lt;FirstFilter&gt; firstFilterRegister() {
    FilterRegistrationBean&lt;FirstFilter&gt; registrationBean = new FilterRegistrationBean&lt;&gt;(new FirstFilter());
    registrationBean.setOrder(1);
    return registrationBean;
}

@Bean
public FilterRegistrationBean&lt;SecondFilter&gt; secondFilterRegister() {
    FilterRegistrationBean&lt;SecondFilter&gt; registrationBean = new FilterRegistrationBean&lt;&gt;(new SecondFilter());
    registrationBean.setOrder(2);
    return registrationBean;
}</code></pre>
<p>실행 순서
: FirstFilter -&gt; SecondFilter -&gt; Servlet</p>
<h2 id="filter의-동작-흐름-요약">Filter의 동작 흐름 요약</h2>
<ol>
<li>Filter Interface를 implements 하여 필터를 구현합니다.</li>
<li>구현한 Filter를 Bean으로 등록(FilterRegistrationBean 활용)합니다.</li>
<li>HTTP 요청이 들어오면 등록된 순서대로 각 필터의 doFilter()가 호출됩니다.</li>
<li>모든 필터가 통과되면 Servlet이 실행됩니다.</li>
<li>필요시 init() 또는 destroy()로 Filter의 라이프사이클을 관리할 수 있습니다.</li>
</ol>
<blockquote>
<h4 id="">+@</h4>
</blockquote>
<ul>
<li>ServletRequest는 기능이 제한적이므로 보통 HttpServletRequest로 다운캐스팅해 사용합니다.</li>
<li>@WebFilter annotation으로도 필터 등록이 가능하지만, 순서 조정이 어려워 FilterRegistrationBean의 사용을 권장합니다.</li>
</ul>
<p>자료 및 코드 출처: 스파르타 코딩클럽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEEK 6-9: Spring JWT]]></title>
            <link>https://velog.io/@my_sql/WEEK6-SPRING-9</link>
            <guid>https://velog.io/@my_sql/WEEK6-SPRING-9</guid>
            <pubDate>Sun, 18 May 2025 14:59:27 GMT</pubDate>
            <description><![CDATA[<h1 id="token">Token</h1>
<p>Token은 Web Application이나 API에서 인증(Authentication)과 인가(Authorization)에 사용되는 디지털 문자열입니다. 사용자의 신원과 권한을 증명하고, 요청의 유효성을 검증하는 역할을 합니다. 인증된 사용자임을 확인하기 위한 고유한 서명을 포함하기 때문에 위조된 요청인지도 확인할 수 있습니다.</p>
<p>cookie처럼 서버가 아닌, 클라이언트에 저장이 되며, 서버는 토큰의 유효성만 검증합니다.</p>
<h2 id="동작-원리">동작 원리</h2>
<p>사용자가 아이디/비밀번호 등 자격 정보를 서버에 보내 로그인을 요청합니다. 여기서 서버는 DB등에서 자격 정보를 확인하고, 검증에 성공하면 토큰을 생성해 클라이언트에 전달합니다. 토큰에는 사용자 정보, 만료 시간, 서명 등이 포함되어있습니다.</p>
<p>클라이언트는 토큰을 안전하게(HttpOnly cookie, localStorage, Secure Storage 등) 저장합니다. 이후 모든 요청의 Authorization 헤더 등에 토큰을 포함하여 서버에 전달합니다.</p>
<p>서버는 토큰의 서명, 만료, 권한 등을 검증한 후 요청을 처리합니다. 만약, 토큰이 만료되면 refresh token 등으로 재발급하거나 재로그인이 필요하게 됩니다.</p>
<h2 id="장점과-단점">장점과 단점</h2>
<p><strong>장점</strong></p>
<ul>
<li>Stateless하기 때문에 확장성이 뛰어납니다.</li>
<li>웹, 모바일, IoT 등 다양한 클라이언트에서 사용이 가능합니다.</li>
<li>token 자체에 서명이 존재하기 때문에 위조나 변조를 방지할 수 있습니다.</li>
<li>서버 간 인증, SSO, OAuth 등과 연동이 쉽습니다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>세션 및 쿠키 방식보다 데이터의 용량이 커 트래픽이 증가합니다.</li>
<li>사용자의 정보 등이 들어가는 Payload 부분은 인코딩만 되어있고 암호화는 되어있지 않아 민감 정보를 저장할 수 없습니다.</li>
<li>탈취된다면 만료 전까지 악용이 가능하기 때문에, 짧은 만료시간이 필요합니다.</li>
</ul>
<h1 id="jwtjson-web-token">JWT(JSON Web Token)</h1>
<p>인증에 필요한 정보를 JSON 포맷으로 담고 서명까지 포함해 하나의 문자열로 만든 토큰입니다. </p>
<h2 id="구조">구조</h2>
<p><code>Header.Payload.Signature</code>와 같은 구조를 가지고 있으며, 각각이 Base64Url로 인코딩 되어있고, 점(.)으로 구분됩니다.</p>
<table>
<thead>
<tr>
<th>구조</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Header</td>
<td>토큰 타입(JWT)와 서명 알고리즘(HS256, RS256 등)</td>
</tr>
<tr>
<td>Payload</td>
<td>실제 인증/인가 정보(Claims), 사용자 정보, 권한, 만료시간 등</td>
</tr>
<tr>
<td>Signature</td>
<td>Header와 Payload를 서버의 Secret Key로 서명</td>
</tr>
</tbody></table>
<h3 id="payload---claims의-종류">Payload - Claims의 종류</h3>
<ul>
<li><p>Registered Claims</p>
<ul>
<li>iss(issuer): 발행자</li>
<li>exp(expiration time): 만료 시간</li>
<li>sub(subject): 제목</li>
<li>iat(issued At): 발생 시각</li>
<li>jti(JWT ID): 토큰의 고유 식별자</li>
</ul>
</li>
<li><p>Public Claims
공개용 정보 전달 목적으로, 충돌 방지가 필요함</p>
</li>
<li><p>Private Claims
당사자들 간 정보를 공유하기 위한 목적</p>
</li>
</ul>
<h2 id="동작-예시">동작 예시</h2>
<p>서버가 로그인 성공시 JWT를 생성합니다. 클라이언트는 JWT를 저장하며, API 요청 시 Authorization 헤더에 JWT를 포함(Bearer {JWT}와 같은 방식)합니다.</p>
<p>서버는 클라이언트로부터 받은 JWT의 서명, 만료, 권한 등을 검증 후 처리합니다.</p>
<h2 id="주의점">주의점</h2>
<p>Header와 Payload는 누구나 디코딩이 가능하므로 민감 정보는 저장하면 안됩니다. Singature와 같은 경우는 서버의 Secret Key로 생성하며 변조 시 서버가 검증에 실패합니다.</p>
<p>Secret Key는 충분히 복잡하고 안전하게 관리해야 합니다.</p>
<p>토큰이 탈취될 우려가 있으므로 HTTPS를 사용, HttpOnly/Secure 쿠키, 짧은 만료시간, Refresh Token의 활용 등이 필요합니다.</p>
<h2 id="유효성-검사">유효성 검사</h2>
<p>Signature부분을 Secret Key로 재계산하여 일치 여부를 확인할 수 있습니다. 이 경우, 만료 시간과 위변조 여부 등을 검증할 수 있으며, 검증 통과시 인증이 성공하고 요청을 처리합니다.</p>
<p>Secret Key로 재계산하기 때문에, secret key가 노출되지 않는 이상, 서버만이 올바른 Signature를 생성할 수 있습니다.</p>
<p>중요한 것은 JWT의 목적은 정보 보호가 아닌, 위조 방지입니다. 그렇기 때문에 Payload에는 절대로 민감 정보를 저장하면 안됩니다.</p>
<h2 id="access-token과-refresh-token">Access Token과 Refresh Token</h2>
<p>Access Token은 인증된 사용자의 정보와 권한이 담긴 토큰입니다. 탈취에 대응하기 위한 목적으로 유효 기간이 보통 짧습니다. API 요청시 Authorization 헤더에 포함하여 요청을 보냅니다.</p>
<p>Refresh Token의 경우, 유효 기간이 보다 짧은 Access Token이 만료되었을 때 재발급을 받기 위해 존재하는 토큰입니다. 그렇기 때문에 유효 기간이 Access Token 보다 깁니다. 보통 데이터베이스에 유저 정보와 같이 저장을 하게 됩니다.</p>
<p>하지만 Refresh Token의 경우에도 탈취의 우려가 있으므로, 토큰 재발급 횟수에 제한을 두거나 Refresh Token Rotation을 사용해 탈취를 예방하는 방법을 생각하며 JWT를 사용해야 합니다.</p>
<p>자료 및 코드 출처: 스파르타 코딩클럽</p>
]]></description>
        </item>
    </channel>
</rss>