<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>loaded_diaper.log</title>
        <link>https://velog.io/</link>
        <description>끝까지 가면 내가 다 이겨</description>
        <lastBuildDate>Wed, 06 May 2026 11:47:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>loaded_diaper.log</title>
            <url>https://velog.velcdn.com/images/loaded_diaper/profile/945e6299-d16c-43ec-9e8c-6b7571a3737a/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. loaded_diaper.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/loaded_diaper" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[스프링 정리 2]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%A0%95%EB%A6%AC-2</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%A0%95%EB%A6%AC-2</guid>
            <pubDate>Wed, 06 May 2026 11:47:16 GMT</pubDate>
            <description><![CDATA[<h1 id="3데이터베이스-접근-방법jpa">3.데이터베이스 접근 방법(JPA)</h1>
<h2 id="01-jpajava-persistence-api">01. JPA(Java Persistence API)</h2>
<blockquote>
<p>JPA(Java Persistence API) 는 자바에서 ORM을 위한 표준 명세 입니다.</p>
</blockquote>
<h3 id="a-ormobject-relational-mapping">a. ORM(Object Relational Mapping)</h3>
<blockquote>
<p>데이터베이스를 Object로 다루는 기술입니다. 자바에서는 Object 는 객체를 의미합니다.</p>
</blockquote>
<h3 id="b-다양한-jpa-구현체">b. 다양한 JPA 구현체</h3>
<blockquote>
<p>JPA 를 실제로 동작하게 하는 여러 구현체가 있습니다. 스프링부트에서 어떤 구현체를 사용할지 개발자가 결정할 수 있지만 기본적으로 <code>Hibernate</code> 구현체를 사용하고 있습니다.</p>
</blockquote>
<h2 id="02-jpa-가-sql-을-실행하는-과정">02. JPA 가 SQL 을 실행하는 과정</h2>
<blockquote>
<p>JPA 로 편하게 DB 를 조작하는 것은
DB 에 SQL 을 보내는 과정을 편하게 만든 것입니다.
→ 실제로 DB 에 SQL 을 보내는 것은 JDBC 가 담당하게 됩니다.</p>
</blockquote>
<p><strong>JDBC(Java Database Connectivity)</strong></p>
<ul>
<li>자바와 DB 사이의 통신 규약(인터페이스)입니다.</li>
<li>구현체는 DB-driver(mysql, mariadb, 등) 가 담당합니다.</li>
</ul>
<h2 id="03--jpa-핵심-키워드--동작원리">03.  JPA 핵심 키워드 &amp; 동작원리</h2>
<h2 id="⭐-a-entitymanagerfactory">⭐ a. EntityManagerFactory</h2>
<hr>
<blockquote>
<p><code>EntityManagerFactory</code> 는 DB 연결에 필요한 모든 세팅을 미리 해놓습니다.
그리고 요청이 들어오면 <code>EntityManager</code> 생성에 사용됩니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/58a1601a-fa5f-4d5c-a381-d2de036651f9/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/99ce96e2-cd9c-4562-9bee-e36273bf28ae/image.png" alt=""></p>
<h2 id="⭐-b-entitymanager">⭐ b. EntityManager</h2>
<hr>
<blockquote>
<p>EntityManager 는 DB 에 저장, 조회, 수정, 삭제하는 작업자 입니다.</p>
</blockquote>
<ul>
<li>*Transaction 단위로 생성됩니다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/84f2373e-092b-47f1-b34b-2a2c6b5d910f/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/b7b75d70-7b9f-4ffe-ac58-afd43d56d0a6/image.png" alt=""></li>
</ul>
<h2 id="⭐-c-엔티티">⭐ c. 엔티티</h2>
<blockquote>
<p>데이터베이스의 테이블 구조를 그대로 표현하는 역할을 가진 객체입니다.</p>
</blockquote>
<pre><code class="language-java">@Entity
public class Book {

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

    private String name;

    private String author;
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/d705007f-8434-4f28-a75d-16311d0971dd/image.png" alt=""></p>
<h2 id="⭐-d-영속성-컨텍스트">⭐ d. 영속성 컨텍스트</h2>
<blockquote>
<p>엔티티의 정보를 메모리에서 관리하는 임시 저장소. 데이터를 반영하기전 변경사항을 모두 기록합니다. 트랜잭션이 끝나면 모든 변경사항을 DB 에 반영합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/e921ea7d-33cf-472c-bf22-e04f98a375fd/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/0d50bf3d-5429-41a8-94e8-848cd494620e/image.png" alt=""></p>
<pre><code class="language-java">class Service {

        // 트랜잭션 시작
        @Transactional 
        public void doService() {

                // 1. 데이터 조회, 생성, 수정, 삭제 
                // ...
                // ...
        }
    // 트랜잭션 끝
}</code></pre>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/e0fd24fc-cb5f-406b-aa97-2cb6f65af165/image.png" alt=""></p>
<p><strong>i. 엔티티의 상태</strong>
<img src="https://velog.velcdn.com/images/loaded_diaper/post/38fa810f-d995-48ae-acc7-e525aeaf4bf6/image.png" alt="">
<strong>ii. 영속성 컨텍스트의 4가지 특징</strong></p>
<ol>
<li><p>1차 캐싱</p>
<pre><code class="language-java">Book a = em.find(Book.class, &quot;의문의책A&quot;); // SELECT * FROM books WHERE id = &#39;의문의책A&#39;
Book b = em.find(Book.class, &quot;의문의책A&quot;); // SELECT 쿼리 발생 X (1차 캐시 사용)</code></pre>
</li>
<li><p>동일성 보장</p>
<pre><code class="language-java">Book a = em.find(Book.class, &quot;의문의책A&quot;);
Book b = em.find(Book.class, &quot;의문의책A&quot;);
</code></pre>
</li>
</ol>
<p>// 동일성 비교 true (같은 객체 반환)
System.out.println(a == b);</p>
<pre><code>
3. 쓰기 지연
```java
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

//데이터 변경시 트랜잭션을 시작
transaction.begin(); // 트랜잭션 시작

em.persist(bookA); // (1) INSERT ...
em.persist(bookB); // (2) INSERT ...

// 쓰기지연은 persist 호출시 SQL 이 바로 실행되지 않습니다.
// (1, 2 의 sql 을 모았다가 보낸다.)
trasnaction.commit(); 트랜젝션 커밋</code></pre><ol start="4">
<li>변경감지
```java</li>
</ol>
<p>--&gt; 트랜잭션 시작</p>
<p>// 영속
Book findBook = em.find(Book.class, &quot;채식주의자&quot;);
findBook.setName(&quot;소년이온다&quot;);</p>
<p>// persist 호출 불필요합니다.
// 변경된 속성은 트랜잭션 커밋 시 자동 반영 됩니다.
// em.persist(findBook);</p>
<p>--&gt; 트랜잭션 끝</p>
<pre><code>
## ⭐ 요약

`EntityManager`  는 실제로 엔티티를 DB 에 저장, 조회, 수정, 삭제하는 작업자입니다. 


`EntityManagerFactory` 는 DB 에 연결에 필요한 모든 세팅을 미리 해놓고 `EntityManager` 생성에 사용됩니다.


`EntityManagerFactory` 는 애플리케이션 시작시 딱 한번 만들어지고 애플리케이션이 종료될때까지 유지됩니다.


 EntityManagerFactory 를 만들려면 `DataSource` 가 정상적으로 생성되어야합니다.


 EntityManager 는 EntityManagerFactory 로부터 생성되며 `Transaction`  단위로 생성되고 종료됩니다.

 `영속성컨텍스트`  는 엔티티를 메모리에서 관리하는 임시 저장소입니다.


 ---

 ## 4. 인증인가 기초(쿠키, 세션, 토큰)

 ## ⭐ 01. 인증인가
 ![](https://velog.velcdn.com/images/loaded_diaper/post/213bb1b1-8eda-4e35-84de-b5b8bd9e908d/image.png)

a. 인증(Authentication) 
&gt;누구인가 확인하는 단계 
![](https://velog.velcdn.com/images/loaded_diaper/post/93cdeb09-9471-4ca8-844c-edbea2b180d0/image.png)

b. 인가(Authorization) 
&gt;자격을 확인하는 단계
![](https://velog.velcdn.com/images/loaded_diaper/post/2ecaf2a2-d044-461f-9858-df10d0902c1b/image.png)

## ⭐ 02. 쿠키
&gt; 브라우저의 저장공간

![](https://velog.velcdn.com/images/loaded_diaper/post/c28b9781-7b70-4e09-9021-56727ab9bd7d/image.png)

### 쿠키인증의 한계
![](https://velog.velcdn.com/images/loaded_diaper/post/8df66b3d-1a61-4b64-b226-9b351f3ef1bf/image.png)


## ⭐ 03. 세션
&gt;클라이언트와 서버의 연결을 나타내는 하나의 상태
![](https://velog.velcdn.com/images/loaded_diaper/post/16485669-8c1e-4cce-93e2-45c55398a43e/image.png)

 ### 세션의 한계
![](https://velog.velcdn.com/images/loaded_diaper/post/0e8eb433-7978-48f3-9185-6a51e033e01f/image.png)


## ⭐ 04. 토큰
&gt;의미 있는 불규칙한 문자열
 ![](https://velog.velcdn.com/images/loaded_diaper/post/4fe7aeb1-f7c0-4bc2-a822-b61bca157a9b/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/218d217c-dafd-436e-bc5e-b199c7169330/image.png)

### 토큰인증방식의 한계
암호화가 아닙니다 - 단순한 문자열(Base64 적용)


## ⭐ 요약
![](https://velog.velcdn.com/images/loaded_diaper/post/a95face2-5a8a-4b14-9234-118d815e2fb6/image.png)


---

## 5. 연관관계

## ⭐ 01. 데이터베이스 모델링
&gt;어떤 서비스를 만들 때 필요한 데이터들을 정리하고 구조화하는 작업(테이블 명세 + ERD)
![](https://velog.velcdn.com/images/loaded_diaper/post/2d0db63a-5efa-46ab-a435-581a4a9a61f5/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/1a0cfcec-7f8f-4e7d-949b-29e9f40545dd/image.png)


### 연관관계
&gt;객체(엔티티) 가 서로 연결되어 있는 모습을 연관관계 라고 합니다. (JPA 와 다른 ORM 에서 사용하는 용어).

a. 연관관계 주인
b. 부모-자식 관계
c. 단방향과 양방향 연관관계


## ⭐ a. 연관관계 주인
&gt;연관관계가 있는 테이블 사이에서
 물리적으로 다른테이블의 외래키를 소유하고 있는 테이블/엔티티를 연관관계의 주인이라고 표현합니다. 

![](https://velog.velcdn.com/images/loaded_diaper/post/05fe0921-f2b3-4dca-bec9-35885602cfed/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/d6d40e6e-c21b-4532-b038-2ff360e8bf98/image.png)

## ⭐ b. 부모 - 자식 관계
&gt;비즈니스 관점에서 한 도메인이 다른 도메인을 포함하는 논리적인 관계를 의미합니다.

- 부모: 포함하고 있는 도메인
- 자식: 포함되는 도메인

![](https://velog.velcdn.com/images/loaded_diaper/post/7679cb3a-b662-402b-913c-5ac4ce9286c6/image.png)

수강생은 연관관계 부모-자식 관계에서 `자식` 이다. 

수업은 연관관계 부모-자식 관계에서 `부모` 이다. 

### 연관관계 정리
![](https://velog.velcdn.com/images/loaded_diaper/post/0af24e5d-fe6a-4804-ab1a-0d9d61e96d61/image.png)

## ⭐ i. 단방향

&gt; 한쪽에서만 다른 엔티티를 참조하는 상황 (`@ManyToOne`)

## ⭐ ii. 양방향

&gt; 엔티티(객체) 들이 서로 참조하는 상황(`@OneToMany`)


## cascadeType 그리고 orphanRemoval
&gt;양방향 연관관계를 더 멋지게? 사용하는 방법

## a. cascadeType

&gt; 양방향 연관관계(@OneToMany) 에서 부모 엔티티를 PERSIT 하거나 REMOVE할때 연관된 자식 엔티티에게도 그 작업을 자동으로 적용할지 결정하는 설정
&gt;![](https://velog.velcdn.com/images/loaded_diaper/post/97b91768-8577-461f-aed1-fcce42360720/image.png)

## b. orphanRemoval 
&gt;부모와의 연관관계가 끊겨 있는 엔티티를 자동으로 삭제할지 결정하는 기능 
→ 부모 없는 엔티티 → 고아(orphan) 이라고 표현합니다. 
![](https://velog.velcdn.com/images/loaded_diaper/post/e389eb60-5bcd-4a9d-8de7-319b7d96b1bb/image.png)

## c. 조합 정리(cascadeType + orphanRemoval) 
 ![](https://velog.velcdn.com/images/loaded_diaper/post/4263d8c5-180e-416e-bb3c-a7385d51e847/image.png)


## ⭐ 05. 양방향 연관관계의 위험성
&gt;양방향 연관관계 사용시 객체의 상태가 양쪽에 존재하기 때문에 어떤 값을 DB 에 반영해야하는지 JPA 가 자동으로 판단할 수 없는 모호한 상황이 발생할 수 있습니다.
![](https://velog.velcdn.com/images/loaded_diaper/post/1534765b-9456-488a-8ddb-d39deb9edcc7/image.png)

실무는 대부분 단방향


---

## 6. 스프링의 예외처리 원리
![](https://velog.velcdn.com/images/loaded_diaper/post/b0ce93a8-8e92-49aa-856d-dcfa64429430/image.png)

## 01. 예외 처리 복습 
&gt;예외 처리 라고 하는 것은 크게 두가지로 나누어져 있습니다. 

예외 처리 
1. 직접 처리
2. 전파 후 처리(책임전가)

## 예외 종류
a. 체크예외(Exception) 
- `RuntimeException` 을 제외한 Exception 클래스를 상속받은 모든 하위 클래스
- 반드시 명시적으로 예외를 처리해야합니다.
- 컴파일러가 컴파일 단계에서 검사해줍니다. 

b. 언체크예외(UncheckedException) 
- `RuntimeException` 클래스를 상속 받은 모든 하위 예외 클래스
- 처리방식은 똑같지만 예외처리를 강제하지 않습니다.
- 컴파일 단계에서 검사해주지 않습니다. 

### 정리
 ![](https://velog.velcdn.com/images/loaded_diaper/post/137d12fa-01a5-4b11-b074-74ec6e320b61/image.png)


## ⭐ 4. 논의 - 언제 check / unchecked 를 사용해야할까?
&gt;프로그램 실행 흐름안에서 직접 처리시 복구가능성이 있는 상황이라면 `checked` 그렇지 않다면 `unchecked` 를 사용합니다. 
 ![](https://velog.velcdn.com/images/loaded_diaper/post/f31748a9-4186-4573-ab3d-8dde808dca36/image.png)

![](https://velog.velcdn.com/images/loaded_diaper/post/3c8e2b45-c83b-4f13-8000-2c731e5d8f00/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/63d8c003-9e10-4a9f-9c51-92932a3858eb/image.png)

## ⭐ 스프링 예외처리 흐름
&gt;예외가 발생하면 /error 경로로 BasicErrorController 를 호출해서 에러 응답 본문을 만들어줍니다.
![](https://velog.velcdn.com/images/loaded_diaper/post/4e9130b9-f26d-42e3-a0a5-392499a592d7/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/990075e3-0bbc-489d-8359-78b231b20a76/image.png)

## 예외처리 흐름 살펴보기 - 필터  
a. 필터 
&gt;스프링필터는 WAS 와 DispatcherServlet 사이에 위치합니다. 이 위치 덕분에 모든 요청 응답에 대한 공통작업(보안검사, 요청/응답 기록 등) 처리에 적합한 역할을 합니다. 
![](https://velog.velcdn.com/images/loaded_diaper/post/f5a33055-f025-4b96-88a5-3d5f05ae8a0a/image.png)

## ⭐ 05. ExceptionResolver
&gt;스프링은 3가지 resolver 를 활용해 예외처리시 사용할 상태코드, 에러메시지 등을 미리 설정합니다.
![](https://velog.velcdn.com/images/loaded_diaper/post/c5edd8f4-ebcb-4d7b-9a8e-4ef7841aaf85/image.png)

스프링에서 예외가 발생했을때  **c → b → a**  순차적으로 예외를 처리하려고 시도합니다.

1. DefaultHandlerExceptionResolver
2. ResponseStatusExceptionResolver
3. *(현업사용) ExceptionHandlerExceptionResolver

### 1. DefaultHandlerExceptionResolver

- 스프링에서 기본적으로 정의해놓은 표준 예외(메시지, 상태코드)를 처리하는 resolver 입니다.
- BasicErrorController 통해서 예외를 처리합니다.
- 파일참고: DefaultHandlerExceptionResolver.java 참고

### 2. ResponseStatusExceptionResolver

- @ResponseStatus  로 설정한 예외를 처리하는 resolver 입니다.
- BasicErrorController 통해서 예외를 처리합니다.

### ⭐ c. ExceptionHandlerExceptionResolver
커스텀하게 특정 예외에 상태코드, 메시지, 응답 본문을 만들어 줄 수 있습니다.
![](https://velog.velcdn.com/images/loaded_diaper/post/20ce57e7-206f-4e91-a78a-264862fa36d1/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/be9da2d4-e6a7-4403-b9eb-9d861d037780/image.png)



















































































** 출처: 내일배움 스파르타 코딩 클럽**

































































</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot 트러블 슈팅] `@ModelAttribute` DTO에 값이 안 들어와서 상태 필터 검증이 동작하지 않았던 문제]]></title>
            <link>https://velog.io/@loaded_diaper/Spring-Boot-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-ModelAttribute-DTO%EC%97%90-%EA%B0%92%EC%9D%B4-%EC%95%88-%EB%93%A4%EC%96%B4%EC%99%80%EC%84%9C-%EC%83%81%ED%83%9C-%ED%95%84%ED%84%B0-%EA%B2%80%EC%A6%9D%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%98%EB%8D%98-%EB%AC%B8%EC%A0%9C-qupc12am</link>
            <guid>https://velog.io/@loaded_diaper/Spring-Boot-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-ModelAttribute-DTO%EC%97%90-%EA%B0%92%EC%9D%B4-%EC%95%88-%EB%93%A4%EC%96%B4%EC%99%80%EC%84%9C-%EC%83%81%ED%83%9C-%ED%95%84%ED%84%B0-%EA%B2%80%EC%A6%9D%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%98%EB%8D%98-%EB%AC%B8%EC%A0%9C-qupc12am</guid>
            <pubDate>Tue, 28 Apr 2026 04:01:39 GMT</pubDate>
            <description><![CDATA[<p>주문 목록 조회 API를 구현하던 중,
분명히 잘못된 상태값을 보냈는데도 <code>400 Bad Request</code>가 아니라 <code>200 OK</code>가 반환되는 이상한 문제를 겪었다.</p>
<p>처음에는 상태 검증 로직이 잘못된 줄 알았는데,
원인은 전혀 다른 곳에 있었다.</p>
<hr>
<h2 id="문제-상황">문제 상황</h2>
<p>주문 목록 조회 API는 아래처럼 구현되어 있었다.</p>
<pre><code class="language-java">@GetMapping
public ResponseEntity&lt;OrderApiResponse&lt;OrderListResponseDto&gt;&gt; getOrders(
        @SessionAttribute(name = SessionConst.ADMIN_ID) Long adminId,
        @ModelAttribute OrderListSearchRequestDto requestDto
) {
    OrderListResponseDto responseDto = orderService.getOrders(adminId, requestDto);

    OrderApiResponse&lt;OrderListResponseDto&gt; response = OrderApiResponse.success(
            HttpStatus.OK.value(),
            &quot;주문 목록 조회 성공&quot;,
            responseDto
    );

    return ResponseEntity.ok(response);
}</code></pre>
<p>서비스에서는 <code>status</code> 값이 들어오면 enum 변환을 통해 유효한 상태값인지 검증하도록 작성해두었다.</p>
<pre><code class="language-java">OrderStatus orderStatus = null;

if (requestDto.getStatus() != null &amp;&amp; !requestDto.getStatus().isBlank()) {
    try {
        orderStatus = OrderStatus.valueOf(requestDto.getStatus());
    } catch (IllegalArgumentException exception) {
        throw new ApiException(ErrorCode.INVALID_ORDER_STATUS);
    }
}</code></pre>
<p>이 상태에서 아래 요청을 보냈다.</p>
<pre><code class="language-text">GET /api/orders?status=REA</code></pre>
<p><code>REA</code>는 유효한 주문 상태값이 아니므로,
당연히 <code>INVALID_ORDER_STATUS</code> 예외가 발생해서 <code>400 Bad Request</code>가 나와야 한다고 생각했다.</p>
<p>그런데 실제 결과는 <strong>200 OK</strong>였다.</p>
<hr>
<h2 id="처음에-의심했던-부분">처음에 의심했던 부분</h2>
<p>처음에는 서비스 로직이 이상하다고 생각했다.</p>
<ul>
<li><code>OrderStatus.valueOf(...)</code>가 잘못된 걸까?</li>
<li>예외 처리가 이상한 걸까?</li>
<li>혹시 <code>status</code> 검증이 호출되지 않는 걸까?</li>
</ul>
<p>코드만 계속 보고 있으면 원인을 찾기 어려웠다.
그래서 실제로 서비스에 어떤 값이 들어오는지 확인해보기로 했다.</p>
<hr>
<h2 id="원인-확인-방법">원인 확인 방법</h2>
<p><code>OrderService</code>의 <code>getOrders()</code> 메서드 맨 위에 아래 로그를 찍었다.</p>
<pre><code class="language-java">System.out.println(&quot;status = &quot; + requestDto.getStatus());
System.out.println(&quot;sortBy = &quot; + requestDto.getSortBy());
System.out.println(&quot;direction = &quot; + requestDto.getDirection());</code></pre>
<p>그리고 다시 같은 요청을 보냈다.</p>
<pre><code class="language-text">GET /api/orders?status=REA</code></pre>
<p>콘솔 결과는 이렇게 나왔다.</p>
<pre><code class="language-text">status = null
sortBy = orderedAt
direction = desc</code></pre>
<p>여기서 원인이 확실해졌다.</p>
<p><strong>쿼리파라미터로 <code>status=REA</code>를 보냈는데도 DTO 안에는 <code>null</code>로 들어오고 있었다.</strong></p>
<p>즉, 문제는 상태 검증 로직이 아니라
<strong>아예 <code>status</code> 값이 DTO에 바인딩되지 않고 있었다</strong>는 점이었다.</p>
<hr>
<h2 id="왜-이런-일이-발생했나">왜 이런 일이 발생했나</h2>
<p>문제의 DTO는 아래처럼 작성되어 있었다.</p>
<pre><code class="language-java">@Getter
@NoArgsConstructor
public class OrderListSearchRequestDto {

    private String keyword;
    private String status;

    @Min(value = 1, message = &quot;페이지 번호는 1 이상이어야 합니다.&quot;)
    private Integer page = 1;

    @Min(value = 1, message = &quot;페이지 크기는 1 이상이어야 합니다.&quot;)
    private Integer size = 10;

    private String sortBy = &quot;orderedAt&quot;;
    private String direction = &quot;desc&quot;;
}</code></pre>
<p>겉보기에는 문제가 없어 보였지만,
여기엔 <strong>setter가 없었다.</strong></p>
<p>이번 API는 <code>GET</code> 요청이고,
컨트롤러에서는 <code>@ModelAttribute</code>를 사용해서 쿼리파라미터를 DTO에 바인딩하고 있었다.</p>
<p>그런데 <code>@ModelAttribute</code> 방식으로 값을 채울 때는
현재 구조에서는 <strong>setter가 있어야 필드에 값이 정상적으로 들어갔다.</strong></p>
<p>즉,</p>
<ul>
<li><code>status=REA</code>를 요청으로 보냄</li>
<li>하지만 DTO에 setter가 없음</li>
<li>그래서 <code>status</code> 필드에 값이 안 들어감</li>
<li>결국 <code>requestDto.getStatus()</code>는 <code>null</code></li>
<li>상태 검증 로직이 실행되지 않음</li>
<li>전체 주문 목록이 그대로 조회됨</li>
</ul>
<p>이 흐름이었다.</p>
<hr>
<h2 id="왜-sortby-direction은-값이-있는-것처럼-보였을까">왜 <code>sortBy</code>, <code>direction</code>은 값이 있는 것처럼 보였을까</h2>
<p>처음에 <code>sortBy = orderedAt</code>, <code>direction = desc</code>가 찍혀서
바인딩이 되고 있는 줄 착각할 수 있었다.</p>
<p>하지만 이 값들은 요청에서 들어온 게 아니라,
DTO에 미리 설정해둔 <strong>기본값</strong>이었다.</p>
<pre><code class="language-java">private Integer page = 1;
private Integer size = 10;
private String sortBy = &quot;orderedAt&quot;;
private String direction = &quot;desc&quot;;</code></pre>
<p>즉,</p>
<ul>
<li><code>status</code>는 바인딩 실패 → <code>null</code></li>
<li><code>sortBy</code>, <code>direction</code>은 바인딩 성공이 아니라 <strong>기본값 출력</strong></li>
</ul>
<p>이었던 것이다.</p>
<p>이 부분 때문에 문제를 처음에 더 늦게 파악했다.</p>
<hr>
<h2 id="해결-방법">해결 방법</h2>
<p>해결은 간단했다.
DTO에 <code>@Setter</code>를 추가했다.</p>
<pre><code class="language-java">@Getter
@Setter
@NoArgsConstructor
public class OrderListSearchRequestDto {

    private String keyword;
    private String status;

    @Min(value = 1, message = &quot;페이지 번호는 1 이상이어야 합니다.&quot;)
    private Integer page = 1;

    @Min(value = 1, message = &quot;페이지 크기는 1 이상이어야 합니다.&quot;)
    private Integer size = 10;

    private String sortBy = &quot;orderedAt&quot;;
    private String direction = &quot;desc&quot;;
}</code></pre>
<p>그리고 다시 같은 요청을 보냈다.</p>
<pre><code class="language-text">GET /api/orders?status=REA</code></pre>
<p>이번에는 콘솔에 이렇게 찍혔다.</p>
<pre><code class="language-text">status = REA
sortBy = orderedAt
direction = desc</code></pre>
<p>이제는 <code>status</code> 값이 DTO에 정상적으로 들어왔고,
서비스의 상태값 검증 로직도 제대로 동작했다.</p>
<p>최종 응답도 기대한 대로 바뀌었다.</p>
<ul>
<li><code>400 Bad Request</code></li>
<li><code>errorCode = INVALID_ORDER_STATUS</code></li>
</ul>
<hr>
<h2 id="해결-전--해결-후">해결 전 / 해결 후</h2>
<h3 id="해결-전">해결 전</h3>
<ul>
<li><code>status=REA</code> 요청</li>
<li>DTO의 <code>status</code> 값이 <code>null</code></li>
<li>상태 검증 로직이 실행되지 않음</li>
<li>전체 주문 목록이 <code>200 OK</code>로 조회됨</li>
</ul>
<h3 id="해결-후">해결 후</h3>
<ul>
<li><code>status=REA</code> 요청</li>
<li>DTO의 <code>status</code> 값이 <code>&quot;REA&quot;</code>로 정상 바인딩됨</li>
<li><code>OrderStatus.valueOf(&quot;REA&quot;)</code>에서 예외 발생</li>
<li><code>INVALID_ORDER_STATUS</code>와 함께 <code>400 Bad Request</code> 반환</li>
</ul>
<hr>
<h2 id="이번-문제에서-배운-점">이번 문제에서 배운 점</h2>
<h3 id="1-로직보다-먼저-실제-입력값을-확인해야-한다">1. 로직보다 먼저 “실제 입력값”을 확인해야 한다</h3>
<p>처음에는 검증 코드가 틀린 줄 알았다.
하지만 실제 문제는 <strong>DTO에 값이 안 들어오는 것</strong>이었다.</p>
<p>코드를 오래 붙잡고 있기보다,
서비스에 어떤 값이 실제로 들어오는지 먼저 확인하는 게 훨씬 빠르다는 걸 배웠다.</p>
<hr>
<h3 id="2-requestbody와-modelattribute는-바인딩-방식이-다르다">2. <code>@RequestBody</code>와 <code>@ModelAttribute</code>는 바인딩 방식이 다르다</h3>
<p>이전에는 <code>POST</code> 요청에서 <code>@RequestBody</code>를 많이 다뤄서
DTO 바인딩을 비슷하게 생각하고 있었다.</p>
<p>하지만 이번 경험으로 차이를 더 분명히 이해했다.</p>
<ul>
<li><code>@RequestBody</code> → JSON 본문 바인딩</li>
<li><code>@ModelAttribute</code> → 쿼리파라미터 바인딩</li>
</ul>
<p>겉보기엔 둘 다 DTO를 받는 것 같지만,
실제로는 동작 방식이 다르다는 점을 체감했다.</p>
<hr>
<h3 id="3-기본값이-있으면-바인딩-문제를-놓치기-쉽다">3. 기본값이 있으면 바인딩 문제를 놓치기 쉽다</h3>
<p><code>sortBy</code>, <code>direction</code>이 계속 값이 찍히니까
처음에는 바인딩이 잘 되고 있다고 착각하기 쉬웠다.</p>
<p>하지만 그 값은 요청에서 들어온 값이 아니라 DTO 기본값이었다.</p>
<p>즉, 기본값이 들어간 DTO를 디버깅할 때는
<strong>“이 값이 진짜 요청에서 들어온 값인지, 그냥 기본값인지”를 꼭 구분해야 한다</strong>는 걸 배웠다.</p>
<hr>
<h2 id="정리">정리</h2>
<p>이번 트러블 슈팅의 핵심은 이 한 문장으로 정리할 수 있다.</p>
<blockquote>
<p><code>@ModelAttribute</code>로 받는 DTO에 setter가 없어서 쿼리파라미터가 바인딩되지 않았고,
그 결과 상태값 검증 로직이 동작하지 않았다.</p>
</blockquote>
<p>처음에는 서비스 로직 문제처럼 보였지만,
실제로는 <strong>DTO 바인딩 문제</strong>였다.</p>
<p>작은 로그 출력 하나로 원인을 바로 확인할 수 있었고,
DTO에 <code>@Setter</code>를 추가해서 해결할 수 있었다.</p>
<hr>
<h2 id="최종-코드">최종 코드</h2>
<h3 id="controller">Controller</h3>
<pre><code class="language-java">@GetMapping
public ResponseEntity&lt;OrderApiResponse&lt;OrderListResponseDto&gt;&gt; getOrders(
        @SessionAttribute(name = SessionConst.ADMIN_ID) Long adminId,
        @ModelAttribute OrderListSearchRequestDto requestDto
) {
    OrderListResponseDto responseDto = orderService.getOrders(adminId, requestDto);

    OrderApiResponse&lt;OrderListResponseDto&gt; response = OrderApiResponse.success(
            HttpStatus.OK.value(),
            &quot;주문 목록 조회 성공&quot;,
            responseDto
    );

    return ResponseEntity.ok(response);
}</code></pre>
<h3 id="dto">DTO</h3>
<pre><code class="language-java">@Getter
@Setter
@NoArgsConstructor
public class OrderListSearchRequestDto {

    private String keyword;
    private String status;

    @Min(value = 1, message = &quot;페이지 번호는 1 이상이어야 합니다.&quot;)
    private Integer page = 1;

    @Min(value = 1, message = &quot;페이지 크기는 1 이상이어야 합니다.&quot;)
    private Integer size = 10;

    private String sortBy = &quot;orderedAt&quot;;
    private String direction = &quot;desc&quot;;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/9bc40de1-574a-4caf-8502-96bf08c420ff/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 에러 로그 읽는 법]]></title>
            <link>https://velog.io/@loaded_diaper/Spring-%EC%97%90%EB%9F%AC-%EB%A1%9C%EA%B7%B8-%EC%9D%BD%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@loaded_diaper/Spring-%EC%97%90%EB%9F%AC-%EB%A1%9C%EA%B7%B8-%EC%9D%BD%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Wed, 22 Apr 2026 06:25:33 GMT</pubDate>
            <description><![CDATA[<h1 id="trouble-shooting---스프링-에러-로그-읽는-법과-not-a-managed-type-문제">Trouble Shooting - 스프링 에러 로그 읽는 법과 <code>Not a managed type</code> 문제</h1>
<p>유저 생성 기능을 추가한 뒤 서버 실행 중 아래와 같은 에러가 발생했다.</p>
<pre><code class="language-text">Error starting ApplicationContext
Application run failed
...
Not a managed type: class com.schedule2.entity.User</code></pre>
<p>처음에는 맨 위의 <code>Application run failed</code> 만 보고 원인을 찾으려고 했는데,
이 문장은 단순히 <strong>“애플리케이션 실행이 실패했다”</strong> 는 요약일 뿐이었다.</p>
<p>실제 원인은 에러 로그를 아래로 내려가며 확인한 <code>Caused by</code> 부분에 있었다.</p>
<p>에러 흐름은 다음과 같았다.</p>
<ul>
<li><code>UserController</code> 생성 실패</li>
<li><code>UserService</code> 생성 실패</li>
<li><code>UserRepository</code> 생성 실패</li>
<li>최종 원인: <code>Not a managed type: class com.schedule2.entity.User</code></li>
</ul>
<p>즉, JPA가 <code>User</code> 클래스를 <strong>엔티티로 관리되는 타입</strong>으로 인식하지 못해서
<code>UserRepository</code>를 생성할 수 없었고, 그 결과로 서비스와 컨트롤러도 연쇄적으로 생성되지 못한 것이었다.</p>
<h3 id="원인">원인</h3>
<p><code>User</code> 엔티티가 JPA 엔티티로 제대로 등록되지 않은 상태였다.</p>
<h3 id="해결-방법">해결 방법</h3>
<p><code>User</code> 클래스에 엔티티 관련 설정이 제대로 들어가 있는지 확인했다.</p>
<pre><code class="language-java">@Entity
@Table(name = &quot;users&quot;)
public class User extends BaseEntity {</code></pre>
<p>그리고 저장/빌드 상태까지 다시 확인한 뒤 서버를 재실행하니 문제가 해결되었다.</p>
<h3 id="이번에-배운-점">이번에 배운 점</h3>
<p>스프링 에러 로그를 볼 때는 맨 위 요약 문장만 보면 안 되고,
반드시 아래쪽의 <code>Caused by</code> 와 <strong>가장 마지막의 구체적인 에러 문장</strong>을 확인해야 한다.</p>
<p>즉,</p>
<ul>
<li>위쪽: 어떤 작업이 실패했는지</li>
<li>아래쪽: 왜 실패했는지</li>
</ul>
<p>이 순서로 읽는 습관이 중요하다.</p>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/fc2d485e-e75b-4108-aa50-016c250f3f79/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 숙련 과제 트러블 슈팅]]></title>
            <link>https://velog.io/@loaded_diaper/Spring-%EC%88%99%EB%A0%A8-%EA%B3%BC%EC%A0%9C-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85</link>
            <guid>https://velog.io/@loaded_diaper/Spring-%EC%88%99%EB%A0%A8-%EA%B3%BC%EC%A0%9C-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85</guid>
            <pubDate>Wed, 22 Apr 2026 05:59:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/77af9889-6ed0-4fca-a874-3d8ea67b7495/image.png" alt=""></p>
<h1 id="trouble-shooting---getcreatedat--getupdatedat-메서드가-인식되지-않은-문제">Trouble Shooting - <code>getCreatedAt()</code> / <code>getUpdatedAt()</code> 메서드가 인식되지 않은 문제</h1>
<p>유저 생성 기능을 구현하면서 <code>UserService</code>에서 아래 코드를 작성했을 때 에러가 발생했다.</p>
<pre><code class="language-java">savedUser.getCreatedAt()
savedUser.getUpdatedAt()</code></pre>
<p>처음에는 <code>BaseEntity</code>에 <code>createdAt</code>, <code>updatedAt</code> 필드와 getter를 만들어 두었기 때문에
<code>User</code> 엔티티에서도 당연히 사용할 수 있을 것이라고 생각했다.</p>
<p>하지만 원인은 <code>User</code> 엔티티가 <code>BaseEntity</code>를 상속받고 있지 않았기 때문이었다.</p>
<p>기존 코드는 다음과 같았다.</p>
<pre><code class="language-java">public class User {</code></pre>
<p>이 상태에서는 <code>User</code> 클래스가 <code>BaseEntity</code>의 필드와 메서드를 물려받지 못하므로
<code>getCreatedAt()</code>과 <code>getUpdatedAt()</code>을 사용할 수 없다.</p>
<p>그래서 클래스 선언부를 아래처럼 수정했다.</p>
<pre><code class="language-java">public class User extends BaseEntity {</code></pre>
<p>수정 후에는 <code>User</code> 엔티티가 <code>BaseEntity</code>의 공통 필드와 메서드를 상속받게 되었고,
<code>createdAt</code>, <code>updatedAt</code>, <code>getCreatedAt()</code>, <code>getUpdatedAt()</code>을 정상적으로 사용할 수 있었다.</p>
<h3 id="정리">정리</h3>
<ul>
<li><code>getCreatedAt()</code>이 안 잡힌 이유: <code>User</code>가 <code>BaseEntity</code>를 상속하지 않았기 때문</li>
<li>해결 방법: <code>User extends BaseEntity</code>로 수정</li>
<li>배운 점: 공통 필드/공통 메서드를 부모 클래스에 만들었더라도, 자식 클래스가 <strong>상속</strong>하지 않으면 사용할 수 없다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/9370b2ba-602f-48b4-857f-c99b691b93cb/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[숙련 스프링 과제하면서 헷갈렸던 것들 ]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%88%99%EB%A0%A8-%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B3%BC%EC%A0%9C%ED%95%98%EB%A9%B4%EC%84%9C-%ED%97%B7%EA%B0%88%EB%A0%B8%EB%8D%98-%EA%B2%83%EB%93%A4</link>
            <guid>https://velog.io/@loaded_diaper/%EC%88%99%EB%A0%A8-%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B3%BC%EC%A0%9C%ED%95%98%EB%A9%B4%EC%84%9C-%ED%97%B7%EA%B0%88%EB%A0%B8%EB%8D%98-%EA%B2%83%EB%93%A4</guid>
            <pubDate>Tue, 21 Apr 2026 11:35:40 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">package com.schedule2.service;

import com.schedule2.dto.ScheduleCreateRequestDto;
import com.schedule2.dto.ScheduleResponseDto;
import com.schedule2.entity.Schedule;
import com.schedule2.repository.ScheduleRepository;
import org.springframework.stereotype.Service;

// 일정 관련 비즈니스 로직을 처리하는 서비스 클래스
@Service
public class ScheduleService {

    // ==================== 속성 ====================

    // 일정 데이터를 데이터베이스에 저장하고 조회할 레포지토리
    private final ScheduleRepository scheduleRepository;


    // ==================== 생성자 ====================

    // 서비스가 레포지토리 객체를 전달받는 생성자
    public ScheduleService(ScheduleRepository scheduleRepository) {
        this.scheduleRepository = scheduleRepository;
    }


    // ==================== 기능 ====================

    // 일정 생성 기능
    public ScheduleResponseDto createSchedule(ScheduleCreateRequestDto requestDto) {

        // 1. 요청값 꺼내기
        String username = requestDto.getUsername();
        String title = requestDto.getTitle();
        String contents = requestDto.getContents();

        // 2. 요청값으로 Schedule 엔티티 만들기
        Schedule schedule = new Schedule(username, title, contents);

        // 3. DB에 저장하기
        Schedule savedSchedule = scheduleRepository.save(schedule);

        // 4. 저장된 결과를 응답 DTO로 만들기
        ScheduleResponseDto responseDto = new ScheduleResponseDto(
                savedSchedule.getId(),
                savedSchedule.getUsername(),
                savedSchedule.getTitle(),
                savedSchedule.getContents(),
                savedSchedule.getCreatedAt(),
                savedSchedule.getUpdatedAt()
        );

        // 5. 응답 DTO 반환하기
        return responseDto;
    }
}</code></pre>
<pre><code class="language-md">### `save()` 결과를 다시 변수에 담는 이유

```java
Schedule savedSchedule = scheduleRepository.save(schedule);</code></pre>
<p>처음 만든 <code>schedule</code> 객체는 아직 DB에 저장되기 전 상태라서
<code>id</code>, <code>createdAt</code>, <code>updatedAt</code> 같은 값이 비어 있을 수 있다.</p>
<p>하지만 <code>save()</code>로 DB에 저장하고 나면</p>
<ul>
<li><code>id</code>가 생성되고</li>
<li><code>createdAt</code>, <code>updatedAt</code> 같은 값도 채워질 수 있다.</li>
</ul>
<p>그래서 저장 결과를 <code>savedSchedule</code>에 다시 담아서
<strong>저장 후 최종 상태의 객체</strong>를 사용한다.</p>
<p>즉,</p>
<ul>
<li><code>schedule</code> = 저장 전 객체</li>
<li><code>savedSchedule</code> = 저장 후 값이 반영된 객체</li>
</ul>
<p>라고 이해하면 된다.</p>
<hr>
<pre><code class="language-java">// 일정 전체 조회 기능
public List&lt;ScheduleResponseDto&gt; getAllSchedules() {

    // 1. DB에서 일정 전체 조회하기
    List&lt;Schedule&gt; scheduleList = scheduleRepository.findAll();

    // 2. 응답 DTO들을 담을 빈 리스트 만들기
    List&lt;ScheduleResponseDto&gt; responseDtoList = new ArrayList&lt;&gt;();

    // 3. 조회한 일정들을 하나씩 꺼내서 응답 DTO로 바꾸기
    for (Schedule schedule : scheduleList) {

        ScheduleResponseDto responseDto = new ScheduleResponseDto(
                schedule.getId(),
                schedule.getUsername(),
                schedule.getTitle(),
                schedule.getContents(),
                schedule.getCreatedAt(),
                schedule.getUpdatedAt()
        );

        // 4. 만들어진 응답 DTO를 리스트에 담기
        responseDtoList.add(responseDto);
    }

    // 5. 최종 응답 리스트 반환하기
    return responseDtoList;
}</code></pre>
<h3 id="일정-전체-조회-로직-정리">일정 전체 조회 로직 정리</h3>
<p><code>getAllSchedules()</code> 메서드는 DB에서 일정 전체를 조회한 뒤,<br>그 결과를 그대로 반환하지 않고 <code>ScheduleResponseDto</code> 리스트로 바꿔서 반환한다.</p>
<p>흐름은 다음과 같다.</p>
<ol>
<li><code>scheduleRepository.findAll()</code>로 DB에 저장된 일정들을 모두 조회한다.</li>
<li>조회한 결과는 <code>List&lt;Schedule&gt;</code> 형태로 받아온다.</li>
<li>응답용 리스트(<code>List&lt;ScheduleResponseDto&gt;</code>)를 새로 만든다.</li>
<li>반복문을 사용해서 <code>Schedule</code> 객체를 하나씩 꺼낸다.</li>
<li>각 <code>Schedule</code> 객체를 <code>ScheduleResponseDto</code>로 변환한다.</li>
<li>변환한 DTO를 응답용 리스트에 담는다.</li>
<li>최종적으로 DTO 리스트를 반환한다.</li>
</ol>
<p>즉,<br><strong>엔티티 리스트를 그대로 주는 것이 아니라, 응답용 DTO 리스트로 변환해서 반환하는 과정</strong>이라고 이해하면 된다.</p>
<hr>
<h2 id="db-세팅">DB 세팅</h2>
<h3 id="ddl-auto-옵션-간단-정리"><code>ddl-auto</code> 옵션 간단 정리</h3>
<p>Spring JPA에서 <code>ddl-auto</code> 옵션은 애플리케이션 실행 시<br>테이블을 어떻게 다룰지 정하는 설정이다.</p>
<ul>
<li><p><code>create</code><br>실행할 때 테이블을 새로 생성한다.  <img src="https://velog.velcdn.com/images/loaded_diaper/post/494eb2b1-2a50-45ba-8cde-83ad2432bebe/image.png" alt=""></p>
<p>기존 데이터가 사라질 수 있어서 개발 중 테스트용으로만 주로 사용한다.</p>
</li>
<li><p><code>update</code><br>엔티티 변경 사항을 기준으로 테이블 구조를 맞춰준다.<br>기존 데이터는 유지되기 때문에 개발 중 가장 많이 사용한다.</p>
</li>
<li><p><code>none</code><br>JPA가 테이블 생성이나 수정에 관여하지 않는다.<br>DB를 직접 준비한 경우에 사용한다.</p>
</li>
<li><p><code>validate</code><br>엔티티와 테이블 구조가 맞는지만 검사한다.<br>테이블을 새로 만들거나 수정하지 않기 때문에 제출용이나 최종 점검용으로 많이 사용한다.</p>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 활용]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Thu, 16 Apr 2026 11:56:38 GMT</pubDate>
            <description><![CDATA[<h1 id="베이직-1회차">베이직 1회차</h1>
<h2 id="스프링이란-→-ioc-컨테이너">스프링이란? → IoC 컨테이너</h2>
<blockquote>
<p>스프링은 <code>IoC 컨테이너</code> 를 제공해주는 프레임워크입니다. 
→ 핵심 기능은 IoC 기반의 DI 을 제공하는 ApplicationContext 라는 IoC 컨테이너입니다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/0b77e2d1-8f8f-4cd5-8f1d-06756eca41eb/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="⭐-개념1---스프링컨테이너ioc-컨테이너">⭐ 개념1 - 스프링컨테이너(IoC 컨테이너)</h2>
<blockquote>
<p>애플리케이션이 시작될때 스프링컨테이너(IoC 컨테이너) 는 애플리케이션 실행에 필요한 객체를 생성하고 각 객체에 필요한 의존성을 주입합니다. 스프링컨테이너는 이렇게 생성한 객체를 미리 생성해두고 관리하기 때문에 요청이 들어오면 요청처리에 적합한 객체를 활용해 요청을 처리합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/dcdc7b14-3ce4-432d-aa1e-2cc9c5d25aae/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/1f6d6334-9c71-4883-8568-602479025ca6/image.png" alt=""></p>
<hr>
<h2 id="⭐-개념2---스프링빈springbean">⭐ 개념2 - 스프링빈(SpringBean)</h2>
<blockquote>
<p>IoC 컨테이너에 의해서 생성되고 관리되는 객체를 스프링빈(SpringBean) 이라고 합니다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/ad12caf6-50e5-445b-b13e-dda0f507ccbb/image.png" alt=""></p>
</blockquote>
<h2 id="⭐-스프링기술---컴포넌트-스캔component-scan">⭐ 스프링기술 - 컴포넌트 스캔(Component Scan)</h2>
<blockquote>
<p>스프링의 자체 기능 - 자바 객체를 스프링 빈으로 등록하는 기술
컴포넌트스캔 을 활용해서 자바 클래스를 스프링 빈 등록 대상으로 지정할 수 있습니다. 
<img src="https://velog.velcdn.com/images/loaded_diaper/post/55d11edd-95a4-4bd8-a9cb-fc9e1fd11704/image.png" alt=""></p>
</blockquote>
<p>다양한 어노테이션 종류들</p>
<ul>
<li><code>@Component</code></li>
<li><code>@RestController</code></li>
<li><code>@Controller</code></li>
<li><code>@Service</code></li>
<li><code>@Repository</code></li>
<li>etc</li>
</ul>
<h2 id="⭐-web-기술---요청-연결-매핑request-mapping">⭐ web 기술 - 요청 연결 매핑(Request Mapping)</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/a1eed98a-3b74-480f-a632-69cea8ea03a9/image.png" alt=""></p>
<ul>
<li><strong><code>@RequestMapping()</code></strong> - 보통 클래스 레벨에서 공통 경로 잡을 때 사용<ul>
<li><strong><code>@GetMapping()</code></strong> - <strong>조회</strong>. 데이터 가져올 때 (목록 조회, 상세 조회)</li>
<li><strong><code>@PostMapping()</code></strong> - <strong>생성</strong>. 새 데이터 만들 때 (회원가입, 글 작성)</li>
<li><strong><code>@PatchMapping()</code></strong> - <strong>부분 수정</strong>. 기존 데이터 일부만 바꿀 때 (이름만 변경)</li>
<li><strong><code>@DeleteMapping()</code></strong> - <strong>삭제</strong>. 데이터 지울 때 (글 삭제, 회원 탈퇴)</li>
</ul>
</li>
</ul>
<h2 id="⭐-web-기술---클라이언트에서-데이터를-받아오는-방법">⭐ web 기술 - 클라이언트에서 데이터를 받아오는 방법</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/3eace1a3-ee44-445d-ae68-e833d0181e43/image.png" alt=""></p>
<ol>
<li>@RequestBody<blockquote>
<p>클라이언트가 HTTP 요청 바디로 데이터를 전송할때 사용해야합니다.</p>
</blockquote>
</li>
</ol>
<pre><code class="language-java">POST example.com/schedule</code></pre>
<pre><code class="language-java">&quot;title&quot;: &quot;코딩&quot;</code></pre>
<pre><code class="language-java"> @PostMapping(&quot;/requestbody&quot;)
    public String useRequestBody(@RequestBody DataTransferObject dto) {
        System.out.println(&quot;message = &quot; + dto.getMessage());
        return &quot;response 문자열&quot;;
    }</code></pre>
<ol start="2">
<li>@PathVariablle<blockquote>
<p>클라이언트가 URL 경로로 데이터를 전송할 때 사용해야합니다.</p>
</blockquote>
</li>
</ol>
<pre><code class="language-java">GET example.com/schedule/1</code></pre>
<pre><code class="language-java">@GetMapping(&quot;/schedule/{id}&quot;)
public String find(@PathVariable Long id) {
    return &quot;조회 ID: &quot; + id;
}</code></pre>
<ol start="3">
<li>@RequestParm<blockquote>
<p>클라이언트가 URL 쿼리 파라미터로 데이터를 전송할때 사용해야합니다.</p>
</blockquote>
<pre><code class="language-java">GET example.com/schedule?title=코딩</code></pre>
<pre><code class="language-java">@GetMapping(&quot;/schedule&quot;)
public String search(@RequestParam String title) {
 return &quot;검색어: &quot; + title;
}</code></pre>
</li>
</ol>
<h2 id="⭐-jpa-기술---데이터베이스를-다루는-방법">⭐ JPA 기술 - 데이터베이스를 다루는 방법</h2>
<p><strong>a. 엔티티</strong></p>
<blockquote>
<p>엔티티는 자바 클래스와 DB 테이블을 연결해주는 개념입니다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/1c60416e-440a-407d-8b10-14c6d6031680/image.png" alt=""></p>
</blockquote>
<pre><code class="language-java">@Entity</code></pre>
<p>b. repository</p>
<blockquote>
<p><code>JpaRepository</code> 를 활용해서 인터페이스를 만들면 스프링이 알아서 <code>ScheduleRepository</code>클래스를 구현해줍니다!</p>
</blockquote>
<pre><code class="language-java">public interface ScheduleRepository extends JpaRepository&lt;Schedule, Long&gt; {}</code></pre>
<ul>
<li>자동으로 구현된 기능:<ul>
<li>전체 조회 - <code>findAll()</code></li>
<li>단건조회 - <code>findById(…)</code></li>
<li>저장 - <code>save()</code></li>
<li>삭제 - <code>delete()</code></li>
</ul>
</li>
</ul>
<h2 id="⭐-3-계층">⭐ 3 계층</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/e1719c90-23e4-4b39-b41e-ed0db416dcff/image.png" alt=""></p>
<h2 id="⭐-요약">⭐ 요약</h2>
<ul>
<li><p>스프링이 관리하는 객체를 <code>스프링 빈</code>이라고 한다.</p>
</li>
<li><p>스프링 빈을 보관하고 관리하는 곳을 <code>Ioc컨테이너</code>라고 한다.</p>
</li>
<li><p>@Component, @Service, @Repository 등이 붙은 클래스를 자동으로 찾아서 스프링 빈으로 등록하는 기술을 <code>컴포넌트스캔</code> 이라고 합니다.</p>
</li>
<li><p>클라이언트가 URL 경로에 데이터를 보낼 때 사용하는 어노테이션은 <code>@PathVariable</code> 이다. 
요청예시:  <code>GET /schedule/1</code></p>
</li>
<li><p>클라이언트가 HTTP 요청 바디로 데이터를 보낼 때 사용하는 어노테이션은 <code>@RequestBody</code> 이다. 
요청예시: <code>POST /schedule {&quot;title&quot;: &quot;코딩&quot;}</code></p>
</li>
<li><p>클라이언트가 쿼리 파라미터로 데이터를 보낼 때 사용하는 어노테이션은 <code>@RequestParam</code> 이다.
요청예시: <code>GET /schedule?title=&quot;코딩&quot;</code></p>
</li>
<li><p>자바 클래스와 DB 테이블을 연결해주는 설계도 역할을 하는 것을 <code>엔티티(entity)</code> 라고 합니다.</p>
</li>
</ul>
<hr>
<h1 id="베이직-2회차">베이직 2회차</h1>
<h2 id="객체간의-결합도를-통한-3계층-이해di-ioc">객체간의 결합도를 통한 3계층 이해(DI, IoC)</h2>
<h2 id="의존dependency">의존(Dependency)</h2>
<blockquote>
<p>객체지향 프로그래밍에서는 객체들이 서로 상호작용합니다. 이 과정에서 한 객체가 다른 객체를 필요하게 되는데 이 관계를 의존(Dependency) 라고 표현합니다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/0c39eb72-aacb-429a-8d4f-a6bfb7b4a08d/image.png" alt=""></p>
</blockquote>
<h2 id="⭐-의존성-주입dependency-injection">⭐ 의존성 주입(Dependency Injection)</h2>
<blockquote>
<p>Car 라는 객체가 필요로 하는 객체(의존객체: Engine) 를 외부에서 생성해서 Car 에 주입해주는 것을 의존성 주입이라고 합니다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/e04418df-5b34-43d0-a733-d711c4aef934/image.png" alt=""></p>
</blockquote>
<h2 id="⭐-객체간의-결합도">⭐ 객체간의 결합도</h2>
<blockquote>
<p>객체간의 의존성을 어떻게 설정해주느냐에 따라서 객체간의 결합도가 달라집니다.</p>
</blockquote>
<h3 id="1단계---직접-의존--스스로-생성">1단계 - 직접 의존 + 스스로 생성</h3>
<blockquote>
<p>필요한 의존 객체를 직접 생성하는 구조
<img src="https://velog.velcdn.com/images/loaded_diaper/post/3b345b7a-aace-4c95-903d-aca3fdb2a199/image.png" alt=""></p>
</blockquote>
<ul>
<li>강한 결합</li>
<li>변경에 취약</li>
</ul>
<h3 id="2단계---직접-의존--외부에서-생성">2단계 - 직접 의존 + 외부에서 생성</h3>
<blockquote>
<p>의존 객체를 외부에서 생성해서 전달받는 구조
<img src="https://velog.velcdn.com/images/loaded_diaper/post/9e36608a-4780-4c65-9a6f-0c86e6c18a63/image.png" alt=""></p>
</blockquote>
<ul>
<li>생성 책임이 외부로 분리됨</li>
<li>하지만 여전히 구체적인 클래스에 의존</li>
</ul>
<h3 id="3단계---추상화에-의존">3단계 - 추상화에 의존</h3>
<blockquote>
<p>구현체가 아니라 추상화(인터페이스)에 의존하는 구조
<img src="https://velog.velcdn.com/images/loaded_diaper/post/f52b4bfb-8448-4321-827a-8a9acc7c9bdc/image.png" alt=""></p>
</blockquote>
<ul>
<li>구현체 자유롭게 변경 가능</li>
<li>느슨한 결합</li>
</ul>
<h2 id="⭐-다형성">⭐ 다형성</h2>
<blockquote>
<p>하나의 형태(인터페이스/부모클래스)로 여러가지 형태(구현체) 를 다룰 수 있는 성질
<img src="https://velog.velcdn.com/images/loaded_diaper/post/056d607c-a407-4dc0-98a2-54e9ade527ec/image.png" alt=""></p>
</blockquote>
<h2 id="⭐-ioc제어-역전-inversion-of-control">⭐ IoC(제어 역전: Inversion of Control)</h2>
<blockquote>
<p>프로그램 제어흐름을 외부(제 3의 주체) 로 넘기는 설계원칙
→ 스프링에서는 IoC 개념을 컨테이너 형태로 제공해줍니다.</p>
</blockquote>
<h2 id="⭐-스프링의-의존성-주입-방법">⭐ 스프링의 의존성 주입 방법</h2>
<ol>
<li><p>생성자 주입</p>
<pre><code class="language-java">@Service
public class MemberService {...}</code></pre>
<pre><code class="language-java">@RestController
public class MemberController {
 private final MemberService memberService;

     @Autowired // 생략 가능
 public UserController(MemberService memberService) {
     this.memberService = memberService;
 }
}</code></pre>
<p>@Autowired생략가능</p>
</li>
<li><p>필드주입</p>
<pre><code class="language-java">@Service
public class MemberService {...}</code></pre>
<pre><code class="language-java">@RestController
public class MemberController {

     @Autowired
 private MemberService memberService;

 ...
}</code></pre>
</li>
<li><p>세터주입</p>
<pre><code class="language-java">@Service
public class MemberService {...}</code></pre>
<pre><code class="language-java">@RestController
public class MemberController {
 private MemberController memberService;

     @Autowired
 public void setMemberService(MemberController memberService) {
     this.memberService = memberService;
 }

     ...
}</code></pre>
</li>
</ol>
<h2 id="⭐-정리">⭐ 정리</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/ba5752ad-558a-4c0f-bd6d-6fe647a716d4/image.png" alt=""></p>
<h2 id="⭐-요약-1">⭐ 요약</h2>
<ul>
<li><p>하나의 데이터타입으로 다양한 구현체를 다룰 수 있는 성질을 <code>다형성</code> 이라고 합니다.</p>
</li>
<li><p>프로그램 제어흐름을 외부로 넘기는 설계 원칙을 <code>제어 역전(IoC: Inversion of Control)</code>  라고 합니다.</p>
</li>
<li><p>구현체가 아닌 인터페이스에 의존하면 Car 코드를 수정하지 않고도 Engine 을 자유롭게 교체할 수 있었습니다. 이를 <code>느슨한 결합</code>  결합이라고 합니다.</p>
</li>
<li><p>객체가 필요로 하는 B객체(의존객체) 를 외부에서 생성해서 주입해주는 것을 <code>의존성주입(DI: dependency Injection)</code> 이라고 합니다.</p>
</li>
<li><p><code>private Engine engine = new ElectricEngine();</code> 처럼 의존 객체를 직접 생성하면 엔진을 교체하려고 할때 코드를 수정해야합니다. 이를 <code>강한 결합</code>  결합이라고 합니다.</p>
</li>
</ul>
<p>출처: 내일배움스파르타</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 15일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-15%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-15%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 30 Mar 2026 08:38:41 GMT</pubDate>
            <description><![CDATA[<h1 id="객체지향-설계에서-무분별한-setter를-지양해야-하는-이유">객체지향 설계에서 무분별한 Setter를 지양해야 하는 이유</h1>
<p>오늘은 자바 객체지향 과제를 진행하면서 <code>Setter</code>를 무조건 만드는 것이 꼭 좋은 것은 아니라는 점을 다시 정리하게 되었다.
처음에는 클래스 필드를 만들면 자연스럽게 <code>Getter</code>, <code>Setter</code>를 전부 작성하는 습관이 있었는데, 과제를 진행하고 피드백을 들으면서 <strong>중요한 값은 함부로 바뀌지 않도록 설계하는 것이 더 객체지향적</strong>이라는 점을 이해하게 되었다.</p>
<hr>
<h2 id="1-처음에는-왜-setter를-다-만들게-될까">1. 처음에는 왜 Setter를 다 만들게 될까?</h2>
<p>자바를 처음 배우면 보통 이렇게 배운다.</p>
<ul>
<li>필드는 <code>private</code>으로 숨긴다</li>
<li><code>Getter</code>로 값을 조회한다</li>
<li><code>Setter</code>로 값을 수정한다</li>
</ul>
<p>그래서 자연스럽게 모든 필드에 대해 <code>Getter</code>, <code>Setter</code>를 전부 만드는 습관이 생긴다.</p>
<p>예를 들어 <code>Product</code> 클래스가 있으면 이렇게 작성하기 쉽다.</p>
<pre><code class="language-java">public class Product {

    private String productName;
    private long productPrice;
    private String productDescription;
    private int productStock;

    public Product(String productName, long productPrice, String productDescription, int productStock) {
        this.productName = productName;
        this.productPrice = productPrice;
        this.productDescription = productDescription;
        this.productStock = productStock;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public long getProductPrice() {
        return productPrice;
    }

    public void setProductPrice(long productPrice) {
        this.productPrice = productPrice;
    }

    public String getProductDescription() {
        return productDescription;
    }

    public void setProductDescription(String productDescription) {
        this.productDescription = productDescription;
    }

    public int getProductStock() {
        return productStock;
    }

    public void setProductStock(int productStock) {
        this.productStock = productStock;
    }
}</code></pre>
<p>처음에는 이 방식이 정석처럼 보인다.
하지만 이렇게 하면 <strong>객체의 중요한 정보가 외부에서 너무 쉽게 변경될 수 있다.</strong></p>
<hr>
<h2 id="2-무분별한-setter가-왜-문제일까">2. 무분별한 Setter가 왜 문제일까?</h2>
<p>예를 들어 상품 가격, 상품명, 재고는 중요한 데이터다.
그런데 Setter를 전부 열어두면 밖에서 이렇게 바꿀 수 있다.</p>
<pre><code class="language-java">product.setProductPrice(-10000);
product.setProductName(&quot;이상한 상품명&quot;);
product.setProductStock(-5);</code></pre>
<p>이렇게 되면 객체가 스스로 자신의 상태를 지키지 못한다.
즉, <strong>객체가 안전하지 않다.</strong></p>
<p>객체지향에서는 단순히 필드를 숨기는 것만 중요한 것이 아니라,
<strong>그 값을 누가, 언제, 어떤 방식으로 바꿀 수 있는지까지 통제하는 것</strong>이 중요하다.</p>
<hr>
<h2 id="3-getter는-조회-setter는-수정이다">3. Getter는 조회, Setter는 수정이다</h2>
<p>오늘 다시 정리한 중요한 포인트는 이것이다.</p>
<ul>
<li><code>Getter</code>는 객체의 값을 읽기 위해 사용한다</li>
<li><code>Setter</code>는 객체의 값을 바꾸기 위해 사용한다</li>
</ul>
<p>문제는 “조회”보다 “수정”이 훨씬 위험하다는 점이다.</p>
<p>값을 읽는 것은 비교적 안전하지만,
값을 수정하는 순간 객체 상태가 달라진다.
그래서 수정 메서드는 정말 필요한 경우에만 열어두는 것이 좋다.</p>
<hr>
<h2 id="4-현재-단계에서는-setter가-꼭-필요하지-않았다">4. 현재 단계에서는 Setter가 꼭 필요하지 않았다</h2>
<p>이번 커머스 과제에서 만든 클래스들은 대략 이런 역할을 가진다.</p>
<ul>
<li><code>Product</code> : 상품 1개 정보</li>
<li><code>Category</code> : 카테고리 이름 + 상품 목록</li>
<li><code>Customer</code> : 고객 정보</li>
<li><code>CommerceSystem</code> : 프로그램 흐름 제어</li>
</ul>
<p>이 단계에서 대부분의 객체는 <strong>조회</strong>가 중심이었다.</p>
<p>예를 들면:</p>
<ul>
<li>상품명을 출력한다</li>
<li>가격을 출력한다</li>
<li>설명을 출력한다</li>
<li>카테고리 이름을 출력한다</li>
<li>고객 정보를 확인한다</li>
</ul>
<p>즉, 값을 읽는 것은 많이 하지만
중간에 값을 계속 수정할 필요는 거의 없었다.</p>
<p>그래서 <code>Setter</code>를 모두 둘 필요가 없다고 판단했다.</p>
<hr>
<h2 id="5-불필요한-setter를-제거한-방향">5. 불필요한 Setter를 제거한 방향</h2>
<p>예를 들어 <code>Category</code>는 생성될 때 이름과 상품 목록이 정해지면,
실행 중에 굳이 계속 이름을 바꿀 필요가 없다.</p>
<p>그래서 이런 식으로 최소한의 구조로 가져가는 것이 더 자연스럽다.</p>
<pre><code class="language-java">public class Category {

    private String categoryName;
    private List&lt;Product&gt; products;

    public Category(String categoryName, List&lt;Product&gt; products) {
        this.categoryName = categoryName;
        this.products = products;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public List&lt;Product&gt; getProducts() {
        return products;
    }
}</code></pre>
<p><code>Customer</code>도 마찬가지다.</p>
<pre><code class="language-java">public class Customer {

    private String customerName;
    private String email;
    private String grade;

    public Customer(String customerName, String email, String grade) {
        this.customerName = customerName;
        this.email = email;
        this.grade = grade;
    }

    public String getCustomerName() {
        return customerName;
    }

    public String getEmail() {
        return email;
    }

    public String getGrade() {
        return grade;
    }
}</code></pre>
<p>이렇게 하면 객체를 만들 때 필요한 값을 넣고,
그 이후에는 <strong>필요한 정보만 조회</strong>하는 구조가 된다.</p>
<hr>
<h2 id="6-setter-대신-더-의미-있는-메서드를-만드는-것이-좋다">6. Setter 대신 더 의미 있는 메서드를 만드는 것이 좋다</h2>
<p>오늘 공부하면서 가장 인상 깊었던 부분은
<strong>무조건 <code>setXXX()</code>를 만드는 것보다, 의도가 드러나는 메서드를 만드는 것이 더 좋다</strong>는 점이다.</p>
<p>예를 들어 상품 재고는 나중에 주문 기능이 생기면 바뀔 수 있다.
그런데 이렇게 작성하는 것보다:</p>
<pre><code class="language-java">product.setProductStock(29);</code></pre>
<p>이렇게 작성하는 것이 훨씬 자연스럽다.</p>
<pre><code class="language-java">product.decreaseStock(1);</code></pre>
<p>왜냐하면 <code>setProductStock(29)</code>는
그 값이 왜 29가 되었는지 문맥이 잘 드러나지 않는다.</p>
<p>반면 <code>decreaseStock(1)</code>은
<strong>“재고를 1개 줄인다”</strong>는 의도가 명확하다.</p>
<p>즉, Setter는 단순 수정이고
의미 있는 메서드는 <strong>행동 중심 설계</strong>에 더 가깝다.</p>
<hr>
<h2 id="7-캡슐화와도-연결된다">7. 캡슐화와도 연결된다</h2>
<p>이번 과제 Step 4가 캡슐화였는데,
Setter를 줄이는 방향은 캡슐화와도 연결된다.</p>
<p>캡슐화는 단순히 <code>private</code>을 붙이는 것만이 아니라
<strong>객체 내부 상태를 안전하게 관리하는 것</strong>이다.</p>
<p>즉:</p>
<ul>
<li>필드는 <code>private</code>으로 숨기고</li>
<li>필요한 값은 <code>Getter</code>로 조회하고</li>
<li>수정은 꼭 필요한 경우에만 허용하고</li>
<li>가능하면 의미 있는 행동 메서드로 표현한다</li>
</ul>
<p>이런 방식이 더 좋은 캡슐화라고 느꼈다.</p>
<hr>
<h2 id="8-오늘-정리한-기준">8. 오늘 정리한 기준</h2>
<p>앞으로는 필드를 만들 때 무조건 Setter를 만들지 않고,
아래 기준으로 생각해보려고 한다.</p>
<h3 id="setter가-필요-없는-경우">Setter가 필요 없는 경우</h3>
<ul>
<li>생성 시 값이 정해지고 이후 잘 바뀌지 않는 경우</li>
<li>외부에서 함부로 수정되면 안 되는 중요한 정보</li>
<li>현재 단계에서 조회만 필요한 경우</li>
</ul>
<h3 id="setter-대신-다른-메서드가-더-좋은-경우">Setter 대신 다른 메서드가 더 좋은 경우</h3>
<ul>
<li>값 수정에 명확한 의도가 있는 경우</li>
<li>단순 대입이 아니라 “행동”을 표현해야 하는 경우</li>
</ul>
<p>예:</p>
<ul>
<li><code>setProductStock()</code> 보다 <code>decreaseStock()</code></li>
<li><code>setOrderStatus()</code> 보다 <code>completeOrder()</code></li>
</ul>
<hr>
<h2 id="9-오늘-느낀-점">9. 오늘 느낀 점</h2>
<p>예전에는 <code>Getter</code>, <code>Setter</code>를 모두 만드는 것이 좋은 습관이라고만 생각했다.
하지만 오늘은 <strong>“모든 Setter가 좋은 것은 아니다”</strong>라는 점을 분명히 이해하게 되었다.</p>
<p>객체지향에서 중요한 것은 단순히 클래스를 나누는 것이 아니라,
<strong>객체가 자신의 데이터를 어떻게 지키는지 설계하는 것</strong>이었다.</p>
<p>특히 이번 커머스 과제를 하면서
<code>Product</code>, <code>Category</code>, <code>Customer</code> 같은 클래스는 지금 단계에서 조회가 중심이기 때문에
불필요한 Setter를 열어두지 않는 것이 더 적절하다고 느꼈다.</p>
<hr>
<h2 id="10-한-줄-요약">10. 한 줄 요약</h2>
<p><strong>Setter는 무조건 만드는 것이 아니라, 정말 필요한 경우에만 열어두고, 가능하면 객체의 의도가 드러나는 메서드로 설계하는 것이 더 객체지향적이다.</strong></p>
<hr>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/d05666f4-9fec-4957-b336-8be87fd81578/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 14일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%A0%81-%EC%BD%94%EB%94%A9%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@loaded_diaper/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%A0%81-%EC%BD%94%EB%94%A9%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Fri, 27 Mar 2026 11:49:45 GMT</pubDate>
            <description><![CDATA[<p>객체지향적 코딩에 대해서 생각해보았다
코딩을 하면서 처음으로 생각해 본 듯?
<img src="https://velog.velcdn.com/images/loaded_diaper/post/10f4692b-d27f-4538-a590-aa7192e152fd/image.png" alt=""></p>
<hr>
<h2 id="생성자-주입이-왜-좋은가">생성자 주입이 왜 좋은가?</h2>
<p>이전에는 <code>Scanner</code>를 <code>CommerceSystem</code> 안에서 직접 생성할지,
아니면 <code>Main</code>에서 만들어서 넘길지 고민했다.</p>
<p>예를 들면 이런 두 방식이 있다.</p>
<h3 id="1-클래스-내부에서-직접-생성하는-방식">1. 클래스 내부에서 직접 생성하는 방식</h3>
<pre><code class="language-java">public class CommerceSystem {

    private Scanner scanner = new Scanner(System.in);

}</code></pre>
<h3 id="2-생성자로-전달받는-방식">2. 생성자로 전달받는 방식</h3>
<pre><code class="language-java">public class CommerceSystem {

    private Scanner scanner;

    public CommerceSystem(Scanner scanner) {
        this.scanner = scanner;
    }
}</code></pre>
<p>이 두 방식 중에서 객체지향적으로 더 깔끔한 방법은
<strong>생성자 주입 방식</strong>이다.</p>
<hr>
<h2 id="1-생성자-주입이란">1. 생성자 주입이란?</h2>
<p>생성자 주입은
<strong>클래스가 필요한 객체를 스스로 만들지 않고, 밖에서 전달받는 방식</strong>이다.</p>
<p>예를 들면:</p>
<pre><code class="language-java">Scanner scanner = new Scanner(System.in);
CommerceSystem commerceSystem = new CommerceSystem(scanner);</code></pre>
<p>이 구조는 이런 흐름이다.</p>
<ol>
<li><code>Main</code>이 필요한 객체를 생성한다.</li>
<li><code>CommerceSystem</code>은 생성자를 통해 그 객체를 전달받는다.</li>
<li>전달받은 객체를 필드에 저장해서 사용한다.</li>
</ol>
<p>즉, <code>CommerceSystem</code>이 <code>Scanner</code>를 직접 만들지 않고
<strong>이미 준비된 <code>Scanner</code>를 받는 구조</strong>다.</p>
<hr>
<h2 id="2-왜-직접-생성하지-않고-전달받는-것이-좋을까">2. 왜 직접 생성하지 않고 전달받는 것이 좋을까?</h2>
<h3 id="이유-1-역할이-더-분명해진다">이유 1. 역할이 더 분명해진다</h3>
<p><code>Main</code>은 프로그램 시작점이고,
필요한 객체를 준비하는 역할을 맡는다.</p>
<p><code>CommerceSystem</code>은
준비된 객체를 받아서 실제 기능을 수행하는 역할을 맡는다.</p>
<p>즉 역할이 이렇게 나뉜다.</p>
<ul>
<li><code>Main</code> : 준비 담당</li>
<li><code>CommerceSystem</code> : 실행 담당</li>
</ul>
<p>이 구조가 더 자연스럽다.</p>
<p>만약 <code>CommerceSystem</code> 안에서 <code>Scanner</code>를 직접 만든다면,
<code>CommerceSystem</code>은 기능 수행뿐 아니라
객체 생성까지 같이 하게 된다.</p>
<p>그러면 역할이 조금 섞인다.</p>
<hr>
<h3 id="이유-2-코드가-더-일관적이다">이유 2. 코드가 더 일관적이다</h3>
<p>이미 <code>CommerceSystem</code>은 <code>products</code>도 밖에서 받고 있다.</p>
<pre><code class="language-java">public CommerceSystem(List&lt;Product&gt; products, Scanner scanner) {
    this.products = products;
    this.scanner = scanner;
}</code></pre>
<p>즉 <code>products</code>는 밖에서 받고, <code>scanner</code>만 내부에서 직접 만들면
설계가 통일되지 않는다.</p>
<p>반면 둘 다 밖에서 전달받으면 흐름이 깔끔하다.</p>
<ul>
<li>필요한 객체는 모두 <code>Main</code>이 준비한다.</li>
<li><code>CommerceSystem</code>은 전달받은 것만 사용한다.</li>
</ul>
<p>이런 구조가 더 읽기 쉽고 설계 의도가 분명하다.</p>
<hr>
<h3 id="이유-3-나중에-변경하기-쉬워진다">이유 3. 나중에 변경하기 쉬워진다</h3>
<p>처음에는 <code>Scanner(System.in)</code>을 쓸 수 있다.
그런데 나중에는 입력 방식이 달라질 수도 있다.</p>
<p>예를 들어:</p>
<ul>
<li>파일에서 입력받기</li>
<li>테스트용 입력값 사용하기</li>
<li>다른 입력 객체로 변경하기</li>
</ul>
<p>만약 <code>CommerceSystem</code> 안에서 직접 <code>new Scanner(System.in)</code>을 써버리면,
입력 방식을 바꾸려면 <code>CommerceSystem</code> 코드를 직접 수정해야 한다.</p>
<p>하지만 생성자 주입 구조라면
<code>CommerceSystem</code>은 그대로 두고,
<code>Main</code>에서 어떤 <code>Scanner</code>를 넘길지만 바꾸면 된다.</p>
<p>즉 <strong>변경에 더 유연하다.</strong></p>
<hr>
<h3 id="이유-4-테스트하기-쉬워진다">이유 4. 테스트하기 쉬워진다</h3>
<p>지금 단계에서는 테스트 코드까지 깊게 안 가도 되지만,
구조적으로는 이것도 큰 장점이다.</p>
<p>직접 생성하는 방식은:</p>
<pre><code class="language-java">private Scanner scanner = new Scanner(System.in);</code></pre>
<p>이렇게 고정돼 있어서 테스트할 때 다루기 불편하다.</p>
<p>반면 생성자 주입은:</p>
<pre><code class="language-java">public CommerceSystem(Scanner scanner) {
    this.scanner = scanner;
}</code></pre>
<p>이렇게 되어 있으니,
테스트할 때 원하는 입력 객체를 넣어볼 수 있다.</p>
<p>즉 <strong>외부에서 조립 가능하다</strong>는 점이 장점이다.</p>
<hr>
<h2 id="3-직접-생성-방식과-생성자-주입-방식-비교">3. 직접 생성 방식과 생성자 주입 방식 비교</h2>
<h3 id="직접-생성-방식">직접 생성 방식</h3>
<pre><code class="language-java">public class CommerceSystem {

    private Scanner scanner = new Scanner(System.in);

}</code></pre>
<p>이 방식의 특징:</p>
<ul>
<li>편해 보인다</li>
<li>바로 쓸 수 있다</li>
<li>하지만 클래스가 도구를 직접 만들고 있다</li>
<li>변경과 테스트에 불리하다</li>
</ul>
<hr>
<h3 id="생성자-주입-방식">생성자 주입 방식</h3>
<pre><code class="language-java">public class CommerceSystem {

    private Scanner scanner;

    public CommerceSystem(Scanner scanner) {
        this.scanner = scanner;
    }
}</code></pre>
<p>이 방식의 특징:</p>
<ul>
<li>처음엔 조금 더 길어 보인다</li>
<li>하지만 구조가 더 깔끔하다</li>
<li>필요한 객체를 외부에서 조립할 수 있다</li>
<li>역할 분리가 명확하다</li>
</ul>
<hr>
<h2 id="4-현재-프로젝트에-적용하면">4. 현재 프로젝트에 적용하면</h2>
<h3 id="mainjava">Main.java</h3>
<pre><code class="language-java">Scanner scanner = new Scanner(System.in);

CommerceSystem commerceSystem = new CommerceSystem(products, scanner);
commerceSystem.start();

scanner.close();</code></pre>
<h3 id="commercesystemjava">CommerceSystem.java</h3>
<pre><code class="language-java">private List&lt;Product&gt; products;
private Scanner scanner;

public CommerceSystem(List&lt;Product&gt; products, Scanner scanner) {
    this.products = products;
    this.scanner = scanner;
}</code></pre>
<p>이 구조의 장점은 명확하다.</p>
<ul>
<li><code>Main</code>이 필요한 객체를 준비한다.</li>
<li><code>CommerceSystem</code>은 전달받은 객체를 사용한다.</li>
<li>설계가 일관적이다.</li>
</ul>
<hr>
<h2 id="5-초보자-눈높이로-이해하면">5. 초보자 눈높이로 이해하면</h2>
<p>이걸 아주 쉽게 생각하면:</p>
<h3 id="직접-생성-방식-1">직접 생성 방식</h3>
<p><code>CommerceSystem</code>이 말한다.</p>
<blockquote>
<p>“상품도 내가 받고, Scanner는 내가 직접 만들게.”</p>
</blockquote>
<h3 id="생성자-주입-방식-1">생성자 주입 방식</h3>
<p><code>Main</code>이 말한다.</p>
<blockquote>
<p>“필요한 재료는 내가 다 준비해줄게.
너는 그걸 받아서 일만 하면 돼.”</p>
</blockquote>
<p>두 번째가 더 깔끔하지 않나?</p>
<p>즉 생성자 주입은
<strong>필요한 재료를 밖에서 넣어주는 방식</strong>이라고 생각하면 된다.</p>
<hr>
<h2 id="6-생성자-주입의-핵심-정리">6. 생성자 주입의 핵심 정리</h2>
<p>생성자 주입이 좋은 이유는 다음과 같다.</p>
<ul>
<li>역할 분리가 더 명확하다</li>
<li>객체 생성 책임이 <code>Main</code>에 모인다</li>
<li>설계가 일관적이다</li>
<li>변경에 유연하다</li>
<li>테스트하기 쉽다</li>
</ul>
<hr>
<h2 id="7-내가-이번에-이해한-점">7. 내가 이번에 이해한 점</h2>
<p>이번 과제를 하면서 느낀 점은,
단순히 “동작만 하면 된다”가 아니라
<strong>누가 만들고, 누가 받고, 누가 사용하는지</strong>를 나누는 것이 중요하다는 점이었다.</p>
<p>특히 <code>Scanner</code>처럼 작은 객체 하나도
어디서 생성하는지가 설계 차이를 만든다는 걸 알게 됐다.</p>
<p>처음에는 <code>CommerceSystem</code> 안에서 직접 <code>new Scanner(System.in)</code>을 해도 된다고 생각했지만,
생성자 주입 방식으로 바꾸고 나니
코드의 역할이 더 분명해지고 구조도 더 자연스러워졌다.</p>
<hr>
<h2 id="한-줄-요약">한 줄 요약</h2>
<p><strong>생성자 주입은 클래스가 필요한 객체를 직접 만들지 않고, 밖에서 전달받아 사용하는 방식이며, 역할 분리와 유지보수 측면에서 더 좋은 설계이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 12일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-12%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-12%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 25 Mar 2026 06:33:10 GMT</pubDate>
            <description><![CDATA[<h1 id="java-계산기-과제에서-제네릭과-number-이해하기">Java 계산기 과제에서 제네릭과 Number 이해하기</h1>
<h2 id="정수-전용-계산기에서-실수까지-처리하도록-확장해보기">정수 전용 계산기에서 실수까지 처리하도록 확장해보기</h2>
<p>계산기 과제를 진행하면서 처음에는 <code>int</code>만 사용하는 계산기를 만들었다.
즉, 정수끼리만 계산할 수 있는 구조였다.</p>
<p>예를 들어 이런 입력은 가능했다.</p>
<ul>
<li><code>1 + 2</code></li>
<li><code>10 - 3</code></li>
<li><code>5 * 4</code></li>
</ul>
<p>하지만 이런 입력은 처리할 수 없었다.</p>
<ul>
<li><code>1.5 + 2.3</code></li>
<li><code>5 / 2 = 2.5</code></li>
</ul>
<p>즉, 기존 계산기는 <strong>정수 전용 계산기</strong>였다.</p>
<p>그런데 과제를 진행하면서
<strong>실수도 받을 수 있게 확장하라</strong>,
그리고 <strong>제네릭을 활용하라</strong>는 요구사항이 나왔다.</p>
<p>처음에는
“그냥 <code>int</code>를 <code>double</code>로 바꾸면 되는 거 아닌가?”
라고 생각했지만, 과제의 핵심은 단순히 실수 계산이 아니라
<strong>여러 숫자 타입을 더 유연하게 받을 수 있는 구조를 이해하는 것</strong>이었다.</p>
<p>이번 글에서는 계산기 과제를 통해 배운
<strong>제네릭과 Number의 역할</strong>,
그리고 <strong>왜 <code>doubleValue()</code>를 사용했는지</strong>를 정리해보려고 한다.</p>
<hr>
<h2 id="1-처음-계산기는-왜-한계가-있었을까">1. 처음 계산기는 왜 한계가 있었을까?</h2>
<p>기존 계산 메서드는 이런 느낌이었다.</p>
<pre><code class="language-java">public int calculate(int a, int b, OperatorType operatorType) {
    int result = 0;

    if (operatorType == OperatorType.ADD) {
        result = a + b;
    } else if (operatorType == OperatorType.SUBTRACT) {
        result = a - b;
    } else if (operatorType == OperatorType.MULTIPLY) {
        result = a * b;
    } else if (operatorType == OperatorType.DIVIDE) {
        result = a / b;
    }

    return result;
}</code></pre>
<p>이 구조의 문제는 명확했다.</p>
<ul>
<li><code>a</code>, <code>b</code>가 <code>int</code>라서 정수만 받을 수 있다</li>
<li>나눗셈 결과도 정수로 잘릴 수 있다</li>
<li>실수 입력인 <code>1.2</code>, <code>2.5</code>를 받을 수 없다</li>
</ul>
<p>예를 들어:</p>
<pre><code class="language-java">5 / 2</code></pre>
<p>를 계산하면 수학적으로는 <code>2.5</code>여야 하지만,
정수 계산에서는 <code>2</code>가 나와버린다.</p>
<p>즉, <strong>정수 타입에 너무 묶여 있는 구조</strong>였다.</p>
<hr>
<h2 id="2-그럼-그냥-double로-바꾸면-되지-않을까">2. 그럼 그냥 double로 바꾸면 되지 않을까?</h2>
<p>처음에는 이런 생각을 했다.</p>
<pre><code class="language-java">public double calculate(double a, double b, OperatorType operatorType)</code></pre>
<p>이렇게 바꾸면 정수도 받을 수 있고, 실수도 받을 수 있다.</p>
<p>실제로 이 방식도 동작은 한다.
하지만 과제에서는 여기서 한 걸음 더 나아가
<strong>제네릭을 사용해서 여러 숫자 타입을 받을 수 있는 구조</strong>를 이해하는 것이 중요했다.</p>
<p>즉 핵심은</p>
<blockquote>
<p>단순히 <code>double 계산기</code>를 만드는 것이 아니라,
<strong>숫자 타입에 유연한 계산기 구조</strong>를 만드는 것</p>
</blockquote>
<p>이었다.</p>
<hr>
<h2 id="3-제네릭generic이란">3. 제네릭(Generic)이란?</h2>
<p>제네릭은 쉽게 말하면</p>
<blockquote>
<p><strong>타입을 나중에 정하는 문법</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어 아래 코드를 보자.</p>
<pre><code class="language-java">public class Box&lt;T&gt; {
    private T item;
}</code></pre>
<p>여기서 <code>T</code>는 아직 정해지지 않은 타입이다.</p>
<p>나중에 사용할 때</p>
<ul>
<li><code>Box&lt;String&gt;</code></li>
<li><code>Box&lt;Integer&gt;</code></li>
</ul>
<p>처럼 실제 타입을 넣어서 사용할 수 있다.</p>
<p>즉 <code>T</code>는 일종의 <strong>임시 타입 이름</strong>이다.</p>
<hr>
<h2 id="4-계산기에서-왜-제네릭이-필요했을까">4. 계산기에서 왜 제네릭이 필요했을까?</h2>
<p>계산기의 입력값은 이제</p>
<ul>
<li>정수일 수도 있고</li>
<li>실수일 수도 있다</li>
</ul>
<p>즉 어떤 때는 <code>Integer</code>, 어떤 때는 <code>Double</code>이 들어올 수 있다.</p>
<p>그런데 이런 서로 다른 숫자 타입을
하나의 계산기 메서드가 받을 수 있으면 좋겠다고 생각했다.</p>
<p>그래서 계산기 클래스를 이렇게 바꿨다.</p>
<pre><code class="language-java">public class ArithmeticCalculator&lt;T extends Number&gt; {
}</code></pre>
<hr>
<h2 id="5-t-extends-number는-무슨-뜻일까">5. <code>T extends Number</code>는 무슨 뜻일까?</h2>
<p>이 문장은 처음 보면 조금 어렵게 느껴진다.</p>
<pre><code class="language-java">public class ArithmeticCalculator&lt;T extends Number&gt;</code></pre>
<p>하지만 뜻은 생각보다 단순하다.</p>
<ul>
<li><code>T</code> = 나중에 정해질 타입</li>
<li><code>extends Number</code> = 그런데 아무 타입이나 안 되고, 숫자 타입만 허용</li>
</ul>
<p>즉 이 계산기는</p>
<ul>
<li><code>Integer</code></li>
<li><code>Double</code></li>
<li><code>Long</code></li>
<li><code>Float</code></li>
</ul>
<p>같은 <strong>숫자 타입만 받을 수 있다</strong>는 의미다.</p>
<p>반대로 이런 건 받을 수 없다.</p>
<ul>
<li><code>String</code></li>
<li><code>Student</code></li>
</ul>
<p>즉 이 코드는 한 줄로 이렇게 해석할 수 있다.</p>
<blockquote>
<p><strong>숫자 타입만 받을 수 있는 계산기 클래스를 만들겠다</strong></p>
</blockquote>
<hr>
<h2 id="6-number는-무엇인가">6. Number는 무엇인가?</h2>
<p><code>Number</code>는 자바에서 숫자 타입들의 부모 클래스 같은 존재다.</p>
<p>대표적으로 이런 클래스들이 <code>Number</code> 계열이다.</p>
<ul>
<li><code>Integer</code></li>
<li><code>Double</code></li>
<li><code>Long</code></li>
<li><code>Float</code></li>
</ul>
<p>즉 <code>Number</code>는
<strong>“숫자 타입들의 공통 부모”</strong>
라고 이해하면 편하다.</p>
<p>그래서 <code>T extends Number</code>라고 쓰면,
정수도 가능하고 실수도 가능한 구조를 만들 수 있다.</p>
<hr>
<h2 id="7-실제-계산기-코드는-어떻게-바뀌었을까">7. 실제 계산기 코드는 어떻게 바뀌었을까?</h2>
<p>기존 <code>Calculator</code> 클래스를
<code>ArithmeticCalculator</code>로 바꾸고,
calculate 메서드를 이렇게 수정했다.</p>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.List;

public class ArithmeticCalculator&lt;T extends Number&gt; {

    private List&lt;Double&gt; results = new ArrayList&lt;&gt;();

    public double calculate(T num1, T num2, OperatorType operatorType) {

        double num1Double = num1.doubleValue();
        double num2Double = num2.doubleValue();

        double result = 0;

        if (operatorType == OperatorType.ADD) {
            result = num1Double + num2Double;
        } else if (operatorType == OperatorType.SUBTRACT) {
            result = num1Double - num2Double;
        } else if (operatorType == OperatorType.MULTIPLY) {
            result = num1Double * num2Double;
        } else if (operatorType == OperatorType.DIVIDE) {
            result = num1Double / num2Double;
        }

        results.add(result);
        return result;
    }
}</code></pre>
<hr>
<h2 id="8-여기서-왜-doublevalue를-썼을까">8. 여기서 왜 <code>doubleValue()</code>를 썼을까?</h2>
<p>이 부분이 가장 중요했다.</p>
<pre><code class="language-java">double num1Double = num1.doubleValue();
double num2Double = num2.doubleValue();</code></pre>
<p><code>num1</code>, <code>num2</code>는 <code>T extends Number</code>이기 때문에
실제로는 <code>Integer</code>일 수도 있고 <code>Double</code>일 수도 있다.</p>
<p>예를 들면:</p>
<ul>
<li><code>num1 = Integer 3</code></li>
<li><code>num2 = Double 2.5</code></li>
</ul>
<p>이런 식이다.</p>
<p>그런데 계산을 하려면 타입을 하나로 통일하는 게 편하다.
그래서 <strong>계산 전에 모두 <code>double</code>로 바꾼 것</strong>이다.</p>
<p><code>Number</code> 계열은 공통적으로 <code>doubleValue()</code> 메서드를 가지고 있기 때문에,
어떤 숫자 타입이 들어와도 <code>double</code>로 바꿀 수 있다.</p>
<p>예를 들어:</p>
<pre><code class="language-java">Integer a = 3;
Double b = 2.5;

System.out.println(a.doubleValue()); // 3.0
System.out.println(b.doubleValue()); // 2.5</code></pre>
<p>즉 <code>doubleValue()</code>는</p>
<blockquote>
<p><strong>어떤 숫자 타입이 오든 계산하기 편한 double로 통일하는 역할</strong></p>
</blockquote>
<p>을 한다고 이해하면 된다.</p>
<hr>
<h2 id="9-왜-결과는-listt가-아니라-listdouble로-저장했을까">9. 왜 결과는 <code>List&lt;T&gt;</code>가 아니라 <code>List&lt;Double&gt;</code>로 저장했을까?</h2>
<p>처음에는 나도 이런 생각을 했다.</p>
<blockquote>
<p>입력 타입이 <code>T</code>니까 결과도 <code>List&lt;T&gt;</code>로 저장하면 되는 거 아닌가?</p>
</blockquote>
<p>그런데 곰곰이 생각해보면 문제가 있다.</p>
<p>예를 들어 계산기가 <code>Integer</code>를 받는다고 해도
나눗셈 결과는 실수가 될 수 있다.</p>
<pre><code class="language-java">5 / 2 = 2.5</code></pre>
<p>즉 입력 타입이 <code>Integer</code>라고 해서
결과도 항상 <code>Integer</code>가 되는 건 아니다.</p>
<p>그래서 결과 저장은 아예</p>
<pre><code class="language-java">private List&lt;Double&gt; results = new ArrayList&lt;&gt;();</code></pre>
<p>이렇게 <strong>Double 타입으로 통일</strong>했다.</p>
<p>이렇게 하면</p>
<ul>
<li>정수 결과도 저장 가능 (<code>3 → 3.0</code>)</li>
<li>실수 결과도 저장 가능 (<code>2.5 → 2.5</code>)</li>
</ul>
<p>즉 결과를 더 유연하게 저장할 수 있다.</p>
<hr>
<h2 id="10-app에서는-어떻게-입력을-처리했을까">10. App에서는 어떻게 입력을 처리했을까?</h2>
<p>입력값은 더 이상 <code>nextInt()</code>로 바로 받을 수 없었다.
왜냐하면 <code>1.2</code> 같은 실수 입력도 받아야 하기 때문이다.</p>
<p>그래서 입력은 먼저 문자열로 받고,
그 문자열이 정수인지 실수인지 판단해서 숫자로 바꾸는 방식을 사용했다.</p>
<pre><code class="language-java">System.out.print(&quot;첫 번째 숫자를 입력하세요: &quot;);
String input1 = scanner.next();

System.out.print(&quot;두 번째 숫자를 입력하세요: &quot;);
String input2 = scanner.next();

Number num1 = parseNumber(input1);
Number num2 = parseNumber(input2);</code></pre>
<p>그리고 <code>parseNumber()</code> 메서드는 이렇게 만들었다.</p>
<pre><code class="language-java">private static Number parseNumber(String number) {
    if (number.contains(&quot;.&quot;)) {
        return Double.parseDouble(number);
    } else {
        return Integer.parseInt(number);
    }
}</code></pre>
<p>즉</p>
<ul>
<li><code>&quot;3&quot;</code> 입력 → <code>Integer</code></li>
<li><code>&quot;1.5&quot;</code> 입력 → <code>Double</code></li>
</ul>
<p>로 처리하도록 만든 것이다.</p>
<p>이렇게 하면 App 입장에서는
정수와 실수를 구분해서 적절한 숫자 타입으로 바꾸고,
계산기에는 <code>Number</code>로 넘겨줄 수 있게 된다.</p>
<hr>
<h2 id="11-정리하면-이번-과제에서-배운-핵심은">11. 정리하면 이번 과제에서 배운 핵심은?</h2>
<p>이번 제네릭 과제를 하면서 정리된 핵심은 이렇다.</p>
<h3 id="1-제네릭은-타입을-유연하게-만든다">1) 제네릭은 타입을 유연하게 만든다</h3>
<pre><code class="language-java">ArithmeticCalculator&lt;T extends Number&gt;</code></pre>
<p>이렇게 하면 정수와 실수 같은 여러 숫자 타입을 받을 수 있다.</p>
<h3 id="2-number는-숫자-타입들의-공통-부모다">2) <code>Number</code>는 숫자 타입들의 공통 부모다</h3>
<p><code>Integer</code>, <code>Double</code> 같은 숫자 타입을 하나의 공통 타입으로 다룰 수 있게 해준다.</p>
<h3 id="3-doublevalue는-계산-전에-타입을-통일하는-역할을-한다">3) <code>doubleValue()</code>는 계산 전에 타입을 통일하는 역할을 한다</h3>
<p>정수든 실수든 상관없이 <code>double</code>로 바꿔 계산하면 훨씬 편하다.</p>
<h3 id="4-결과는-double로-저장하는-것이-자연스럽다">4) 결과는 <code>Double</code>로 저장하는 것이 자연스럽다</h3>
<p>입력은 정수일 수도 있지만, 결과는 실수가 될 수 있기 때문이다.</p>
<hr>
<h2 id="12-느낀-점">12. 느낀 점</h2>
<p>처음에는 <code>T extends Number</code>가 굉장히 추상적으로 느껴졌다.
하지만 계산기처럼 “정수도 받고 실수도 받고 싶다”는 상황에 적용해보니,
왜 이런 문법이 필요한지 조금 이해가 됐다.</p>
<p>이번 과제를 통해 느낀 건,</p>
<blockquote>
<p>제네릭은 단순히 어려운 문법이 아니라,
<strong>타입을 더 유연하고 재사용 가능하게 만드는 도구</strong>라는 점이었다.</p>
</blockquote>
<p>그리고 <code>Number</code>와 <code>doubleValue()</code>를 함께 사용하면
정수와 실수를 하나의 계산 흐름 안에서 자연스럽게 다룰 수 있다는 것도 알게 되었다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>이번 계산기 과제에서 제네릭과 Number를 적용해보면서
기존의 정수 전용 계산기를
<strong>정수와 실수 모두 처리 가능한 계산기</strong>로 확장할 수 있었다.</p>
<p>한 줄로 정리하면:</p>
<blockquote>
<p><code>T extends Number</code>는 숫자 타입만 받게 제한하는 것이고,
<code>doubleValue()</code>는 그 숫자들을 계산하기 쉬운 형태로 통일하는 역할을 한다.</p>
</blockquote>
<p>처음에는 조금 어렵게 느껴졌지만,
직접 계산기에 붙여보면서 제네릭이 “왜 필요한지”를 조금은 이해할 수 있었던 시간이었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/5de1d17b-11bc-47fa-b3b1-4a41476a1cdc/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 11일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-11%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-11%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 24 Mar 2026 08:22:52 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="java-계산기-과제에서-enum-이해하기">Java 계산기 과제에서 Enum 이해하기</h1>
<h2 id="char-대신-왜-enum을-쓰는지-처음부터-정리"><code>char</code> 대신 왜 <code>enum</code>을 쓰는지 처음부터 정리</h2>
<p>계산기 과제를 진행하면서 STEP 3에서 <code>enum</code>을 활용하라는 요구사항을 만났다.
처음에는 솔직히 이렇게 생각했다.</p>
<blockquote>
<p><code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>는 그냥 문자니까 <code>char</code>로 하면 되는 거 아닌가?</p>
</blockquote>
<p>실제로 STEP 1, STEP 2까지는 <code>char</code> 타입으로도 계산기를 충분히 만들 수 있었다.
하지만 STEP 3에서는 단순히 “동작하는 계산기”를 넘어서,
<strong>연산자 타입을 더 명확하고 안전하게 관리하는 방식</strong>으로 코드를 개선하는 것이 핵심이었다.</p>
<p>이번 글에서는 내가 계산기 과제를 하면서 이해한
<strong>enum이 무엇인지</strong>,
<strong>왜 계산기에서 enum을 쓰는지</strong>,
<strong>기존 코드에 어떻게 연결했는지</strong>를 정리해보려고 한다.</p>
<hr>
<h2 id="1-enum이란-무엇인가">1. enum이란 무엇인가?</h2>
<p><code>enum</code>은 <strong>정해진 값들만 가질 수 있도록 만든 특별한 타입</strong>이다.</p>
<p>예를 들어 이런 것들이 있다.</p>
<ul>
<li>요일: 월, 화, 수, 목, 금, 토, 일</li>
<li>계절: 봄, 여름, 가을, 겨울</li>
<li>상태: 대기중, 진행중, 완료</li>
<li>연산자: 더하기, 빼기, 곱하기, 나누기</li>
</ul>
<p>이런 값들은 공통점이 있다.</p>
<blockquote>
<p>값의 종류가 미리 정해져 있다.</p>
</blockquote>
<p>자바에서는 이렇게 <strong>정해진 종류만 있는 값들</strong>을 표현할 때 <code>enum</code>을 사용한다.</p>
<p>예를 들어 계산기의 연산자를 enum으로 만들면 이렇게 표현할 수 있다.</p>
<pre><code class="language-java">public enum OperatorType {
    ADD,
    SUBTRACT,
    MULTIPLY,
    DIVIDE
}</code></pre>
<p>이제 <code>OperatorType</code>은 새로운 타입이 되고,
이 타입은 오직 아래 네 값만 가질 수 있다.</p>
<ul>
<li><code>OperatorType.ADD</code></li>
<li><code>OperatorType.SUBTRACT</code></li>
<li><code>OperatorType.MULTIPLY</code></li>
<li><code>OperatorType.DIVIDE</code></li>
</ul>
<p>즉, <code>enum</code>의 핵심은</p>
<blockquote>
<p><strong>아무 값이나 들어오지 못하게 하고, 정해진 값만 다루게 만드는 것</strong></p>
</blockquote>
<p>이라고 이해하면 된다.</p>
<hr>
<h2 id="2-기존-계산기에서는-왜-char를-썼을까">2. 기존 계산기에서는 왜 <code>char</code>를 썼을까?</h2>
<p>처음 만들었던 계산기에서는 사용자가 사칙연산 기호를 직접 입력했다.</p>
<p>예를 들면 이런 식이다.</p>
<pre><code class="language-java">char operator = scanner.next().charAt(0);</code></pre>
<p>그리고 계산할 때는 이렇게 비교했다.</p>
<pre><code class="language-java">if (operator == &#39;+&#39;) {
    result = a + b;
} else if (operator == &#39;-&#39;) {
    result = a - b;
} else if (operator == &#39;*&#39;) {
    result = a * b;
} else if (operator == &#39;/&#39;) {
    result = a / b;
}</code></pre>
<p>이 방식은 동작은 잘 한다.
하지만 문제는 <code>char</code> 타입은 연산자만 넣는 타입이 아니라는 점이다.</p>
<p>즉 아래 같은 값도 모두 들어갈 수 있다.</p>
<ul>
<li><code>&#39;a&#39;</code></li>
<li><code>&#39;?&#39;</code></li>
<li><code>&#39;1&#39;</code></li>
</ul>
<p>즉 <code>char</code>는 그저 “문자 한 글자”를 저장하는 타입일 뿐이고,
<strong>“연산자 전용 타입”은 아니다.</strong></p>
<p>여기서 enum을 쓰는 이유가 생긴다.</p>
<hr>
<h2 id="3-계산기에서-enum을-쓰는-이유">3. 계산기에서 enum을 쓰는 이유</h2>
<p>계산기에서는 사실 연산자의 종류가 딱 4개로 정해져 있다.</p>
<ul>
<li>더하기</li>
<li>빼기</li>
<li>곱하기</li>
<li>나누기</li>
</ul>
<p>즉 아무 문자나 들어오는 게 아니라
<strong>정해진 연산자만 관리하고 싶다</strong>는 요구가 있다.</p>
<p>이럴 때 enum을 사용하면 코드가 훨씬 명확해진다.</p>
<h3 id="기존-방식">기존 방식</h3>
<pre><code class="language-java">char operator = &#39;+&#39;;</code></pre>
<h3 id="enum-방식">enum 방식</h3>
<pre><code class="language-java">OperatorType operatorType = OperatorType.ADD;</code></pre>
<p>둘 다 결국 더하기를 의미하지만,
<code>OperatorType.ADD</code> 쪽이 훨씬 의미가 분명하다.</p>
<p><code>&#39;+&#39;</code>는 그냥 문자 하나처럼 보이지만,
<code>ADD</code>는 누가 봐도 “더하기 연산자”라는 걸 알 수 있다.</p>
<p>즉 enum을 쓰면</p>
<ul>
<li>의미가 더 명확해지고</li>
<li>잘못된 값을 관리하기 쉬워지고</li>
<li>연산자의 종류를 타입 차원에서 제한할 수 있다</li>
</ul>
<p>는 장점이 있다.</p>
<hr>
<h2 id="4-계산기에-enum을-어떻게-붙였는가">4. 계산기에 enum을 어떻게 붙였는가?</h2>
<p>사용자는 여전히 <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>를 입력한다.
하지만 프로그램 내부에서는 이 문자를 바로 계산에 쓰지 않고,
먼저 enum 값으로 바꾸도록 했다.</p>
<p>즉 흐름은 이렇게 바뀐다.</p>
<h3 id="예전-방식">예전 방식</h3>
<pre><code class="language-java">&#39;+&#39; -&gt; 바로 Calculator에 전달</code></pre>
<h3 id="enum-적용-후-방식">enum 적용 후 방식</h3>
<pre><code class="language-java">&#39;+&#39; -&gt; OperatorType.ADD 로 변환 -&gt; Calculator에 전달</code></pre>
<p>즉 사용자 입력은 여전히 문자지만,
프로그램 내부 계산 로직은 enum 기반으로 동작하게 바꾼 것이다.</p>
<hr>
<h2 id="5-operatortype-enum-만들기">5. OperatorType enum 만들기</h2>
<p>먼저 연산자의 종류를 enum으로 정의했다.</p>
<pre><code class="language-java">package com.example.calculator;

public enum OperatorType {
    ADD,
    SUBTRACT,
    MULTIPLY,
    DIVIDE
}</code></pre>
<p>이제 <code>OperatorType</code>은 계산기에서 사용하는 연산자 타입이 되었다.</p>
<hr>
<h2 id="6-문자-입력을-enum으로-바꾸는-메서드-추가">6. 문자 입력을 enum으로 바꾸는 메서드 추가</h2>
<p>문제는 사용자가 입력하는 값은 <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code> 같은 문자라는 점이다.
그래서 이 문자를 enum 값으로 변환해주는 메서드가 필요했다.</p>
<pre><code class="language-java">package com.example.calculator;

public enum OperatorType {
    ADD,
    SUBTRACT,
    MULTIPLY,
    DIVIDE;

    public static OperatorType fromChar(char operator) {
        if (operator == &#39;+&#39;) {
            return ADD;
        } else if (operator == &#39;-&#39;) {
            return SUBTRACT;
        } else if (operator == &#39;*&#39;) {
            return MULTIPLY;
        } else if (operator == &#39;/&#39;) {
            return DIVIDE;
        } else {
            throw new IllegalArgumentException(&quot;잘못된 연산자입니다.&quot;);
        }
    }
}</code></pre>
<p>이 메서드는 말 그대로
<strong>문자(char)를 받아서 OperatorType으로 바꾸는 역할</strong>을 한다.</p>
<p>예를 들어:</p>
<pre><code class="language-java">OperatorType type = OperatorType.fromChar(&#39;+&#39;);</code></pre>
<p>이렇게 하면 결과는:</p>
<pre><code class="language-java">OperatorType.ADD</code></pre>
<p>가 된다.</p>
<p>즉, 사용자 입력을 프로그램 내부에서 안전하게 사용할 수 있는 enum 값으로 바꾸는 과정이라고 보면 된다.</p>
<hr>
<h2 id="7-기존-calculator-클래스-수정하기">7. 기존 Calculator 클래스 수정하기</h2>
<p>원래 Calculator 클래스는 <code>char</code>를 매개변수로 받았었다.</p>
<h3 id="기존-코드">기존 코드</h3>
<pre><code class="language-java">public int calculate(int a, int b, char operator) {
    int result = 0;

    if (operator == &#39;+&#39;) {
        result = a + b;
    } else if (operator == &#39;-&#39;) {
        result = a - b;
    } else if (operator == &#39;*&#39;) {
        result = a * b;
    } else if (operator == &#39;/&#39;) {
        result = a / b;
    }

    results.add(result);
    return result;
}</code></pre>
<p>이제는 <code>char</code> 대신 <code>OperatorType</code>을 받도록 바꾸었다.</p>
<h3 id="수정-후-코드">수정 후 코드</h3>
<pre><code class="language-java">public int calculate(int a, int b, OperatorType operatorType) {
    int result = 0;

    if (operatorType == OperatorType.ADD) {
        result = a + b;
    } else if (operatorType == OperatorType.SUBTRACT) {
        result = a - b;
    } else if (operatorType == OperatorType.MULTIPLY) {
        result = a * b;
    } else if (operatorType == OperatorType.DIVIDE) {
        result = a / b;
    }

    results.add(result);
    return result;
}</code></pre>
<p>여기서 핵심 변화는 두 가지였다.</p>
<ol>
<li>매개변수가 <code>char</code>에서 <code>OperatorType</code>으로 바뀜</li>
<li>비교 대상이 <code>&#39;+&#39;</code> 같은 문자에서 <code>OperatorType.ADD</code> 같은 enum 값으로 바뀜</li>
</ol>
<p>이렇게 바꾸니 계산 로직이 훨씬 의미 중심으로 읽히기 시작했다.</p>
<hr>
<h2 id="8-app-클래스에서는-어떻게-연결했는가">8. App 클래스에서는 어떻게 연결했는가?</h2>
<p>사용자 입력은 그대로 문자로 받았다.</p>
<pre><code class="language-java">char operator![](https://velog.velcdn.com/images/loaded_diaper/post/3c15d636-f7a5-4d3c-af62-619e293eb424/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/2b29aaa4-8fab-4ea9-9c49-699f0e312eb9/image.png)
![](https://velog.velcdn.com/images/loaded_diaper/post/f04d90c3-ea63-4161-88c9-4f473e001d2c/image.png)
 = scanner.next().charAt(0);</code></pre>
<p>그다음 바로 Calculator에 넘기는 대신,
먼저 enum으로 바꿨다.</p>
<pre><code class="language-java">OperatorType operatorType = OperatorType.fromChar(c);</code></pre>
<p>그리고 이 enum 값을 Calculator에 전달했다.</p>
<pre><code class="language-java">int result = calculator.calculate(a, b, operatorType);</code></pre>
<p>즉 App 클래스의 역할은</p>
<ul>
<li>입력 받기</li>
<li>입력받은 문자를 enum으로 변환하기</li>
<li>Calculator에 넘기기</li>
</ul>
<p>가 되었다.</p>
<hr>
<h2 id="9-enum을-적용하면서-느낀-점">9. enum을 적용하면서 느낀 점</h2>
<p>처음에는 솔직히
“문자 하나 받아서 if문으로 계산하면 되는데 굳이 enum까지 써야 하나?”
라는 생각이 들었다.</p>
<p>하지만 직접 적용해보니 enum의 장점이 조금씩 보이기 시작했다.</p>
<h3 id="1-코드-의미가-더-분명해진다">1) 코드 의미가 더 분명해진다</h3>
<p><code>&#39;+&#39;</code>보다는 <code>OperatorType.ADD</code>가 훨씬 읽기 쉽다.</p>
<h3 id="2-정해진-값만-다룬다는-것이-코드에서-드러난다">2) 정해진 값만 다룬다는 것이 코드에서 드러난다</h3>
<p>연산자는 아무 문자나 들어오는 값이 아니라,
정해진 4가지 중 하나라는 사실이 코드 구조에 반영된다.</p>
<h3 id="3-나중에-확장하기-좋다">3) 나중에 확장하기 좋다</h3>
<p>지금은 단순히 연산 종류만 관리하지만,
나중에는 각 enum에 기호, 설명, 계산 로직까지 붙일 수도 있다.</p>
<p>즉 enum은 단순히 “문법 하나 더 배운 것”이 아니라
<strong>정해진 종류를 더 안전하게 표현하는 방식</strong>이라는 걸 느꼈다.</p>
<hr>
<h2 id="10-정리">10. 정리</h2>
<p>이번 계산기 과제에서 enum을 적용하면서 가장 중요하게 느낀 점은 다음과 같다.</p>
<ul>
<li><code>enum</code>은 <strong>정해진 값들만 가지는 타입</strong>이다.</li>
<li>계산기의 연산자처럼 종류가 미리 정해져 있는 값에 잘 어울린다.</li>
<li>기존에는 <code>char</code>로 직접 비교했지만,</li>
<li>enum을 사용하면 <strong>의미가 더 분명하고 안전한 코드</strong>가 된다.</li>
<li>사용자 입력은 문자로 받더라도, 프로그램 내부에서는 enum으로 변환해서 처리할 수 있다.</li>
</ul>
<p>한 줄로 정리하면:</p>
<blockquote>
<p>계산기에서 enum을 사용한다는 것은
연산자를 단순한 문자로 다루는 것이 아니라,
<strong>정해진 연산 타입으로 명확하게 관리하겠다는 의미</strong>였다.</p>
</blockquote>
<hr>
<h2 id="마무리">마무리</h2>
<p>처음에는 enum이 조금 추상적으로 느껴졌지만,
계산기처럼 연산자 종류가 명확한 예제에 직접 붙여보니 왜 필요한지 이해가 됐다.</p>
<p>이번 과제를 통해
단순히 “계산이 되게 만드는 것”에서 한 걸음 더 나아가,
<strong>코드를 더 명확하고 구조적으로 표현하는 방법</strong>을 조금씩 배우고 있다는 느낌이 들었다.</p>
<p>다음에는 enum뿐만 아니라
제네릭, 람다, 스트림도 계산기 과제에 어떻게 연결되는지 차근차근 정리해보고 싶다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 10일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-10%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-10%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 23 Mar 2026 10:51:06 GMT</pubDate>
            <description><![CDATA[<h2 id="getter--setter-문법을-외우는-가장-쉬운-공식">getter / setter 문법을 외우는 가장 쉬운 공식</h2>
<h3 id="getter-공식">getter 공식</h3>
<pre><code class="language-java">public 필드타입 get필드이름첫글자대문자() {
    return 필드이름;
}</code></pre>
<hr>
<h3 id="setter-공식">setter 공식</h3>
<pre><code class="language-java">public void set필드이름첫글자대문자(필드타입 필드이름) {
    this.필드이름 = 필드이름;
}</code></pre>
<h3 id="getter--setter를-만들-때-초보자가-자주-헷갈리는-것">getter / setter를 만들 때 초보자가 자주 헷갈리는 것</h3>
<p>1) getter는 return이 필요하다</p>
<p>getter는 값을 꺼내오는 거니까 <code>return</code>이 있어야 한다.</p>
<p>틀린 예:</p>
<pre><code class="language-java">public String getName() {
    name;
}</code></pre>
<p>맞는 예:</p>
<pre><code class="language-java">public String getName() {
    return name;
}</code></pre>
<hr>
<p>2) setter는 보통 void다
setter는 값을 저장하는 역할이라 보통 반환값이 없다.</p>
<p>맞는 형태:</p>
<pre><code class="language-java">public void setName(String name) {
    this.name = name;
}
```![](https://velog.velcdn.com/images/loaded_diaper/post/e4edacdf-2b57-41e5-ac46-bd3046bba676/image.png)


</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java 계산기 만들기 트러블 슈팅]]></title>
            <link>https://velog.io/@loaded_diaper/Java-%EA%B3%84%EC%82%B0%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85</link>
            <guid>https://velog.io/@loaded_diaper/Java-%EA%B3%84%EC%82%B0%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85</guid>
            <pubDate>Fri, 20 Mar 2026 06:30:26 GMT</pubDate>
            <description><![CDATA[<h2 id="exit를-입력해도-while문이-종료되지-않았던-이유"><code>exit</code>를 입력해도 while문이 종료되지 않았던 이유</h2>
<p>오늘 간단한 자바 계산기를 만들다가,
계산 결과를 출력한 뒤 <code>&quot;exit&quot;</code>를 입력하면 반복문이 종료되어야 하는데
계속 다시 <code>첫 번째 숫자를 입력하세요:</code>가 뜨는 문제가 있었다.</p>
<p>처음에는 <code>if (answer.equals(&quot;exit&quot;))</code> 비교가 잘못된 줄 알았는데,
원인은 <strong>Scanner 입력 방식 차이</strong>와 <strong>입력 버퍼에 남아 있는 엔터값</strong> 때문이었다.</p>
<hr>
<h2 id="문제-상황">문제 상황</h2>
<p>내가 작성한 계산기 코드는 대략 이런 구조였다.</p>
<pre><code class="language-java">package com.example.calculator;

import java.util.Scanner;

public class Calculator {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        int result = 0;

        while (true) {

            System.out.print(&quot;첫 번째 숫자를 입력하세요: &quot;);
            int a = scanner.nextInt();

            System.out.print(&quot;두 번째 숫자를 입력하세요: &quot;);
            int b = scanner.nextInt();

            System.out.print(&quot;사칙연산 기호를 입력하세요: &quot;);
            char c = scanner.next().charAt(0);

            if (c == &#39;+&#39;) {
                result = a + b;
            }
            else if (c == &#39;-&#39;) {
                result = a - b;
            }
            else if (c == &#39;*&#39;) {
                result = a * b;
            }
            else if (c == &#39;/&#39;) {
                if (b == 0) {
                    System.out.println(&quot;나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.&quot;);
                    continue;
                } else {
                    result = a / b;
                }
            }

            System.out.println(&quot;결과: &quot; + result);

            System.out.print(&quot;더 계산하시겠습니까? (exit 입력 시 종료): &quot;);
            String answer = scanner.nextLine();

            if (answer.equals(&quot;exit&quot;)) {
                break;
            }
        }
    }
}</code></pre>
<hr>
<h2 id="실제로-발생한-문제">실제로 발생한 문제</h2>
<p>예를 들어 아래처럼 입력했다.</p>
<pre><code class="language-text">첫 번째 숫자를 입력하세요: 1
두 번째 숫자를 입력하세요: 2
사칙연산 기호를 입력하세요: +
결과: 3
더 계산하시겠습니까? (exit 입력 시 종료): exit
첫 번째 숫자를 입력하세요:</code></pre>
<p>분명히 <code>exit</code>를 입력했는데도 반복문이 종료되지 않았다.</p>
<p>심지어 어떤 경우에는 아래와 같은 에러도 발생했다.</p>
<pre><code class="language-text">java.util.InputMismatchException</code></pre>
<hr>
<h2 id="처음에-헷갈렸던-부분">처음에 헷갈렸던 부분</h2>
<p>처음에는 이렇게 생각했다.</p>
<ul>
<li><code>&quot;exit&quot;</code> 비교를 <code>equals()</code>로 했으니 문제없다</li>
<li><code>exit</code>를 입력했는데 왜 <code>break</code>가 안 걸리지?</li>
<li><code>if</code>문의 break를 밖으로 빼야하나?</li>
</ul>
<p>그런데 문제는 문자열 비교가 아니라,
<strong><code>answer</code> 변수에 실제로 <code>&quot;exit&quot;</code>가 저장되지 않고 있었다는 점</strong>이었다.</p>
<hr>
<h2 id="원인-nextint-next와-nextline의-차이">원인: <code>nextInt()</code>, <code>next()</code>와 <code>nextLine()</code>의 차이</h2>
<h3 id="1-nextint와-next는-값만-읽는다">1. <code>nextInt()</code>와 <code>next()</code>는 값만 읽는다</h3>
<p><code>nextInt()</code>와 <code>next()</code>는 입력값 자체만 읽고,
사용자가 마지막에 누른 <strong>엔터(<code>\n</code>)는 입력 버퍼에 남겨둘 수 있다.</strong></p>
<p>예를 들어 사용자가 <code>+</code>를 입력하고 엔터를 누르면,
프로그램 입장에서는 <code>+</code>는 읽었지만 엔터는 남아 있을 수 있다.</p>
<hr>
<h3 id="2-그-다음-nextline은-남아-있던-엔터를-먼저-읽는다">2. 그 다음 <code>nextLine()</code>은 남아 있던 엔터를 먼저 읽는다</h3>
<p>문제는 여기서 발생했다.</p>
<pre><code class="language-java">String answer = scanner.nextLine();</code></pre>
<p>나는 이 줄이 <code>exit</code>를 읽을 거라고 생각했지만,
실제로는 <strong>바로 직전에 남아 있던 엔터만 읽고 끝나버렸다.</strong></p>
<p>즉, <code>answer</code>에는 <code>&quot;exit&quot;</code>가 아니라
<strong>빈 문자열 <code>&quot;&quot;</code></strong> 이 들어가게 된 것이다.</p>
<p>그래서 아래 조건문은 false가 된다.</p>
<pre><code class="language-java">if (answer.equals(&quot;exit&quot;)) {
    break;
}</code></pre>
<p>실제로는 이런 느낌이었던 셈이다.</p>
<pre><code class="language-java">if (&quot;&quot;.equals(&quot;exit&quot;)) {
    break;
}</code></pre>
<p>당연히 종료되지 않는다.</p>
<hr>
<h2 id="왜-inputmismatchexception까지-발생했을까">왜 <code>InputMismatchException</code>까지 발생했을까?</h2>
<p>반복문이 종료되지 않고 다시 처음으로 돌아가면,
프로그램은 또 숫자를 입력받으려고 한다.</p>
<pre><code class="language-java">int a = scanner.nextInt();</code></pre>
<p>그런데 나는 방금 <code>exit</code>를 입력한 상태였다.</p>
<p>즉, 프로그램 입장에서는
<strong>정수를 입력받아야 하는 자리에 <code>&quot;exit&quot;</code>가 들어온 것</strong>이다.</p>
<p>그래서 이런 예외가 발생했다.</p>
<pre><code class="language-text">java.util.InputMismatchException</code></pre>
<p>이 에러의 뜻은 간단하다.</p>
<blockquote>
<p>숫자를 받아야 하는데 숫자가 아닌 값이 들어왔다.</p>
</blockquote>
<hr>
<h2 id="해결-방법">해결 방법</h2>
<p>해결 방법은 <strong>남아 있는 엔터를 먼저 한 번 비워주고</strong>,
그 다음 실제 사용자 입력을 받는 것이다.</p>
<h3 id="수정한-코드">수정한 코드</h3>
<pre><code class="language-java">System.out.print(&quot;더 계산하시겠습니까? (exit 입력 시 종료): &quot;);
scanner.nextLine();          // 남아 있는 엔터 제거
String answer = scanner.nextLine();   // 실제 입력 받기

if (answer.equals(&quot;exit&quot;)) {
    break;
}</code></pre>
<p>이렇게 순서를 바꾸니 정상적으로 동작했다.</p>
<hr>
<h2 id="왜-이-코드는-제대로-동작할까">왜 이 코드는 제대로 동작할까?</h2>
<p>이제 입력 흐름은 이렇게 된다.</p>
<h3 id="연산자-입력-직후">연산자 입력 직후</h3>
<pre><code class="language-java">char c = scanner.next().charAt(0);</code></pre>
<ul>
<li><code>+</code>는 읽음</li>
<li>엔터는 남아 있을 수 있음</li>
</ul>
<h3 id="첫-번째-nextline">첫 번째 <code>nextLine()</code></h3>
<pre><code class="language-java">scanner.nextLine();</code></pre>
<ul>
<li>남아 있던 엔터 제거</li>
</ul>
<h3 id="두-번째-nextline">두 번째 <code>nextLine()</code></h3>
<pre><code class="language-java">String answer = scanner.nextLine();</code></pre>
<ul>
<li>이제 진짜 내가 입력한 <code>exit</code>를 읽음</li>
</ul>
<h3 id="조건-확인">조건 확인</h3>
<pre><code class="language-java">if (answer.equals(&quot;exit&quot;)) {
    break;
}</code></pre>
<ul>
<li>이번에는 <code>answer</code>에 실제로 <code>&quot;exit&quot;</code>가 들어 있으므로 종료됨</li>
</ul>
<hr>
<h2 id="내가-배운-점">내가 배운 점</h2>
<p>이번 트러블 슈팅을 하면서 가장 크게 느낀 건
<strong>Scanner의 입력 방식 차이를 정확히 이해해야 한다는 것</strong>이었다.</p>
<h3 id="nextint"><code>nextInt()</code></h3>
<ul>
<li>숫자만 읽음</li>
<li>엔터는 남을 수 있음</li>
</ul>
<h3 id="next"><code>next()</code></h3>
<ul>
<li>공백 전까지 한 단어만 읽음</li>
<li>엔터는 남을 수 있음</li>
</ul>
<h3 id="nextline"><code>nextLine()</code></h3>
<ul>
<li>엔터 전까지 한 줄 전체를 읽음</li>
</ul>
<p>즉, <code>nextInt()</code>나 <code>next()</code> 뒤에 바로 <code>nextLine()</code>을 쓰면
내가 원하는 입력이 아니라 <strong>남아 있던 엔터를 먼저 읽어버릴 수 있다.</strong></p>
<hr>
<h2 id="최종-코드">최종 코드</h2>
<pre><code class="language-java">package com.example.calculator;

import java.util.Scanner;

public class Calculator {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        int result = 0;

        while (true) {

            System.out.print(&quot;첫 번째 숫자를 입력하세요: &quot;);
            int a = scanner.nextInt();

            System.out.print(&quot;두 번째 숫자를 입력하세요: &quot;);
            int b = scanner.nextInt();

            System.out.print(&quot;사칙연산 기호를 입력하세요: &quot;);
            char c = scanner.next().charAt(0);

            if (c == &#39;+&#39;) {
                result = a + b;
            }
            else if (c == &#39;-&#39;) {
                result = a - b;
            }
            else if (c == &#39;*&#39;) {
                result = a * b;
            }
            else if (c == &#39;/&#39;) {
                if (b == 0) {
                    System.out.println(&quot;나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.&quot;);
                    continue;
                } else {
                    result = a / b;
                }
            }

            System.out.println(&quot;결과: &quot; + result);

            System.out.print(&quot;더 계산하시겠습니까? (exit 입력 시 종료): &quot;);
            scanner.nextLine();
            String answer = scanner.nextLine();

            if (answer.equals(&quot;exit&quot;)) {
                break;
            }
        }

        scanner.close();
    }
}</code></pre>
<hr>
<h2 id="마무리">마무리</h2>
<p>이번 문제는 단순히 <code>break</code>나 <code>equals()</code>의 문제가 아니라,
<strong>입력 버퍼와 Scanner 메서드의 동작 방식</strong>을 이해해야 해결할 수 있는 문제였다.</p>
<p>처음에는 왜 <code>exit</code>가 안 먹는지 감이 안 왔지만,
원인을 하나씩 따라가 보니
결국 <strong>남아 있던 엔터가 문제</strong>라는 걸 알 수 있었다.</p>
<p>앞으로 <code>Scanner</code>를 사용할 때는
특히 <code>nextInt()</code>, <code>next()</code>, <code>nextLine()</code>을 섞어 쓸 때
입력 버퍼를 꼭 의식해야겠다고 느꼈다.</p>
<p>---<img src="https://velog.velcdn.com/images/loaded_diaper/post/1101205f-c534-46e4-8eb6-ccce7114f3c6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 9일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-9%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-9%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 19 Mar 2026 11:48:06 GMT</pubDate>
            <description><![CDATA[<h2 id="챕터3-3--컬렉션collection">챕터3-3 : 컬렉션(Collection)</h2>
<h3 id="컬렉션collection이란">컬렉션(Collection)이란?</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/9bf6fed5-6e0a-4f39-8704-9ad7ece3c090/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/57c28aa0-fcbf-45d8-8b85-a1e177960371/image.png" alt=""></p>
<h2 id="배열의-한계">배열의 한계</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/5480abae-4cc8-49c7-9657-c248f88f1bb8/image.png" alt=""></p>
<pre><code class="language-java">// 배열은 길이가 고정됨
int[] numbers = new int[3];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40; // ❌ 요소 추가시 에러발생</code></pre>
<h3 id="선언-방법">선언 방법</h3>
<pre><code class="language-java">컬렉션객체&lt;자료형&gt; 변수이름 = new 컬렉션객체&lt;자료형&gt;();

// 객체&lt;다룰 데이터: 정수&gt; 변수이름 = new 컬렉션객체생성자&lt;정수&gt;();
ArrayList&lt;Integer&gt; arrayList = new ArrayList&lt;Integer&gt;();</code></pre>
<pre><code class="language-java">ArrayList&lt;Integer&gt; arrayList = new ArrayList&lt;&gt;();
arrayList.add(10);
arrayList.add(20);
arrayList.add(30);
arrayList.add(40); // ✅ 정상 동작 (길이 제한 없음)</code></pre>
<ul>
<li>배열과 다르게 컬렉션은 길이를 동적으로 변경할 수 있습니다.(추가 삭제 시 유연하게 길이가 변경됩니다.)</li>
</ul>
<h2 id="컬렉션-종류와-특징">컬렉션 종류와 특징</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/3cd22061-c909-4a18-852a-e056050e72ef/image.png" alt=""></p>
<h3 id="list-인터페이스를-구현한-arraylist">List 인터페이스를 구현한 ArrayList</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/0ccff294-179a-4bc4-ab81-4005b8bcb446/image.png" alt=""></p>
<pre><code class="language-java">// List 를 구현한 ArrayList
ArrayList&lt;String&gt; names = new ArrayList&lt;&gt;();
names.add(&quot;Spartan&quot;);      // 1 번째 요소 추가
names.add(&quot;Steve&quot;);        // 2 번째 요소 추가
names.add(&quot;Isac&quot;);         // 3 번째 요소 추가
names.add(&quot;1&quot;);
names.add(&quot;2&quot;);

 // ✅ 순서 보장
System.out.println(&quot;names = &quot; + names);

// ✅ 중복 데이터 허용
names.add(&quot;Spartan&quot;);
System.out.println(&quot;names = &quot; + names);

// ✅ 단건 조회
System.out.println(&quot;1 번째 요소 조회: &quot; + names.get(0)); // 조회 Spartan

// ✅ 데이터 삭제
names.remove(&quot;Steve&quot;); 
System.out.println(&quot;names = &quot; + names);
</code></pre>
<h3 id="set-인터페이스를-구현한-hashset">Set 인터페이스를 구현한 HashSet</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/b60d1b8b-0b91-44ce-8a8c-633c471c7cf2/image.png" alt=""></p>
<pre><code class="language-java">// Set 을 구현한 HashSet
HashSet&lt;String&gt; uniqueNames = new HashSet&lt;&gt;();

// ✅ 추가
uniqueNames.add(&quot;Spartan&quot;);
uniqueNames.add(&quot;Steve&quot;);
uniqueNames.add(&quot;Isac&quot;);
uniqueNames.add(&quot;1&quot;);
uniqueNames.add(&quot;2&quot;);

// ⚠️ 순서를 보장 안함
System.out.println(&quot;uniqueNames = &quot; + uniqueNames); 
uniqueNames.get(0); // ❌ get 사용 불가

// ⚠️ 중복 불가
uniqueNames.add(&quot;Spartan&quot;);
System.out.println(&quot;uniqueNames = &quot; + uniqueNames); 

// ✅ 제거
uniqueNames.remove(&quot;Spartan&quot;);
System.out.println(&quot;uniqueNames = &quot; + uniqueNames); </code></pre>
<h3 id="map-인터페이스를-구현한-hashmap">Map 인터페이스를 구현한 HashMap</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/1d7348c3-daf4-41f6-9ad4-581f8c0f51f1/image.png" alt=""></p>
<pre><code class="language-java![](https://velog.velcdn.com/images/loaded_diaper/post/3a5fe89c-bcdd-4b62-86d1-4d940542d706/image.png)">
// Map 을 구현한 HashMap
HashMap&lt;String, Integer&gt; memberMap = new HashMap&lt;&gt;();

// ✅ 추가
memberMap.put(&quot;Spartan&quot;, 15);
memberMap.put(&quot;Steve&quot;, 15); // ✅ 값은 중복 가능
memberMap.put(&quot;Isac&quot;, 1);
memberMap.put(&quot;John&quot;, 2);
memberMap.put(&quot;Alice&quot;, 3);

// ⚠️ 순서 보장 안함 
System.out.println(&quot;memberMap = &quot; + memberMap);

// ⚠️ 키 중복 불가: 값 덮어쓰기 발생
memberMap.put(&quot;Alice&quot;, 5);
System.out.println(&quot;memberMap = &quot; + memberMap);

// ✅ 조회: 15
System.out.println(memberMap.get(&quot;Steve&quot;));

// ✅ 삭제 가능
memberMap.remove(&quot;Spartan&quot;); 
System.out.println(&quot;memberMap = &quot; + memberMap);

// ✅ 키 확인
Set&lt;String&gt; keys = memberMap.keySet();
System.out.println(&quot;keys = &quot; + keys);

// ✅ 값 확인
Collection&lt;Integer&gt; values = memberMap.values();
System.out.println(&quot;values = &quot; + values);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 8일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-8%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-8%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 18 Mar 2026 11:52:31 GMT</pubDate>
            <description><![CDATA[<h2 id="챕터3-1--예외exception과-예외처리try-catch">챕터3-1 : 예외(Exception)과 예외처리(try-catch)</h2>
<h3 id="예외exception란">예외(Exception)란?</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/07b80fa6-0ff7-4945-be91-173f467dcc7d/image.png" alt=""></p>
<h3 id="의도하지-않은-예외">의도하지 않은 예외</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/ea832d02-f6af-4090-8ec9-c40ab75714e0/image.png" alt=""></p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        System.out.println(&quot;프로그램 시작&quot;);
        int result = 10 / 0; // ❌ 예외 발생 (ArithmeticException)
        System.out.println(&quot;이 문장은 실행되지 않음&quot;);
    }
}```

```java
Exception in thread &quot;main&quot; java.lang.ArithmeticException: / by zero
    at chapter3.exception.Main.main(Main.java:8)

Process finished with exit code 1</code></pre>
<h3 id="의도적인-예외----throw">의도적인 예외  - throw</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/69f19e3f-1019-4c8d-b723-817fcd251e2c/image.png" alt=""></p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        int age = 10;
        if (age &lt; 18) {
                // ✅ 의도적으로 예외를 발생시키는 부분
            throw new IllegalArgumentException(&quot;미성년자는 접근할 수 없습니다!&quot;);
        }
        System.out.println(&quot;....&quot;);
    }
}</code></pre>
<h3 id="예외-구조와-종류">예외 구조와 종류</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/b8c56fa5-b7f7-4f08-a117-366b5e25d4ee/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/2eafbf45-f703-4f99-9b0c-ef4e38248c93/image.png" alt=""></p>
<h3 id="예외-전파">예외 전파</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/2c195569-3ed7-42d2-a62b-27c4bcd1404c/image.png" alt=""></p>
<h3 id="runtimeexception---uncheckedexception">RuntimeException - UncheckedException</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/e13ef89d-3156-433d-9f4a-5d4e170d16c4/image.png" alt=""></p>
<h3 id="try-catch-활용">try-catch 활용</h3>
<pre><code class="language-java">public class ExceptionPractice {
    public void callUncheckedException() {
        if (true) {
            System.out.println(&quot;언체크 예외 발생&quot;);
            throw new RuntimeException(); // ✅ 예외발생 
        }
    }
}</code></pre>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        // 예외 실습 객체 인스턴스화
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // ✅ 언체크 예외 호출
        exceptionPractice.callUncheckedException();

        // ❌ 예외처리를 해주지 않았기 때문에 프로그램이 종료됩니다.
        System.out.println(&quot;이 줄은 실행되지 않습니다.&quot;); 
    }
}</code></pre>
<pre><code class="language-java">public class Main {

    public static void main(String[] args) {
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // ✅ 상위로 전파된 예외처리
        try {
            exceptionPractice.callUncheckedException();

        } catch (RuntimeException e) { // ✅ 예외처리
            System.out.println(&quot;언체크 예외 처리&quot;);   

        } catch (Exception e) {
            System.out.println(&quot;체크 예외 처리&quot;);
        }

        System.out.println(&quot;프로그램 종료&quot;);
    }
}
</code></pre>
<h3 id="exception---checkedexception">Exception - CheckedException</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/c474940d-2c1d-431f-93ef-8c212428a1de/image.png" alt=""></p>
<h3 id="try-catch-활용-1">try-catch 활용</h3>
<pre><code class="language-java">public class ExceptionPractice {

    public void callCheckedException() {
            // ✅ try-catch 로 예외 처리
        try { 
            if (true) {
                System.out.println(&quot;체크예외 발생&quot;);
                throw new Exception();
            }
        } catch (Exception e) {
            System.out.println(&quot;예외 처리&quot;);
        }
    }
}</code></pre>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {

        // 예외 실습 객체 인스턴스화
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // ✅ 체크예외 호출
        exceptionPractice.callCheckedException();
    }
}</code></pre>
<h3 id="throws-활용">throws 활용</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/3a090d60-1338-4ac8-b2c6-9d7f2f2ace41/image.png" alt=""></p>
<pre><code class="language-java">public class ExceptionPractice {

    public void callCheckedException() throws Exception { // ✅ throws 예외를 상위로 전파
        if (true) {
            System.out.println(&quot;체크예외 발생&quot;);
            throw new Exception();
        }
    }
}</code></pre>
<pre><code class="language-java">package chapter3.exception;

public class Main {
    public static void main(String[] args) {

        // 예외 실습 객체 인스턴스화
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // 체크 예외 사용
        // ✅ 반드시 상위 메서드에서 try-catch 를 활용해 주어야합니다.
        try {
            exceptionPractice.callCheckedException();
        } catch (Exception e) {
            System.out.println(&quot;예외처리&quot;);
        }
    }
}</code></pre>
<h2 id="챕터3-2--optional---null-을-다루는-법">챕터3-2 : Optional - null 을 다루는 법</h2>
<h3 id="optional-이란">Optional 이란?</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/b013c3b6-5c23-44e3-b6dc-6c34fd8c8b1b/image.png" alt=""></p>
<h3 id="optional-이-왜-필요한가">Optional 이 왜 필요한가?</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/5218274a-42d5-45e0-a288-373353e69cd9/image.png" alt=""></p>
<pre><code class="language-java">public class Student {

    // 속성
    private String name;

    // 생성자

    // 기능
    public String getName() {
        return this.name;
    }
}</code></pre>
<pre><code class="language-java">public class Camp {

    // 속성
    private Student student;

    // 생성자

    // 기능: ⚠️ null 을 반환할 수 있는 메서드
    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
            this.student = student;    
    }
}</code></pre>
<pre><code class="language-java">public class Main {

    public static void main(String[] args) {

        Camp camp = new Camp();
        Student student = camp.getStudent(); // ⚠️ student 에는 null 이 담김
        // ⚠️ 아래 코드에서 NPE 발생! 컴파일러가 잡아주지 않음
        String studentName = student.getName(); // 🔥 NPE 발생 -&gt; 프로그램 종료
        System.out.println(&quot;studentName = &quot; + studentName);
    }
}</code></pre>
<h3 id="null-을-직접-처리의-한계">NULL 을 직접 처리의 한계</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/8665649f-a373-44fe-bddc-5c993b4716c9/image.png" alt=""></p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        Camp camp = new Camp();
        Student student = camp.getStudent();

        String studentName;
        if (student != null) { // ⚠️ 가능은하지만 현실적으로 어려움
            studentName = student.getName();
        } else {
            studentName = &quot;등록된 학생 없음&quot;; // 기본값 제공
        }

        System.out.println(&quot;studentName = &quot; + studentName);
    }
}
</code></pre>
<h3 id="optional-활용하기">Optional 활용하기</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/d18b39ba-97e9-43ec-936f-c365a93b8d38/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/7db0245a-bb76-438c-be25-b2713e22ca61/image.png" alt=""></p>
<pre><code class="language-java">import java.util.Optional;

public class Camp {

    // 속성
    private Student student;

    // 생성자

    // 기능
    // ✅ null 이 반환될 수 있음을 명확하게 표시
    public Optional&lt;Student&gt; getStudent() {
        return Optional.ofNullable(student);
    }

    public void setStudent(Student student) {
            this.student = student;    
    }
}
</code></pre>
<pre><code class="language-java">public class Main {

    public static void main(String[] args) {

        Camp camp = new Camp();

        // isPresent() 활용시 true 를 반환하고 싶을때 활용
        // Student newStudent = new Student();
        // camp.setStudent(newStudent);

        //  Optional 객체 반환받음
        Optional&lt;Student&gt; studentOptional = camp.getStudent();

        // Optional 객체의 기능 활용
        boolean flag = studentOptional.isPresent(); // false 반환
        if (flag) {
            // 존재할 경우
            Student student = studentOptional.get(); // ✅ 안전하게 Student 객체 가져오기
            String studentName = student.getName();
            System.out.println(&quot;studentName = &quot; + studentName);

        } else {
            // null 일 경우
            System.out.println(&quot;학생이 없습니다.&quot;);
        }
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/f1407fa3-f4d2-4174-9928-35f14a43a491/image.png" alt=""></p>
<pre><code class="language-java">import java.util.Optional;

public class Camp {

    // 속성
    private Student student;

    // 생성자

    // 기능
    // ✅ null 이 반환될 수 있음을 명확하게 표시
    public Optional&lt;Student&gt; getStudent() {
        return Optional.ofNullable(student);
    }
}
</code></pre>
<pre><code class="language-java![](https://velog.velcdn.com/images/loaded_diaper/post/553d285a-2733-4ad8-aa87-97e0141f07dd/image.png)">
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Camp camp = new Camp();

        // ✅ Optional 객체의 기능 활용 (orElseGet 사용)
        Student student = camp.getStudent()
                              .orElseGet(() -&gt; new Student(&quot;미등록 학생&quot;));

        System.out.println(&quot;studentName = &quot; + student.getName());
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 7일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-7%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-7%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 17 Mar 2026 11:03:58 GMT</pubDate>
            <description><![CDATA[<h2 id="챕터4-1--github-협업---fork--pull-request-이해">챕터4-1 : Github 협업 - Fork &amp; Pull Request 이해</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/a6d92153-a5ec-491f-860e-acce2c78723c/image.png" alt=""></p>
<h3 id="fork-clone-차이">fork, clone 차이</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/2eeba4a0-9b03-4670-995c-c47b2f0257bd/image.png" alt=""></p>
<h3 id="pull-request-생성과-개념-이해">Pull Request 생성과 개념 이해</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/13fca78d-431d-445f-8515-58f68561c92b/image.png" alt=""></p>
<h2 id="챕터5-1--프로젝트-관리하기-2---브랜치-전략-1">챕터5-1 : 프로젝트 관리하기 (2) - 브랜치 전략 1</h2>
<h3 id="dev-stage-prod-branch-구성">Dev, Stage, Prod Branch 구성</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/7800e6e0-c68c-49ea-b8d5-881c290fb027/image.png" alt=""></p>
<p>📚 Git에서 <strong>Dev, Stage, Prod 브랜치</strong> 구성을 사용하는 이유는 개발, 테스트, 그리고 배포 단계를 명확히 구분하여 안정성과 협업 효율성을 높이기 위함입니다. 각 브랜치의 역할과 구성 방식은 다음과 같습니다.</p>
<p>1️⃣</p>
<p><strong>Dev 브랜치 (Development Branch)</strong></p>
<p><strong>역할</strong></p>
<ul>
<li>새로운 기능 개발(feature)과 버그 수정(bugfix)을 포함한 모든 작업의 기반 브랜치입니다.</li>
<li>개발자들이 가장 활발히 작업하는 브랜치로, 실험적 코드와 아직 완전하지 않은 변경 사항도 포함될 수 있습니다.</li>
</ul>
<p><strong>특징</strong></p>
<ul>
<li>이 브랜치의 상태는 안정적이지 않을 수 있으며, 테스트가 완벽히 이루어지지 않은 상태의 코드가 포함됩니다.</li>
</ul>
<p><strong>병합 흐름</strong></p>
<ul>
<li>개발 완료 후, <code>stage</code> 브랜치로 병합.</li>
</ul>
<p>2️⃣</p>
<p><strong>Stage 브랜치 (Staging Branch)</strong></p>
<p><strong>역할</strong></p>
<ul>
<li>배포 전의 최종 테스트 환경을 제공하며, <strong>통합 테스트</strong>(Integration Test)와 품질 보증(QA)이 이루어지는 단계입니다.</li>
<li>실질적으로 배포 준비가 완료된 코드를 포함합니다.</li>
</ul>
<p><strong>특징</strong></p>
<ul>
<li><code>dev</code> 브랜치에서 충분히 검증된 코드를 이 브랜치로 병합하여 테스트.</li>
<li>테스트 환경에서는 실제 운영 환경(Prod)과 유사한 설정을 사용하여 예상치 못한 문제를 미리 발견합니다.</li>
</ul>
<p><strong>병합 흐름</strong></p>
<ul>
<li><code>dev</code> → <code>stage</code>로 병합 후 테스트 진행.</li>
<li>테스트 통과 후, <code>prod</code> 브랜치로 병합.</li>
</ul>
<p>3️⃣ <strong>Prod 브랜치 (Production Branch)</strong></p>
<p><strong>역할</strong></p>
<ul>
<li>실제 사용자가 접근하는 <strong>프로덕션 환경</strong>의 코드를 포함하는 브랜치입니다.</li>
<li>이 브랜치의 코드는 반드시 안정적이고, 배포 가능한 상태여야 합니다.</li>
</ul>
<p><strong>특징</strong></p>
<ul>
<li>사용자가 직접 사용하는 서비스의 모든 변경 사항은 이 브랜치에 반영됩니다.</li>
<li>긴급 수정이 필요한 경우, <code>hotfix/</code> 브랜치를 사용하여 Prod 브랜치에서 작업 후 바로 병합할 수도 있습니다.</li>
<li>버전 태그(tag)를 통해 배포 이력을 관리하는 경우가 많습니다.</li>
</ul>
<p><strong>병합 흐름</strong></p>
<ul>
<li><code>stage</code> → <code>prod</code>로 병합하여 배포.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/0b176d90-52e2-48ba-b377-ef8ba0240331/image.png" alt=""></p>
<p>📚 master (최근 생성되는 브랜치에서는 모두 main), develop은 우리가 앞전에 봤기 때문에 스킵하지만, 여기서 hotfix, release, feature 3개 브랜치의 용도에 대해서는 이해할 필요가 있습니다.</p>
<p><strong><code>feature</code> 브랜치</strong></p>
<p><strong>역할</strong></p>
<ul>
<li>새로운 기능 개발을 위한 브랜치입니다.</li>
<li>보통 <code>develop</code> 브랜치에서 분기하여 작업을 시작하고, 개발이 완료되면 다시 <code>develop</code> 브랜치로 병합됩니다.</li>
</ul>
<p><strong>작명 규칙</strong></p>
<ul>
<li><code>feature/login-page</code>, <code>feature/api-integration</code> 등 기능 이름을 포함.</li>
</ul>
<p><strong>사용 시점</strong></p>
<ul>
<li>특정 기능이나 개선 사항을 독립적으로 작업.</li>
<li>코드 리뷰와 기본 테스트를 거친 후 <code>develop</code> 브랜치에 병합.</li>
</ul>
<p>2️⃣</p>
<p><strong><code>release</code> 브랜치</strong></p>
<p><strong>역할</strong></p>
<ul>
<li>배포 전 최종 테스트 및 QA(품질 보증)를 위한 브랜치입니다.</li>
<li><code>develop</code> 브랜치에서 분기하며, 모든 버그가 수정되고 테스트를 통과하면 <code>master</code> 브랜치에 병합됩니다.</li>
<li>테스트 중 발견된 버그는 <code>release</code> 브랜치에서 바로 수정한 후 병합.</li>
</ul>
<p><strong>사용 시점</strong></p>
<ul>
<li>새로운 릴리스를 준비할 때.</li>
<li>안정화 작업 완료 후 <code>master</code> 및 <code>develop</code>에 병합.</li>
</ul>
<p>3️⃣ <strong><code>hotfix</code> 브랜치</strong></p>
<p><strong>역할</strong></p>
<ul>
<li>프로덕션에서 발생한 <strong>긴급 버그를 수정</strong>하기 위한 브랜치입니다.</li>
<li><code>master</code> 브랜치에서 바로 분기하며, 수정이 완료되면 다시 <code>master</code>와 <code>develop</code> 브랜치로 병합됩니다.</li>
</ul>
<p><strong>사용 시점</strong></p>
<ul>
<li>프로덕션에서 긴급한 수정이 필요할 때.</li>
<li>예: 치명적인 보안 결함, 서비스 중단 이슈 등.</li>
</ul>
<p><strong>작명 규칙</strong></p>
<ul>
<li><code>hotfix/critical-bug</code>, <code>hotfix/payment-error</code> 등.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/a5d74113-8dea-4a32-8a00-be8c4d0f875f/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 6일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-6%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-6%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 16 Mar 2026 11:33:37 GMT</pubDate>
            <description><![CDATA[<h2 id="git-github">Git, Github</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/7af95c11-81c5-4f7e-a3e5-39a34b233241/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/6bb9743f-21c9-48ff-a66e-0baf0ff2a6a7/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/057bd7f7-2a09-427c-abfb-5484efdbbd6b/image.png" alt=""></p>
<h3 id="git의-기본---분산형-버전-관리-시스템">Git의 기본 - 분산형 버전 관리 시스템</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/2c244fa8-1c2f-4e31-872a-5dd057d62e7e/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/3280752e-7874-43b0-85be-923c8e2410f0/image.png" alt=""></p>
<h2 id="git-의-작업-흐름을-시각적으로-나타내는-구조도">git 의 작업 흐름을 시각적으로 나타내는 구조도</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/0a2c9c0a-dc23-4619-a469-61a331a03431/image.png" alt=""></p>
<h3 id="1working-tree">1.Working Tree</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/d58b3a9f-ae01-41d6-afb6-79e7f6c23673/image.png" alt=""></p>
<h3 id="2-working-directory">2. Working Directory</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/752e15fe-fe59-4911-957a-0b5061288589/image.png" alt=""></p>
<h3 id="3-stage-area">3. Stage Area</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/916d9230-a083-4c69-9c57-1a1d0fa42970/image.png" alt=""></p>
<h3 id="4-local-directory">4. Local Directory</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/9cb74ac7-a249-4694-96b8-1eb71d912e84/image.png" alt=""></p>
<hr>
<h3 id="1-git-commit">1. git commit</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/10d6269e-2083-47de-94f0-355805e5a00a/image.png" alt=""></p>
<h3 id="2-snapshot">2. snapshot</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/1fe7361d-9632-4e77-af9d-5ae30bad6863/image.png" alt=""></p>
<h3 id="3-head">3. Head</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/afafad33-4aac-4e18-8eac-9d0016d068b5/image.png" alt=""></p>
<h3 id="4-branch">4. Branch</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/12119563-dafe-40d5-bcad-fe86d097448a/image.png" alt=""></p>
<h3 id="5-merge">5. Merge</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/6625f654-cf50-4381-8335-58f0632e3934/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/6baef878-55f6-4879-b5ee-286483ac8d70/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git 트러블 슈팅] clone, branch, commit, push, PR까지 하면서 헷갈렸던 점 정리]]></title>
            <link>https://velog.io/@loaded_diaper/Git-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-clone-branch-commit-push-PR%EA%B9%8C%EC%A7%80-%ED%95%98%EB%A9%B4%EC%84%9C-%ED%97%B7%EA%B0%88%EB%A0%B8%EB%8D%98-%EC%A0%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@loaded_diaper/Git-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-clone-branch-commit-push-PR%EA%B9%8C%EC%A7%80-%ED%95%98%EB%A9%B4%EC%84%9C-%ED%97%B7%EA%B0%88%EB%A0%B8%EB%8D%98-%EC%A0%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 16 Mar 2026 11:06:53 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="1번째-오류">1번째 오류</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/7b27a410-b56a-4125-9331-9c38cf5f94e8/image.png" alt="">
팀 레포를 clone 받는 건 성공했는데, 내 브랜치에서 push를 하려고 하니 403 에러가 났다.</p>
<p>원인은 권한 문제였다.</p>
<p>clone은 public 레포라서 가능했지만, push는 쓰기 권한이 있어야 가능한 작업이었다.
즉, 팀 레포에 collaborator 또는 write 권한이 없어서 막힌 상태였다.</p>
<hr>
<h3 id="2번째-헷갈렸던-점">2번째 헷갈렸던 점</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/d2f815d7-448b-4d2b-b5e9-a3b72b09a13f/image.png" alt="">
다른 분들이 올린 코드를 어떻게 승인 하는지 몰랐는데 팀원분 덕분에 찾을 수 있었다.</p>
<hr>
<p>팀장님께서 과제 순서를 잘 정리해주셔서 강의를 다 듣지 못한 상태에서 잘 완료 한 것 같다.<img src="https://velog.velcdn.com/images/loaded_diaper/post/a054060a-e9c2-4654-82b8-ecf3fe6e741e/image.png" alt=""></p>
<ol>
<li>저장소 복제</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/dcbc68cd-b34b-4b0e-a4e4-03d4a99fcb5d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/919ef75b-d1ed-40ba-927e-f2d62a72544e/image.png" alt=""></p>
<ol start="2">
<li>새 브렌치</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/14f4a011-8858-4f13-b1b1-d608ae3d02eb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/ea5f2e32-81d9-4523-8f1f-d21fa166ddf9/image.png" alt=""></p>
<ol start="3">
<li>위에 브렌치가 바꼈는지 확인 후 <a href="http://%EB%82%B4%EC%9D%B4%EB%A6%84.md">내이름.md</a> 파일 생성 후 작성하기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/2b43e694-cf83-4d92-83ae-52b7047b922c/image.png" alt=""></p>
<ol start="4">
<li>커밋 및 푸시</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/8a0d3072-3b20-465e-b02a-6b083daad46e/image.png" alt=""></p>
<ol start="5">
<li>풀 리퀘스트</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/44bd6da7-bb61-4e79-9ec7-5a41281b25bd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/eee184e9-6f95-47ab-b0a6-d463033c37eb/image.png" alt=""></p>
<ol start="6">
<li>다른 사람 문서에 코멘트 하나씩 달기.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/b2c3342b-ad3a-4082-bcb4-14999f71f022/image.png" alt=""></p>
<ol start="7">
<li>코멘트 달린거 확인 후 팀원들이 승인해주면(수정할게 더 없다고 판단되면) Merge pull request를 누르기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/ab0a5969-ad0c-4533-8f3a-1c90a250b5bc/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 5일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-5%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-5%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 13 Mar 2026 10:44:45 GMT</pubDate>
            <description><![CDATA[<h2 id="챕터1-10--메서드---모듈화의-시작">챕터1-10 : 메서드 - 모듈화의 시작</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/dfc61c6c-cfea-4b29-b477-24f27156abb4/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/e5aa7d89-d222-4c8d-8713-836811e82569/image.png" alt=""></p>
<h2 id="챕터2-1--클래스와-객체">챕터2-1 : 클래스와 객체</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/6702d912-d40a-47ad-9f45-e6833db0696d/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/bd0a32eb-c7d6-4102-8080-cfe057285dfd/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/5fd6058c-7ee3-44b4-b6af-935f41a4ce16/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/e2847b34-43f9-49c4-b6dd-ba0a7ddddc3e/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/54a510cc-dc62-46b3-8876-043ba9f0b0d6/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/13e41120-12c1-4fa0-8fc2-91d777e8a402/image.png" alt=""></p>
<h2 id="-챕터2-2--jvm-메모리-영역"># 챕터2-2 : JVM 메모리 영역</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/cdc44bbe-ced5-4b77-ba85-0c2ba5954735/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/f7ce0a3d-5af7-4658-81ab-6e3b8c4a8588/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/83b3cdb4-51ab-40d5-8840-3ef46433ffa4/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/e5d40597-5c9a-4bc6-86b1-342519703e2f/image.png" alt=""></p>
<h2 id="챕터2-3--레퍼클래스기본형-참조형">챕터2-3 : 레퍼클래스(기본형 참조형)</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/cdf72288-e64e-45a6-822f-b4ac217888b3/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/d41ecf15-0fd0-4a40-ae21-84b35de530fa/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/e649fd50-2183-47c9-9096-0a311b364c30/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/61a9d796-9bb9-42b6-ba7e-4a5a4d48911b/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/47f65c84-248d-437c-bede-c2db52261b39/image.png" alt=""></p>
<h2 id="챕터2-4--static----클래스가-공유하는-공간">챕터2-4 : static  - 클래스가 공유하는 공간</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/dba81603-1660-4cde-80f6-628128bfbf3d/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/93567b89-5196-45da-9b00-aa2b0a5d87be/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/619bc92b-210b-4441-918e-54980ca56956/image.png" alt=""></p>
<h2 id="챕터2-5--final---변하지-않는-값">챕터2-5 : final - 변하지 않는 값</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/be0533aa-0a95-44f5-a418-b82a90b8b93f/image.png" alt=""></p>
<h2 id="챕터2-6--인터페이스---표준화의-시작">챕터2-6 : 인터페이스 - 표준화의 시작</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/e4d9be66-9bed-4113-a81f-48772e2352b1/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/ff082020-5e2b-4709-a92a-0f73ed971db7/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/2a1dda3a-99bf-451a-8c77-c29c261dfc6d/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/8fc984d3-671a-4504-ae7b-df985b2f32eb/image.png" alt=""></p>
<h2 id="챕터2-7--객체지향-part-1---캡슐화접근제어자">챕터2-7 : 객체지향 PART 1 - 캡슐화(접근제어자)</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/a69867e0-d4a7-4f64-813e-3bee0da95df9/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/f31c5e7e-a27d-48f7-9138-06d25539ecbf/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/ed4354a0-f257-475b-a4bf-e263d214537c/image.png" alt=""></p>
<h2 id="챕터2-8--객체지향-part-2---상속">챕터2-8 : 객체지향 PART 2 - 상속</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/bc1af5a2-e699-4c1a-930f-46a4ef4fbeb8/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/f148e045-d468-457c-af73-563095581960/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/18815b98-5f81-4f83-9547-0bd99d7852ea/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/391d4cb1-5bc9-451a-ac02-8a0a5d19a503/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/6a3ce69c-1528-440c-9695-4971bccb7eb4/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/dc3e3092-5055-49dd-86f8-31d75e2566d5/image.png" alt=""></p>
<h2 id="챕터2-9--객체지향-part-3---추상화">챕터2-9 : 객체지향 PART 3 - 추상화</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/34e913c3-6cf5-4eb5-87af-b4292d8e2c73/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/f9f2efa0-7849-4ecb-a3be-f20129d2c6fa/image.png" alt=""></p>
<h2 id="-챕터2-10--객체지향-part-4---다형성"># 챕터2-10 : 객체지향 PART 4 - 다형성</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/6071c70a-8bf0-4671-af5f-5672a9eb0eea/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/c4d55ba9-5a10-4b87-97d1-cdfe97e1ae4f/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/47c513ff-871d-4140-824e-41cd60e0f6c4/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/02f821b7-701b-4b6b-a2bf-5a948615b3a1/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/7822d9e3-7986-43c5-a721-c7e245a4235c/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/433b4424-61ce-4c16-bd29-cb6a6bde8e7e/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/4c7fb607-8a85-45bc-90d6-10f0ed4b771b/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/fdfa757d-0f31-441b-ad3b-975fb2317052/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/433e20f7-7917-411f-9cd3-10e110125789/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스파르타 4일차]]></title>
            <link>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-4%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@loaded_diaper/%EC%8A%A4%ED%8C%8C%EB%A5%B4%ED%83%80-4%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 12 Mar 2026 11:01:21 GMT</pubDate>
            <description><![CDATA[<h2 id="반복문---자동화의-첫걸음">반복문 - 자동화의 첫걸음</h2>
<h2 id="첫-번째-반복문-for">첫 번째 반복문 for</h2>
<p>문법: for 문의 구조
<img src="https://velog.velcdn.com/images/loaded_diaper/post/be0563bb-7764-4e62-bd65-34db03aaf153/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/414e53fc-265b-400e-b6e8-1994422d374c/image.png" alt=""></p>
<h3 id="break문">break문</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/b5aaa4ef-15f3-4353-960b-5f0cb19086fb/image.png" alt=""></p>
<h3 id="continue문">continue문</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/bbf29b6b-7e7b-454c-a0df-bfae20aebcd0/image.png" alt=""></p>
<h2 id="두-번째-반복문-while">두 번째 반복문 while</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/43aa325e-54b5-41c6-af4f-9139bbbb7139/image.png" alt=""></p>
<h2 id="세-번째-반복문-do-while">세 번째 반복문 do-while</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/8108169f-2daa-42b9-89f5-b3b89816704d/image.png" alt=""></p>
<hr>
<h2 id="챕터1-9--배열---데이터-관리의-시작">챕터1-9 : 배열 - 데이터 관리의 시작</h2>
<h2 id="배열array">배열(Array)</h2>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/566852bb-56c3-4a37-9ce6-44e37ab0a36a/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/24d48303-b75a-4e80-8ed5-aed2b79ef2cc/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/463564d5-35a5-4849-b3b0-5cf5ba4dc335/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/3d4ebc3e-3b9d-4e65-addd-4b270221f518/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/401c3da5-3547-4c9a-be51-d04ab3fd3dc7/image.png" alt=""></p>
<h3 id="배열-탐색">배열 탐색</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/b2640d95-b1a0-4fa1-8922-ce826ec63b7c/image.png" alt=""></p>
<h3 id="향상된-for-문---현업에서-많이-활용됩니다">향상된 for 문 - 현업에서 많이 활용됩니다</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/243f2207-8527-4eb2-b166-93a2cfaeed70/image.png" alt=""></p>
<h3 id="2차원-배열two-dimensional-array">2차원 배열(Two-Dimensional Array)</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/62bfa71f-1d1a-4615-9c08-58c504af0f17/image.png" alt="">
<img src="https://velog.velcdn.com/images/loaded_diaper/post/b123aa63-e1b2-4b95-973d-75650067f68c/image.png" alt=""></p>
<h3 id="2차원-배열을-직접-탐색">2차원 배열을 직접 탐색</h3>
<p><img src="https://velog.velcdn.com/images/loaded_diaper/post/f78aaae2-a156-4d46-9c37-ed44f45ffea9/image.png" alt=""></p>
<hr>
<p>자바 기초 문법을 다시 복습할 수 있는 시간이어서 좋았다.<img src="https://velog.velcdn.com/images/loaded_diaper/post/661d65ff-db6f-41a5-985a-56489efe55de/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블 슈팅] mockAPI로 맛집 CRUD 구현하며 겪은 문제 정리]]></title>
            <link>https://velog.io/@loaded_diaper/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-mockAPI%EB%A1%9C-%EB%A7%9B%EC%A7%91-CRUD-%EA%B5%AC%ED%98%84%ED%95%98%EB%A9%B0-%EA%B2%AA%EC%9D%80-%EB%AC%B8%EC%A0%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@loaded_diaper/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-mockAPI%EB%A1%9C-%EB%A7%9B%EC%A7%91-CRUD-%EA%B5%AC%ED%98%84%ED%95%98%EB%A9%B0-%EA%B2%AA%EC%9D%80-%EB%AC%B8%EC%A0%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 11 Mar 2026 08:58:26 GMT</pubDate>
            <description><![CDATA[<p>오늘은 mockAPI를 이용해서 맛집 목록 CRUD를 구현했다.
기능 자체는 등록, 조회, 수정, 삭제였지만, 실제로 구현하면서 몇 가지 헷갈리는 포인트가 있었다. 오늘 겪은 문제와 해결 과정을 간단히 정리해보려고 한다.</p>
<h3 id="1-카드가-안-뜨는-게-오류인지-정상인지-헷갈렸다">1. 카드가 안 뜨는 게 오류인지 정상인지 헷갈렸다</h3>
<p>처음 <code>GET /places</code>를 연결했을 때 화면에 카드가 안 떴다.
처음에는 코드 오류라고 생각했지만, 실제로는 mockAPI에 데이터가 없어서 응답이 빈 배열(<code>[]</code>)이었던 상태였다.</p>
<p>이 과정에서 중요한 건 <strong>“안 보이는 것”과 “오류”를 구분하는 것</strong>이었다.</p>
<p>확인 방법은 아래 순서로 했다.</p>
<ul>
<li>Console에 에러가 있는지 확인</li>
<li>Network에서 <code>GET /places</code> 요청 상태 코드 확인</li>
<li>Response가 <code>[]</code>인지 확인</li>
</ul>
<p>결과적으로 이번 경우는 <strong>오류가 아니라 정상 작동인데 데이터가 없는 상태</strong>였다.</p>
<hr>
<h3 id="2-프론트-필드와-서버-필드-이름이-달랐다">2. 프론트 필드와 서버 필드 이름이 달랐다</h3>
<p>HTML 폼에서는 위치 입력칸 id가 <code>location</code>이었는데, mockAPI에는 <code>address</code> 필드로 저장해야 했다.
즉, 프론트에서 입력받은 값을 서버가 원하는 구조로 바꿔줘야 했다.</p>
<p>예를 들어:</p>
<ul>
<li>프론트: <code>location</code></li>
<li>서버: <code>address</code></li>
</ul>
<p>이 경험을 통해 <strong>프론트 필드명과 서버 필드명이 항상 같을 필요는 없지만, 중간에서 정확히 매핑해주는 코드가 필요하다</strong>는 걸 배웠다.</p>
<hr>
<h3 id="3-call-description-필드를-처음에는-제대로-안-맞췄다">3. <code>call</code>, <code>description</code> 필드를 처음에는 제대로 안 맞췄다</h3>
<p>처음에는 HTML에는 <code>description</code>이 있는데 mockAPI에는 해당 필드가 없었고, 반대로 mockAPI에는 <code>call</code>이 있는데 폼에는 입력칸이 없었다.</p>
<p>이 상태에서는 입력한 값이 제대로 저장되지 않거나, 임시값으로 처리해야 하는 문제가 생겼다.</p>
<p>그래서 최종적으로는</p>
<ul>
<li>HTML에 <code>call</code> 입력칸 추가</li>
<li>mockAPI에 <code>description</code> 필드 추가</li>
<li>POST 요청 body에 둘 다 포함</li>
</ul>
<p>하는 방식으로 맞췄다.</p>
<p>이 과정을 통해 <strong>프론트와 백엔드는 필드 구조가 맞아야 제대로 동작한다</strong>는 걸 느꼈다.</p>
<hr>
<h3 id="4-삭제와-수정에서는-id가-핵심이었다">4. 삭제와 수정에서는 <code>id</code>가 핵심이었다</h3>
<p>삭제와 수정 기능을 구현하면서 <code>id</code>의 중요성을 확실히 느꼈다.</p>
<ul>
<li>삭제는 어떤 데이터를 지울지 알아야 하고</li>
<li>수정은 어떤 데이터를 수정할지 알아야 한다</li>
</ul>
<p>그래서 카드마다 <code>data-id</code>를 넣고, 그 값을 기준으로</p>
<ul>
<li><code>DELETE /places/:id</code></li>
<li><code>PUT /places/:id</code></li>
</ul>
<p>를 요청했다.</p>
<p>이 과정에서 <code>id</code>는 단순 번호가 아니라 <strong>데이터를 식별하는 핵심 값</strong>이라는 걸 이해하게 됐다.</p>
<hr>
<h3 id="5-수정-기능은-상태-관리가-필요했다">5. 수정 기능은 상태 관리가 필요했다</h3>
<p>등록 기능은 폼 값을 읽어서 <code>POST</code>만 보내면 됐지만, 수정 기능은 더 복잡했다.</p>
<p>수정 버튼을 누르면</p>
<ul>
<li>기존 데이터를 폼에 채워야 하고</li>
<li>지금이 등록 모드인지 수정 모드인지 구분해야 하고</li>
<li>제출할 때 <code>POST</code>가 아니라 <code>PUT</code>을 보내야 했다</li>
</ul>
<p>그래서 <code>editingId</code>라는 변수를 두고 수정 중인 데이터를 구분했다.</p>
<p>이 과정을 통해 수정 기능은 단순히 값만 바꾸는 게 아니라, <strong>현재 상태를 관리하는 로직</strong>이 필요하다는 걸 배웠다.</p>
<hr>
<h3 id="마무리">마무리</h3>
<p>오늘 mockAPI로 CRUD를 구현하면서 느낀 건, 프론트와 백엔드 연결은 단순히 fetch 하나로 끝나는 게 아니라는 점이었다.</p>
<p>중간에는 항상 이런 확인이 필요했다.</p>
<ul>
<li>지금 안 되는 이유가 오류인가?</li>
<li>데이터가 없는 정상 상태인가?</li>
<li>프론트 필드와 서버 필드가 맞는가?</li>
<li>어떤 id를 기준으로 수정/삭제할 것인가?</li>
</ul>
<p>이번 작업을 통해 <strong>폼 입력 → API 요청 → 서버 데이터 변경 → 다시 화면 반영</strong>이라는 웹 서비스의 기본 흐름을 직접 경험할 수 있었다.
<img src="https://velog.velcdn.com/images/loaded_diaper/post/94041b35-f7be-4996-b958-d67cdbed9e76/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>