<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>MoonCommit DevLog</title>
    <link>https://mooncommit-dev.tistory.com/</link>
    <description>mooncommit-dev 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Sat, 1 Aug 2026 08:21:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mooncommit</managingEditor>
    <item>
      <title>[이커머스 백오피스] 프로젝트 시작과 설계 기록</title>
      <link>https://mooncommit-dev.tistory.com/30</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로젝트 시작 &quot;이커머스 백오피스 서비스&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;이번 프로젝트는 관리자 회원가입/인증, 관리자 관리, 고객 관리, 상품 관리, 주문 관리 기능을 포함하고 있고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;나는 관리자 부분을 맡았다. 프로젝트 초반에는 기능 구현보다 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;협업 방식과 공통 구조를 먼저 맞추는 일이 더 중요했고, 이번 글에서는 그 과정을 정리해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 프로젝트를 시작하고 가장 먼저 한 일&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 한 작업은 테이블 명세서, ERD, API 명세서를 정리하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 읽어보니 관리자, 고객, 상품, 주문까지 도메인이 여러 개였고 각 도메인이 서로 연결되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 먼저 아래 순서로 전체 구조를 잡았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테이블 명세서 작성&lt;/li&gt;
&lt;li&gt;ERD 작성&lt;/li&gt;
&lt;li&gt;API 명세서 작성&lt;/li&gt;
&lt;li&gt;GitHub 저장소 생성 및 협업 세팅&lt;/li&gt;
&lt;li&gt;공통 작업 진행&lt;/li&gt;
&lt;li&gt;역할 분담 후 각자 브랜치에서 개발&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 거치면서 느낀 점은 구현 전에 명세를 먼저 정리하는 과정이 생각보다 훨씬 중요하다는 것이었다.&lt;br /&gt;코드는 나중에 바꿀 수 있어도, 구조를 잘못 잡으면 기준이 명확하지 않아 수정 범위가 너무 커질 것 같다는 생각이 들었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  테이블 명세서, ERD, API 명세서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 내가 참여하지는 못했지만 프로젝트를 진행하며 왜 첫 과정으로 했는지 이유를 알 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  관리자 테이블 명세서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름, 이메일, 비밀번호, 전화번호, 역할, 상태, 승인일시, 거부일시, 거부사유&lt;/li&gt;
&lt;li&gt;테이블 명세서로 정리되어 있으니, 어떤 필드가 필요한지 명확하게 보였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  ERD&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCKuo/dJMcad2Ocvk/ieWedWwOsfKO8zkwIbAkd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCKuo/dJMcad2Ocvk/ieWedWwOsfKO8zkwIbAkd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCKuo/dJMcad2Ocvk/ieWedWwOsfKO8zkwIbAkd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCKuo%2FdJMcad2Ocvk%2FieWedWwOsfKO8zkwIbAkd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 도메인이 어떤 관계를 가지는지 생각할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상품은 등록한 관리자와 연결된다.&lt;/li&gt;
&lt;li&gt;주문은 고객과 연결된다.&lt;/li&gt;
&lt;li&gt;CS 주문의 경우 주문 등록 관리자와도 연결된다.&lt;/li&gt;
&lt;li&gt;주문은 주문 상품과 연결된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  관리자 API 명세서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;승인, 거부, 다건 조회, 단건 조회, 정보 수정, 역할 수정, 상태 수정, 삭제&lt;/li&gt;
&lt;li&gt;내 프로필 조회, 내 프로필, 비밀번호 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 엔티티를 만들 때 &quot;이 필드가 왜 필요한지&quot;, &quot;이 상태값이 어느 API에서 쓰이는지&quot; 쉽게 이해할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  GitHub 저장소를 만들고 협업 방식을 맞춘 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명세 작업 이후 다시 합류 하여 GitHub 저장소를 만들고 협업 환경을 세팅했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 엔티티, enum, 예외 처리, BaseEntity 같은 공통 작업을 먼저 맞춘 뒤, 역할을 나누어 각자 브랜치를 생성하도록 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식이 중요했던 이유는 공통 기준 없이 각자 개발을 시작하면 나중에 코드 스타일과 구조가 서로 달라질 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 사람은 상태값을 문자열로 저장하고&lt;/li&gt;
&lt;li&gt;어떤 사람은 enum으로 만들고&lt;/li&gt;
&lt;li&gt;어떤 사람은 예외 RuntimeException으로 던지고&lt;/li&gt;
&lt;li&gt;어떤 사람은 ResponseEntity를 직접 반환하는 식이면&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 전체 코드가 통일되지 않게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  GitHub 기본 흐름&lt;/h4&gt;
&lt;pre id=&quot;code_1777051499716&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git init
git remote add origin &amp;lt;원격저장소주소&amp;gt;
git checkout -b master
git add .
git commit -m &quot;init: 프로젝트 초기 세팅&quot;
git push -u origin main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 각자 역할에 따른 브랜치를 만들어서 작업했다.&lt;/p&gt;
&lt;pre id=&quot;code_1777051591156&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git checkout master
git pull origin master
git checkout -b feat/admin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 후에는 아래처럼 커밋하고 push한 뒤 PR을 생성하는 방식으로 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1777051670149&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git add .
git commit -m &quot;feat : admin ... 추가&quot;
git push -u origin feat/admin&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  README에 정리한 협업 규칙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업 과정에서 혼란을 줄이기 위해 README에 규칙을 정리했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master 브랜치에 직접 push 하지 않는다.&lt;/li&gt;
&lt;li&gt;각자 브랜치를 생성하여 작업한다.&lt;/li&gt;
&lt;li&gt;작업 완료 후 Pull Request로 병합한다.&lt;/li&gt;
&lt;li&gt;작업 전 최신 master 브랜치를 pull 받는다.&lt;/li&gt;
&lt;li&gt;커밋 타입은 &lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;feat&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;fix&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;refactor&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;를 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;커밋 메시지는 &lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;커밋타입 : 제목 &lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;형식으로 작성한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;커밋은 기능 단위로 나눈다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;클래스 작명은 &lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412); color: #1a1c1f; text-align: start;&quot;&gt;도메인 + 액션 + 요청/응답 + Dto&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형식으로 작성한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  공통 엔티티 BaseEntity를 먼저 만든 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 하다 보면 거의 모든 테이블에서 공통으로 필요한 필드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 생성 시간, 수정 시간 &amp;rarr; 모든 엔티티마다 반복해서 작성하지 않기 위해 공통 부모 클래스로 BaseEntity를 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1777052431472&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;createdAt : 데이터가 처음 생성된 시간&lt;/li&gt;
&lt;li&gt;updatedAt : 데이터가 마지막으로 수정된 시간&lt;/li&gt;
&lt;li&gt;여러 엔티티에서 공통으로 사용할 수 있는 시간 필드를 한 곳에서 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  문자열 대신 enum을 사용한 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 도메인을 설계하면서 역할과 상태를 문자열이 아니라 enum으로 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  관리자 역할 enum&lt;/p&gt;
&lt;pre id=&quot;code_1777053201241&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum AdminRole {
    // 슈퍼 관리자
    SUPER_ADMIN,
    // 운영 관리자
    OPERATIONS_ADMIN,
    // CS 관리자
    CS_ADMIN
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  관리자 상태 enum&lt;/p&gt;
&lt;pre id=&quot;code_1777053225423&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum AdminStatus {
    // 활성
    ACTIVE,
    // 비활성
    INACTIVE,
    // 정지
    SUSPENDED,
    // 승인대기
    PENDING,
    // 거부
    REJECTED
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;허용된 값만 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;오타를 줄일 수 있다.&lt;/li&gt;
&lt;li&gt;상태 비교가 더 명확해진다.&lt;/li&gt;
&lt;li&gt;나중에 비즈니스 로직을 작성할 때 가독성이 좋아진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1777053368708&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예를 들면
if (admin.getStatus() != AdminStatus.ACTIVE) {
    throw new AdminNotApprovedException();
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  관리자 엔티티를 설계하면서 배운 점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 과제에서 관리자는 회원가입 후 바로 사용 가능한 것이 아니라 승인 과정을 거쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고해 아래와 같이 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1777053640857&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Entity
@Table(name = &quot;admins&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Admin extends BaseEntity {

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 관리자 고유 식별자

    @Column(nullable = false, length = 20)
    private String name; // 관리자 이름

    @Column(nullable = false, unique = true, length = 255)
    private String email; // 로그인에 사용하는 이메일

    @Column(nullable = false, length = 255)
    private String password; // 암호화된 비밀번호 저장

    @Column(nullable = false, length = 20)
    private String phone; // 전화번호

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private AdminRole role; // 슈퍼 관리자 / 운영 관리자 / CS 관리자 구분

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private AdminStatus status; // 활성 / 비활성 / 정지 / 승인대기 / 거부 상태 관리

    private LocalDateTime approvedAt; // 관리자 승인 시각
    private LocalDateTime rejectedAt; // 관리자 거부 시간
    private String rejectionReason; // 관리자 거부 사유

    public Admin(String name, String email, String password, String phone, AdminRole role, AdminStatus status) {
        this.name = name;
        this.email = email;
        this.password = password;
        this.phone = phone;
        this.role = role;
        this.status = status;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;⭐️ 예외 처리를 먼저 설계한 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 가장 크게 배운 것 중 하나는 예외 처리도 설계의 일부 라는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 로그인만 생각해봐도 케이스가 정말 많았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일이 존재하지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;비밀번호가 다를 수 있다.&lt;/li&gt;
&lt;li&gt;승인 대기 상태일 수 있다.&lt;/li&gt;
&lt;li&gt;거부된 상태일 수 있다.&lt;/li&gt;
&lt;li&gt;정지된 상태일 수 있다.&lt;/li&gt;
&lt;li&gt;비활성 상태일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로직은 항상 성공 케이스와 실패 케이스를 함께 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 공통 부모 예외를 먼저 만들고, 그 아래에 도메인별 예외를 세분화하는 구조를 잡았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예외 처리의 부모 클래스 : ServiceException&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 비즈니스 예외의 부모가 되는 클래스이다.&lt;/p&gt;
&lt;pre id=&quot;code_1777054391021&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
public class ServiceException extends RuntimeException {

    private final HttpStatus status;

    public ServiceException(HttpStatus status, String message) {
        super(message);
        this.status = status;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 계층에서 발생하는 비즈니스 예외를 공통 형식으로 처리하기 위한 커스텀 예외 클래스&lt;/li&gt;
&lt;li&gt;부모 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스를 만든 이유는, 예외가 발생했을 때 단순히 메시지만 전달하는 것이 아니라 HTTP 상태코드와 메시지를 함께 담기 위해서였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 하위 예외 클래스들은 각각 자신에게 맞는 상태코드와 메시지를 지정할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  관리자 관련 예외 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 도메인에서 필요한 예외를 다음과 같이 정리했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;PasswordMismatchException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;비밀번호가 일치하지 않습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;AdminNotFoundException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 404, &quot;관리자를 찾을 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;DuplicateEmailException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;이미 사용중인 이메일입니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;InvalidPasswordException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;이메일 또는 비밀번호가 올바르지 않습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;UnauthorizedException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 401, &quot;로그인이 필요합니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;TokenMissingException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 401, &quot;인증 토큰이 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;TokenExpiredException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 401, &quot;인증 토큰이 만료되었습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;AccessDeniedException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 403, &quot;접근 권한이 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;AdminAlreadyProcessedException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;이미 처리된 관리자입니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;InvalidAdminInputException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;입력값이 올바르지 않습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;AdminNotApprovedException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 401, &quot;승인되지 않은 관리자입니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;SelfRoleChangeNotAllowedException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;자신의 역할은 변경할 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;InvalidRoleException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;유효하지 않은 역할입니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;InvalidAdminStatusException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;변경할 수 없는 상태입니다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 예시&lt;/p&gt;
&lt;pre id=&quot;code_1777054649130&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AdminNotFoundException extends ServiceException {
    public AdminNotFoundException() {
        super(HttpStatus.NOT_FOUND, &quot;관리자를 찾을 수 없습니다.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  고객, 상품, 주문 관련 예외도 도메인별로 분리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외는 관리자 도메인만 있는 것이 아니라 고객, 상품, 주문 도메인에도 각각 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 고객&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;CustomerNotFoundException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 404, &quot;고객을 찾을 수 없습니다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 상품 관련&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;ProductNotFoundException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 404, &quot;상품을 찾을 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;DiscontinuedProductException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;단종된 상품은 주문할 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;SoldOutProductException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;품절된 상품은 주문할 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;OutOfStockException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;재고가 부족합니다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 주문 관련&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1a1c1f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;OrderNotFoundException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 404, &quot;주문을 찾을 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;InvalidOrderStatusException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;주문 상태를 변경할 수 없습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.22559 -0.00131416 -0.00642684 / 0.0329412);&quot;&gt;OrderCancelNotAllowedException&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 400, &quot;준비중 상태에서만 취소할 수 있습니다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 도메인별로 나누어서 정리하니 예외 이름만 봐도 어떤 상황인지 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 실패여도 이유는 각각 다르기 때문에 예외도 구체적으로 나눠야 한다는 걸 배웠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  HTTP 상태코드도 함께 이해하기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/svqvZ/dJMcaarx1dj/BGDe7dKxXm4UsnvVQlsoBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/svqvZ/dJMcaarx1dj/BGDe7dKxXm4UsnvVQlsoBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/svqvZ/dJMcaarx1dj/BGDe7dKxXm4UsnvVQlsoBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsvqvZ%2FdJMcaarx1dj%2FBGDe7dKxXm4UsnvVQlsoBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;배운 점&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;지금은 공통으로 사용하는 예외 처리, 엔티티, enum 같은 기본 구조를 잡는 단계까지 진행했다. 이 과정에서 개인 프로젝트와 팀 프로젝트는 시작 방식부터 다르다는 걸 많이 느꼈다. 개인 프로젝트에서는 테이블 구조를 대략만 잡고 바로 구현에 들어가도 괜찮았는데, 팀 프로젝트에서는 기능 구현 전에 전체 구조와 공통 규칙을 먼저 맞추는 게 훨씬 중요했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;특히 예외 처리를 커스텀 방식으로 설계해본 건 처음이라 새롭게 배운 점이 많았다. 그리고 공통 구조를 먼저 잡아두니깐 아직 기능 코드를 시작하지 않았지만, 틀이 잡히는 느낌이어서 신기했다. 생각해야 하는 범위가 넓어져서 어렵기도 했지만, 이런 식으로 구조를 먼저 고민해본 경험 자체가 나한테는 의미 있고 성장할 수 있는 기회라고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1c1f; text-align: start;&quot;&gt;정말 뜻깊은 과정이었다 ◡̈⋆*&lt;/span&gt;&lt;/p&gt;</description>
      <category>spring boot</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <category>프로젝트</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/30</guid>
      <comments>https://mooncommit-dev.tistory.com/30#entry30comment</comments>
      <pubDate>Sat, 25 Apr 2026 03:54:17 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot 일정 관리 트러블 슈팅</title>
      <link>https://mooncommit-dev.tistory.com/29</link>
      <description>&lt;h2&gt;Trouble Shooting List&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; Port already in use&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; Schedule 생성자에서 값이 null로 저장되는 문제&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 400 Bad Request&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; author 필드 제거 후 연관관계 적용&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; Schedule 생성 시 userId로 User를 못 찾는 문제&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 세션 검증 적용 후 userId 제거&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; (User) 형변환(캐스팅) 오류&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;&quot; disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 수정/삭제 시 본인 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. Port already in use&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Web server failed to start. Port 8081 was already in use.&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;  Cause&lt;/h3&gt;
&lt;p&gt;이전에 실행했던 서버가 종료되지 않은 채로 다시 실행하려고 해서 발생&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsof -i :8081
kill -9 [PID숫자]&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2. Schedule 생성자에서 값이 null로 저장되는 문제&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;p&gt;일정 생성 시 title, content, author가 null로 저장됨&lt;/p&gt;
&lt;h3&gt;  Cause&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Schedule&lt;/code&gt; 생성자에서 필드에 값을 할당하지 않음&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 문제 코드
public Schedule(String title, String content, String author) {
    super(); // title, content, author 할당 안 함
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public Schedule(String title, String content, String author) {
    this.title = title;
    this.content = content;
    this.author = author;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 400 Bad Request&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &amp;quot;status&amp;quot;: 400,
    &amp;quot;error&amp;quot;: &amp;quot;Bad Request&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;  Cause&lt;/h3&gt;
&lt;p&gt;포스트맨에서 GET 요청을 POST로 잘못 보냄&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;p&gt;포스트맨에서 HTTP 메서드를 GET으로 변경&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. author 필드 제거 후 연관관계 적용&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;p&gt;Lv2에서 &lt;code&gt;Schedule&lt;/code&gt; 엔티티의 &lt;code&gt;author(String)&lt;/code&gt; 필드를 &lt;code&gt;User&lt;/code&gt; 연관관계로 변경하면서 Service, DTO 등 여러 곳에서 오류 발생&lt;/p&gt;
&lt;h3&gt;  Cause&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;author&lt;/code&gt; 필드를 제거하고 &lt;code&gt;User&lt;/code&gt; 객체로 바꾸면서 연관된 코드들을 전부 수정해야 했음&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Schedule&lt;/code&gt; 엔티티에서 &lt;code&gt;author&lt;/code&gt; 제거 후 &lt;code&gt;@ManyToOne&lt;/code&gt; 연관관계 추가&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = &amp;quot;user_id&amp;quot;)
private User user;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DTO&lt;/code&gt;에서 &lt;code&gt;String author&lt;/code&gt; → &lt;code&gt;Long userId&lt;/code&gt;로 변경&lt;/li&gt;
&lt;li&gt;Service에서 &lt;code&gt;userId&lt;/code&gt;로 &lt;code&gt;User&lt;/code&gt; 조회 후 &lt;code&gt;Schedule&lt;/code&gt; 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. Schedule 생성 시 userId로 User를 못 찾는 문제&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Schedule&lt;/code&gt; 생성 시 &lt;code&gt;request.getUserId()&lt;/code&gt;로 &lt;code&gt;User&lt;/code&gt;를 찾아야 하는데 &lt;code&gt;UserRepository&lt;/code&gt;가 &lt;code&gt;ScheduleService&lt;/code&gt;에 없어서 오류 발생&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ScheduleService&lt;/code&gt;에 &lt;code&gt;UserRepository&lt;/code&gt; 추가&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private final ScheduleRepository scheduleRepository;
private final UserRepository userRepository; // 추가&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6. 세션 검증 적용 후 userId 제거&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;p&gt;Lv4 로그인 구현 후 기존에 클라이언트가 &lt;code&gt;userId&lt;/code&gt;를 직접 보내던 방식이 보안상 위험함&lt;/p&gt;
&lt;h3&gt;  Cause&lt;/h3&gt;
&lt;p&gt;클라이언트가 다른 사람의 &lt;code&gt;userId&lt;/code&gt;를 넣어서 요청할 수 있음&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;userId&lt;/code&gt; 대신 세션에서 로그인한 유저를 꺼내서 사용&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Controller
HttpSession session = servletRequest.getSession();
User user = (User) session.getAttribute(&amp;quot;loginUser&amp;quot;);
scheduleService.save(request, user);&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Service - 기존
User user = userRepository.findById(request.getUserId()).orElseThrow();

// Service - 변경 후
public CreateScheduleResponseDto save(CreateScheduleRequestDto request, User user) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. (User) 형변환(캐스팅) 오류&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;session.getAttribute(&amp;quot;loginUser&amp;quot;)&lt;/code&gt; 반환 타입이 &lt;code&gt;Object&lt;/code&gt;라서 &lt;code&gt;User&lt;/code&gt; 타입으로 바로 사용 불가&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;(User)&lt;/code&gt;로 캐스팅해서 사용&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;User user = (User) session.getAttribute(&amp;quot;loginUser&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. 수정/삭제 시 본인 확인&lt;/h2&gt;
&lt;h3&gt;  Problem&lt;/h3&gt;
&lt;p&gt;로그인한 유저가 다른 사람의 일정을 수정/삭제할 수 있는 문제&lt;/p&gt;
&lt;h3&gt;✅ Solution&lt;/h3&gt;
&lt;p&gt;Service에서 로그인한 유저와 일정 작성자의 이메일을 비교&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;if (!user.getEmail().equals(schedule.getUser().getEmail())) {
    throw new RuntimeException(&amp;quot;본인 일정만 수정할 수 있습니다.&amp;quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt; &lt;br&gt;모든 트러블 슈팅 해결 완료⎝⍥⎠&lt;/p&gt;</description>
      <category>Spring</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <category>트러블슈팅</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/29</guid>
      <comments>https://mooncommit-dev.tistory.com/29#entry29comment</comments>
      <pubDate>Thu, 23 Apr 2026 10:20:57 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] Cookie / Session (로그인 인증)</title>
      <link>https://mooncommit-dev.tistory.com/28</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot Lv4 로그인 기능 구현을 위해 &lt;span style=&quot;color: #1a5490;&quot;&gt;Cookie와 Session 개념&lt;/span&gt;을 공부했다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Session이 필요한가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 기본적으로 무상태(Stateless) 프로토콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 서버는 각 요청을 독립적으로 처리하기 때문에 이전 요청을 기억하지 못한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776823142600&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;✔️ 1번 요청: &quot;나 스파르타야, 로그인할게&quot;
✔️ 2번 요청: &quot;나 스파르타야, 장바구니 보여줘&quot;
⭐️ 서버 입장에서는 1번이랑 2번이 같은 사람인지 알 수 없다  &lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래서 &quot;나는 로그인한 사람이야!&quot; 를 증명할 수단이 필요 &amp;rarr; Session&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cookie vs Session&lt;/h2&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BRrOR/dJMcagFgSFD/EdSl8ktE1bjGZhCUjJqpi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BRrOR/dJMcagFgSFD/EdSl8ktE1bjGZhCUjJqpi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BRrOR/dJMcagFgSFD/EdSl8ktE1bjGZhCUjJqpi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBRrOR%2FdJMcagFgSFD%2FEdSl8ktE1bjGZhCUjJqpi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 비유로 알아보기&lt;/p&gt;
&lt;pre id=&quot;code_1776823957641&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  세션 = 놀이공원 입장 명부 (서버에 있음)
  쿠키 = 내 손목에 찍힌 도장 (브라우저에 있음)

  손목 도장(쿠키)을 보여주면 서버가 명부(세션)에서 확인하는 것!&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 전체 흐름&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1776824122834&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 클라이언트가 이메일 + 비밀번호로 로그인 요청
2. 서버가 확인 후 세션 생성 (세션 ID : abc123)
3. 서버가 세션 ID를 쿠키에 담아서 브라우저에 전달
4. 이후 요청마다 브라우저가 쿠키(세션 ID)를 자동으로 서버에 전송
5. 서버가 세션 ID로 &quot;이 사람 로그인한 사람이구나!&quot; 확인
6. 로그아웃 시 세션 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐️ 핵심 : 기억하는 건 세션(서버), 전달해주는 건 쿠키(브라우저)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;HttpSession에 대해서 알고 가자&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;session 객체는 HttpSession 타입이다.&lt;br /&gt;setAttribute, getAttribute, invalidate 메서드는 직접 구현할 필요 없이, 기본으로 제공된다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  HttpSession 기본 제공 메서드&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H4CYG/dJMcahKVTta/hCkxHPx23EhHnMRicsewPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H4CYG/dJMcahKVTta/hCkxHPx23EhHnMRicsewPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H4CYG/dJMcahKVTta/hCkxHPx23EhHnMRicsewPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH4CYG%2FdJMcahKVTta%2FhCkxHPx23EhHnMRicsewPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  주요 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1776826612494&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HttpSession session = request.getSession();

// 세션에 데이터 저장
session.setAttribute(&quot;loginUser&quot;, user);

// 세션에서 데이터 꺼내기
session.getAttribute(&quot;loginUser&quot;);

// 세션 전체 삭제 (로그아웃)
session.invalidate();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  getSession() vs getSession(false)&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1776826535766&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// getSession()        &amp;rarr; 세션이 없으면 새로 생성
// getSession(false)   &amp;rarr; 세션이 없으면 null 반환 (생성 X)

HttpSession session = request.getSession(false); // 로그인 확인 시 사용&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;getSession() &amp;rarr; 세션 있으면 가져오고, 없으면 새로 생성&lt;br /&gt;getSession(false) &amp;rarr; 세션 있으면 가져오고, 없으면 null 반환&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 구현 패턴&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 로그인 API&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1776826827389&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/login&quot;)
public ResponseEntity&amp;lt;?&amp;gt; login(
        @RequestBody LoginRequestDto request,
        HttpServletRequest servletRequest) {

    // 세션 생성
    HttpSession session = servletRequest.getSession();

    // 로그인 성공 시 세션에 유저 저장
    session.setAttribute(&quot;loginUser&quot;, user);

    return ResponseEntity.ok(...);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 세션 확인 (로그인 필요한 API)&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1776826866361&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping
public ResponseEntity&amp;lt;?&amp;gt; getSchedules(HttpServletRequest servletRequest) {

    HttpSession session = servletRequest.getSession(false);

    if (session == null || session.getAttribute(&quot;loginUser&quot;) == null) {
        throw new RuntimeException(&quot;로그인이 필요합니다.&quot;);
    }

    // 이후 로직 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 로그아웃 API&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1776826904055&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/logout&quot;)
public ResponseEntity&amp;lt;Void&amp;gt; logout(HttpServletRequest servletRequest) {
    HttpSession session = servletRequest.getSession(false);
    if (session != null) {
        session.invalidate(); // 세션 삭제
    }
    return ResponseEntity.ok().build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Lv4 로그인 구현 실습&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. UserRepository에 이메일 조회 메서드 추가&lt;/p&gt;
&lt;pre id=&quot;code_1776840367209&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;User&amp;gt; findByEmail(String email);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA가 메서드 이름만 보고 자동으로 쿼리를 만들어 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. LoginRequestDto 생성&lt;/p&gt;
&lt;pre id=&quot;code_1776840672916&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LoginRequestDto {

    private String email;
    private String password;

    public String getEmail() {
        return email;
    }

    public String getPassword() {
        return password;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일, 비밀번호 필드 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. UserController에 로그인 API 추가&lt;/p&gt;
&lt;pre id=&quot;code_1776841455437&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/login&quot;)
public ResponseEntity&amp;lt;String&amp;gt; login(
        @RequestBody LoginRequestDto request,
        HttpServletRequest servletRequest) {
        
        // Service에 이메일, 비밀번호 확인 요청해서 유저 가져오기
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. UserService에 Login 메서드 추가&lt;/p&gt;
&lt;pre id=&quot;code_1776841798381&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Login
@Transactional(readOnly = true)
public User login(LoginRequestDto request) {
    // 1. request 값 꺼내오기
    String email = request.getEmail();
    String password = request.getPassword();
    
    // 2. email로 유저 찾기
    User findUser = userRepository.findByEmail(email)
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;유저를 찾을 수 없습니다.&quot;));
    
    // 3. 찾은 유저 비밀번호 가져오기
    String findPassword = findUser.getPassword();
    
    // 4. 비밀번호 비교하기 (틀리면 예외처리)
    if (!password.equals(findPassword)) {
        throw new RuntimeException(&quot;비밀번호가 맞지 않습니다.&quot;);
    }
    
    // 5. 유저 반환
    return findUser;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. UserController - 세션 저장&lt;/p&gt;
&lt;pre id=&quot;code_1776842074656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. service에서 찾은 유저 가져오기
User user = userService.login(request);

// 2. 세션 가져오기 (없으면 새로 생성)
HttpSession session = servletRequest.getSession();

// 3. 세션에 유저 저장하기
session.setAttribute(&quot;loginUser&quot;, user); // 키, 값

// 4. 반환 객체 만들기
ResponseEntity&amp;lt;String&amp;gt; response = new ResponseEntity&amp;lt;&amp;gt;(&quot;로그인 성공&quot;, HttpStatus.OK);

// 5. 반환
return response;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Cookie는 servletRequest.getSession() 호출하면 Spring이 자동으로&lt;br /&gt;세션 생성, 세션 ID를 쿠키에 담아서 브라우저에 전달&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Postman 테스트&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIWfoB/dJMcaaSAr8J/OJgohzzqk10mb1HaJVfYOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIWfoB/dJMcaaSAr8J/OJgohzzqk10mb1HaJVfYOk/img.png&quot; data-alt=&quot;users 테이블 정보&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIWfoB/dJMcaaSAr8J/OJgohzzqk10mb1HaJVfYOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIWfoB%2FdJMcaaSAr8J%2FOJgohzzqk10mb1HaJVfYOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1534&quot; height=&quot;232&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;users 테이블 정보&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLUDCT/dJMcadhr4Ic/T2KK6cy0Z1SMSVE0tGzLUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLUDCT/dJMcadhr4Ic/T2KK6cy0Z1SMSVE0tGzLUK/img.png&quot; data-alt=&quot;로그인 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLUDCT/dJMcadhr4Ic/T2KK6cy0Z1SMSVE0tGzLUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLUDCT%2FdJMcadhr4Ic%2FT2KK6cy0Z1SMSVE0tGzLUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1612&quot; height=&quot;976&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;976&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그인 성공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1609&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beLSIK/dJMb99TElBK/af3KNDNugg1KuAWsWxcXr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beLSIK/dJMb99TElBK/af3KNDNugg1KuAWsWxcXr1/img.png&quot; data-alt=&quot;로그인 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beLSIK/dJMb99TElBK/af3KNDNugg1KuAWsWxcXr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeLSIK%2FdJMb99TElBK%2Faf3KNDNugg1KuAWsWxcXr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1609&quot; height=&quot;978&quot; data-origin-width=&quot;1609&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그인 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oX72m/dJMcabcQavy/mw5DVcamEPjq83xaT9ZTcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oX72m/dJMcabcQavy/mw5DVcamEPjq83xaT9ZTcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oX72m/dJMcabcQavy/mw5DVcamEPjq83xaT9ZTcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoX72m%2FdJMcabcQavy%2Fmw5DVcamEPjq83xaT9ZTcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1002&quot; height=&quot;60&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실습하며 헷갈린 부분 정리&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ HttpServletRequest가 뭔지?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 서버에 보낸 요청 정보를 담고 있는 객체&lt;/li&gt;
&lt;li&gt;세션, 쿠키, 헤더 등을 꺼낼 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 세션 처리는 Controller vs Service?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 처리는 Controller 담당&lt;/li&gt;
&lt;li&gt;Service는 비즈니스 로직만 담당하고, 세션 저장은 Controller가 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ session.setAttribute(&quot;키&quot;, 값)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;키&quot; &amp;rarr; 세션에 저장할 때 쓰는 이름표, 나중에 꺼낼 때 이 이름으로 찾는다.&lt;/li&gt;
&lt;li&gt;값 &amp;rarr; 실제로 저장할 데이터, 이번 실습에는 유저 객체를 저장&lt;/li&gt;
&lt;li&gt;예시 : session.setAttribute(&quot;loginUser&quot;, user) &amp;rarr; &quot;loginUser&quot;라는 이름으로 user 객체 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ getSession() vs getSession(false)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getSession() &amp;rarr; 세션 없으면 새로 생성 (로그인할 때)&lt;/li&gt;
&lt;li&gt;getSession(false) &amp;rarr; 세션 없으면 null 반환 (다른 API에서 확인할 때)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;느낀 점&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 Lv4 로그인 기능을 구현하면서 처음에는 막막할 줄 알았는데, Cookie와 Session 개념을 먼저 공부하고 실습에 들어가니 생각보다 어렵지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어려웠다기보다는 처음 써보는 기능이라 코드에 어떻게 녹여야 할지 감이 잡히지 않았다. 특히 HttpServletRequest가 왜 필요한지 이해하는 데 시간이 걸렸는데, 여기저기 알아본 결과 &quot;클라이언트가 보낸 요청 봉투&quot; 라고 생각하니 자연스럽게 이해가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;session.setAttribute()도 처음엔 낯설었지만 키와 값의 개념을 이해하고 나서는 전혀 어렵지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실습때는 바로 코드부터 보지 않고 개념부터 내가 헷갈리지 않을 정도로 잡고 시작했더니 전보다 수월했다. 앞으로도 처음 접하는 개념은 먼저 확실히 이해하고 코드로 넘어가는 방식으로 공부법을 바꿔야겠다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; ️ NEXT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수정/삭제 API에 세션 검증 추가 (비밀번호 검증 &amp;rarr; 로그인 여부 확인으로 변경)&lt;/li&gt;
&lt;li&gt;지금까지 배운 내용을 새로운 주제로 처음부터 혼자 구현해보기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;TIL 끝⎝⍥⎠⎝⍢⎠⋆◡̎⋆  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>cookie</category>
      <category>Session</category>
      <category>Spring</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <category>일정관리앱</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/28</guid>
      <comments>https://mooncommit-dev.tistory.com/28#entry28comment</comments>
      <pubDate>Wed, 22 Apr 2026 17:28:58 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] Spring 숙련 - JPA &amp;amp; Cookie/Session 정리</title>
      <link>https://mooncommit-dev.tistory.com/27</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;To Do List&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ResponseEntity 이해&lt;/li&gt;
&lt;li&gt;JPA 어노테이션 (@Entity, @Id, @GeneratedValue 등)&lt;/li&gt;
&lt;li&gt;연관관계 (@ManyToOne, @JoinColumn)&lt;/li&gt;
&lt;li&gt;Cookie/Session 인증/인가&lt;/li&gt;
&lt;li&gt;Bean Validation (@Valid 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Part 1 : ResponseEntity&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개념 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서 API를 만들 때, 클라이언트에게 응답을 돌려줄 때 쓰는 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 그냥 값만 리턴할 수도 있지만, ResponseEntity를 쓰면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 상태 코드 (200, 400, 404 등)&lt;/li&gt;
&lt;li&gt;응답 헤더&lt;/li&gt;
&lt;li&gt;응답 바디 (데이터)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지를 한 번에 제어할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 예시 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ResponseEntity 없이&lt;/p&gt;
&lt;pre id=&quot;code_1776319304946&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/user/{id}&quot;)
public UserResponseDto getUser(@PathVariable Long id) {
    return userService.getUser(id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 데이터는 주지만, HTTP 상태 코드를 직접 제어 못함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ResponseEntity 사용&lt;/p&gt;
&lt;pre id=&quot;code_1776319350029&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/user/{id}&quot;)
public ResponseEntity&amp;lt;UserResponseDto&amp;gt; getUser(@PathVariable Long id) {
    UserResponseDto user = userService.getUser(id);
    return ResponseEntity.ok(user); // 200 OK + 데이터
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  자주 쓰는 패턴&lt;/p&gt;
&lt;pre id=&quot;code_1776319407233&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 200 OK
return ResponseEntity.ok(data);

// 201 Created (생성 성공)
return ResponseEntity.status(HttpStatus.CREATED).body(data);

// 400 Bad Request
return ResponseEntity.badRequest().body(&quot;잘못된 요청입니다&quot;);

// 404 Not Found
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(&quot;유저를 찾을 수 없습니다&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 실전 예시(전체 흐름)&lt;/p&gt;
&lt;pre id=&quot;code_1776319450321&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {

    private final UserService userService;

    @PostMapping
    public ResponseEntity&amp;lt;UserResponseDto&amp;gt; createUser(@RequestBody UserRequestDto requestDto) {
        UserResponseDto responseDto = userService.createUser(requestDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); // 201
    }

    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&amp;lt;UserResponseDto&amp;gt; getUser(@PathVariable Long id) {
        UserResponseDto responseDto = userService.getUser(id);
        return ResponseEntity.ok(responseDto); // 200
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⭐️ Key Point ⭐️&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황코드HTTP 상태&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 77.4419%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.3352%;&quot;&gt;&lt;span style=&quot;background-color: #f3ffd5;&quot;&gt;&amp;nbsp; &amp;nbsp;조회 성공&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 49.7811%;&quot;&gt;ResponseEntity.ok(data)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.2093%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.3352%;&quot;&gt;&lt;span style=&quot;background-color: #f3ffd5;&quot;&gt;&amp;nbsp; &amp;nbsp;생성 성공&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 49.7811%;&quot;&gt;ResponseEntity.status(CREATED).body(data)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.2093%;&quot;&gt;201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.3352%;&quot;&gt;&lt;span style=&quot;background-color: #e9e6e6;&quot;&gt;&amp;nbsp;잘못된 요청&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 49.7811%;&quot;&gt;ResponseEntity.badRequest().body(msg)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.2093%;&quot;&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.3352%;&quot;&gt;&lt;span style=&quot;background-color: #e9e6e6;&quot;&gt;&amp;nbsp;찾을 수 없음&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 49.7811%;&quot;&gt;ResponseEntity.status(NOT_FOUND).body(msg)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.2093%;&quot;&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Part 2 : JPA 어노테이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개념 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA는 Java 객체와 데이터베이스 테이블을 자동으로 연결해주는 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션을 클래스/필드에 붙여주면 JPA가 알아서 테이블을 만들고 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 핵심 어노테이션 살펴보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @Entity&lt;/p&gt;
&lt;pre id=&quot;code_1776320081858&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class User {
    // 이 클래스가 DB 테이블과 연결된다는 선언
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 클래스가 DB 테이블과 매핑된다고 JPA에게 알려주는 것&lt;/li&gt;
&lt;li&gt;@Entity가 없으면 JPA가 그냥 일반 클래스로 취급&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @Id&lt;/p&gt;
&lt;pre id=&quot;code_1776320136116&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class User {
    @Id  // 이 필드가 기본 키(PK)야!
    private Long id;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블의 Primary Key가 될 필드에 붙인다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @GeneratedValue&lt;/p&gt;
&lt;pre id=&quot;code_1776320334096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // DB가 알아서 1, 2, 3... 자동 증가
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PK를 자동으로 생성하는 전략을 설정&lt;/li&gt;
&lt;li&gt;IDENTITY = DB가 알아서 번호 매겨줌 (MySQL의 AUTO_INCREMENT)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 실전 예시(전체 Entity)&lt;/p&gt;
&lt;pre id=&quot;code_1776320422679&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;users&quot;)  // 테이블 이름을 &quot;users&quot;로 지정 (생략하면 클래스명 그대로)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)  // NOT NULL 조건
    private String username;

    @Column(nullable = false, unique = true)  // 중복 불가
    private String email;

    private String password;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이렇게 클래스 하나를 만들면 테이블이 자동으로 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffeb9a;&quot;&gt;&amp;nbsp; id&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffeb9a;&quot;&gt;&amp;nbsp; username&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffeb9a;&quot;&gt;&amp;nbsp; email&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffeb9a;&quot;&gt;&amp;nbsp; password&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;hong&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;&lt;a href=&quot;mailto:hong@test.com&quot;&gt;hong@test.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;1234&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;kim&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;&lt;a href=&quot;mailto:kim@test.com&quot;&gt;kim@test.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px;&quot;&gt;5678&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @Repository&lt;/p&gt;
&lt;pre id=&quot;code_1776320869121&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
    // JPA가 기본 CRUD를 자동으로 제공
    // save(), findById(), findAll(), delete() 등등
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DAO 역할을 하는 인터페이스에 붙인다&lt;/li&gt;
&lt;li&gt;JpaRepository&amp;lt;엔티티, PK타입&amp;gt; 를 상속받으면 기본 CRUD 메서드 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;DAO (Data Access Object)란? &lt;br /&gt;데이터베이스에 접근하는 역할만 담당하는 객체 (데이터 조회 / 저장 / 수정 / 삭제)&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor (feat. Lombok)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA는 기본 생성자가 반드시 필요&lt;/p&gt;
&lt;pre id=&quot;code_1776321117624&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@NoArgsConstructor  // 기본 생성자 자동 생성 (JPA 필수!)
@AllArgsConstructor // 전체 필드 생성자 자동 생성
@RequiredArgsConstructor // final 필드만 생성자 생성
@Table(name = &quot;users&quot;)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String email;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @Getter / @Setter (feat. Lombok)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA Entity에서 필드에 접근하려면 getter/setter가 필요한데, Lombok으로 자동 생성&lt;/p&gt;
&lt;pre id=&quot;code_1776321383642&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter         // 모든 필드의 getter 자동 생성
@Setter         // 모든 필드의 setter 자동 생성
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;단, Entity에 @Setter는 신중하게 써야 한다.&lt;br /&gt;아무데서나 값을 바꿀 수 있어서 나중에 버그 찾기 힘들어지기 때문에 필요한 메서드만 따로 만드는 것을 추천!&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 최종 실전 Entity 예시&lt;/p&gt;
&lt;pre id=&quot;code_1776323554093&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@NoArgsConstructor
@Table(name = &quot;users&quot;)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false, unique = true)
    private String email;

    private String password;

    // 생성자는 필요한 필드만 받아서 직접 만들기
    public User(String username, String email, String password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Part 3 : 연관관계 (@ManyToOne, @OneToMany)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개념 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 테이블끼리 관계가 있을 때 JPA에서 표현하는 방법이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 : 게시글(Post) 과 유저(User) 관계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 1명은 게시글을 여러 개 쓸 수 있다&lt;/li&gt;
&lt;li&gt;게시글 1개는 한 명의 유저가 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @ManyToOne - 많은 쪽에 붙인다&lt;/p&gt;
&lt;pre id=&quot;code_1776323954881&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@NoArgsConstructor
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;

    @ManyToOne  // Post &amp;rarr; User
    @JoinColumn(name = &quot;user_id&quot;)  // FK 컬럼 이름 지정
    private User user;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;posts 테이블에 user_id 컬럼이 자동으로 생긴다&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 85.814%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.2883%;&quot;&gt;id&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.3358%;&quot;&gt;title&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 27.6206%;&quot;&gt;content&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.411%;&quot;&gt;user_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.2883%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.3358%;&quot;&gt;안녕&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 27.6206%;&quot;&gt;내용1&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.411%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.2883%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.3358%;&quot;&gt;반가워&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 27.6206%;&quot;&gt;내용2&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.411%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 15.2883%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.3358%;&quot;&gt;잘가&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 27.6206%;&quot;&gt;내용3&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.411%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@ManyToOne 읽는 법 : &quot;나(Many)는 저쪽(One)에 속해있어!&quot;&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 70.4651%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.7132%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #bbf5ff;&quot;&gt;&amp;nbsp; Many (많은 쪽)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.6357%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #d5fdff;&quot;&gt;&amp;nbsp; One (하나인 쪽)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.7132%; text-align: center;&quot;&gt;게시글 (여러개)&lt;/td&gt;
&lt;td style=&quot;width: 35.6357%; text-align: center;&quot;&gt;유저 (1명)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.7132%; text-align: center;&quot;&gt;학생 (여러명)&lt;/td&gt;
&lt;td style=&quot;width: 35.6357%; text-align: center;&quot;&gt;반 (1개)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.7132%; text-align: center;&quot;&gt;주문 (여러개)&lt;/td&gt;
&lt;td style=&quot;width: 35.6357%; text-align: center;&quot;&gt;고객 (1명)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.7132%; text-align: center;&quot;&gt;댓글 (여러개)&lt;/td&gt;
&lt;td style=&quot;width: 35.6357%; text-align: center;&quot;&gt;게시글 (1개)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @OneToMany - 일반적으로 잘 안 쓴다&lt;/p&gt;
&lt;pre id=&quot;code_1776324563259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    @OneToMany(mappedBy = &quot;user&quot;)  // Post의 user 필드랑 연결
    private List&amp;lt;Post&amp;gt; posts = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;단방향을 기본으로 쓴다. @ManyToOne만 쓰는 게 기본이고, &lt;br /&gt;@OneToMany는정말 필요할 때만 추가한다. 양방향으로 하면 관리가 복잡해짐!&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @JoinColumn&lt;/p&gt;
&lt;pre id=&quot;code_1776324670210&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ManyToOne
@JoinColumn(name = &quot;user_id&quot;)  // DB의 FK 컬럼 이름
private User user;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 컬럼으로 JOIN할지 지정&lt;/li&gt;
&lt;li&gt;생략하면 JPA가 자동으로 이름 만들어주는데, 명시적으로 써주는 게 좋다!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 전체 흐름 예시&lt;/p&gt;
&lt;pre id=&quot;code_1776324772970&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 유저 저장
User user = new User(&quot;스파르타&quot;, &quot;moon@test.com&quot;, &quot;1234&quot;);
userRepository.save(user);

// 게시글 저장 (유저 연결)
Post post = new Post(&quot;제목&quot;, &quot;내용&quot;, user);
postRepository.save(post);

// 게시글에서 유저 정보 가져오기
Post findPost = postRepository.findById(1L).orElseThrow();
String username = findPost.getUser().getUsername(); // &quot;스파르타&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: center;&quot;&gt;⭐️ Key Point ⭐️&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 76px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;어노테이션&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;의미&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;사용 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;@ManyToOne&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;많은 &amp;rarr; 적은&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;Post에서 User를 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;@OneToMany&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;적은 &amp;rarr; 많은&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;필요할 때만 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;@JoinColumn&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;FK 컬럼 지정&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;@ManyToOne 옆에&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;단방향 원칙 기억하기! @ManyToOne 하나만 쓰는 게 기본! &lt;br /&gt;@OneToMany는 진짜 필요할 때만 추가! (거의 없음)&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Part 4 : Cookie/Session 인증/인가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개념 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 기능을 만들 때 사용하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 Cookie/Session이 필요할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  HTTP의 특징 - &quot;무상태(Stateless)&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 기본적으로 요청이 끝나면 상태를 기억 못한다!&lt;/p&gt;
&lt;pre id=&quot;code_1776406887947&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;클라이언트: &quot;나 스파르타야, 로그인할게!&quot;
서버: &quot;오케이 로그인 됐어!&quot;

-- 1초 후 --

클라이언트: &quot;내 게시글 목록 줘!&quot;
서버: &quot;너 누구야...?&quot;  // 기억 못함!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하는 게 Cookie/Session 이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Cookie vs Session 차이&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 98.1395%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 18.1396%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 41.7442%;&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&amp;nbsp; Cookie&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 38.1395%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; Session&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 18.1396%;&quot;&gt;저장 위치&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 41.7442%;&quot;&gt;클라이언트 (브라우저)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 38.1395%;&quot;&gt;서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 18.1396%;&quot;&gt;보안&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 41.7442%;&quot;&gt;상대적으로 취약&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 38.1395%;&quot;&gt;상대적으로 안전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 18.1396%;&quot;&gt;용량&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 41.7442%;&quot;&gt;작음 (4KB)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 38.1395%;&quot;&gt;큼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  동작 흐름&lt;/p&gt;
&lt;pre id=&quot;code_1776407137749&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 로그인 요청
클라이언트 &amp;rarr; 서버: &quot;아이디/비밀번호 보낼게!&quot;

2. 서버가 Session 생성
서버: &quot;맞네! Session 만들어서 저장해놓을게&quot;
서버 메모리: { &quot;ABC123&quot;: { userId: 1, username: &quot;스파르타&quot; } }

3. Cookie에 SessionId 전달
서버 &amp;rarr; 클라이언트: &quot;이 SessionId(ABC123) 가지고 있어!&quot;
브라우저: 쿠키에 ABC123 저장

4. 다음 요청부터 자동으로 Cookie 전송
클라이언트 &amp;rarr; 서버: &quot;게시글 줘!&quot; + Cookie(ABC123)
서버: &quot;ABC123으로 조회해보니 스파르타네! 게시글 줘야겠다&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실제 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 로그인 (Session 저장)&lt;/p&gt;
&lt;pre id=&quot;code_1776407217331&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/login&quot;)
public ResponseEntity&amp;lt;String&amp;gt; login(
        @RequestBody LoginRequestDto requestDto,
        HttpServletRequest request) {

    // 1. 유저 확인
    User user = userService.login(requestDto);

    // 2. Session 생성 &amp;amp; 저장
    HttpSession session = request.getSession();
    session.setAttribute(&quot;loginUser&quot;, user.getId());

    return ResponseEntity.ok(&quot;로그인 성공!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 로그아웃 (Session 삭제)&lt;/p&gt;
&lt;pre id=&quot;code_1776436343915&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/logout&quot;)
public ResponseEntity&amp;lt;String&amp;gt; logout(HttpServletRequest request) {

    // Session 무효화 (삭제)
    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate();
    }

    return ResponseEntity.ok(&quot;로그아웃 성공!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 인가 (로그인한 사람만 접근 가능)&lt;/p&gt;
&lt;pre id=&quot;code_1776436373969&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/posts&quot;)
public ResponseEntity&amp;lt;?&amp;gt; getPosts(HttpServletRequest request) {

    // Session에서 로그인 정보 확인
    HttpSession session = request.getSession(false);
    if (session == null || session.getAttribute(&quot;loginUser&quot;) == null) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                             .body(&quot;로그인이 필요합니다!&quot;);
    }

    // 로그인 된 경우만 실행
    Long userId = (Long) session.getAttribute(&quot;loginUser&quot;);
    return ResponseEntity.ok(postService.getPosts(userId));
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: center;&quot;&gt;⭐️ Key Point ⭐️&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1776436479781&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;로그인  &amp;rarr; Session 생성 &amp;rarr; Cookie에 SessionId 저장
요청마다 &amp;rarr; Cookie 자동 전송 &amp;rarr; 서버가 Session 확인
로그아웃 &amp;rarr; Session 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인증(Authentication) = &quot;너 누구야?&quot; (로그인 확인) &lt;br /&gt;인가(Authorization) = &quot;너 이거 해도 돼?&quot; (권한 확인)&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Part 5 : Bean Validation (@Valid 등)&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개념 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;클라이언트가 보내는 데이터를 검증&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하는 방법&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 회원가입할 때 :&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일 형식이 맞는지&lt;/li&gt;
&lt;li&gt;비밀번호가 최소 8자 이상인지&lt;/li&gt;
&lt;li&gt;이름이 비어있지 않은지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 걸 일일이 if문으로 체크하면 너무 번거롭기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션 하나로 검증할 수 있게 해주는 게 Bean Validation이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  검증 없이 짜면&lt;/p&gt;
&lt;pre id=&quot;code_1776436658124&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   검증 없이 짜면 이렇게 됨
@PostMapping(&quot;/signup&quot;)
public ResponseEntity&amp;lt;String&amp;gt; signup(@RequestBody SignupRequestDto requestDto) {

    if (requestDto.getUsername() == null || requestDto.getUsername().isEmpty()) {
        return ResponseEntity.badRequest().body(&quot;이름을 입력해주세요!&quot;);
    }
    if (requestDto.getEmail() == null || !requestDto.getEmail().contains(&quot;@&quot;)) {
        return ResponseEntity.badRequest().body(&quot;이메일 형식이 맞지 않습니다!&quot;);
    }
    if (requestDto.getPassword() == null || requestDto.getPassword().length() &amp;lt; 8) {
        return ResponseEntity.badRequest().body(&quot;비밀번호는 8자 이상이어야 합니다!&quot;);
    }
    // ... 끝도 없이 계속됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @Valid 사용하면?&lt;/p&gt;
&lt;pre id=&quot;code_1776436705995&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @Valid 사용하면 이렇게 깔끔해짐!
@PostMapping(&quot;/signup&quot;)
public ResponseEntity&amp;lt;String&amp;gt; signup(@Valid @RequestBody SignupRequestDto requestDto) {
    // 검증은 DTO에서 알아서 다 해줌!
    userService.signup(requestDto);
    return ResponseEntity.ok(&quot;회원가입 성공!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp; 자주 쓰는 검증 어노테이션&lt;/p&gt;
&lt;pre id=&quot;code_1776436743413&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
public class SignupRequestDto {

    @NotBlank(message = &quot;이름은 필수입니다!&quot;)
    // null, &quot;&quot;, &quot;   &quot; 모두 거부
    private String username;

    @Email(message = &quot;이메일 형식이 올바르지 않습니다!&quot;)
    // hong@test.com 같은 형식인지 체크
    private String email;

    @Size(min = 8, max = 20, message = &quot;비밀번호는 8~20자여야 합니다!&quot;)
    // 글자 수 체크
    private String password;

    @Min(value = 1, message = &quot;나이는 1살 이상이어야 합니다!&quot;)
    @Max(value = 150, message = &quot;나이는 150살 이하여야 합니다!&quot;)
    // 숫자 범위 체크
    private int age;

    @NotNull(message = &quot;약관 동의는 필수입니다!&quot;)
    // null만 거부 (빈 문자열은 허용)
    private Boolean agreeTerms;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ @NotNull vs @NotEmpty vs @NotBlank 차이&lt;/p&gt;
&lt;pre id=&quot;code_1776436792957&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String a = null;     // null
String b = &quot;&quot;;       // 빈 문자열
String c = &quot;   &quot;;    // 공백만 있는 문자열
String d = &quot;홍길동&quot;; // 정상

@NotNull  &amp;rarr; null만 거부   &amp;rarr; b, c, d 통과 / a 거부
@NotEmpty &amp;rarr; null, &quot;&quot; 거부 &amp;rarr; c, d 통과   / a, b 거부
@NotBlank &amp;rarr; null, &quot;&quot;, &quot;  &quot; 모두 거부 &amp;rarr; d만 통과&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;문자열 검증할 땐 @NotBlank를 제일 많이 쓴다! 공백만 입력하는 경우까지 막아줌&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  검증 실패할 때 처리 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Valid 검증이 실패하면 MethodArgumentNotValidException이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 @ExceptionHandler로 잡아서 처리할 수 있다!&lt;/p&gt;
&lt;pre id=&quot;code_1776436907404&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestControllerAdvice  // 전역 예외 처리 클래스
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity&amp;lt;String&amp;gt; handleValidationException(
            MethodArgumentNotValidException e) {

        // 첫 번째 에러 메시지만 꺼내기
        String errorMessage = e.getBindingResult()
                .getAllErrors()
                .get(0)
                .getDefaultMessage();

        return ResponseEntity.badRequest().body(errorMessage);
        // 예: &quot;이름은 필수입니다!&quot; 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 전체 흐름 정리&lt;/p&gt;
&lt;pre id=&quot;code_1776436938955&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;클라이언트: POST /signup  { username: &quot;&quot;, email: &quot;잘못된형식&quot;, password: &quot;123&quot; }
                &amp;darr;
Controller: @Valid 검증 시작!
                &amp;darr;
검증 실패! MethodArgumentNotValidException 발생
                &amp;darr;
GlobalExceptionHandler가 잡아서 처리
                &amp;darr;
클라이언트: 400 Bad Request &quot;이름은 필수입니다!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐️ 자주 쓰는 어노테이션 정리&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;@NotBlank&lt;/td&gt;
&lt;td&gt;문자열 필수 입력 (공백 불가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@NotNull&lt;/td&gt;
&lt;td&gt;null 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Email&lt;/td&gt;
&lt;td&gt;이메일 형식 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Size(min, max)&lt;/td&gt;
&lt;td&gt;문자열 길이 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Min(value)&lt;/td&gt;
&lt;td&gt;최솟값 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Max(value)&lt;/td&gt;
&lt;td&gt;최댓값 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Pattern(regexp)&lt;/td&gt;
&lt;td&gt;정규식으로 형식 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  다음 글 : [Spring 숙련] 일정 관리 앱 만들기 - 일정 CRUD 구현&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/27</guid>
      <comments>https://mooncommit-dev.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 16 Apr 2026 16:28:05 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] Spring 개념 정리 | DI / IoC / Bean / MVC 요청 흐름</title>
      <link>https://mooncommit-dev.tistory.com/26</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;To Do List&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;DI (Dependency Injection, &lt;span&gt;의존성&lt;/span&gt; &lt;span&gt;주입&lt;/span&gt;)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;IoC (Inversion of Control, &lt;span&gt;제어의&lt;/span&gt; &lt;span&gt;역전&lt;/span&gt;)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Spring Bean (빈 스코프 &amp;amp; 생명주기)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;왜&lt;span&gt; Spring Bean&lt;/span&gt;은&lt;span&gt; &lt;/span&gt;기본적으로&lt;span&gt; &lt;/span&gt;싱글톤으로&lt;span&gt; &lt;/span&gt;설계했을까&lt;span&gt;?&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;요청이&lt;span&gt; &lt;/span&gt;들어왔을&lt;span&gt; &lt;/span&gt;때&lt;span&gt;, &lt;/span&gt;디스패처&lt;span&gt; &lt;/span&gt;서블릿이&lt;span&gt; &lt;/span&gt;컨트롤러를&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;찾아주는가&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Part&amp;nbsp;1 : DI (Dependency Injection, 의존성 주입)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 A가 B를 사용한다면, &quot;A는 B에 의존한다&quot; 라고 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 코드 예시&lt;/p&gt;
&lt;pre id=&quot;code_1776234314658&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OrderService {
    private PaymentService paymentService = new PaymentService(); // 직접 생성
    
    public void order() {
        paymentService.pay();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService는 PaymentService에 의존하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 OrderService가 PaymentService를 직접 new로 생성했는데&amp;nbsp;왜 문제일까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vlpw9/dJMcadPbkGs/yWzKE8bdwzC5ff850k49mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vlpw9/dJMcadPbkGs/yWzKE8bdwzC5ff850k49mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vlpw9/dJMcadPbkGs/yWzKE8bdwzC5ff850k49mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvlpw9%2FdJMcadPbkGs%2FyWzKE8bdwzC5ff850k49mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;직접 만들지 말고, 외부에서 받아라&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1776234826909&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DI 적용 전 &amp;mdash; 내가 직접 생성
public class OrderService {
    private PaymentService paymentService = new PaymentService();
}

// DI 적용 후 &amp;mdash; 외부에서 주입받음
public class OrderService {
    private PaymentService paymentService; // 선언만

    // 생성자를 통해 외부에서 주입
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService는 PaymentService가 어떻게 만들어졌는지 몰라도 된다. 그냥 주어진 걸 사용하기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나중에 PaymentService를 MoonPayService로 바꿔도 OrderService 코드는 전혀 수정할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ DI를 해주는 3가지 방법&lt;/p&gt;
&lt;pre id=&quot;code_1776236655340&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 방법 1 : 생성자 주입 (Spring에서 가장 권장하는 방법)
@Service
public class OrderService {
    private final PaymentService paymentService;

	@Autowired // 생략 가능
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

// 방법 2 : Setter 주입
@Service
public class OrderService {
    private PaymentService paymentService;

    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

// 방법 3 : 필드 주입 (간단하지만 권장하지 않음)
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-3&quot; data-message-id=&quot;50f063b8-c115-4c6f-a1d3-fca62d2a428b&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-end=&quot;82&quot; data-start=&quot;0&quot; data-ke-style=&quot;style2&quot;&gt;생성자 주입을 사용하는 이유는 final로 선언할 수 있어서 값이 바뀌지 않게 만들 수 있고, &lt;br /&gt;테스트할 때도 직접 값을 넣어주기 쉽기 때문이다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Part&amp;nbsp;2 : IoC (Inversion of Control, 제어의 역전)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC는 &amp;ldquo;제어권이 뒤집혔다&amp;rdquo;는 의미다.&lt;br /&gt;원래는 내가 직접 객체를 만들고 흐름을 제어하지만, IoC에서는 이 제어를 스프링이 대신 관리해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1776236850397&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 내가 제어 (IoC 없음)
PaymentService pay = new PaymentService();
OrderService order = new OrderService(pay);
order.order();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC가 적용되면 객체 생성과 흐름을 제어하는 권한이 나에게서 프레임워크(Spring)로 넘어간다.&lt;/p&gt;
&lt;pre id=&quot;code_1776236892200&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Spring이 제어 (IoC 있음)
// 개발자는 그냥 이것만 씀
@Autowired
private OrderService orderService; // Spring이 알아서 만들고 주입해줌&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 객체를 어떻게 만들지 직접 구현하는 게 아니라, 어떤 것이 필요한지만 선언하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR1wVB/dJMb9969b1y/rDiZOqx2kPmWmqWe1gEyD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR1wVB/dJMb9969b1y/rDiZOqx2kPmWmqWe1gEyD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR1wVB/dJMb9969b1y/rDiZOqx2kPmWmqWe1gEyD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR1wVB%2FdJMb9969b1y%2FrDiZOqx2kPmWmqWe1gEyD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  DI와 IoC의 관계 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;IoC는 개념이고, DI는 그걸 구현하는 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-3&quot; data-message-id=&quot;f96cd4da-c08a-46ee-b636-c4597165e40a&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;112&quot; data-start=&quot;32&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;72&quot; data-start=&quot;32&quot; data-section-id=&quot;zbpqb&quot;&gt;IoC : 제어권을 프레임워크(Spring)에게 넘긴다는 설계 개념&lt;/li&gt;
&lt;li data-end=&quot;112&quot; data-start=&quot;73&quot; data-section-id=&quot;6oh4f2&quot;&gt;DI : 필요한 객체를 직접 생성하지 않고 외부에서 주입받는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;156&quot; data-start=&quot;114&quot; data-ke-style=&quot;style2&quot;&gt;즉, Spring은 IoC를 실현하기 위해 DI를 사용한다고 보면 된다.&lt;br /&gt;정리하면 DI는 &amp;ldquo;내가 직접 new 하지 않고 받는 것&amp;rdquo;, IoC는 &amp;ldquo;그 제어권 자체가 Spring으로 넘어간 것&amp;rdquo;이다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Part 3 : Spring Bean &amp;mdash; 빈 스코프 &amp;amp; 생명주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ Bean이 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 IoC 컨테이너에서 직접 관리하는 객체를 Bean(빈) 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 new OrderService()로 직접 만든 객체는 빈이 아니다.&lt;br /&gt;Spring 컨테이너에 등록되어서, Spring이 생성하고 주입하고 소멸까지 관리해주는 객체만 빈이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  빈 스코프 (Bean Scope)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스코프 = &quot;이 빈을 얼마나 넓은 범위에서 공유하냐&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 쓰는 스코프 3가지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekkkDZ/dJMcagZtUaG/05iyPBA7JFKUkqPbUyYNmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekkkDZ/dJMcagZtUaG/05iyPBA7JFKUkqPbUyYNmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekkkDZ/dJMcagZtUaG/05iyPBA7JFKUkqPbUyYNmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekkkDZ%2FdJMcagZtUaG%2F05iyPBA7JFKUkqPbUyYNmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  싱글톤 : 컨테이너에 하나만 만들어두고 모두가 함께 사용한다. Spring의 기본값이다.&lt;/p&gt;
&lt;p data-end=&quot;107&quot; data-start=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;  프로토타입 : 요청할 때마다 새로운 객체를 만들어준다. 각각 독립적인 상태가 필요할 때 사용한다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;167&quot; data-start=&quot;109&quot; data-ke-size=&quot;size16&quot;&gt;  리퀘스트 : 웹 요청이 들어올 때 생성되고, 요청이 끝나면 사라진다. 같은 요청 안에서는 하나를 공유한다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;167&quot; data-start=&quot;109&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;167&quot; data-start=&quot;109&quot; data-ke-size=&quot;size20&quot;&gt;빈 생명주기 (Lifecycle)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈이 태어나서 죽기까지의 전체 흐름&lt;/p&gt;
&lt;pre id=&quot;code_1776237575218&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Spring 컨테이너 시작
    &amp;darr;
1. 빈 생성 (객체 new)
    &amp;darr;
2. 의존관계 주입 (DI)
    &amp;darr;
3. 초기화 콜백 (@PostConstruct)  &quot;다 준비됐으면 이 메서드 실행해줘&quot;
    &amp;darr;
4. 사용 (비즈니스 로직 실행)
    &amp;darr;
5. 소멸 전 콜백 (@PreDestroy)    &quot;죽기 전에 이 메서드 실행해줘&quot;
    &amp;darr;
Spring 컨테이너 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ 코드 예시&lt;/p&gt;
&lt;pre id=&quot;code_1776237641675&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class DatabaseConnector {

    @PostConstruct
    public void init() {
        // 빈이 완전히 준비된 직후 실행
        // DB 연결 풀 초기화, 캐시 로딩 등
        System.out.println(&quot;DB 연결 초기화!&quot;);
    }

    @PreDestroy
    public void cleanup() {
        // 빈이 소멸되기 직전 실행
        // DB 연결 종료, 리소스 반납 등
        System.out.println(&quot;DB 연결 종료!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@PostConstruct는 의존성 주입이 모두 끝난 후 실행된다.&lt;br /&gt;생성자에서는 아직 주입이 안 끝났을 수 있기 때문에, 초기화 로직은 여기서 처리하는 것이 좋다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Part 4 : 왜 Spring Bean은 기본적으로 싱글톤으로 설계했을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 단순히 &amp;ldquo;그렇게 만들어진 것&amp;rdquo;이 아니라, 분명한 이유가 있다. 웹 서버의 특성을 먼저 이해해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 웹 서버 환경&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCMP0s/dJMcafM5NH1/45nmTjFwP6MfXff0ALkBEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCMP0s/dJMcafM5NH1/45nmTjFwP6MfXff0ALkBEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCMP0s/dJMcafM5NH1/45nmTjFwP6MfXff0ALkBEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCMP0s%2FdJMcafM5NH1%2F45nmTjFwP6MfXff0ALkBEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;878&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 서비스에서는 수천 명이 동시에 요청을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 요청마다 OrderService, UserService, PaymentService를 매번 new로 생성하면 메모리가 금방 부족해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Spring은 기본적으로 싱글톤을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;245&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;✔️ 첫째, 메모리 효율&lt;br /&gt;OrderService 같은 서비스 객체는 상태가 없고 로직만 있기 때문에 하나만 만들어 여러 요청이 함께 사용해도 문제없다.&lt;/p&gt;
&lt;p data-end=&quot;245&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;340&quot; data-start=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;✔️ 둘째, 성능&lt;br /&gt;객체를 생성하는 데는 메모리 할당과 초기화 비용이 든다. 요청마다 반복하면 낭비가 크지만,&lt;/p&gt;
&lt;p data-end=&quot;340&quot; data-start=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;싱글톤은 한 번만 만들어 재사용하므로 훨씬 효율적이다.&lt;/p&gt;
&lt;p data-end=&quot;340&quot; data-start=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;433&quot; data-start=&quot;342&quot; data-ke-size=&quot;size16&quot;&gt;✔️ 셋째, Spring의 DI 구조와 잘 맞는다.&lt;br /&gt;Spring은 객체를 한 번 생성해두고 필요한 곳에 주입하는 방식이기 때문에, 싱글톤 구조가 자연스럽게 어울린다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;433&quot; data-start=&quot;342&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;433&quot; data-start=&quot;342&quot; data-ke-size=&quot;size18&quot;&gt;  싱글톤의 주의점은?&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;433&quot; data-start=&quot;342&quot; data-ke-size=&quot;size16&quot;&gt;싱글톤은 상태(인스턴스 변수)를 가지면 위험하다.&lt;/p&gt;
&lt;pre id=&quot;code_1776238118862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 위험한 코드!
@Service
public class OrderService {
    private int orderCount = 0; // 인스턴스 변수 (상태)

    public void order() {
        orderCount++; // 여러 사용자가 동시에 이걸 바꾸면?
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 객체는 모든 사용자가 함께 쓰기 때문에, 사용자 A가 바꾼 값을 사용자 B도 보게 될 수 있다. 이게 동시성 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;178&quot; data-start=&quot;73&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Spring 빈은 상태를 가지지 않는 방식으로 설계하는 것이 중요하다.&lt;br /&gt;메서드 안에서만 사용하는 지역변수는 괜찮지만, 여러 요청이 함께 쓰는 인스턴스 변수는 문제가 될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776238190454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 올바른 코드
@Service
public class OrderService {
    // 인스턴스 변수 없음. 상태 없음.

    public void order(int count) {
        int localCount = count; // 지역변수는 각 스레드마다 독립적이라 안전
        // 비즈니스 로직...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-3&quot; data-message-id=&quot;8de89376-2d5d-4bbc-a1e1-2f505b97fa18&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-end=&quot;120&quot; data-start=&quot;0&quot; data-ke-style=&quot;style2&quot;&gt;Spring 빈이 기본적으로 싱글톤인 이유는 웹 환경에서 많은 요청을 처리할 때 메모리와 성능을 효율적으로 사용할 수 있고, 서비스 객체는 보통 상태가 없어서 여러 요청이 함께 사용해도 안전하기 때문이다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Part 5 : 요청이 들어왔을 때, 디스패처 서블릿이 컨트롤러를 어떻게 찾아주는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 이해하려면 3단계로 나눠서 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;107&quot; data-start=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;✔️ 1단계 : 전체 흐름을 먼저 큰 그림으로 이해하기&lt;br /&gt;✔️ 2단계 : 핵심 구성 요소가 무엇인지 파악하기&lt;br /&gt;✔️ 3단계 : 각 단계를 하나씩 자세히 살펴보기&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;107&quot; data-start=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;107&quot; data-start=&quot;28&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1단계 : 큰 그림&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nlBRb/dJMb99TBkJL/cQysM7aVhK4nj0ARTyDLkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nlBRb/dJMb99TBkJL/cQysM7aVhK4nj0ARTyDLkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nlBRb/dJMb99TBkJL/cQysM7aVhK4nj0ARTyDLkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnlBRb%2FdJMb99TBkJL%2FcQysM7aVhK4nj0ARTyDLkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DispatcherServlet = 프론트 컨트롤러란&lt;/span&gt;&lt;br /&gt;모든 클라이언트 요청을 가장 먼저 받는 입구 역할, 요청을 직접 처리하지 않고, 어디로 보낼지 &amp;ldquo;교통정리&amp;rdquo;만 함&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;흐름을 말로 푼다면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;226&quot; data-start=&quot;207&quot; data-section-id=&quot;mqiet3&quot;&gt;클라이언트 요청이 들어온다&lt;/li&gt;
&lt;li data-end=&quot;263&quot; data-start=&quot;227&quot; data-section-id=&quot;1kf0cdi&quot;&gt;DispatcherServlet이 받는다 (입구)&lt;/li&gt;
&lt;li data-end=&quot;304&quot; data-start=&quot;264&quot; data-section-id=&quot;kdj4pr&quot;&gt;어떤 컨트롤러가 처리할지 찾는다 (Handler Mapping)&lt;/li&gt;
&lt;li data-end=&quot;342&quot; data-start=&quot;305&quot; data-section-id=&quot;1uj7lvl&quot;&gt;실제 컨트롤러 실행 (Handler Adapter 통해서)&lt;/li&gt;
&lt;li data-end=&quot;367&quot; data-start=&quot;343&quot; data-section-id=&quot;7xosr1&quot;&gt;결과(ModelAndView) 받음&lt;/li&gt;
&lt;li data-end=&quot;385&quot; data-start=&quot;368&quot; data-section-id=&quot;13hnicv&quot;&gt;View 찾아서 렌더링&lt;/li&gt;
&lt;li data-end=&quot;397&quot; data-start=&quot;386&quot; data-section-id=&quot;k42oe9&quot;&gt;최종 응답 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2단계 : 핵심 부품들&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  Spring MVC 핵심 구성요소  &lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/navIb/dJMcabDTG5V/szDqe7XR7KzCANkv4xOnKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/navIb/dJMcabDTG5V/szDqe7XR7KzCANkv4xOnKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/navIb/dJMcabDTG5V/szDqe7XR7KzCANkv4xOnKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnavIb%2FdJMcabDTG5V%2FszDqe7XR7KzCANkv4xOnKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Spring MVC는 DispatcherServlet을 중심으로 &quot;누가 처리할지 찾고 &amp;rarr; 실행하고 &amp;rarr; 결과를 화면으로 바꿔주는 구조&quot;이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  DispatcherServlet&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;모든 요청의 시작점 (입구)&lt;/li&gt;
&lt;li&gt;클라이언트 요청 = 무조건 여기로 들어옴&lt;/li&gt;
&lt;li&gt;전체 흐름을 컨트롤하는 &quot;총괄 관리자&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  HandlerMapping&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&quot;이 요청 누가 처리함?&quot; 담당&lt;/li&gt;
&lt;li&gt;서버 시작 시 : @RequestMapping, @GetMapping 등 전부 스캔, URL &amp;harr; Controller 매핑 테이블 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @RequestMapping&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;정보를 쭉 스캔해서 URL과 컨트롤러를 매핑해 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  HandlerAdapter&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;실제로 컨트롤러를 실행시켜주는 어댑터&lt;/li&gt;
&lt;li&gt;왜 필요할까? : 컨트롤러가 여러 종류일 수 있다. (@Controller, 옛날 방식 등) 어댑터가 중간에서 형식 맞춰서 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Controller&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;개발자가 작성한 비즈니스 로직 결과로 ModelAndView 또는 @ResponseBody (JSON) 를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  ViewResolver&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;컨트롤러가 &quot;home&quot;이라는 뷰 이름을 반환하면, 실제 파일 경로 (/templates/home.html)로 변환해주는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;한 줄 정리&lt;br /&gt;DispatcherServlet &amp;rarr; HandlerMapping &amp;rarr; HandlerAdapter &amp;rarr; Controller &amp;rarr; ViewResolver&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3단계: 단계별 초세세 흐름&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;① HandlerMapping 조회 &amp;mdash; &quot;이 URL 담당이 누구야?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 처음 뜰 때, Spring이 @Controller 달린 클래스들을 다 스캔해서 이런 표를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1776301495722&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /users         &amp;rarr;  UserController.getUsers()
POST /orders       &amp;rarr;  OrderController.createOrder()
GET /products/{id} &amp;rarr;  ProductController.getProduct()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 들어오면 디스패처 서블릿이 이 표에서 URL을 찾아서 담당 컨트롤러 메서드를 꺼낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 HandlerMapping의 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;② HandlerAdapter &amp;mdash; &quot;컨트롤러를 표준 방식으로 실행&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러를&amp;nbsp;찾았다고&amp;nbsp;바로&amp;nbsp;실행할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;건&amp;nbsp;아니다.&lt;br /&gt;디스패처 서블릿이 컨트롤러를 직접 호출하면, 컨트롤러&amp;nbsp;방식이&amp;nbsp;바뀔&amp;nbsp;때마다&amp;nbsp;디스패처&amp;nbsp;서블릿&amp;nbsp;코드도&amp;nbsp;함께&amp;nbsp;수정해야&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;그래서 다양한 컨트롤러를 일관된 방식으로 실행하기 위해 중간에&amp;nbsp;HandlerAdapter를&amp;nbsp;두었다.&lt;/p&gt;
&lt;pre id=&quot;code_1776301943394&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DispatcherServlet &amp;rarr; HandlerAdapter.handle() &amp;rarr; 실제 컨트롤러 메서드 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑터가 파라미터 바인딩(@PathVariable, @RequestParam, @RequestBody 파싱)도 여기서 다 처리해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;③ Controller 실행 &amp;rarr; ModelAndView 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러가 실행되고, 두 가지 방식으로 결과를 돌려준다.&lt;/p&gt;
&lt;pre id=&quot;code_1776302084109&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 방식 1 : 뷰 이름 반환 (서버사이드 렌더링)
@GetMapping(&quot;/home&quot;)
public String home(Model model) {
    model.addAttribute(&quot;username&quot;, &quot;home&quot;);
    return &quot;home&quot;; // &amp;rarr; ViewResolver가 home.html 찾아줌
}

// 방식 2 : JSON 반환 (REST API)
@GetMapping(&quot;/api/users&quot;)
@ResponseBody  // 또는 클래스에 @RestController
public List&amp;lt;User&amp;gt; getUsers() {
    return userList; // &amp;rarr; 뷰 없이 바로 JSON으로 직렬화
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ResponseBody가 붙으면 ViewResolver를 아예 안 거치고 바로 HTTP 응답 바디에 JSON을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;④ ViewResolver &amp;mdash; &quot;뷰 이름을 실제 파일로&quot;&lt;/p&gt;
&lt;pre id=&quot;code_1776302146427&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;home&quot;  &amp;rarr;  /templates/home.html
&quot;error&quot; &amp;rarr;  /templates/error.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.properties에 이렇게 설정해두면&lt;/p&gt;
&lt;pre id=&quot;code_1776302212893&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;home&quot; 이라는 이름만 받아도 알아서 /templates/home.html 파일을 찾아서 렌더링해준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름&lt;/p&gt;
&lt;pre id=&quot;code_1776302587760&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 브라우저가 GET /users 요청 전송

2. DispatcherServlet이 요청 수신
   (모든 요청의 단일 진입점)

3. HandlerMapping에게 물어봄
   &quot;GET /users 담당 컨트롤러가 누구야?&quot;
   &amp;rarr; UserController.getUsers()반환

4. HandlerAdapter에게 실행 요청
   &quot;이 컨트롤러 메서드 실행해줘&quot;
   &amp;rarr; 파라미터 바인딩, 실행

5. UserController.getUsers()실행
   &amp;rarr; &quot;users&quot; 뷰 이름 + User 데이터 반환

6. ViewResolver에게 뷰 이름 전달
   &quot;users&quot;라는 이름의 실제 파일 찾아줘
   &amp;rarr; /templates/users.html 반환

7. users.html에 데이터 채워서 HTML 완성
   &amp;rarr; 클라이언트에게 응답&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; ️ 디스패처 서블릿은 직접 하지 않고,&lt;br /&gt;✔️ &quot;누가 할지&quot;는 HandlerMapping한테 물어보고&lt;br /&gt;✔️ &quot;실행&quot;은 HandlerAdapter한테 시킨다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리 끝⎝⍢⎠&lt;/p&gt;</description>
      <category>Spring</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/26</guid>
      <comments>https://mooncommit-dev.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 15 Apr 2026 23:57:02 +0900</pubDate>
    </item>
    <item>
      <title>Layer Architecture + JPA로 User CRUD API 만들기</title>
      <link>https://mooncommit-dev.tistory.com/25</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;style&gt;
    * { margin: 0; padding: 0; box-sizing: border-box; }

    body {
  font-family: 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
  color: #333;
  line-height: 1.8;
  background: #fff;
}

/* ⭐ 티스토리 안전 영역 */
.post-wrap {
  max-width: 720px;
  margin: 0 auto;
  padding: 40px 20px 60px;
}

    .post-date {
      font-size: 13px;
      color: #aaa;
      margin-bottom: 40px;
    }

    /* TO DO LIST 카드 */
    .todo-card {
      background: #fafafa;
      border: 1px solid #eee;
      border-radius: 16px;
      padding: 28px 32px;
      margin-bottom: 48px;
    }

    .todo-card-header {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-bottom: 20px;
    }

    .todo-card-header .dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: #222;
    }

    .todo-card-header h3 {
      font-size: 13px;
      font-weight: 700;
      color: #222;
      letter-spacing: 0.08em;
    }

    .todo-list {
      list-style: none;
      display: flex;
      flex-direction: column;
      gap: 10px;
    }

    .todo-item {
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 12px 14px;
      background: #fff;
      border-radius: 10px;
      border: 1px solid #f0f0f0;
    }

    .circle {
      width: 18px;
      height: 18px;
      border-radius: 50%;
      border: 2px solid #ddd;
      flex-shrink: 0;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .circle.done {
      background: #222;
      border-color: #222;
    }

    .circle.done::after {
      content: '';
      width: 4px;
      height: 8px;
      border: 2px solid white;
      border-top: none;
      border-left: none;
      transform: rotate(45deg) translateY(-1px);
    }

    .todo-text {
      font-size: 13px;
      color: #444;
      flex: 1;
    }

    .todo-item.done-item .todo-text {
      text-decoration: line-through;
      color: #bbb;
    }

    .badge {
      font-size: 10px;
      padding: 2px 8px;
      border-radius: 20px;
      font-weight: 600;
    }

    .badge-done { background: #e8f5e9; color: #4caf50; }
    .badge-todo { background: #f0f0f0; color: #999; }

    /* 본문 섹션 */
    .section {
      margin-bottom: 48px;
    }

    .section-label {
      display: inline-block;
      font-size: 11px;
      font-weight: 700;
      color: #999;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      margin-bottom: 12px;
    }

    .section h2 {
      font-size: 20px;
      font-weight: 800;
      color: #111;
      margin-bottom: 16px;
    }

    .section p {
      font-size: 15px;
      color: #555;
      line-height: 1.9;
      margin-bottom: 14px;
    }

    /* 코드 블록 */
    .code-block {
      background: #1e1e1e;
      border-radius: 10px;
      padding: 20px 24px;
      margin: 16px 0;
      overflow-x: auto;
    }

    .code-block pre {
      font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
      font-size: 13px;
      color: #d4d4d4;
      line-height: 1.7;
      white-space: pre;
    }

    .code-block .comment { color: #6a9955; }
    .code-block .keyword { color: #569cd6; }
    .code-block .annotation { color: #dcdcaa; }
    .code-block .string { color: #ce9178; }
    .code-block .type { color: #4ec9b0; }

    /* 개념 박스 */
    .concept-box {
      border-left: 3px solid #222;
      padding: 14px 18px;
      background: #f8f8f8;
      border-radius: 0 10px 10px 0;
      margin: 16px 0;
    }

    .concept-box p {
      font-size: 14px;
      color: #444;
      margin: 0;
    }

    .concept-box strong {
      color: #111;
    }

    /* 흐름 다이어그램 */
    .flow {
      display: flex;
      align-items: center;
      gap: 8px;
      flex-wrap: wrap;
      margin: 16px 0;
      padding: 16px 20px;
      background: #f8f8f8;
      border-radius: 10px;
    }

    .flow-item {
      font-size: 13px;
      font-weight: 700;
      color: #fff;
      background: #222;
      padding: 6px 14px;
      border-radius: 20px;
    }

    .flow-arrow {
      font-size: 16px;
      color: #bbb;
    }

    /* 테이블 */
    table {
      width: 100%;
      border-collapse: collapse;
      margin: 16px 0;
      font-size: 14px;
    }

    th {
      background: #f0f0f0;
      padding: 10px 14px;
      text-align: left;
      font-weight: 700;
      color: #333;
    }

    td {
      padding: 10px 14px;
      border-bottom: 1px solid #f0f0f0;
      color: #555;
    }

    /* 회고 섹션 */
    .retrospect {
      background: #111;
      color: #eee;
      border-radius: 16px;
      padding: 28px 32px;
      margin-top: 48px;
    }

    .retrospect h2 {
      font-size: 18px;
      font-weight: 800;
      color: #fff;
      margin-bottom: 16px;
    }

    .retrospect p {
      font-size: 14px;
      color: #aaa;
      line-height: 1.9;
      margin-bottom: 10px;
    }

    .divider {
      border: none;
      border-top: 1px solid #eee;
      margin: 40px 0;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

  &lt;!-- 포스트 타이틀 --&gt;
  &lt;p class=&quot;post-date&quot;&gt;2026. 04. 09&lt;/p&gt;
  &lt;!-- TO DO LIST --&gt;
  &lt;div class=&quot;todo-card&quot;&gt;
    &lt;div class=&quot;todo-card-header&quot;&gt;
      &lt;div class=&quot;dot&quot;&gt;&lt;/div&gt;
      &lt;h3&gt;TO DO LIST&lt;/h3&gt;
    &lt;/div&gt;
    &lt;ul class=&quot;todo-list&quot;&gt;
      &lt;li class=&quot;todo-item done-item&quot;&gt;
        &lt;div class=&quot;circle done&quot;&gt;&lt;/div&gt;
        &lt;span class=&quot;todo-text&quot;&gt;3 Layer Architecture 구조 이해하기&lt;/span&gt;
        &lt;span class=&quot;badge badge-done&quot;&gt;Done&lt;/span&gt;
      &lt;/li&gt;
      &lt;li class=&quot;todo-item done-item&quot;&gt;
        &lt;div class=&quot;circle done&quot;&gt;&lt;/div&gt;
        &lt;span class=&quot;todo-text&quot;&gt;Entity, JPA 개념 잡기&lt;/span&gt;
        &lt;span class=&quot;badge badge-done&quot;&gt;Done&lt;/span&gt;
      &lt;/li&gt;
      &lt;li class=&quot;todo-item done-item&quot;&gt;
        &lt;div class=&quot;circle done&quot;&gt;&lt;/div&gt;
        &lt;span class=&quot;todo-text&quot;&gt;Repository, Service, Controller 작성하기&lt;/span&gt;
        &lt;span class=&quot;badge badge-done&quot;&gt;Done&lt;/span&gt;
      &lt;/li&gt;
      &lt;li class=&quot;todo-item done-item&quot;&gt;
        &lt;div class=&quot;circle done&quot;&gt;&lt;/div&gt;
        &lt;span class=&quot;todo-text&quot;&gt;DTO (Request / Response) 설계하기&lt;/span&gt;
        &lt;span class=&quot;badge badge-done&quot;&gt;Done&lt;/span&gt;
      &lt;/li&gt;
      &lt;li class=&quot;todo-item done-item&quot;&gt;
        &lt;div class=&quot;circle done&quot;&gt;&lt;/div&gt;
        &lt;span class=&quot;todo-text&quot;&gt;User CRUD API 완성 (저장, 조회, 수정, 삭제)&lt;/span&gt;
        &lt;span class=&quot;badge badge-done&quot;&gt;Done&lt;/span&gt;
      &lt;/li&gt;
      &lt;li class=&quot;todo-item&quot;&gt;
        &lt;div class=&quot;circle&quot;&gt;&lt;/div&gt;
        &lt;span class=&quot;todo-text&quot;&gt;ResponseEntity로 HTTP 상태코드 응답 처리하기&lt;/span&gt;
        &lt;span class=&quot;badge badge-todo&quot;&gt;Todo&lt;/span&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;

  &lt;p style=&quot;text-align:center; color:#ccc; letter-spacing: 8px; font-size: 24px;&quot;&gt;···&lt;/p&gt;

  &lt;!-- 섹션 1: 전체 구조 --&gt;
  &lt;div class=&quot;section&quot;&gt;
    &lt;span class=&quot;section-label&quot;&gt;Architecture&lt;/span&gt;
    &lt;h2&gt;3 Layer Architecture 란?&lt;/h2&gt;
    &lt;p&gt;오늘 실습의 핵심 뼈대. 역할에 따라 계층을 나눠서 코드를 관리하는 구조다. 각 계층이 딱 자기 역할만 하도록 분리하는 게 핵심이다.&lt;/p&gt;

    &lt;div class=&quot;flow&quot;&gt;
      &lt;span class=&quot;flow-item&quot;&gt;Client&lt;/span&gt;
      &lt;span class=&quot;flow-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;flow-item&quot;&gt;Controller&lt;/span&gt;
      &lt;span class=&quot;flow-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;flow-item&quot;&gt;Service&lt;/span&gt;
      &lt;span class=&quot;flow-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;flow-item&quot;&gt;Repository&lt;/span&gt;
      &lt;span class=&quot;flow-arrow&quot;&gt;→&lt;/span&gt;
      &lt;span class=&quot;flow-item&quot;&gt;DB&lt;/span&gt;
    &lt;/div&gt;

    &lt;table&gt;
      &lt;tr&gt;&lt;th&gt;계층&lt;/th&gt;&lt;th&gt;역할&lt;/th&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;Controller&lt;/td&gt;&lt;td&gt;요청을 받고 응답을 돌려주는 창구&lt;/td&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;Service&lt;/td&gt;&lt;td&gt;비즈니스 로직 처리 (Request → Entity → Response)&lt;/td&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;Repository&lt;/td&gt;&lt;td&gt;DB에 직접 접근하는 유일한 계층&lt;/td&gt;&lt;/tr&gt;
    &lt;/table&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;divider&quot;&gt;

  &lt;!-- 섹션 2: Entity --&gt;
  &lt;div class=&quot;section&quot;&gt;
    &lt;span class=&quot;section-label&quot;&gt;Entity&lt;/span&gt;
    &lt;h2&gt;Entity가 뭔지 이해하는 데 시간이 걸렸다&lt;/h2&gt;
    &lt;p&gt;처음엔 그냥 클래스 아닌가 싶었는데, JPA를 쓰면 이 클래스가 DB 테이블이랑 1:1로 매핑된다는 걸 배웠다. DB의 행(row) 하나 = Java 객체 하나.&lt;/p&gt;

    &lt;div class=&quot;code-block&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;annotation&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;annotation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;annotation&quot;&gt;@Table&lt;/span&gt;(name = &lt;span class=&quot;string&quot;&gt;&quot;users&quot;&lt;/span&gt;)
&lt;span class=&quot;annotation&quot;&gt;@NoArgsConstructor&lt;/span&gt;(access = AccessLevel.PROTECTED)
&lt;span class=&quot;keyword&quot;&gt;public class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;User&lt;/span&gt; {

    &lt;span class=&quot;annotation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;annotation&quot;&gt;@GeneratedValue&lt;/span&gt;(strategy = GenerationType.IDENTITY)
    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Long&lt;/span&gt; id;

    &lt;span class=&quot;annotation&quot;&gt;@Column&lt;/span&gt;(length = 50, nullable = &lt;span class=&quot;keyword&quot;&gt;false&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; name;

    &lt;span class=&quot;annotation&quot;&gt;@Column&lt;/span&gt;(unique = &lt;span class=&quot;keyword&quot;&gt;true&lt;/span&gt;, nullable = &lt;span class=&quot;keyword&quot;&gt;false&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; email;
    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; address;
}&lt;/pre&gt;&lt;/div&gt;

    &lt;div class=&quot;concept-box&quot;&gt;
      &lt;p&gt;&lt;strong&gt;@NoArgsConstructor(access = AccessLevel.PROTECTED)&lt;/strong&gt; — JPA는 내부적으로 기본 생성자가 필요하다. 근데 외부에서 &lt;code&gt;new User()&lt;/code&gt;처럼 텅 빈 객체를 만드는 건 막고 싶어서 PROTECTED로 설정한다. JPA는 쓸 수 있되, 개발자가 실수로 쓰는 건 컴파일 타임에 막아주는 안전장치.&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;divider&quot;&gt;

  &lt;!-- 섹션 3: JPA / Repository --&gt;
  &lt;div class=&quot;section&quot;&gt;
    &lt;span class=&quot;section-label&quot;&gt;JPA &amp; Repository&lt;/span&gt;
    &lt;h2&gt;SQL 없이 DB를 다루는 방법&lt;/h2&gt;
    &lt;p&gt;JPA는 Java 코드로 DB를 다룰 수 있게 해주는 기술이다. 우리가 Java로 메서드를 호출하면 JPA가 알아서 SQL로 번역해서 DB에 보내준다.&lt;/p&gt;
    &lt;p&gt;Repository는 JpaRepository를 상속받는 것만으로 기본 CRUD 기능을 다 가져온다. 코드 안에 아무것도 안 써도 된다.&lt;/p&gt;

    &lt;div class=&quot;code-block&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;keyword&quot;&gt;public interface&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;UserRepository&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;JpaRepository&lt;/span&gt;&amp;lt;&lt;span class=&quot;type&quot;&gt;User&lt;/span&gt;, &lt;span class=&quot;type&quot;&gt;Long&lt;/span&gt;&amp;gt; {
    &lt;span class=&quot;comment&quot;&gt;// 아무것도 안 써도 save, findById, findAll, delete 다 됨&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;

    &lt;div class=&quot;concept-box&quot;&gt;
      &lt;p&gt;&lt;strong&gt;JpaRepository&amp;lt;User, Long&amp;gt;&lt;/strong&gt; — 첫 번째는 다룰 엔티티, 두 번째는 그 엔티티의 PK 타입. User의 id가 Long이라서 Long을 넣었다.&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;divider&quot;&gt;

  &lt;!-- 섹션 4: DTO --&gt;
  &lt;div class=&quot;section&quot;&gt;
    &lt;span class=&quot;section-label&quot;&gt;DTO&lt;/span&gt;
    &lt;h2&gt;Entity를 그대로 쓰면 안 되는 이유&lt;/h2&gt;
    &lt;p&gt;처음엔 Entity를 그대로 반환하면 되지 않나 싶었는데, 두 가지 문제가 있다는 걸 배웠다. 첫째로 password 같은 민감한 정보까지 노출될 수 있고, 둘째로 DB 구조가 바뀌면 API 응답도 같이 바뀌어버린다.&lt;/p&gt;

    &lt;table&gt;
      &lt;tr&gt;&lt;th&gt;DTO&lt;/th&gt;&lt;th&gt;용도&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;CreateUserRequest&lt;/td&gt;&lt;td&gt;클라이언트 → 서버&lt;/td&gt;&lt;td&gt;id 없음, final 없음 (Spring이 채워줌)&lt;/td&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;CreateUserResponse&lt;/td&gt;&lt;td&gt;서버 → 클라이언트&lt;/td&gt;&lt;td&gt;id 있음, final 있음 (값 변경 불가)&lt;/td&gt;&lt;/tr&gt;
    &lt;/table&gt;

    &lt;div class=&quot;concept-box&quot;&gt;
      &lt;p&gt;Request에 id가 없는 이유 — id는 DB가 자동 생성하는 값이라 클라이언트가 아직 알 수 없다. 저장되고 나서야 id가 생기기 때문에 Response에만 id가 들어간다.&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;divider&quot;&gt;

  &lt;!-- 섹션 5: Service --&gt;
  &lt;div class=&quot;section&quot;&gt;
    &lt;span class=&quot;section-label&quot;&gt;Service&lt;/span&gt;
    &lt;h2&gt;비즈니스 로직이 뭔지 알게 됐다&lt;/h2&gt;
    &lt;p&gt;Service는 데이터를 저장하는 곳이 아니라, 받아서 처리하고 넘겨주는 곳이다. Request를 Entity로 변환 → Repository에 저장 → 결과를 Response로 변환해서 반환하는 흐름을 담당한다.&lt;/p&gt;

    &lt;div class=&quot;code-block&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;annotation&quot;&gt;@Transactional&lt;/span&gt;
&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;CreateUserResponse&lt;/span&gt; save(&lt;span class=&quot;type&quot;&gt;CreateUserRequest&lt;/span&gt; request) {
    &lt;span class=&quot;type&quot;&gt;User&lt;/span&gt; user = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;User&lt;/span&gt;(
        request.getName(), request.getEmail(), request.getAddress()
    );
    &lt;span class=&quot;type&quot;&gt;User&lt;/span&gt; savedUser = userRepository.save(user);

    &lt;span class=&quot;keyword&quot;&gt;return new&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;CreateUserResponse&lt;/span&gt;(
        savedUser.getId(), savedUser.getName(),
        savedUser.getEmail(), savedUser.getAddress()
    );
}&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;

  &lt;hr class=&quot;divider&quot;&gt;

  &lt;!-- 섹션 6: Controller --&gt;
  &lt;div class=&quot;section&quot;&gt;
    &lt;span class=&quot;section-label&quot;&gt;Controller&lt;/span&gt;
    &lt;h2&gt;@RestController vs @Controller&lt;/h2&gt;
    &lt;p&gt;옛날 방식은 서버에서 HTML까지 직접 만들어서 돌려줬다. 요즘은 프론트엔드가 화면을 담당하고 서버는 JSON 데이터만 주고받기 때문에 @RestController를 쓴다. @Controller에 JSON 자동 변환이 더해진 것!&lt;/p&gt;

    &lt;div class=&quot;code-block&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;annotation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;annotation&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class=&quot;keyword&quot;&gt;public class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;UserController&lt;/span&gt; {

    &lt;span class=&quot;keyword&quot;&gt;private final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;UserService&lt;/span&gt; userService;

    &lt;span class=&quot;annotation&quot;&gt;@PostMapping&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;/users&quot;&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;CreateUserResponse&lt;/span&gt; createUser(&lt;span class=&quot;annotation&quot;&gt;@RequestBody&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;CreateUserRequest&lt;/span&gt; request) {
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; userService.save(request);
    }

    &lt;span class=&quot;annotation&quot;&gt;@GetMapping&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;/users/{userId}&quot;&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;GetOneUserResponse&lt;/span&gt; getOneUser(&lt;span class=&quot;annotation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Long&lt;/span&gt; userId) {
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; userService.getOne(userId);
    }

    &lt;span class=&quot;annotation&quot;&gt;@PutMapping&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;/users/{userId}&quot;&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;UpdateUserResponse&lt;/span&gt; update(&lt;span class=&quot;annotation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Long&lt;/span&gt; userId,
                                         &lt;span class=&quot;annotation&quot;&gt;@RequestBody&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;UpdateUserRequest&lt;/span&gt; request) {
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; userService.update(userId, request);
    }

    &lt;span class=&quot;annotation&quot;&gt;@DeleteMapping&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;/users/{userId}&quot;&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;public void&lt;/span&gt; delete(&lt;span class=&quot;annotation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Long&lt;/span&gt; userId) {
        userService.delete(userId);
    }
}&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;

  &lt;p style=&quot;text-align:center; color:#ccc; letter-spacing: 8px; font-size: 24px;&quot;&gt;···&lt;/p&gt;

  &lt;!-- 회고 --&gt;
  &lt;div class=&quot;retrospect&quot;&gt;
    &lt;h2 style=&quot;color: #ffffff;&quot;&gt;  오늘 학습을 마치며&lt;/h2&gt;
    &lt;p&gt;아직도 많이 어렵지만 어노테이션 하나하나가 왜 붙는지 어떤 역할을 하는지 공부하며 실습해보니 약간은 알 것 같다.&lt;/p&gt;
    &lt;p&gt;JAVA에서 하던 것처럼(속성, 생성자, 기능 / Main에서 객체 생성) 흐름은 비슷했다.&lt;/p&gt;
    &lt;br&gt;
    &lt;p&gt;하지만 Spring은 클라이언트에서 Http 요청을 하면 Layer Architecture 패턴으로 흘러가고 의존성 주입,&lt;/p&gt;
    &lt;p&gt;그리고 서로 데이터를 주고 받을 때 DTO를 사용하고 또 Entity에서 데이터를 받아&lt;/p&gt;
    &lt;p&gt;그걸 넘기고 저장하고 이런 흐름인 것 같다. 계속 코딩하다보니 약간의 패턴이 보였다.&lt;/p&gt;
    &lt;br&gt;
    &lt;p&gt;아직 많은 컴포넌트가 낯설게 느껴져서 많이 해보며 익숙해져야 할 것 같다.&lt;/p&gt;
    &lt;br&gt;
    &lt;p&gt;내일은 ResponseEntity로 HTTP 상태코드까지 응답에 담는 걸 해볼 예정이다.&lt;/p&gt;
  &lt;/div&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>spring boot</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/25</guid>
      <comments>https://mooncommit-dev.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 9 Apr 2026 20:06:56 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] Spring - 실전에 필요한 개념</title>
      <link>https://mooncommit-dev.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;오늘은 튜터님의 말씀을 듣고, 실전 위주 중요 포인트를 공부했다.&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background: #fafafa; border: 1px solid #e8e8e8; border-radius: 12px; padding: 28px 32px; margin-bottom: 40px; font-family: 'DM Sans', sans-serif;&quot;&gt;
&lt;p style=&quot;font-family: 'DM Mono', monospace; font-size: 11px; color: #bbb; letter-spacing: 2px; margin: 0 0 20px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;To Do List &lt;/span&gt;&amp;middot; 2026.04.08&lt;/p&gt;
&lt;ul style=&quot;list-style: none; padding: 0; margin: 0;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;01&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;Spring Boot 개념&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;02&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;IoC / DI&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;03&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;Controller &amp;rarr; Service &amp;rarr; Repository 흐름&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;04&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;MVC 패턴&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;05&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;주요 어노테이션&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;06&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;Lombok&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;07&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;Thymeleaf&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;08&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;URL 매핑 어노테이션&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;09&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;application.properties&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;display: flex; align-items: baseline; gap: 12px; padding: 8px 0;&quot;&gt;&lt;span style=&quot;font-family: 'DM Mono',monospace; font-size: 10px; color: #ccc; min-width: 18px;&quot;&gt;10&lt;/span&gt; &lt;span style=&quot;font-size: 14px; color: #333;&quot;&gt;프로젝트 폴더 구조&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 도입 문장 --&gt;&lt;!-- 1. Spring Boot란? --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spring Boot란?&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java로 웹 개발을 쉽게 할 수 있게 만든 프레임워크.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Spring은 설정이 너무 복잡했는데, Spring Boot는 그 복잡한 설정을 자동으로 해준다&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCo5B8/dJMcahRv3Lj/0O0YKOiX9ffITfNiKwK8Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCo5B8/dJMcahRv3Lj/0O0YKOiX9ffITfNiKwK8Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCo5B8/dJMcahRv3Lj/0O0YKOiX9ffITfNiKwK8Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCo5B8%2FdJMcahRv3Lj%2F0O0YKOiX9ffITfNiKwK8Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;571&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;요즘 Java 백엔드 개발은 거의 다 Spring Boot로 한다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 2. IoC와 DI --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. IoC와 DI (Spring의 핵심 원리)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;IoC (제어의 역전)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서는 내가 직접 객체를 만들었지만, Spring에서는 Spring이 대신 만들고 관리해준다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//   순수 Java 방식 - 내가 직접 객체 생성
UserService userService = new UserService();
userService.doSomething();

// ☺️ Spring 방식 - Spring이 알아서 만들어서 넣어줌
@Autowired
UserService userService;   // 선언만 하면 끝!&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp; ️ Spring이 관리하는 객체를 'Spring Bean'이라고 한다.&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;@Component &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; @Controller &lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;@Service &lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;@Repository&amp;nbsp;&lt;/span&gt; 가 붙으면 자동으로 Bean이 된다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DI (의존성 주입)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 필요한 객체를 자동으로 넣어주는 것. &lt;span class=&quot;inline-code&quot;&gt;@Autowired&lt;/span&gt;를 쓰면 된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemoService {

    @Autowired
    MemoRepository memoRepository;  // Spring이 자동으로 넣어줌

    public List&amp;lt;Memo&amp;gt; getMemos() {
        return memoRepository.findAll();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 3. 전체 흐름 --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 전체 흐름 (요청이 들어올 때)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 URL을 요청하면 이 순서로 처리된다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvoMJo/dJMcaf7et04/raQGAxkMtYHlWoB4IfOjsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvoMJo/dJMcaf7et04/raQGAxkMtYHlWoB4IfOjsK/img.png&quot; data-alt=&quot;다시 역순으로 반환&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvoMJo/dJMcaf7et04/raQGAxkMtYHlWoB4IfOjsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvoMJo%2FdJMcaf7et04%2FraQGAxkMtYHlWoB4IfOjsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;359&quot; height=&quot;532&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다시 역순으로 반환&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;blockquote data-ke-style=&quot;style2&quot;&gt; ️ DispatcherServlet은 Spring이 알아서 관리해줌. 개발자는 Controller / Service / Repository만 만들면 된다&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 4. MVC 패턴 --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. MVC 패턴&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션을 세 가지 역할로 나눠서 개발하는 방식&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyovn/dJMcagkLM1g/kvrY1Pq7SAp74Lthr6QkL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyovn/dJMcagkLM1g/kvrY1Pq7SAp74Lthr6QkL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyovn/dJMcagkLM1g/kvrY1Pq7SAp74Lthr6QkL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkyovn%2FdJMcagkLM1g%2FkvrY1Pq7SAp74Lthr6QkL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;264&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 5. @Controller --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. @Controller &amp;mdash; URL 요청 처리&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 HTTP 요청을 받아서 처리하는 클래스&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class MemoController {

    @Autowired
    MemoService memoService;

    // GET /memos &amp;rarr; 메모 목록 조회
    @GetMapping(&quot;/memos&quot;)
    public String getMemos(Model model) {
        List&amp;lt;Memo&amp;gt; memos = memoService.getMemos();
        model.addAttribute(&quot;memos&quot;, memos);  // View로 데이터 전달
        return &quot;memo-list&quot;;                  // memo-list.html 렌더링
    }

    // POST /memos &amp;rarr; 메모 저장
    @PostMapping(&quot;/memos&quot;)
    public String createMemo(@RequestParam String contents) {
        memoService.createMemo(contents);
        return &quot;redirect:/memos&quot;;  // 저장 후 목록으로 이동
    }

    // GET /memos/1 &amp;rarr; 특정 메모 조회
    @GetMapping(&quot;/memos/{id}&quot;)
    public String getMemo(@PathVariable Long id, Model model) {
        Memo memo = memoService.getMemo(id);
        model.addAttribute(&quot;memo&quot;, memo);
        return &quot;memo-detail&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; ️ @PathVariable은 URL의 {id} 부분을 받고, @RequestParam은 폼에서 입력한 값을 받는다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 6. @Service --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. @Service &amp;mdash; 비즈니스 로직&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 기능(비즈니스 로직)을 담당하는 클래스. Controller와 Repository 사이에 위치한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemoService {

    @Autowired
    MemoRepository memoRepository;

    // 전체 메모 조회
    public List&amp;lt;Memo&amp;gt; getMemos() {
        return memoRepository.findAll();
    }

    // 특정 메모 조회
    public Memo getMemo(Long id) {
        return memoRepository.findById(id)
                .orElseThrow(() -&amp;gt; new RuntimeException(&quot;메모 없음&quot;));
    }

    // 메모 저장
    public void createMemo(String contents) {
        Memo memo = new Memo(contents);
        memoRepository.save(memo);
    }

    // 메모 삭제
    public void deleteMemo(Long id) {
        memoRepository.deleteById(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;!-- 7. @Repository --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. @Repository &amp;mdash; DB 접근&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 데이터를 조회하고 저장하는 역할. &lt;span class=&quot;inline-code&quot;&gt;JpaRepository&lt;/span&gt;를 상속받으면 기본 CRUD가 자동으로 생긴다!&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Repository
public interface MemoRepository extends JpaRepository&amp;lt;Memo, Long&amp;gt; {

    // ✅ 자동 제공되는 메서드:
    // findAll()           &amp;rarr; 전체 조회
    // findById(id)        &amp;rarr; id로 조회
    // save(memo)          &amp;rarr; 저장/수정
    // deleteById(id)      &amp;rarr; 삭제
    // count()             &amp;rarr; 개수 세기
    // existsById(id)      &amp;rarr; 존재 여부 확인

    // 필요하면 직접 추가도 가능:
    List&amp;lt;Memo&amp;gt; findByContentsContaining(String keyword);
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; ️ 인터페이스만 만들면 Spring이 구현체를 자동으로 만들어줌. 직접 SQL 안 써도 된다!&lt;/blockquote&gt;
&lt;/div&gt;
&lt;!-- 8. @Entity --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. @Entity &amp;mdash; DB 테이블 매핑&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스가 DB 테이블과 1:1로 연결된다는 표시. JPA가 자동으로 테이블을 만들어준다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Getter                          // getter 자동 생성 (Lombok)
@Entity                          // DB 테이블과 연결
@Table(name = &quot;memos&quot;)           // 테이블 이름 지정
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Memo {

    @Id                          // Primary Key
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // AUTO_INCREMENT
    private Long id;

    @Column(nullable = false, length = 100)
    private String contents;

    public Memo(String contents) {
        this.contents = contents;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; ️ @NoArgsConstructor(access = AccessLevel.PROTECTED)&lt;br /&gt;JPA는 빈 생성자가 필요하지만 public으로 열면 불완전한 객체가 만들어질 수 있어서 protected로 막는다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 9. Lombok --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. Lombok &amp;mdash; 반복 코드 자동 생성&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 getter/setter/생성자를 직접 쓰는 게 귀찮으니까 어노테이션 하나로 자동 생성해주는 도구&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/901C7/dJMb99MHkgl/sPAwVzFbtTKKFLQOm8cil0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/901C7/dJMb99MHkgl/sPAwVzFbtTKKFLQOm8cil0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/901C7/dJMb99MHkgl/sPAwVzFbtTKKFLQOm8cil0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F901C7%2FdJMb99MHkgl%2FsPAwVzFbtTKKFLQOm8cil0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1756&quot; height=&quot;886&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Builder 예시&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Builder
@Getter
public class Memo {
    private Long id;
    private String contents;
}

// 사용할 때 - 필요한 것만 골라서 설정 가능
Memo memo = Memo.builder()
        .id(1L)
        .contents(&quot;오늘 할 일&quot;)
        .build();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 10. Thymeleaf --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. Thymeleaf &amp;mdash; View 화면 만들기&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 HTML을 동적으로 만들어주는 템플릿 엔진. Controller에서 보낸 데이터를 HTML에 채워준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Controller에서 데이터 보내기&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@GetMapping(&quot;/memos&quot;)
public String getMemos(Model model) {
    List&amp;lt;Memo&amp;gt; memos = memoService.getMemos();
    model.addAttribute(&quot;memos&quot;, memos);      // &quot;memos&quot; 이름으로 데이터 전달
    model.addAttribute(&quot;title&quot;, &quot;메모 목록&quot;);
    return &quot;memo-list&quot;;  // templates/memo-list.html 렌더링
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;memo-list.html에서 데이터 받기&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&amp;gt;
&amp;lt;body&amp;gt;

    &amp;lt;!-- ${변수명}으로 데이터 꺼내기 --&amp;gt;
    &amp;lt;h1 th:text=&quot;${title}&quot;&amp;gt;기본 제목&amp;lt;/h1&amp;gt;

    &amp;lt;!-- th:each로 리스트 반복 --&amp;gt;
    &amp;lt;ul&amp;gt;
        &amp;lt;li th:each=&quot;memo : ${memos}&quot;&amp;gt;
            &amp;lt;span th:text=&quot;${memo.id}&quot;&amp;gt;id&amp;lt;/span&amp;gt;.
            &amp;lt;span th:text=&quot;${memo.contents}&quot;&amp;gt;내용&amp;lt;/span&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;

    &amp;lt;!-- th:if로 조건문 --&amp;gt;
    &amp;lt;p th:if=&quot;${memos.isEmpty()}&quot;&amp;gt;메모가 없어요!&amp;lt;/p&amp;gt;

    &amp;lt;!-- 폼에서 데이터 전송 --&amp;gt;
    &amp;lt;form th:action=&quot;@{/memos}&quot; method=&quot;post&quot;&amp;gt;
        &amp;lt;input type=&quot;text&quot; name=&quot;contents&quot; placeholder=&quot;메모 입력&quot;&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;저장&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 11. URL 매핑 --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. URL 매핑 어노테이션&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rbTSV/dJMcaarlBY9/9k2SXAX7k6vl2ihanNcQWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rbTSV/dJMcaarlBY9/9k2SXAX7k6vl2ihanNcQWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rbTSV/dJMcaarlBY9/9k2SXAX7k6vl2ihanNcQWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrbTSV%2FdJMcaarlBY9%2F9k2SXAX7k6vl2ihanNcQWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1740&quot; height=&quot;664&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Controller
public class MemoController {

    @GetMapping(&quot;/memos&quot;)          // GET  /memos      &amp;rarr; 목록 조회
    @PostMapping(&quot;/memos&quot;)         // POST /memos      &amp;rarr; 저장
    @GetMapping(&quot;/memos/{id}&quot;)     // GET  /memos/1    &amp;rarr; 단건 조회
    @DeleteMapping(&quot;/memos/{id}&quot;)  // DELETE /memos/1  &amp;rarr; 삭제
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 12. application.properties --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. application.properties &amp;mdash; 설정 파일&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연결, JPA 설정 등 앱 전반적인 설정을 하는 파일. 위치: &lt;span class=&quot;inline-code&quot;&gt;src/main/resources/application.properties&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# ===== MySQL 연결 설정 =====
spring.datasource.url=jdbc:mysql://localhost:3306/데이터베이스명?useSSL=false
spring.datasource.username=root
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# ===== JPA 설정 =====
spring.jpa.hibernate.ddl-auto=update        # 테이블 자동 생성/수정
spring.jpa.show-sql=true                    # 실행 SQL 콘솔에 출력
spring.jpa.properties.hibernate.format_sql=true  # SQL 예쁘게 출력

# ===== Thymeleaf 설정 =====
spring.thymeleaf.cache=false  # 개발 중에는 false (수정 즉시 반영)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; ️ ddl-auto=update &amp;rarr; 코드 변경 시 테이블 자동으로 업데이트. 개발할 때 편리하다!&lt;/blockquote&gt;
&lt;/div&gt;
&lt;!-- 13. 어노테이션 총정리 --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. 어노테이션 총정리&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;1568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz2AbM/dJMcacQdLFj/a7FGIXCRFxyR00qaNY3QW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz2AbM/dJMcacQdLFj/a7FGIXCRFxyR00qaNY3QW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz2AbM/dJMcacQdLFj/a7FGIXCRFxyR00qaNY3QW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz2AbM%2FdJMcacQdLFj%2Fa7FGIXCRFxyR00qaNY3QW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1164&quot; height=&quot;1568&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;1568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 14. 폴더 구조 --&gt;
&lt;div class=&quot;section&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 초보자를 위한 Spring Boot 개발 순서&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;과제나 실습을 할 때 어떻게 시작해야 할지 막막해서 쉽게 접근할 수 있는 방법을 찾아봤다.&lt;/i&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; 1단계&amp;nbsp;&amp;nbsp;&lt;/span&gt;&amp;nbsp; Entity 만들기 (데이터 구조 설계)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;DB 테이블과 연결될 자바 객체를 가장 먼저 만든다.&lt;/li&gt;
&lt;li&gt;&quot;내가 어떤 데이터를 다룰 것인가&quot; 정해져야 그 다음 단계가 진행 (메모 제목, 내용, 작성자 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; 2단계&amp;nbsp; &lt;/span&gt;&amp;nbsp; Repository 만들기 (DB 통로 확보)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entity를 DB에 저장하고 불러올 인터페이스를 만든다.&lt;/li&gt;
&lt;li&gt;JPA를 상속받기만 하면 save(), findAll() 같은 강력한 기능을 바로 쓸 수 있는 '도구'가 생기는 단계이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; 3단계&amp;nbsp; &lt;/span&gt;&amp;nbsp; Service 만들기 (비즈니스 로직 구현)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핵심 기능을 코딩한다.&lt;/li&gt;
&lt;li&gt;Repository라는 도구를 활용해 실제로 &quot;메모를 수정한다&quot;, &quot;리스트를 역순으로 보여준다&quot; 같은 알맹이 기능을 만드는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; 4단계&amp;nbsp;&amp;nbsp;&lt;/span&gt;&amp;nbsp; Controller 만들기 (사용자 연결)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 요청(URL)을 받아 서비스와 연결해 준다.&lt;/li&gt;
&lt;li&gt;앞서 만든 기능들을 사용자가 실제로 클릭하거나 접속해서 사용할 수 있게 문을 열어주는 최종 단계이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background: #fafafa; border: 1px solid #e8e8e8; border-radius: 12px; padding: 24px 32px; margin-top: 48px;&quot;&gt;
&lt;p style=&quot;font-family: 'DM Mono', monospace; font-size: 11px; color: #bbb; letter-spacing: 2px; margin: 0 0 16px;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;TODAY POINT&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #444; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;  Spring은 어노테이션만 잘 붙이면 나머지는 알아서 해준다.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #444; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;  Controller &amp;rarr; Service &amp;rarr; Repository 이 흐름만 머릿속에 넣으면 과제 시작 가능.&lt;/p&gt;
&lt;p style=&quot;font-size: 14px; color: #444; margin: 0;&quot; data-ke-size=&quot;size16&quot;&gt;  어려운 게 당연하다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  오늘 학습을 마치며  &lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 처음 접하다 보니 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp;@ 어노테이션&amp;nbsp;&lt;/span&gt; 들도 낯설고 종류도 많아서 막막하게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 튜터님께서 &amp;ldquo;원래 배울 것이 많고 시간이 오래 걸리는 분야지만, 지금은 실습과 실무에 필요한 부분 위주로 학습하면 된다&amp;rdquo;고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말씀해 주셔서 방향을 잡을 수 있었다. 정말 다행이다 ꒰⍢꒱ ༘*&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 내용을 이해하려 하다가, 꼭 필요한 핵심에 집중하자고 생각하니 학습 범위가 정리되는 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 핵심은 'Controller &amp;rarr; Service &amp;rarr; Repository' 이 세 가지인 것 같다. 확실히 학습하고 넘어갈 것이다⎝⍥⎠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내일은 이 흐름 속에서 각 계층이 어떤 역할을 하고, 어떤 요소들로 구성되는지 더 구체적으로 학습해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링을 접한 지 이제 이틀째인 만큼 조급해하지 않고 차근차근 익혀나가면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내일부터는 본격적으로 과제에도 도전해볼 생각이다. 화이팅!  &lt;/p&gt;</description>
      <category>Spring</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/23</guid>
      <comments>https://mooncommit-dev.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 8 Apr 2026 23:55:09 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] Spring 시작하기</title>
      <link>https://mooncommit-dev.tistory.com/22</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성일 : 2026-04-07&lt;br /&gt;강의 : 입문 Spring&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  오늘 들은 강의&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-1 OT - 스프링으로 백엔드 개발 시작하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-2 스프링을 위한 클래스(Class)와 객체(Object)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-3 스프링을 위한 인터페이스(Interface)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-4 개발환경 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-5 꼭 알아야할 기본 개발 용어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-6 라이브러리와 프레임워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-7 HTTP(1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-8 HTTP(2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-9 API 이해하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-10 Annotation과 Lombok&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-11 스프링 프로젝트 시작 &amp;amp; 의존성 관리 (Gradle/Maven)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-12 Servlet 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ chapter 1-13 Servlet 기반 영화 추천 API 실습&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; text-align: start;&quot;&gt; ️ 오늘 배운 핵심 개념&lt;/span&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #ffffff; color: #141413; text-align: start;&quot; data-diff-line=&quot;28&quot; data-diff-type=&quot;normal&quot;&gt;내일배움캠프 초반에 배운 내용들이 많이 나와서 오늘 처음 배운 것들을 위주로 정리해 보았다.&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프레임워크&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;는 frame(틀) + work(일하다)의 합성어. 개발자가 틀 안에서 개발하도록 강제한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라이브러리&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;는 개발 중 필요한 기능을&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가져다 쓰는 도구 모음이다. &lt;/span&gt;&lt;b&gt;프레임워크 = 라이브러리들의 모음&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이라고 볼 수 있고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그 안에서 라이브러리들이 호환되도록 세팅되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #141413; text-align: start;&quot; data-diff-line=&quot;28&quot; data-diff-type=&quot;normal&quot;&gt;
&lt;div&gt;&lt;br /&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;제어권&lt;/td&gt;
&lt;td&gt;프레임워크가 내 코드를 호출&lt;/td&gt;
&lt;td&gt;내가 라이브러리를 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;전체 구조(frame)를 잡기 위해&lt;/td&gt;
&lt;td&gt;특정 기능 구현을 위해&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;Spring, Django, Rails&lt;/td&gt;
&lt;td&gt;Lombok, Jackson, Guava&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프레임워크 장점 :&lt;/b&gt; 코드 일관성 &amp;uarr;, 팀 협업 편함, 보안 기능 기본 제공, 테스트 환경 통합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프레임워크 단점 :&lt;/b&gt; 초기 학습 비용 높음, 버전 호환성 문제, 구조 변경 자유도 낮음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; text-align: start;&quot;&gt; ️ &lt;/span&gt;스프링(Spring) 프레임워크&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바(Java) 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크. 핵심은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;개발자는 스프링을 사용하여 비즈니스 로직에 더 집중할 수 있다&quot; 는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 사용 전후를 보면 확 와닿는다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BEFORE (Spring 없이 - 소켓 직접 처리)&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;processing&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;// 요청 파싱, 헤더 처리, JSON 파싱, 응답 작성... 모두 직접 구현해야 함
public void handleRequest(Socket clientSocket) throws IOException {
    BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
    // ... HTTP 요청 파싱, JSON 수동 파싱, 응답 작성 등 수십 줄
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AFTER (Spring 사용)&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dart&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;@RestController
public class SpringCalculator {
    @PostMapping(&quot;/api/add&quot;)
    public Map&amp;lt;String, Integer&amp;gt; add(@RequestBody Map&amp;lt;String, Integer&amp;gt; numbers) {
        int result = numbers.get(&quot;a&quot;) + numbers.get(&quot;b&quot;);
        return Map.of(&quot;result&quot;, result);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라와 기술적 복잡성은 Spring이 처리 &amp;rarr; 개발자는 로직에만 집중!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #141413; text-align: start;&quot;&gt; ️ &lt;/span&gt;HTTP 상태 코드 (Status Code)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청에 대한 처리 상태를 응답하는 코드. 100 단위 숫자의 의미만 알면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;코드의미대표 예시
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;1xx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;정보 (처리 중)&lt;/td&gt;
&lt;td&gt;잘 사용 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;2xx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;성공&lt;/td&gt;
&lt;td&gt;200 OK, 201 Created, 204 No Content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;3xx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;리다이렉션&lt;/td&gt;
&lt;td&gt;OAuth 로그인 시 자주 등장 (카카오, 구글 로그인 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;4xx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클라이언트 에러&lt;/td&gt;
&lt;td&gt;400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;5xx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;서버 에러&lt;/td&gt;
&lt;td&gt;500 Internal Server Error, 503 Service Unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자주 쓰는 코드 정리:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;200 OK : 요청 성공&lt;/li&gt;
&lt;li&gt;201 Created : 새 리소스 생성 성공&lt;/li&gt;
&lt;li&gt;204 No Content : 성공했지만 응답 데이터 없음 (삭제 성공 시)&lt;/li&gt;
&lt;li&gt;400 Bad Request : 요청 형식이 잘못됨 (예: GET API에 POST로 요청)&lt;/li&gt;
&lt;li&gt;401 Unauthorized : 인증 필요 (로그인 안 됨)&lt;/li&gt;
&lt;li&gt;403 Forbidden : 인증은 됐지만 권한 없음 (일반 유저 vs 관리자)&lt;/li&gt;
&lt;li&gt;404 Not Found : 리소스 없음 (숨기기 용도로 쓰기도 함)&lt;/li&gt;
&lt;li&gt;500 Internal Server Error : 서버 내부 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ Annotation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 코드에 @로 메타데이터를 추가해 특별한 의미나 동작을 부여하는 것. 복잡한 기능도 어노테이션 하나로 쉽게 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;public class LogitechMouse implements Mouse {
    @Override  // &amp;larr; 이게 어노테이션! 인터페이스에 없는 메서드면 에러 표시해줌
    public void leftClick() { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ Lombok&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보일러플레이트 코드(getter/setter/생성자 등 반복 코드)를 어노테이션으로 자동 생성해주는 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;어노테이션역할
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 93.2558%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.5116%;&quot;&gt;@Getter / @Setter&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%;&quot;&gt;모든 필드의 getter/setter 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.5116%;&quot;&gt;@ToString&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%;&quot;&gt;toString() 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.5116%;&quot;&gt;@NoArgsConstructor&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%;&quot;&gt;기본 생성자 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.5116%;&quot;&gt;@AllArgsConstructor&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%;&quot;&gt;모든 필드를 받는 생성자 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.5116%;&quot;&gt;@RequiredArgsConstructor&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%;&quot;&gt;final 필드만 받는 생성자 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.5116%;&quot;&gt;@Slf4j&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%;&quot;&gt;로그용 Logger 객체 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;// Lombok 사용 예시
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private int age;
    // getName(), setName(), getAge(), setAge(), 생성자 모두 자동 생성!
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;느낀 점&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에 배운 내용들이 많이 겹쳐 있어서 정리하며 공부하니 챕터 1은 많이 어렵지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 사용 전후 코드 비교가 제일 인상 깊었다. 복잡한 걸 어노테이션 몇 개로 줄일 수 있다는 게 신기했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그래서 스프링을 쓰는구나&quot; 라고 느낄 수 있었다.&lt;/p&gt;</description>
      <category>til</category>
      <category>내일배움캠프</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/22</guid>
      <comments>https://mooncommit-dev.tistory.com/22#entry22comment</comments>
      <pubDate>Tue, 7 Apr 2026 23:17:39 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 알고리즘 기초 개념 정리</title>
      <link>https://mooncommit-dev.tistory.com/21</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;  알고리즘 강의 챕터 1-2&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffe7dd;&quot;&gt;&amp;nbsp; 알고리즘&amp;nbsp; &lt;/span&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;background-color: #fff2bb;&quot;&gt; &amp;nbsp;시간복잡도&amp;nbsp;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;background-color: #ddf1d1;&quot;&gt; &amp;nbsp;빅오표기법&amp;nbsp; &lt;/span&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;background-color: #bee8ef;&quot;&gt; &amp;nbsp;코딩테스트&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kNRXP/dJMcajuXAO1/YzSogziLOJczZjjkMvnVPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kNRXP/dJMcajuXAO1/YzSogziLOJczZjjkMvnVPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kNRXP/dJMcajuXAO1/YzSogziLOJczZjjkMvnVPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkNRXP%2FdJMcajuXAO1%2FYzSogziLOJczZjjkMvnVPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;465&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 알고리즘이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;알고리즘(Algorithm)&lt;/b&gt; : 문제를 해결하기 위한 단계적 절차나 규칙&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요리 레시피처럼, 시작부터 끝까지 순서대로 무엇을 해야 하는지 적어놓은 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;알고리즘을 왜 배울까?&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 72.5572%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3677%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f1ffe0; color: #000000;&quot;&gt;&amp;nbsp; 논리적 사고력 향상&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.0741%; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;체계적&amp;middot;논리적인 사고방식을 기를 수 있음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3677%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f1ffe0; color: #000000;&quot;&gt;&amp;nbsp; 효율적인 문제 해결&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.0741%; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;더 빠르고 효율적인 코드 작성 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3677%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f1ffe0; color: #000000;&quot;&gt;&amp;nbsp; 취업 (코딩테스트)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.0741%; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;문제 이해도 + 구현 능력 + 효율적 해결 능력을 종합 평가&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;알고리즘 표현 방법&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt; &lt;/span&gt;&amp;nbsp;의사코드 (Pseudocode)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그래밍 언어와 유사하지만 문법에 얽매이지 않음&lt;/li&gt;
&lt;li&gt;단계별 논리 흐름을 구조적으로 표현하기 좋음&lt;/li&gt;
&lt;li&gt;실제 코드로 옮기기 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;matlab&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;FUNCTION findMax(numbers):
    max &amp;larr; numbers[0]
    FOR i = 1 TO length(numbers) - 1
        IF numbers[i] &amp;gt; max THEN
            max &amp;larr; numbers[i]
    RETURN max&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ꙭ̮ &lt;b&gt;자연어 표기법&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일상적인 언어로 설명 &amp;rarr; 비전문가도 쉽게 이해&lt;/li&gt;
&lt;li&gt;전체 흐름과 핵심 아이디어를 빠르게 전달&lt;/li&gt;
&lt;li&gt;단, 모호한 표현이 생기지 않도록 주의&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;1. 첫 번째 숫자를 최댓값으로 저장한다.
2. 두 번째 숫자부터 순서대로 확인한다.
   - 현재 숫자가 최댓값보다 크면, 최댓값을 갱신한다.
3. 모든 숫자 확인 후, 최댓값을 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신만의 언어로 문제 해결 아이디어를 표현하는 것이 중요. 의사코드와 자연어를 혼용해도 OK!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 좋은 알고리즘의 조건&lt;/h2&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;조건핵심 내용예시&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 91.3953%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f5eeff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 정확성&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.9302%;&quot;&gt;같은 입력 &amp;rarr; 항상 같은 올바른 결과&lt;/td&gt;
&lt;td style=&quot;width: 45.9302%;&quot;&gt;[1,3,2] 정렬 &amp;rarr; 항상 [1,2,3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f5eeff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 효율성&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.9302%;&quot;&gt;시간&amp;middot;공간 복잡도가 적절해야 함&lt;/td&gt;
&lt;td style=&quot;width: 45.9302%;&quot;&gt;버블정렬 대신 퀵정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f5eeff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 명확성&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.9302%;&quot;&gt;각 단계가 누가 봐도 이해하기 쉬움&lt;/td&gt;
&lt;td style=&quot;width: 45.9302%;&quot;&gt;변수명 tmp &amp;rarr; totalAmount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f5eeff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 확장성&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.9302%;&quot;&gt;다양한 상황에 적용 가능, 수정&amp;middot;디버깅 용이&lt;/td&gt;
&lt;td style=&quot;width: 45.9302%;&quot;&gt;새로운 로그인 방식을 쉽게 추가 가능한 구조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 알고리즘 성능 분석&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  시간복잡도란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘이 문제를 해결하는 데 &lt;b&gt;얼마나 많은 시간이 필요한지&lt;/b&gt;를 표현하는 방법 &amp;rarr; 정확한 실행 시간이 아닌,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입력 크기 증가에 따른 실행 시간의 증가 추세&lt;/b&gt;를 나타냄.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공간복잡도(메모리)도 있지만, 사용자가 직접 체감하기 어렵기 때문에 시간복잡도를 더 중요하게 봄.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  빅오(Big-O) 표기법&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(1)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 배열 인덱스 접근, 스택/큐 삽입&amp;middot;삭제&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 입력 크기와 무관, 항상 동일한 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(log N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 이진 탐색&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 입력이 커져도 로그 값으로만 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 배열 순차 탐색, 최대/최솟값 찾기&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 입력과 시간이 비례&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(N log N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 병합정렬, 퀵정렬, 힙정렬&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 가장 효율적인 비교 기반 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(N&amp;sup2;)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 버블/삽입/선택 정렬, 2중 반복문&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 입력의 제곱으로 시간 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(2^N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 부분집합 생성&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 지수적으로 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 15.3488%;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp; O(N!)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&amp;nbsp; 순열 생성&lt;/td&gt;
&lt;td style=&quot;width: 41.1628%;&quot;&gt;&amp;nbsp; 팩토리얼로 폭발적 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  시간복잡도 계산 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명령문 개수를 세고 &amp;rarr; 가장 큰 항만 남기기&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 실행 횟수 분석 예시 : 1부터 n까지의 합
int sum = 0;           // 1회
for(int i = 0; i &amp;lt; n; i++) {   // 초기화 1회 + 조건 n+1회 + 증감 n회
    sum += i;          // n회
}
System.out.println(sum); // 1회

// 전체 : 1 + 1 + (n+1) + n + n + 1 = 3n + 4
// &amp;rarr; 최고차항만: O(n)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;n 이 매우 커지면 작은 항들의 영향은 미미해짐&lt;/li&gt;
&lt;li&gt;상수항은 실행 환경에 따라 달라지므로 제거&lt;/li&gt;
&lt;li&gt;증가 추세가 핵심이지, 정확한 횟수는 중요하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;T(n) = 5n&amp;sup3; + 100n&amp;sup2; + 200n + 1000
&amp;rarr; 최고차항: 5n&amp;sup3;
&amp;rarr; 계수 제거: n&amp;sup3;
&amp;rarr; 최종: O(n&amp;sup3;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  코딩테스트 시간 제한 기준 (테스트 케이스당 1~2초)&lt;/h4&gt;
&lt;div&gt;시간복잡도데이터 크기 상한비고
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;&lt;span style=&quot;background-color: #9ae09f;&quot;&gt;&amp;nbsp; O(log N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&amp;nbsp;10^18&lt;/td&gt;
&lt;td style=&quot;width: 46.8605%;&quot;&gt;&amp;nbsp;N이 매우 커도 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;&lt;span style=&quot;background-color: #9ae09f;&quot;&gt;&amp;nbsp; O(N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&amp;nbsp;10^8 (1억)&lt;/td&gt;
&lt;td style=&quot;width: 46.8605%;&quot;&gt;&amp;nbsp;1초에 수행 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;&lt;span style=&quot;background-color: #9ae09f;&quot;&gt;&amp;nbsp; O(N log N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&amp;nbsp;10^6 (백만)&lt;/td&gt;
&lt;td style=&quot;width: 46.8605%;&quot;&gt;&amp;nbsp;N=10^7이면 약 2.3초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;&lt;span style=&quot;background-color: #9ae09f;&quot;&gt;&amp;nbsp; O(N&amp;sup2;)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&amp;nbsp;10^4 (만)&lt;/td&gt;
&lt;td style=&quot;width: 46.8605%;&quot;&gt;&amp;nbsp;2중 반복문 주의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;&lt;span style=&quot;background-color: #9ae09f;&quot;&gt;&amp;nbsp; O(2^N)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&amp;nbsp;20&lt;/td&gt;
&lt;td style=&quot;width: 46.8605%;&quot;&gt;&amp;nbsp;N=30만 돼도 10초 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.0698%;&quot;&gt;&lt;span style=&quot;background-color: #9ae09f;&quot;&gt;&amp;nbsp; O(N!)&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&amp;nbsp;10&lt;/td&gt;
&lt;td style=&quot;width: 46.8605%;&quot;&gt;&amp;nbsp;N이 조금만 커져도 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 입력 크기 N을 먼저 파악하고, 허용 시간 안에 드는 알고리즘을 선택해야 한다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 알고리즘 문제 풀이 5단계&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;절대 문제를 읽자마자 코딩하지 말 것! &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제를 제대로 이해하지 않고 시작하면 &amp;rarr; 잘못된 방향으로 구현 &amp;rarr; 디버깅에 몇 배의 시간 소요&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;STEP 1.&amp;nbsp; 문제 이해 및 요구사항 분석&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제를 천천히 정독&lt;/li&gt;
&lt;li&gt;예제 입출력으로 의도 파악&lt;/li&gt;
&lt;li&gt;입력/출력 형식 정확히 파악&lt;/li&gt;
&lt;li&gt;제약 조건과 규칙 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;STEP 2.&amp;nbsp; 접근 방법 구상&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비슷한 유형의 문제 떠올리기&lt;/li&gt;
&lt;li&gt;어떤 알고리즘 유형인지 판단&lt;/li&gt;
&lt;li&gt;문제 해결 절차를 단계별로 도식화&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;STEP 3.&amp;nbsp; 세부 구현 설계 및 검토&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의사코드로 각 단계 표현&lt;/li&gt;
&lt;li&gt;예외 상황과 극단적인 케이스 고려&lt;/li&gt;
&lt;li&gt;시간복잡도 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;STEP 4.&amp;nbsp; 코드 작성 및 구현&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계한 아이디어를 실제 코드로&lt;/li&gt;
&lt;li&gt;변수명&amp;middot;함수명을 명확하게&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;STEP 5.&amp;nbsp; 테스트와 디버깅&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시 입출력 테스트&lt;/li&gt;
&lt;li&gt;극단적인 케이스 테스트 (0, 음수, 최대값 등)&lt;/li&gt;
&lt;li&gt;시간 초과 시 &amp;rarr; 아이디어 수정, 자료구조 최적화, 메모이제이션, 백트래킹 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 알고리즘 주요 유형 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  구현 &amp;amp; 시뮬레이션&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현 : 요구사항을 그대로 코드로 옮기는 능력&lt;/li&gt;
&lt;li&gt;시뮬레이션 :&amp;nbsp;주어진 시나리오&amp;middot;규칙을 차례대로 실행&lt;/li&gt;
&lt;li&gt;인덱스 범위, 예외 처리 실수가 많으니 꼼꼼하게!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  완전 탐색 (Brute Force)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 경우의 수를 탐색해 정답 도출&lt;/li&gt;
&lt;li&gt;입력 규모가 작을 때 유리, 정답 보장&lt;/li&gt;
&lt;li&gt;반복문 or 재귀 함수로 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  그리디 (Greedy)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매 순간 가장 최선의 선택 &amp;rarr; 전체 최적해&lt;/li&gt;
&lt;li&gt;적용 조건: 탐욕 선택 속성 + 최적 부분 구조&lt;/li&gt;
&lt;li&gt;빠르고 직관적이지만 조건 불만족 시 오답&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  백트래킹 (Backtracking)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;완전 탐색 + 유망하지 않은 경우 가지치기&lt;/li&gt;
&lt;li&gt;탐색 범위를 줄여 효율 향상&lt;/li&gt;
&lt;li&gt;주로 재귀 함수로 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  분할 정복 (Divide and Conquer)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분할 &amp;rarr; 정복(해결) &amp;rarr; 병합&lt;/li&gt;
&lt;li&gt;분할된 하위 문제들은 서로 독립적이어야 함&lt;/li&gt;
&lt;li&gt;대표: 병합정렬, 퀵정렬, 이진탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  동적 계획법 (DP)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;큰 문제 &amp;rarr; 작은 부분 문제로 나눠 해결 결과를 저장(메모이제이션) 후 재활용&lt;/li&gt;
&lt;li&gt;적용 조건: 최적 부분 구조 + 중복되는 부분 문제&lt;/li&gt;
&lt;li&gt;점화식을 올바르게 세우는 것이 핵심!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  알고리즘 선택 가이드&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;문제 파악
  &amp;darr;
입력 크기 N 확인 &amp;rarr; 시간복잡도 허용 범위 계산
  &amp;darr;
- 요구사항 그대로 구현 &amp;rarr; 구현/시뮬레이션
- N이 작고 모든 경우 탐색 &amp;rarr; 완전 탐색
- 매 순간 최선 선택이 전체 최적 &amp;rarr; 그리디
- 완전 탐색 + 가지치기 &amp;rarr; 백트래킹
- 독립적 하위 문제로 분할 가능 &amp;rarr; 분할 정복
- 중복 부분 문제 + 최적 부분 구조 &amp;rarr; DP
  &amp;darr;
필요 시 알고리즘 조합 (예: 백트래킹 + DP)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 실습 문제 풀이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실습 1 - 배열에서 최댓값 찾기 (O(N))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 : 정수 배열에서 최댓값을 찾아라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 풀이 설계&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;1. 배열의 첫 번째 원소를 최댓값으로 설정한다.
2. 두 번째 원소부터 마지막 원소까지 순서대로 비교한다.
   - 현재 원소가 최댓값보다 크면, 최댓값을 갱신한다.
3. 모든 비교가 끝나면 최댓값을 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 구현&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;public class FindMax {
    public static int findMax(int[] numbers) {
        int max = numbers[0]; // 첫 번째 원소를 최댓값으로 초기화

        for (int i = 1; i &amp;lt; numbers.length; i++) {
            if (numbers[i] &amp;gt; max) {
                max = numbers[i]; // 더 큰 값 발견 시 갱신
            }
        }
        return max;
    }

    public static void main(String[] args) {
        int[] arr = {3, 7, 1, 9, 4, 6};
        System.out.println(&quot;최댓값: &quot; + findMax(arr)); // 출력: 9
    }
}
// 시간복잡도: O(N) - 배열을 한 번 순회&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실습 2 - 1부터 N까지의 합 (O(N) vs O(1) 비교)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 : 1부터 N까지의 합을 구하는 두 가지 방법을 비교하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 풀이 설계&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;방법 1 - 반복문:
  1. sum = 0으로 초기화한다.
  2. 1부터 N까지 반복하며 sum에 더한다.
  3. sum을 반환한다.

방법 2 - 가우스 공식:
  1. N * (N+1) / 2 를 계산하여 바로 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 구현&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;public class SumComparison {

    // 방법 1: 반복문 - O(N)
    public static int sumLoop(int n) {
        int total = 0;
        for (int i = 1; i &amp;lt;= n; i++) {
            total += i;
        }
        return total;
    }

    // 방법 2: 가우스 공식 - O(1)
    public static int sumGauss(int n) {
        return n * (n + 1) / 2;
    }

    public static void main(String[] args) {
        int n = 1000000;
        long start, end;

        // 방법 1 시간 측정
        start = System.nanoTime();
        int result1 = sumLoop(n);
        end = System.nanoTime();
        System.out.println(&quot;반복문 결과: &quot; + result1);
        System.out.println(&quot;반복문 실행 시간: &quot; + (end - start) + &quot; ns&quot;);

        // 방법 2 시간 측정
        start = System.nanoTime();
        int result2 = sumGauss(n);
        end = System.nanoTime();
        System.out.println(&quot;\n가우스 공식 결과: &quot; + result2);
        System.out.println(&quot;가우스 공식 실행 시간: &quot; + (end - start) + &quot; ns&quot;);
    }
}
// &amp;rarr; N이 클수록 가우스 공식의 압도적인 속도 차이를 체감할 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실습 3 - 거스름돈 문제 (그리디 알고리즘)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 : 500원, 100원, 50원, 10원 동전으로 N원을 거슬러줄 때 최소 동전 개수를 구하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 풀이 설계&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;1. 동전을 큰 단위부터 작은 단위 순으로 준비한다: [500, 100, 50, 10]
2. 남은 금액을 N으로 초기화한다.
3. 가장 큰 동전부터 순서대로:
   - 현재 동전으로 최대한 많이 거슬러준다 (나눗셈)
   - 남은 금액을 업데이트한다 (나머지)
   - 남은 금액이 0이면 즉시 종료
4. 사용된 동전 개수를 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어 : 큰 단위가 작은 단위의 배수 &amp;rarr; 탐욕 선택 속성 성립&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 구현&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;import java.util.*;

public class CoinChange {

    public static int coinChange(int[] coins, int target) {
        Arrays.sort(coins); // 오름차순 정렬

        int remaining = target;
        int count = 0;

        // 큰 단위부터 (뒤에서부터 순회)
        for (int i = coins.length - 1; i &amp;gt;= 0; i--) {
            count += remaining / coins[i];   // 해당 동전으로 거슬러줄 수 있는 개수
            remaining %= coins[i];           // 남은 금액 갱신

            if (remaining == 0) return count; // 0원이면 조기 종료
        }
        return count;
    }

    public static void main(String[] args) {
        int[] coins = {500, 100, 50, 10};
        Scanner scanner = new Scanner(System.in);
        int target = scanner.nextInt();
        System.out.println(coinChange(coins, target));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 케이스 (입력출력설명)&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 65.6977%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2558%;&quot;&gt;1260&lt;/td&gt;
&lt;td style=&quot;width: 10.3488%;&quot;&gt;&amp;nbsp;6&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;500&amp;times;2 + 100&amp;times;2 + 50&amp;times;1 + 10&amp;times;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2558%;&quot;&gt;830&lt;/td&gt;
&lt;td style=&quot;width: 10.3488%;&quot;&gt;&amp;nbsp;7&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;500&amp;times;1 + 100&amp;times;3 + 10&amp;times;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2558%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.3488%;&quot;&gt;&amp;nbsp;0&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;거스름돈 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2558%;&quot;&gt;500&lt;/td&gt;
&lt;td style=&quot;width: 10.3488%;&quot;&gt;&amp;nbsp;1&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;500원 1개&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도 : O(K) &amp;mdash; K는 동전 종류 수 (이 경우 K=4이므로 사실상 O(1))&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 정리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 공부의 핵심은 코드 암기가 아니라, 문제를 보고 &quot;어떤 방식으로 접근할지&quot; 생각하는 능력을 키우는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알고리즘 = 문제를 해결하는 단계적 절차&lt;/li&gt;
&lt;li&gt;좋은 알고리즘 = 정확성 + 효율성 + 명확성 + 확장성&lt;/li&gt;
&lt;li&gt;시간복잡도 &amp;rarr; 빅오 표기법으로 증가 추세 파악&lt;/li&gt;
&lt;li&gt;문제 풀이 순서 : 이해 &amp;rarr; 구상 &amp;rarr; 설계 &amp;rarr; 구현 &amp;rarr; 테스트 (코드 먼저 X!)&lt;/li&gt;
&lt;li&gt;알고리즘 유형 : 구현, 완전탐색, 그리디, 백트래킹, 분할정복, DP&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 챕터에서는 자료구조와 탐색 알고리즘을 다룰 예정⎝⍢⎠&lt;/p&gt;</description>
      <category>til</category>
      <category>내일배움캠프</category>
      <category>알고리즘</category>
      <category>자료구조</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/21</guid>
      <comments>https://mooncommit-dev.tistory.com/21#entry21comment</comments>
      <pubDate>Fri, 3 Apr 2026 20:58:45 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] Java 컬렉션 - HashSet과 HashMap</title>
      <link>https://mooncommit-dev.tistory.com/20</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ HashSet&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Set&lt;/code&gt; 인터페이스를 구현한 컬렉션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순서를 보장하지 않는다&lt;/li&gt;
&lt;li&gt;중복을 허용하지 않는다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get()&lt;/code&gt; 사용 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;HashSet&amp;lt;String&amp;gt; set = new HashSet&amp;lt;&amp;gt;();

set.add(&quot;값&quot;);       // 추가
set.remove(&quot;값&quot;);    // 제거&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이럴 때 사용!&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 없이 데이터를 관리할 때 : 예를 들어 장바구니에 같은 상품이 중복으로 담기는 걸 방지할 때 활용할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;⭐️ 구글에서 가져온 예시&lt;/p&gt;
&lt;pre id=&quot;code_1775041169643&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashSet;

class Main {
    public static void main(String[] args) {
        HashSet&amp;lt;Integer&amp;gt; evenNumber = new HashSet&amp;lt;&amp;gt;();

        // Using add() method
        evenNumber.add(2);
        evenNumber.add(4);
        evenNumber.add(6);
        System.out.println(&quot;HashSet: &quot; + evenNumber);

        HashSet&amp;lt;Integer&amp;gt; numbers = new HashSet&amp;lt;&amp;gt;();
        
        // Using addAll() method
        numbers.addAll(evenNumber);
        numbers.add(5);
        System.out.println(&quot;New HashSet: &quot; + numbers);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1775041206772&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HashSet: [2, 4, 6]
New HashSet: [2, 4, 5, 6]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;  HashMap&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Map&lt;/code&gt; 인터페이스를 구현한 컬렉션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;키(Key) - 값(Value)&lt;/code&gt; 구조로 데이터를 저장한다&lt;/li&gt;
&lt;li&gt;키는 중복 불가, 값은 중복 가능&lt;/li&gt;
&lt;li&gt;순서를 보장하지 않는다&lt;/li&gt;
&lt;li&gt;키가 중복되면 값이 덮어씌워진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

map.put(&quot;키&quot;, 값);      // 추가
map.get(&quot;키&quot;);          // 조회
map.remove(&quot;키&quot;);       // 제거
map.keySet();           // 키 확인
map.values();           // 값 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이럴 때 사용!&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품명과 수량처럼 짝을 이루는 데이터를 관리할 때 &amp;mdash; 장바구니에서 상품명: 수량 형태로 저장할 때 활용할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;⭐️ 구글에서 가져온 예시&lt;/p&gt;
&lt;pre id=&quot;code_1775041656932&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.*;  
  
public class Scaler {  
    public static void main(String[] args)  
    {  
        HashMap&amp;lt;String, Integer&amp;gt; hashMap = new HashMap&amp;lt;&amp;gt;(); 
        
       // 해시맵 생성 (이름 -&amp;gt; 문자열, 셀 번호 -&amp;gt; 정수)
        hashMap.put(&quot;Boyd Redding&quot;, 140);  
        hashMap.put(&quot;Andy Dufresne&quot;, 142);  
        hashMap.put(&quot;Heywood&quot;, 146);  
        hashMap.put(&quot;Brooks&quot;, 145);  
        hashMap.put(&quot;Tommy&quot;, 148);

 
        // 향상된 for 루프를 사용하여 해시맵을 순회
        
        //Iterating keys
        for(String name: hashMap.keySet())  
        {  
            //Printing keys and their corresponding values
            System.out.println(name + &quot;:&quot; + hashMap.get(name));  
        } 
        
        //Map.Entry as a reference to the entrySet elements (key -&amp;gt; value)
        for(Map.Entry&amp;lt;String, Integer&amp;gt; entry: hashMap.entrySet())  
        {  
            System.out.println(entry.getKey() + &quot;:&quot; + entry.getValue());  
        }
        
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;✔️ 실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1775041656933&quot; class=&quot;oxygene&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Printing elements with keySet method:
Boyd Redding:140
Andy Dufresne:142
Heywood:146
Brooks:145
Tommy:148

Printing elements with entrySet method:
Boyd Redding:140
Andy Dufresne:142
Heywood:146
Brooks:145
Tommy:148&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;ArrayList vs HashSet vs HashMap 비교ArrayList&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1775111416523&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ArrayList&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서 있고 중복 허용되는 목록이 필요할 때 사용&lt;/p&gt;
&lt;pre id=&quot;code_1775111430929&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HashSet&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 없는 목록이 필요할 때&lt;/p&gt;
&lt;pre id=&quot;code_1775111443882&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HashMap&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품명: 수량 처럼 연결된 데이터를 관리할 때&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp; HashMap으로 장바구니 구현하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장바구니에&amp;nbsp;같은&amp;nbsp;상품이&amp;nbsp;중복으로&amp;nbsp;담기면&amp;nbsp;안&amp;nbsp;되고,&lt;br /&gt;상품마다&amp;nbsp;수량을&amp;nbsp;관리해야&amp;nbsp;하기&amp;nbsp;때문에&amp;nbsp;`HashMap`을&amp;nbsp;활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommerceSystem에 먼저 장바구니 활용을 해보고 잘 되면 그 때 메서드를 분리할 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommerceSystem 필드에 HashMap을 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1775104329166&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private HashMap&amp;lt;Product, Integer&amp;gt; cart = new HashMap&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;`Key`&amp;nbsp;&amp;rarr;&amp;nbsp;`Product`&amp;nbsp;(상품&amp;nbsp;객체)&lt;br /&gt;-&amp;nbsp;`Value`&amp;nbsp;&amp;rarr;&amp;nbsp;`Integer`&amp;nbsp;(수량)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Value 에는 int 같은 기본 자료형이 올 수 없다. &lt;br /&gt;그래서 int 대신 래퍼클래스(Wrapper Class)인 Integer을 사용한다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1775104145234&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;선택한 상품: iPhone 15 | 1,350,000원 | Apple의 최신 스마트폰 | 재고: 30개
위 상품을 장바구니에 추가하시겠습니까?
1. 확인        2. 취소
1 &amp;lt;-

// 1번을 누르면 나오는 메뉴입니다.
iPhone 15가 장바구니에 추가되었습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 출력되게 만들어 볼 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; ️ HashMap 메서드&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 90.814%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 47.5672%; height: 19px; text-align: center;&quot;&gt;&lt;b&gt;메서드&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.2468%; height: 19px; text-align: center;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 47.5672%; height: 19px;&quot;&gt;put(key, value)&lt;/td&gt;
&lt;td style=&quot;width: 43.2468%; height: 19px;&quot;&gt;추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 47.5672%; height: 19px;&quot;&gt;get(key)&lt;/td&gt;
&lt;td style=&quot;width: 43.2468%; height: 19px;&quot;&gt;조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 47.5672%; height: 19px;&quot;&gt;remove(key)&lt;/td&gt;
&lt;td style=&quot;width: 43.2468%; height: 19px;&quot;&gt;제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 47.5672%; height: 17px;&quot;&gt;containsKey(key)&lt;/td&gt;
&lt;td style=&quot;width: 43.2468%; height: 17px;&quot;&gt;키(key) 존재 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap 메서드를 사용해 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1775105176055&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.out.print(&quot;입력 : &quot;);
int addCart = scanner.nextInt();
// 1 선택 : 추가 / 2 선택 : 취소 continue; 그 외 : 올바른 숫자를 입력하세요
// 1이면 추가
if (addCart == 1) {
// 여기서 이미 있으면 + 1 담아주기(put 메서드)
	if (cart.containsKey(selectedProduct)) {
		cart.put(selectedProduct, cart.get(selectedProduct) + 1);
        System.out.println(cart.get(selectedProduct));
		// 없으면 그냥 한 개 담아주기
	} else {
	cart.put(selectedProduct, 1);
    }
    System.out.println(selectedProduct.getProductName() + &quot;가 장바구니에 추가되었습니다.&quot;);
	 // 만약 2를 선택하면 취소
} else if (addCart == 2) {
continue;
// 1, 2 외 다른 것을 입력
} else {
System.out.println(&quot;올바른 번호를 입력하세요&quot;);
continue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;먼저 입력값에 따라 `if-else`로 분기 처리했다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;✔️ 1을 입력하면 장바구니에 추가된다. 이때 &lt;/span&gt;&lt;span style=&quot;color: #8100c2;&quot;&gt;&lt;span style=&quot;color: #953b34;&quot;&gt;&lt;b&gt;`containsKey()`&lt;/b&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메서드&lt;/span&gt;&lt;/span&gt;&lt;span&gt;로 이미 담긴 상품인지 확인한다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;✔️ 이미 있으면 &amp;rarr; &lt;/span&gt;&lt;span style=&quot;color: #8100c2;&quot;&gt;&lt;span style=&quot;color: #953b34;&quot;&gt;&lt;b&gt;`get()`&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메서드&lt;span style=&quot;text-align: start;&quot;&gt;(조회)&lt;/span&gt;로&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;현재 수량을 꺼내서 +1 후 다시 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #953b34;&quot;&gt;`put()`&lt;/span&gt;&lt;/b&gt;&lt;span&gt;메서드&lt;span style=&quot;color: #000000;&quot;&gt;(추가)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;✔️ 같은 상품이 없으면 수량 1로 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #953b34;&quot;&gt;`put()` &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;✔️ 2를 입력하면 취소 후 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`continue`&lt;/span&gt;&lt;span&gt;로 다시 메인으로 돌아간다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;✔️ 그 외 입력은 안내 메시지 출력 후 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`continue`&lt;/span&gt;&lt;span&gt;로 다시 돌아간다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;장바구니 추가 성공⎝⍢⎠&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgavi9/dJMcaaSlBtB/pkkO6i518dWjvWdHcM0Vp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgavi9/dJMcaaSlBtB/pkkO6i518dWjvWdHcM0Vp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgavi9/dJMcaaSlBtB/pkkO6i518dWjvWdHcM0Vp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgavi9%2FdJMcaaSlBtB%2FpkkO6i518dWjvWdHcM0Vp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1376&quot; height=&quot;708&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;느낀 점&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;몰랐던 클래스나 메서드를 배우고 그걸 활용해 완성해 가는 과정이 너무 재밌는 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 객체 생성도 어려웠는데 정말 많이 성장한 것 같다 ㅎㅎ 앞으로도 화이팅!  &lt;/p&gt;</description>
      <category>HashMap</category>
      <category>java</category>
      <category>til</category>
      <category>내일배움캠프</category>
      <author>mooncommit</author>
      <guid isPermaLink="true">https://mooncommit-dev.tistory.com/20</guid>
      <comments>https://mooncommit-dev.tistory.com/20#entry20comment</comments>
      <pubDate>Thu, 2 Apr 2026 15:16:57 +0900</pubDate>
    </item>
  </channel>
</rss>