<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gob.log</title>
        <link>https://velog.io/</link>
        <description>예 마 함 가보입시더</description>
        <lastBuildDate>Sun, 26 Jul 2026 15:43:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gob.log</title>
            <url>https://velog.velcdn.com/images/baeksh_8/profile/3b8dacfd-967e-4d24-a282-5b118a05abd2/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gob.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/baeksh_8" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[26/07/21 IL(I Learned) - Step 2]]></title>
            <link>https://velog.io/@baeksh_8/260721-ILI-Learned-Step-2</link>
            <guid>https://velog.io/@baeksh_8/260721-ILI-Learned-Step-2</guid>
            <pubDate>Sun, 26 Jul 2026 15:43:44 GMT</pubDate>
            <description><![CDATA[<h2 id="mybatis-mapper와-service-계층의-역할-이해">MyBatis Mapper와 Service 계층의 역할 이해</h2>
<p>가장 크게 달라진 부분은 <strong>Repository 대신 MyBatis Mapper를 사용하여 데이터베이스와 통신</strong>한다는 점이다.</p>
<p>이전 프로젝트에서는 <code>JdbcTemplate</code>을 이용하여 Repository 안에서 SQL을 직접 실행하였다. 반면 이번 프로젝트에서는 <strong>Mapper 인터페이스만 작성하고 SQL은 XML에서 관리</strong>하도록 구조가 변경되었다.</p>
<p>또한 Service 계층에서는 단순히 Mapper를 호출하는 것이 아니라 <strong>DTO와 Entity를 변환하고 필요한 비즈니스 로직을 수행</strong>하는 역할을 담당하였다.</p>
<p>이번 파트에서는 <strong>Mapper와 Service가 실제로 어떻게 동작하는지</strong>를 중심으로 정리하였다.</p>
<hr>
<h3 id="boardmapper">BoardMapper</h3>
<h3 id="📚-이전-프로젝트와-달라진-점">📚 이전 프로젝트와 달라진 점</h3>
<table>
<thead>
<tr>
<th>Spring JDBC</th>
<th>MyBatis</th>
</tr>
</thead>
<tbody><tr>
<td>Repository 클래스</td>
<td>Mapper Interface</td>
</tr>
<tr>
<td>JdbcTemplate 사용</td>
<td>Mapper 메서드 호출</td>
</tr>
<tr>
<td>SQL을 Java에서 작성</td>
<td>SQL을 XML에서 작성</td>
</tr>
<tr>
<td>RowMapper 필요</td>
<td>자동 매핑</td>
</tr>
</tbody></table>
<p>Repository 클래스가 없어지고 Mapper 인터페이스가 데이터 접근을 담당하게 되었다.</p>
<hr>
<h3 id="mapper-interface">Mapper Interface</h3>
<pre><code class="language-java">@Mapper
public interface BoardMapper {

    int insert(Board board);

    List&lt;Board&gt; findAll();

    Board findById(long id);

    int update(Board board);

    int delete(long id);
}</code></pre>
<p>Mapper에는 SQL이 존재하지 않는다.</p>
<p>메서드만 선언하면 MyBatis가 XML에 작성된 SQL과 연결하여 실행한다.</p>
<hr>
<h3 id="mybatis-내부-동작">MyBatis 내부 동작</h3>
<pre><code class="language-text">BoardService

↓

BoardMapper.insert()

↓

MyBatis

↓

BoardMapper.xml

↓

INSERT SQL 실행

↓

Database</code></pre>
<p>즉, 우리가 호출하는 것은 인터페이스의 메서드이지만 실제 실행은 XML에 작성된 SQL이 담당한다.</p>
<p>Spring은 실행 시 Mapper 인터페이스의 구현 객체(Proxy)를 자동으로 생성하여 주입한다.</p>
<p>따라서 개발자는 구현 클래스를 직접 만들 필요가 없다.</p>
<hr>
<h3 id="boardservice">BoardService</h3>
<h4 id="service의-역할">Service의 역할</h4>
<p>이번 프로젝트에서도 Service의 역할은 이전 프로젝트와 동일하다.</p>
<ul>
<li>Controller와 Mapper 연결</li>
<li>비즈니스 로직 수행</li>
<li>DTO ↔ Entity 변환</li>
</ul>
<p>하지만 Repository 대신 Mapper를 호출한다는 점이 달라졌다.</p>
<hr>
<h3 id="게시글-생성">게시글 생성</h3>
<pre><code class="language-java">public void create(BoardFormDTO dto) {
    boardMapper.insert(dto.toEntity());
}</code></pre>
<p>Service에서는</p>
<pre><code class="language-text">BoardFormDTO

↓

toEntity()

↓

Board Entity

↓

BoardMapper.insert()</code></pre>
<p>순서로 처리된다.  </p>
<p>DTO 내부의 <code>toEntity()</code>를 이용하여 Entity를 만든 뒤 Mapper에 전달한다.</p>
<hr>
<h3 id="게시글-조회">게시글 조회</h3>
<p>조회에서는 조금 다른 흐름을 사용한다.</p>
<pre><code class="language-java">return boardMapper.findAll()
        .stream()
        .map(BoardViewDTO::fromEntity)
        .toList();</code></pre>
<hr>
<h3 id="stream-api-활용">Stream API 활용</h3>
<p>조회 결과는</p>
<pre><code class="language-text">List&lt;Board&gt;</code></pre>
<p>이다.</p>
<p>하지만 화면에서는</p>
<pre><code class="language-text">List&lt;BoardViewDTO&gt;</code></pre>
<p>가 필요하다.</p>
<p>그래서 Stream API를 이용하여</p>
<pre><code class="language-text">List&lt;Board&gt;

↓

stream()

↓

BoardViewDTO.fromEntity()

↓

List&lt;BoardViewDTO&gt;</code></pre>
<p>로 변환한다.</p>
<p>기존 프로젝트에서는 반복문을 사용할 수도 있었지만, Stream API를 사용하면 객체 변환을 더 간결하게 작성할 수 있다.</p>
<hr>
<h3 id="게시글-수정">게시글 수정</h3>
<p>수정 과정은 다음과 같다.</p>
<pre><code class="language-text">게시글 조회

↓

Entity 수정

↓

Mapper.update()</code></pre>
<p>코드를 보면</p>
<pre><code class="language-java">Board board = boardMapper.findById(id);

board.setTitle(dto.title());

boardMapper.update(board);</code></pre>
<p>기존 데이터를 조회한 뒤 필요한 값만 변경하여 다시 저장하는 방식이다.</p>
<hr>
<h3 id="membermapper">MemberMapper</h3>
<p>MemberMapper에서 흥미로운 점은</p>
<pre><code class="language-java">List&lt;MemberViewDTO&gt; findAll();</code></pre>
<p>이다.</p>
<p>BoardMapper는 Entity를 반환하지만,</p>
<p>MemberMapper는 처음부터 DTO를 반환한다.</p>
<p>즉,</p>
<pre><code class="language-text">Database

↓

MyBatis

↓

MemberViewDTO</code></pre>
<p>로 바로 변환된다.</p>
<p>필드명이 동일하면 MyBatis가 자동으로 DTO에 값을 매핑할 수 있기 때문에 가능한 방식이다.</p>
<hr>
<h3 id="memberservice">MemberService</h3>
<p>MemberService 역시 구조는 동일하다.</p>
<pre><code class="language-java">memberMapper.insert(dto.toEntity());</code></pre>
<p>입력 DTO를 Entity로 변환한 뒤 Mapper를 호출한다. </p>
<p>조회는</p>
<pre><code class="language-java">return memberMapper.findAll();</code></pre>
<p>한 줄로 처리된다.</p>
<p>MemberMapper가 DTO를 바로 반환하기 때문에 Service에서 별도의 변환 과정이 필요하지 않다.</p>
<p>이 부분은 BoardService와 비교했을 때 가장 큰 차이점이다.</p>
<hr>
<h3 id="board와-member의-차이">Board와 Member의 차이</h3>
<table>
<thead>
<tr>
<th>Board</th>
<th>Member</th>
</tr>
</thead>
<tbody><tr>
<td>Entity 반환 후 DTO 변환</td>
<td>DTO를 바로 반환</td>
</tr>
<tr>
<td>Stream API 사용</td>
<td>변환 과정 없음</td>
</tr>
<tr>
<td>fromEntity() 호출</td>
<td>자동 매핑 활용</td>
</tr>
</tbody></table>
<p>같은 MyBatis를 사용하더라도 설계 방식에 따라 구현이 달라질 수 있다는 점을 확인하였다.</p>
<hr>
<h3 id="이번-프로젝트에서-새롭게-이해한-점">이번 프로젝트에서 새롭게 이해한 점</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>이해한 점</th>
</tr>
</thead>
<tbody><tr>
<td>Mapper</td>
<td>Repository 없이도 데이터 접근 계층을 구성할 수 있다.</td>
</tr>
<tr>
<td>Proxy 객체</td>
<td>MyBatis가 Mapper 인터페이스의 구현 객체를 자동 생성한다.</td>
</tr>
<tr>
<td>XML 매핑</td>
<td>SQL을 Java 코드와 분리하여 관리할 수 있다.</td>
</tr>
<tr>
<td>Stream API</td>
<td>Entity 목록을 DTO 목록으로 간결하게 변환할 수 있다.</td>
</tr>
<tr>
<td>DTO 직접 반환</td>
<td>상황에 따라 Mapper가 DTO를 바로 반환하도록 설계할 수도 있다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<p>이번 프로젝트를 통해 MyBatis는 단순히 SQL을 실행하는 라이브러리가 아니라, <strong>Mapper 인터페이스와 XML을 연결하여 데이터 접근을 추상화하는 프레임워크</strong>라는 점을 이해하게 되었다. 또한 Board와 Member의 구현 방식을 비교하면서 항상 하나의 방식만 사용하는 것이 아니라, 상황에 따라 <strong>Entity를 반환한 뒤 Service에서 DTO로 변환할지, Mapper에서 DTO를 직접 반환할지 선택할 수 있다</strong>는 점도 새롭게 배울 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/21 IL(I Learned) - Step 1]]></title>
            <link>https://velog.io/@baeksh_8/260721-ILI-Learned-Step-1</link>
            <guid>https://velog.io/@baeksh_8/260721-ILI-Learned-Step-1</guid>
            <pubDate>Sun, 26 Jul 2026 15:01:36 GMT</pubDate>
            <description><![CDATA[<h2 id="mybatis를-적용하며-달라진-프로젝트-구조-이해">MyBatis를 적용하며 달라진 프로젝트 구조 이해</h2>
<p>이전 Spring JDBC 프로젝트에서 사용했던 <code>JdbcTemplate</code> 대신 <strong>MyBatis Framework</strong>를 적용하여 게시판과 회원 관리 기능을 구현하였다.</p>
<p>기존 프로젝트에서는 Repository에서 SQL을 직접 작성하고 <code>JdbcTemplate</code>을 통해 실행했지만, 이번 프로젝트에서는 <strong>Mapper 인터페이스와 XML 매핑</strong>을 이용하여 SQL과 Java 코드를 분리하였다.</p>
<p>MVC 구조는 이전 프로젝트와 동일하지만, 데이터 접근 계층이 <code>Repository</code>에서 <code>Mapper</code>로 변경되면서 코드가 더욱 간결해졌고 SQL 관리도 쉬워졌다.</p>
<p><strong>MyBatis를 사용하는 이유와 기존 프로젝트와 달라진 점</strong>도 중심으로 정리하였다.</p>
<hr>
<h3 id="mybatis를-사용하는-이유">MyBatis를 사용하는 이유</h3>
<table>
<thead>
<tr>
<th>Spring JDBC</th>
<th>MyBatis</th>
</tr>
</thead>
<tbody><tr>
<td>JdbcTemplate 사용</td>
<td>Mapper 사용</td>
</tr>
<tr>
<td>Repository에서 SQL 실행</td>
<td>XML에서 SQL 관리</td>
</tr>
<tr>
<td>RowMapper 직접 작성</td>
<td>자동 객체 매핑</td>
</tr>
<tr>
<td>SQL과 Java 코드가 함께 존재</td>
<td>SQL과 Java 코드 분리</td>
</tr>
</tbody></table>
<p>기존 프로젝트에서는 Repository 안에서 SQL을 작성하고 <code>JdbcTemplate</code>을 호출하였다.</p>
<p>이번 프로젝트에서는 Mapper 인터페이스만 작성하고 실제 SQL은 XML 파일에서 관리한다.</p>
<p>이처럼 <strong>Java 코드와 SQL을 분리</strong>하면 유지보수가 훨씬 쉬워진다.</p>
<hr>
<h3 id="프로젝트-구조-변화">프로젝트 구조 변화</h3>
<p>기존 프로젝트</p>
<pre><code class="language-text">Controller
      ↓
Service
      ↓
Repository
      ↓
JdbcTemplate
      ↓
Database</code></pre>
<p>이번 프로젝트</p>
<pre><code class="language-text">Controller
      ↓
Service
      ↓
Mapper
      ↓
MyBatis
      ↓
Database</code></pre>
<p>기존 Repository가 담당하던 데이터 접근 역할을 MyBatis의 Mapper가 대신하게 되었다.</p>
<hr>
<h3 id="controller-구조는-그대로-유지된다">Controller 구조는 그대로 유지된다.</h3>
<p>MVC 구조 자체는 이전 프로젝트와 동일하다.</p>
<p>BoardController 역시 요청을 받은 뒤 Service를 호출하고 결과를 View에 전달하는 역할만 수행한다.</p>
<pre><code class="language-java">@PostMapping
public String create(@ModelAttribute BoardFormDTO dto) {
    boardService.create(dto);
    return &quot;redirect:/&quot;;
}</code></pre>
<p>Controller는 게시글 저장 방법이나 SQL을 알지 못한다.</p>
<p>오직</p>
<ul>
<li>요청 받기</li>
<li>Service 호출</li>
<li>View 반환</li>
</ul>
<p>세 가지 역할만 담당한다.</p>
<p>이처럼 <strong>Controller가 데이터 처리 로직을 가지지 않는 점은 이전 프로젝트와 동일하지만, 내부적으로 Repository 대신 Mapper를 사용한다는 점이 가장 큰 차이</strong>이다.</p>
<hr>
<h3 id="게시글-조회-흐름">게시글 조회 흐름</h3>
<pre><code class="language-text">브라우저

↓

BoardController

↓

BoardService

↓

BoardMapper

↓

MyBatis

↓

Database</code></pre>
<p>조회 결과는 다시</p>
<pre><code class="language-text">Database

↓

BoardMapper

↓

BoardService

↓

BoardController

↓

JSP</code></pre>
<p>순서로 사용자에게 전달된다.</p>
<p>MVC 구조는 그대로 유지되면서도 데이터 접근 방식만 변경되었다는 점을 확인할 수 있었다.</p>
<hr>
<h3 id="maincontroller의-변화">MainController의 변화</h3>
<p>MainController는 게시글 목록을 조회하는 기능을 담당한다.</p>
<pre><code class="language-java">model.addAttribute(
    &quot;boards&quot;,
    boardService.findAll()
);</code></pre>
<p>이 코드 역시 이전 프로젝트와 거의 동일하다.</p>
<p>달라진 점은 <code>findAll()</code> 내부 구현이다.</p>
<p>이전에는 Repository가 JdbcTemplate을 이용하여 조회했지만,</p>
<p>이번에는</p>
<pre><code class="language-text">BoardMapper.findAll()

↓

MyBatis

↓

SQL 실행</code></pre>
<p>과정으로 변경되었다.</p>
<p>Controller 입장에서는 구현 방식이 변경되어도 동일한 메서드만 호출하면 된다.</p>
<hr>
<h3 id="membercontroller-추가">MemberController 추가</h3>
<p>이번 프로젝트에서는 게시판뿐 아니라 회원 관리 기능도 함께 구현하였다.</p>
<p>MemberController 역시 동일한 구조를 따른다.</p>
<pre><code class="language-java">@PostMapping
public String save(MemberFormDTO dto) {
    memberService.create(dto);
    return &quot;redirect:/mem&quot;;
}</code></pre>
<p>게시글과 회원 기능이 모두 같은 MVC 패턴을 사용하기 때문에 새로운 기능을 추가하더라도 프로젝트 구조가 일관되게 유지된다.</p>
<hr>
<h3 id="dto-사용-방식의-변화">DTO 사용 방식의 변화</h3>
<p>이번 프로젝트에서도 DTO를 사용하지만,</p>
<p>이전 프로젝트보다 <strong>변환 책임이 더 명확하게 분리</strong>되었다.</p>
<p>예를 들어 BoardFormDTO에는</p>
<pre><code class="language-java">public Board toEntity()</code></pre>
<p>메서드가 존재한다. </p>
<p>즉,</p>
<pre><code class="language-text">사용자 입력

↓

BoardFormDTO

↓

toEntity()

↓

Board Entity</code></pre>
<p>변환이 DTO 내부에서 이루어진다.</p>
<p>반대로 조회에서는</p>
<pre><code class="language-java">BoardViewDTO.fromEntity(...)</code></pre>
<p>를 이용하여 Entity를 화면에 필요한 DTO로 변환한다. </p>
<pre><code class="language-text">Board Entity

↓

fromEntity()

↓

BoardViewDTO</code></pre>
<p>이처럼 <strong>입력과 출력의 변환을 DTO가 직접 담당하도록 구성된 점</strong>이 이전 프로젝트보다 더 명확해졌다.</p>
<hr>
<h3 id="이번-프로젝트에서-새롭게-이해한-점">이번 프로젝트에서 새롭게 이해한 점</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>이해한 점</th>
</tr>
</thead>
<tbody><tr>
<td>MyBatis</td>
<td>Repository 대신 Mapper를 사용하여 데이터 접근을 수행한다.</td>
</tr>
<tr>
<td>SQL 분리</td>
<td>SQL을 XML에서 관리하여 Java 코드와 역할을 분리하였다.</td>
</tr>
<tr>
<td>Mapper</td>
<td>인터페이스만 작성하면 MyBatis가 구현 객체를 생성한다.</td>
</tr>
<tr>
<td>DTO 변환</td>
<td><code>toEntity()</code>, <code>fromEntity()</code>를 통해 객체 변환 책임을 명확히 분리하였다.</td>
</tr>
<tr>
<td>MVC</td>
<td>전체 구조는 유지하면서 데이터 접근 방식만 변경되었다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<p>Spring MVC 구조는 이전 프로젝트와 크게 달라지지 않았지만, <strong>데이터 접근 계층이 Repository에서 Mapper로 변경된 것만으로도 코드가 훨씬 단순해졌다</strong>. 특히 SQL을 Java 코드에서 직접 작성하지 않고 XML에서 관리하는 방식은 유지보수 측면에서 큰 장점이 있다는 것을 이해할 수 있었다.</p>
<p>또한 <code>BoardFormDTO</code>의 <code>toEntity()</code>와 <code>BoardViewDTO</code>의 <code>fromEntity()</code>를 통해 입력과 출력 데이터를 명확하게 분리하는 구조를 다시 확인하면서, 객체 간 책임을 나누는 설계가 프로젝트의 확장성과 가독성을 높여준다는 점을 체감하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/20 IL(I Learned) - Step 4]]></title>
            <link>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-3-j3nhige5</link>
            <guid>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-3-j3nhige5</guid>
            <pubDate>Sun, 26 Jul 2026 14:11:24 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-mvc-프로젝트-전체-흐름과-최종-회고">Spring MVC 프로젝트 전체 흐름과 최종 회고</h2>
<p>단순히 계좌 정보를 조회하거나 저장하는 기능을 구현하는 것을 넘어, <strong>Spring MVC의 전체 요청 처리 과정</strong>을 직접 구현하였다. 사용자가 브라우저에서 요청을 보내면 Controller가 이를 받아 Service를 호출하고, Service는 Repository를 통해 데이터베이스와 통신한 뒤 결과를 다시 Controller와 View(JSP)로 전달하는 전체 흐름을 경험할 수 있었다.</p>
<p>또한 DTO와 Entity를 분리하고, Repository 패턴과 Service 계층을 적용하면서 <strong>객체지향 설계 원칙(SRP, 역할 분리)</strong>이 실제 프로젝트에서 왜 중요한지 이해할 수 있었다.</p>
<p>이번 실습은 Spring Framework가 단순히 코드를 줄여주는 프레임워크가 아니라, <strong>유지보수성과 확장성을 고려한 구조를 제공하는 프레임워크</strong>라는 점을 배우는 과정이었다.</p>
<hr>
<h2 id="프로젝트-전체-실행-흐름">프로젝트 전체 실행 흐름</h2>
<hr>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Spring MVC 흐름</td>
<td>브라우저 요청부터 JSP 응답까지 전체 과정을 이해하였다.</td>
</tr>
<tr>
<td>계층 구조</td>
<td>Controller → Service → Repository → Database 순서로 동작한다.</td>
</tr>
<tr>
<td>DTO 변환</td>
<td>요청 DTO와 응답 DTO를 분리하여 사용하였다.</td>
</tr>
<tr>
<td>JdbcTemplate</td>
<td>Repository에서 데이터베이스와 통신하였다.</td>
</tr>
<tr>
<td>View 출력</td>
<td>Controller가 Model을 통해 JSP에 데이터를 전달하였다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="프로젝트-구조">프로젝트 구조</h3>
<pre><code class="language-text">Browser

↓

DispatcherServlet

↓

Controller

↓

Service

↓

Repository

↓

Database

↓

Repository

↓

Service

↓

Controller

↓

JSP

↓

Browser</code></pre>
<p>이번 프로젝트는 모든 요청이 위 구조를 따라 처리된다.</p>
<hr>
<h3 id="계좌-생성create">계좌 생성(Create)</h3>
<p>사용자가</p>
<pre><code class="language-text">이름 입력

↓

등록 버튼 클릭</code></pre>
<p>을 수행하면</p>
<p>다음과 같은 과정이 실행된다.</p>
<pre><code class="language-text">HTML Form

↓

POST /

↓

MainController

↓

AccountFormDTO 생성

↓

BankService.makeAccount()

↓

Account Entity 생성

↓

Repository.save()

↓

JdbcTemplate.update()

↓

INSERT SQL

↓

Database</code></pre>
<h3 id="내부-동작-설명">내부 동작 설명</h3>
<p>Controller에서는</p>
<pre><code class="language-java">@ModelAttribute</code></pre>
<p>를 통해 HTML Form 데이터를 <code>AccountFormDTO</code>로 자동 변환한다. </p>
<p>Service에서는 DTO를 Entity로 변환한 뒤 Repository에 저장을 요청한다. </p>
<p>Repository에서는</p>
<pre><code class="language-sql">INSERT INTO accounts(name)
VALUES(?)</code></pre>
<p>를 JdbcTemplate로 실행하여 데이터베이스에 저장한다. </p>
<hr>
<h3 id="계좌-조회read">계좌 조회(Read)</h3>
<p>조회는 다음과 같이 진행된다.</p>
<pre><code class="language-text">GET /account/3

↓

AccountController

↓

findAccount()

↓

Repository.findById()

↓

SELECT SQL

↓

Database

↓

Account Entity

↓

AccountViewDTO

↓

Model

↓

account.jsp</code></pre>
<p>Repository는 Entity를 반환하고,</p>
<p>Service는</p>
<pre><code class="language-java">AccountViewDTO.fromEntity(...)</code></pre>
<p>를 이용하여 화면에 필요한 DTO로 변환한다.  </p>
<hr>
<h3 id="계좌-수정update">계좌 수정(Update)</h3>
<p>수정은 조회보다 조금 더 많은 단계가 존재한다.</p>
<pre><code class="language-text">사용자 입력

↓

POST /account/{id}

↓

Controller

↓

AccountUpdateDTO

↓

Repository.findById()

↓

Entity 수정

↓

Repository.update()

↓

UPDATE SQL

↓

Database</code></pre>
<p>Service에서는</p>
<p>먼저 기존 Entity를 조회한다.</p>
<p>그 이후</p>
<pre><code class="language-java">account.setName(...)</code></pre>
<p>으로 값을 변경한 뒤</p>
<p>Repository.update()를 호출한다. </p>
<hr>
<h3 id="계좌-삭제delete">계좌 삭제(Delete)</h3>
<p>삭제 과정은 가장 단순하다.</p>
<pre><code class="language-text">GET /account/3/delete

↓

Controller

↓

Service

↓

Repository

↓

DELETE SQL

↓

Database</code></pre>
<p>Repository에서는</p>
<pre><code class="language-sql">DELETE FROM accounts
WHERE id=?</code></pre>
<p>를 수행한다. </p>
<hr>
<h3 id="mvc-전체-요청-흐름">MVC 전체 요청 흐름</h3>
<pre><code class="language-text">브라우저

↓

DispatcherServlet

↓

Controller

↓

Service

↓

Repository

↓

JdbcTemplate

↓

Database

↓

Repository

↓

Service

↓

Controller

↓

Model

↓

JSP

↓

브라우저</code></pre>
<p>이 흐름이 Spring MVC 프로젝트의 핵심이라는 것을 이해하였다.</p>
<hr>
<h3 id="계층별-역할">계층별 역할</h3>
<table>
<thead>
<tr>
<th>계층</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Browser</td>
<td>사용자 요청</td>
</tr>
<tr>
<td>Controller</td>
<td>요청 처리 및 View 연결</td>
</tr>
<tr>
<td>Service</td>
<td>비즈니스 로직 수행</td>
</tr>
<tr>
<td>Repository</td>
<td>데이터베이스 접근</td>
</tr>
<tr>
<td>Database</td>
<td>데이터 저장</td>
</tr>
<tr>
<td>JSP</td>
<td>사용자 화면 출력</td>
</tr>
</tbody></table>
<p>각 계층이 자신의 역할만 수행하도록 설계되어 있기 때문에 유지보수가 쉬워진다.</p>
<hr>
<h3 id="spring-mvc를-사용하는-이유">Spring MVC를 사용하는 이유</h3>
<table>
<thead>
<tr>
<th>기존 방식</th>
<th>Spring MVC</th>
</tr>
</thead>
<tbody><tr>
<td>main()에서 모든 작업 수행</td>
<td>계층별 역할 분리</td>
</tr>
<tr>
<td>SQL 직접 실행</td>
<td>Repository 담당</td>
</tr>
<tr>
<td>객체 생성 직접 수행</td>
<td>Spring Bean 관리</td>
</tr>
<tr>
<td>Request 직접 처리</td>
<td>자동 바인딩</td>
</tr>
<tr>
<td>View 직접 연결</td>
<td>ViewResolver 사용</td>
</tr>
</tbody></table>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>Spring MVC</td>
<td>전체 요청 흐름을 직접 구현해 보니 각 계층의 역할이 명확하게 이해되었다.</td>
</tr>
<tr>
<td>객체지향</td>
<td>하나의 클래스가 하나의 책임만 가지도록 설계하는 이유를 체감하였다.</td>
</tr>
<tr>
<td>유지보수</td>
<td>역할을 분리하면 기능을 수정할 때 다른 계층에 영향을 거의 주지 않는다는 점을 알게 되었다.</td>
</tr>
<tr>
<td>Spring Framework</td>
<td>단순한 라이브러리가 아니라 애플리케이션 구조를 설계하도록 도와주는 프레임워크라는 점을 이해하였다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="배운-점">배운 점</h3>
<table>
<thead>
<tr>
<th>배운 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>MVC 구조</td>
<td>요청 처리 흐름을 계층별로 나누면 코드의 가독성과 유지보수성이 향상된다.</td>
</tr>
<tr>
<td>Service Layer</td>
<td>비즈니스 로직을 한곳에서 관리하면 Controller가 단순해진다.</td>
</tr>
<tr>
<td>Repository Pattern</td>
<td>데이터 접근을 분리하면 데이터베이스 구현이 변경되어도 다른 계층에 영향을 주지 않는다.</td>
</tr>
<tr>
<td>DTO</td>
<td>요청과 응답을 분리하면 보안성과 확장성이 향상된다.</td>
</tr>
<tr>
<td>JdbcTemplate</td>
<td>반복적인 JDBC 코드를 줄이고 개발 생산성을 높여준다.</td>
</tr>
</tbody></table>
<hr>
<h1 id="📌-최종-정리">📌 최종 정리</h1>
<p>이번 Spring MVC 프로젝트는 단순히 CRUD 기능을 구현하는 것이 아니라 <strong>웹 애플리케이션이 요청을 처리하는 전체 구조를 이해하는 과정</strong>이었다. Controller는 사용자의 요청을 받아 적절한 Service를 호출하고, Service는 비즈니스 로직을 수행하며 Repository를 통해 데이터베이스와 통신한다. Repository는 <code>JdbcTemplate</code>을 활용하여 SQL을 실행하고, 조회한 Entity는 필요한 정보만 담은 DTO로 변환되어 다시 Controller와 JSP로 전달된다.</p>
<p>이번 실습을 통해 <strong>Controller → Service → Repository → Database</strong>라는 계층형 구조와 MVC 패턴의 장점을 체감할 수 있었으며, 역할을 명확히 분리하는 설계가 유지보수성과 확장성을 높인다는 점을 이해하게 되었다. 또한 DTO와 Entity를 분리하고 <code>JdbcTemplate</code>, <code>RowMapper</code>, <code>@Transactional</code>을 활용하는 방법을 익히면서 앞으로 <strong>Spring Data JPA, MyBatis, 대규모 Spring Boot 프로젝트</strong>를 학습하는 데 필요한 핵심 기반을 다질 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/20 IL(I Learned) - Step 3]]></title>
            <link>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-3</link>
            <guid>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-3</guid>
            <pubDate>Sun, 26 Jul 2026 13:57:18 GMT</pubDate>
            <description><![CDATA[<h2 id="repository와-service-계층을-활용한-비즈니스-로직-구현">Repository와 Service 계층을 활용한 비즈니스 로직 구현</h2>
<p><strong>Repository와 Service 계층을 분리하여 사용하는 이유</strong>를 학습하였다. 이전 JDBC 프로젝트에서는 Main 클래스가 Repository를 직접 호출하여 데이터를 처리했지만, 이번 프로젝트에서는 <strong>Controller → Service → Repository</strong> 구조를 적용하여 계층별 역할을 명확하게 분리하였다.</p>
<p>또한 <code>JdbcTemplate</code>과 <code>RowMapper</code>를 이용하여 기존 JDBC 코드보다 훨씬 간결하게 데이터베이스를 다루는 방법을 익혔으며, <code>@Transactional</code>을 통해 여러 작업을 하나의 트랜잭션으로 관리하는 방법도 함께 학습하였다.</p>
<p>그리고 Spring은 단순히 JDBC를 대신해 주는 프레임워크가 아니라 <strong>객체 간의 역할을 명확하게 나누고 유지보수성을 높여주는 구조를 제공하는 프레임워크</strong>라는 점을 이해하게 되었다.</p>
<hr>
<h3 id="1-accountrepository">1) AccountRepository</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Repository Interface</td>
<td>데이터 접근 기능을 정의하는 인터페이스이다.</td>
</tr>
<tr>
<td>CRUD</td>
<td>Create, Read, Update, Delete 기능을 하나의 인터페이스에서 관리한다.</td>
</tr>
<tr>
<td>인터페이스</td>
<td>구현체가 바뀌어도 사용하는 코드는 변경하지 않아도 된다.</td>
</tr>
<tr>
<td>추상화</td>
<td>구현 방법보다 기능 자체를 먼저 정의하는 객체지향 설계 방식이다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public interface BankAccountRepository {

    void save(Account account);

    void update(Account account);

    List&lt;Account&gt; findAll();

    Account findById(long id);

    void deleteById(long id);

}</code></pre>
<p>이번 프로젝트에서는 Repository를 <strong>인터페이스</strong>로 먼저 작성하였다.</p>
<p>인터페이스에는</p>
<ul>
<li>저장</li>
<li>조회</li>
<li>수정</li>
<li>삭제</li>
</ul>
<p>기능만 정의하였다.</p>
<p>실제 SQL은 작성하지 않는다.</p>
<p>즉,</p>
<pre><code class="language-text">무엇을 할 것인가</code></pre>
<p>만 정의한다.</p>
<p>실제로</p>
<pre><code class="language-text">어떻게 할 것인가</code></pre>
<p>는 구현 클래스가 담당한다.</p>
<hr>
<h3 id="인터페이스를-사용하는-이유">인터페이스를 사용하는 이유</h3>
<p>예를 들어</p>
<p>현재는</p>
<pre><code class="language-text">SQLAccountRepository</code></pre>
<p>를 사용하지만</p>
<p>나중에는</p>
<pre><code class="language-text">MongoRepository</code></pre>
<p>또는</p>
<pre><code class="language-text">RedisRepository</code></pre>
<p>로 변경할 수도 있다.</p>
<p>Controller와 Service는</p>
<p>Repository가 어떻게 구현되어 있는지 알 필요가 없다.</p>
<p>오직</p>
<pre><code class="language-java">save()

findAll()

findById()</code></pre>
<p>만 알고 있으면 된다.</p>
<hr>
<h3 id="repository-추상화-구조">Repository 추상화 구조</h3>
<pre><code class="language-text">Controller

↓

Service

↓

AccountRepository

↓

SQLAccountRepository</code></pre>
<p>이러한 구조를</p>
<p><strong>다형성(Polymorphism)</strong></p>
<p>이라고 한다.</p>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>인터페이스</td>
<td>구현보다 기능을 먼저 설계하는 객체지향 방식을 이해하였다.</td>
</tr>
<tr>
<td>유지보수</td>
<td>구현체가 변경되어도 다른 코드를 수정하지 않아도 되는 장점을 알게 되었다.</td>
</tr>
<tr>
<td>다형성</td>
<td>인터페이스를 사용하는 이유를 실제 프로젝트를 통해 이해하였다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-sqlaccountrepository">2) SQLAccountRepository</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>JdbcTemplate</td>
<td>Spring에서 제공하는 JDBC 도우미 클래스이다.</td>
</tr>
<tr>
<td>RowMapper</td>
<td>조회 결과(ResultSet)를 객체로 변환하는 인터페이스이다.</td>
</tr>
<tr>
<td>query()</td>
<td>여러 데이터를 조회한다.</td>
</tr>
<tr>
<td>queryForObject()</td>
<td>하나의 데이터를 조회한다.</td>
</tr>
<tr>
<td>update()</td>
<td>INSERT, UPDATE, DELETE를 수행한다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">@Repository
@RequiredArgsConstructor
public class SqlAccountRepository
        implements AccountRepository {

    private final JdbcTemplate jdbcTemplate;

}</code></pre>
<p>기존 JDBC에서는</p>
<pre><code class="language-java">Connection

PreparedStatement

ResultSet</code></pre>
<p>을 모두 직접 생성해야 했다.</p>
<p>하지만 Spring에서는</p>
<pre><code class="language-java">JdbcTemplate</code></pre>
<p>하나만 있으면</p>
<p>이 모든 과정을 내부에서 자동으로 처리해 준다.</p>
<p>즉,</p>
<p>개발자는</p>
<p>SQL만 작성하면 된다.</p>
<hr>
<h3 id="jdbctemplate-내부-동작">JdbcTemplate 내부 동작</h3>
<pre><code class="language-text">JdbcTemplate

↓

Connection 생성

↓

PreparedStatement 생성

↓

SQL 실행

↓

ResultSet 반환

↓

Connection 종료</code></pre>
<p>이 과정을 Spring이 모두 대신 수행한다.</p>
<hr>
<h3 id="insert">INSERT</h3>
<pre><code class="language-java">jdbcTemplate.update(query, account.getName());</code></pre>
<p>update() 메서드는</p>
<p>INSERT</p>
<p>UPDATE</p>
<p>DELETE</p>
<p>모두 수행할 수 있다.</p>
<p>반환값은</p>
<p>영향 받은 행(Row)의 개수이다.</p>
<hr>
<h3 id="select">SELECT</h3>
<p>여러 개 조회</p>
<pre><code class="language-java">jdbcTemplate.query()</code></pre>
<p>한 개 조회</p>
<pre><code class="language-java">jdbcTemplate.queryForObject()</code></pre>
<p>를 사용하였다.</p>
<hr>
<h3 id="rowmapper">RowMapper</h3>
<p>Repository에는</p>
<pre><code class="language-java">private static final RowMapper&lt;Account&gt;</code></pre>
<p>가 존재한다.</p>
<p>RowMapper는</p>
<p>ResultSet을</p>
<p>객체로 변환하는 역할을 수행한다.</p>
<p>즉,</p>
<pre><code class="language-text">ResultSet

↓

Account 객체</code></pre>
<p>변환기 역할을 한다.</p>
<hr>
<h3 id="rowmapper-흐름">RowMapper 흐름</h3>
<pre><code class="language-text">SELECT

↓

ResultSet

↓

RowMapper

↓

Account

↓

List&lt;Account&gt;</code></pre>
<hr>
<h3 id="jdbctemplate의-장점">JdbcTemplate의 장점</h3>
<table>
<thead>
<tr>
<th>기존 JDBC</th>
<th>JdbcTemplate</th>
</tr>
</thead>
<tbody><tr>
<td>Connection 직접 생성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>Statement 생성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>close() 호출</td>
<td>자동 처리</td>
</tr>
<tr>
<td>SQLException 처리</td>
<td>Spring이 관리</td>
</tr>
</tbody></table>
<hr>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>코드 간결화</td>
<td>반복되던 JDBC 코드가 크게 줄어들었다.</td>
</tr>
<tr>
<td>RowMapper</td>
<td>ResultSet을 객체로 변환하는 과정이 매우 편리하였다.</td>
</tr>
<tr>
<td>Spring의 역할</td>
<td>Spring이 반복적인 작업을 자동으로 처리해 준다는 점을 체감하였다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-bankservice--bankserviceimpl">3) BankService &amp; BankServiceImpl</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Service Layer</td>
<td>비즈니스 로직을 담당하는 계층이다.</td>
</tr>
<tr>
<td>@Service</td>
<td>Service Bean으로 등록한다.</td>
</tr>
<tr>
<td>@Transactional</td>
<td>여러 작업을 하나의 트랜잭션으로 관리한다.</td>
</tr>
<tr>
<td>DTO → Entity</td>
<td>사용자의 입력을 Entity로 변환한다.</td>
</tr>
<tr>
<td>Entity → DTO</td>
<td>조회 결과를 ViewDTO로 변환한다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class BankServiceImpl implements BankService{

    private final AccountRepository repository;

}</code></pre>
<p>Service는</p>
<p>Controller와 Repository 사이에서</p>
<p>중간 역할을 수행한다.</p>
<p>즉,</p>
<p>Controller는</p>
<p>Repository를 모른다.</p>
<p>Repository 역시</p>
<p>Controller를 모른다.</p>
<p>둘 사이를</p>
<p>Service가 연결한다.</p>
<hr>
<h3 id="service-흐름">Service 흐름</h3>
<pre><code class="language-text">Controller

↓

BankService

↓

Repository</code></pre>
<hr>
<h3 id="계좌-생성">계좌 생성</h3>
<pre><code class="language-java">Account account =
        Account.builder()

        .name(dto.name())

        .build();</code></pre>
<p>FormDTO를</p>
<p>Entity로 변환하였다.</p>
<p>그 이후</p>
<pre><code class="language-java">repository.save(account);</code></pre>
<p>를 호출한다.</p>
<hr>
<h3 id="계좌-조회">계좌 조회</h3>
<p>Repository는</p>
<p>Entity를 반환한다.</p>
<p>하지만</p>
<p>Controller는</p>
<p>ViewDTO를 원한다.</p>
<p>그래서</p>
<pre><code class="language-java">AccountViewDTO.fromEntity(...)</code></pre>
<p>를 호출하여</p>
<p>DTO로 변환하였다.</p>
<hr>
<h3 id="계좌-수정">계좌 수정</h3>
<p>수정 과정은</p>
<p>조금 더 복잡하다.</p>
<pre><code class="language-text">DTO

↓

Repository.findById()

↓

Entity

↓

이름 변경

↓

Repository.update()</code></pre>
<p>즉,</p>
<p>기존 Entity를 조회한 뒤</p>
<p>필드만 변경하여</p>
<p>Repository에게 전달한다.</p>
<hr>
<h3 id="transactional">@Transactional</h3>
<pre><code class="language-java">@Transactional</code></pre>
<p>은</p>
<p>여러 작업을</p>
<p>하나의 작업처럼 처리한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">계좌 생성

↓

로그 저장

↓

알림 발송</code></pre>
<p>세 작업 중</p>
<p>하나라도 실패하면</p>
<p>모두 취소된다.</p>
<p>이를</p>
<p>Rollback이라고 한다.</p>
<hr>
<h3 id="transaction-흐름">Transaction 흐름</h3>
<pre><code class="language-text">BEGIN

↓

SQL1

↓

SQL2

↓

SQL3

↓

COMMIT

(실패 시 ROLLBACK)</code></pre>
<hr>
<h3 id="service-계층을-사용하는-이유">Service 계층을 사용하는 이유</h3>
<table>
<thead>
<tr>
<th>Controller</th>
<th>Service</th>
</tr>
</thead>
<tbody><tr>
<td>요청 처리</td>
<td>비즈니스 로직</td>
</tr>
<tr>
<td>화면 연결</td>
<td>업무 처리</td>
</tr>
<tr>
<td>View 반환</td>
<td>Repository 호출</td>
</tr>
</tbody></table>
<p>Service를 분리하면</p>
<p>Controller가 훨씬 단순해지고</p>
<p>비즈니스 로직을 한곳에서 관리할 수 있다.</p>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>Service Layer</td>
<td>단순히 Repository를 호출하는 것이 아니라 비즈니스 규칙을 관리하는 계층이라는 점을 이해하였다.</td>
</tr>
<tr>
<td>Transaction</td>
<td>여러 작업을 하나의 작업 단위로 묶는 이유를 배웠다.</td>
</tr>
<tr>
<td>Spring 구조</td>
<td>Controller, Service, Repository를 분리하면 유지보수성과 확장성이 크게 향상된다는 점을 느꼈다.</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/20 IL(I Learned) - Step 2]]></title>
            <link>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-2</link>
            <guid>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-2</guid>
            <pubDate>Sun, 26 Jul 2026 13:31:27 GMT</pubDate>
            <description><![CDATA[<h2 id="dto와-entity를-활용한-계층-간-데이터-전달">DTO와 Entity를 활용한 계층 간 데이터 전달</h2>
<p>Spring MVC 프로젝트에서 <strong>Entity와 DTO(Data Transfer Object)를 분리하여 사용하는 이유</strong>를 학습하였다. 이전 JDBC 프로젝트에서는 Repository가 반환한 Entity를 그대로 사용하였지만, 이번 프로젝트에서는 사용 목적에 따라 <code>AccountFormDTO</code>, <code>AccountUpdateDTO</code>, <code>AccountViewDTO</code>를 각각 따로 만들어 사용하였다.</p>
<p>사용자의 입력을 받는 객체와 화면에 보여주는 객체, 그리고 실제 데이터베이스에 저장되는 객체는 서로 역할이 다르다는 점을 이해하게 되었다.</p>
<p>또한 Java Record를 이용하여 DTO를 작성하면서 불필요한 코드를 줄이고, Builder Pattern을 이용해 Entity를 생성하는 방법도 함께 익힐 수 있었다.</p>
<hr>
<h2 id="1-accountentity">1) Account(Entity)</h2>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Entity</td>
<td>데이터베이스 테이블과 직접 연결되는 객체이다.</td>
</tr>
<tr>
<td>@Builder</td>
<td>객체 생성 시 필요한 값만 선택하여 생성할 수 있도록 도와주는 Lombok 기능이다.</td>
</tr>
<tr>
<td>@Getter / @Setter</td>
<td>필드에 직접 접근하지 않고 메서드를 통해 값을 읽고 수정한다.</td>
</tr>
<tr>
<td>객체지향 캡슐화</td>
<td>데이터를 객체 내부에서 관리하도록 하여 유지보수성을 높인다.</td>
</tr>
<tr>
<td>DB와 매핑</td>
<td>Account 객체 하나가 accounts 테이블의 한 행(Row)을 의미한다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">@Getter
@Setter
@Builder
public class Account {

    private long accountId;

    private String accountName;

    private String createdDate;

}</code></pre>
<p><strong>Account Entity</strong>는 데이터베이스의 <code>accounts</code> 테이블과 대응되는 객체이다.</p>
<p>예를 들어 데이터베이스에 다음과 같은 데이터가 있다고 가정해 보자.</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>created_at</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>홍길동</td>
<td>2026-07-20</td>
</tr>
</tbody></table>
<p>Repository가 이 데이터를 조회하면 다음과 같은 객체가 만들어진다.</p>
<pre><code class="language-java">Account account = Account.builder()
        .id(1L)
        .name(&quot;홍길동&quot;)
        .createdAt(&quot;2026-07-20&quot;)
        .build();</code></pre>
<p>Entity는 <strong>데이터베이스의 실제 데이터를 표현하는 객체</strong>이기 때문에 CRUD 과정에서 가장 많이 사용된다.</p>
<p>이번 프로젝트에서는 Lombok의 <code>@Builder</code>를 활용하여 객체를 생성하였다.</p>
<p>Builder Pattern을 사용하면 생성자의 매개변수 순서를 기억할 필요가 없으며, 필요한 값만 선택적으로 설정할 수 있어 코드의 가독성이 크게 향상된다.</p>
<hr>
<h3 id="entity의-역할">Entity의 역할</h3>
<pre><code class="language-text">Database

↓

Account(Entity)

↓

Repository

↓

Service</code></pre>
<p>Entity는 항상 <strong>Repository와 Service 사이에서 실제 데이터를 전달하는 역할</strong>을 수행한다.</p>
<hr>
<h3 id="builder-pattern을-사용하는-이유">Builder Pattern을 사용하는 이유</h3>
<table>
<thead>
<tr>
<th>일반 생성자</th>
<th>Builder Pattern</th>
</tr>
</thead>
<tbody><tr>
<td>매개변수 순서를 기억해야 함</td>
<td>이름으로 값을 지정할 수 있음</td>
</tr>
<tr>
<td>가독성이 떨어질 수 있음</td>
<td>읽기 쉽고 유지보수가 편리함</td>
</tr>
<tr>
<td>일부 값만 설정하기 어려움</td>
<td>필요한 값만 설정 가능</td>
</tr>
</tbody></table>
<hr>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>Entity의 역할</td>
<td>단순한 객체가 아니라 데이터베이스의 한 행(Row)을 표현하는 중요한 객체라는 점을 이해하였다.</td>
</tr>
<tr>
<td>Builder Pattern</td>
<td>생성자보다 Builder를 사용하는 것이 훨씬 읽기 쉽고 유지보수하기 편리하다는 것을 느꼈다.</td>
</tr>
<tr>
<td>객체지향 설계</td>
<td>데이터를 객체로 관리하는 것이 코드의 안정성과 가독성을 높여준다는 점을 배웠다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-accountformdto--accountupdatedto">2) AccountFormDTO &amp; AccountUpdateDTO</h3>
<h1 id="📚-오늘-배운-내용">📚 오늘 배운 내용</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>DTO(Data Transfer Object)</td>
<td>계층 간 데이터를 전달하기 위한 객체이다.</td>
</tr>
<tr>
<td>FormDTO</td>
<td>사용자가 입력한 데이터를 저장하는 객체이다.</td>
</tr>
<tr>
<td>UpdateDTO</td>
<td>수정 시 필요한 데이터(id, 변경 값)를 전달하는 객체이다.</td>
</tr>
<tr>
<td>Record</td>
<td>DTO를 간결하게 작성하기 위해 사용하였다.</td>
</tr>
<tr>
<td>데이터 검증</td>
<td>DTO를 이용하면 입력값 검증과 데이터 전달을 분리할 수 있다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public record AccountCreateDTO(
        String accountName
) {
}</code></pre>
<pre><code class="language-java">public record AccountModifyDTO(

        long accountId,

        String accountName

) {
}</code></pre>
<p>DTO를 하나만 사용하지 않고 <strong>용도에 따라 분리</strong>하였다.</p>
<h4 id="1-계좌-생성">1. 계좌 생성</h4>
<p>계좌를 생성할 때는 사용자가 입력하는 값이 이름뿐이다.</p>
<pre><code class="language-text">이름 입력

↓

AccountFormDTO</code></pre>
<p>따라서 id는 필요하지 않다.</p>
<hr>
<h4 id="2-계좌-수정">2. 계좌 수정</h4>
<p>수정은 다르다.</p>
<p>어떤 계좌를 수정할지 알아야 하기 때문에</p>
<pre><code class="language-text">id

+

변경할 이름</code></pre>
<p>두 정보가 모두 필요하다.</p>
<p>그래서</p>
<pre><code class="language-java">AccountUpdateDTO</code></pre>
<p>를 별도로 만들었다.</p>
<p>이처럼 <strong>작업 목적에 따라 DTO를 분리하면 필요한 데이터만 전달할 수 있어 코드가 더욱 명확해진다.</strong></p>
<hr>
<h3 id="dto를-분리하는-이유">DTO를 분리하는 이유</h3>
<table>
<thead>
<tr>
<th>DTO</th>
<th>사용하는 상황</th>
</tr>
</thead>
<tbody><tr>
<td>FormDTO</td>
<td>생성(Create)</td>
</tr>
<tr>
<td>UpdateDTO</td>
<td>수정(Update)</td>
</tr>
<tr>
<td>ViewDTO</td>
<td>조회(Read)</td>
</tr>
</tbody></table>
<p>각 DTO는 <strong>하나의 목적만 수행</strong>하도록 설계하였다.</p>
<hr>
<h3 id="record를-사용하는-이유">Record를 사용하는 이유</h3>
<p>DTO는 데이터를 저장하고 전달하는 역할만 수행한다.</p>
<p>따라서</p>
<ul>
<li>Getter</li>
<li>equals()</li>
<li>hashCode()</li>
<li>toString()</li>
</ul>
<p>이 자동 생성되는 Record가 매우 적합하다.</p>
<p>코드도 훨씬 간결해진다.</p>
<p>실무에서도 요청(Request)마다 DTO를 분리하는 경우가 많다.</p>
<p>예를 들어 회원 기능이라면 다음과 같이 구성할 수 있다.</p>
<ul>
<li>MemberCreateRequest</li>
<li>MemberUpdateRequest</li>
<li>MemberLoginRequest</li>
<li>MemberResponse</li>
</ul>
<p>하나의 DTO를 재사용하기보다 <strong>기능별로 DTO를 분리하여 관리</strong>하는 것이 일반적이다.</p>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>DTO 분리</td>
<td>같은 Account라도 용도에 따라 다른 DTO를 사용하는 이유를 이해하였다.</td>
</tr>
<tr>
<td>Record 활용</td>
<td>DTO 작성 시 코드가 훨씬 간결해지고 가독성이 좋아졌다.</td>
</tr>
<tr>
<td>유지보수</td>
<td>목적에 맞게 DTO를 분리하면 변경 범위가 줄어든다는 점을 배웠다.</td>
</tr>
</tbody></table>
<hr>
<h1 id="3-accountviewdto">3) AccountViewDTO</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>ViewDTO</td>
<td>화면에 보여줄 데이터를 전달하는 DTO이다.</td>
</tr>
<tr>
<td>fromEntity()</td>
<td>Entity를 ViewDTO로 변환하는 정적 메서드이다.</td>
</tr>
<tr>
<td>Entity 보호</td>
<td>Entity를 View에 직접 노출하지 않도록 한다.</td>
</tr>
<tr>
<td>EL(Expression Language)</td>
<td>JSP에서 객체의 값을 출력하기 위한 표현식이다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public static AccountViewDTO fromEntity(Account entity){

    return new AccountViewDTO(

            entity.getId(),

            entity.getName(),

            entity.getCreatedAt()

    );

}</code></pre>
<p>Service에서는 Repository가 반환한 Entity를 그대로 JSP에 전달하지 않는다.</p>
<p>대신</p>
<pre><code class="language-text">Entity

↓

ViewDTO

↓

JSP</code></pre>
<p>순서로 변환한다.</p>
<p>이 과정을 담당하는 메서드가</p>
<pre><code class="language-java">fromEntity()</code></pre>
<p>이다.</p>
<p>즉,</p>
<pre><code class="language-java">Account</code></pre>
<p>객체를</p>
<pre><code class="language-java">AccountViewDTO</code></pre>
<p>로 변환하는 역할을 수행한다.</p>
<hr>
<h3 id="왜-entity를-직접-넘기지-않을까">왜 Entity를 직접 넘기지 않을까?</h3>
<p>예를 들어 Entity에 다음과 같은 정보가 추가되었다고 가정해 보자.</p>
<ul>
<li>password</li>
<li>주민등록번호</li>
<li>계좌 비밀번호</li>
</ul>
<p>이 객체를 그대로 View에 전달하면 민감한 정보가 노출될 수 있다.</p>
<p>하지만 ViewDTO에는 필요한 데이터(id, name, createdAt)만 담기므로 <strong>불필요한 정보의 노출을 막을 수 있다.</strong></p>
<hr>
<h3 id="entity와-dto의-관계">Entity와 DTO의 관계</h3>
<pre><code class="language-text">Database

↓

Entity

↓

Repository

↓

Service

↓

ViewDTO

↓

Controller

↓

JSP</code></pre>
<hr>
<h3 id="느낀점">느낀점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>DTO의 중요성</td>
<td>Entity와 화면을 분리하면 보안과 유지보수성이 모두 향상된다는 점을 이해하였다.</td>
</tr>
<tr>
<td>fromEntity()</td>
<td>객체를 다른 객체로 변환하는 패턴을 익힐 수 있었다.</td>
</tr>
<tr>
<td>MVC 구조</td>
<td>Controller와 View 사이에서도 DTO를 사용하는 이유를 명확하게 이해하게 되었다.</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/20 IL(I Learned) - Step 1]]></title>
            <link>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-1</link>
            <guid>https://velog.io/@baeksh_8/260720-ILI-Learned-Step-1</guid>
            <pubDate>Sun, 26 Jul 2026 11:47:51 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-mvc와-controller의-역할-이해">Spring MVC와 Controller의 역할 이해</h2>
<p>기존 JDBC 프로젝트를 Spring Framework 기반으로 발전시키면서 <strong>Spring MVC(Model-View-Controller)</strong> 구조를 직접 구현해 보았다. 이전에는 <code>main()</code> 메서드에서 직접 Repository를 호출하여 CRUD를 수행했지만, 이번 프로젝트에서는 <strong>Controller → Service → Repository</strong> 구조를 적용하여 각 계층의 역할을 분리하였다.</p>
<p>또한 JSP를 View로 사용하면서 사용자의 요청(Request)이 어떻게 Controller로 전달되고, Controller가 Service와 Repository를 거쳐 데이터를 조회한 후 다시 View로 전달하는지 전체적인 흐름을 이해할 수 있었다.</p>
<p>Spring Boot를 사용할 때는 이러한 과정이 자동으로 이루어진다고만 생각했는데, 이번 실습을 통해 MVC 패턴이 왜 필요한지, 그리고 각 계층이 어떤 역할을 수행하는지를 보다 깊게 이해할 수 있었다.</p>
<hr>
<h3 id="1-maincontroller">1) MainController</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>@Controller</td>
<td>해당 클래스를 Spring MVC의 Controller로 등록한다. 사용자의 요청을 가장 먼저 처리하는 역할을 수행한다.</td>
</tr>
<tr>
<td>@RequestMapping</td>
<td>Controller가 처리할 기본 URL을 지정한다.</td>
</tr>
<tr>
<td>@GetMapping</td>
<td>GET 요청을 처리하는 메서드를 지정한다. 주로 조회 기능에서 사용된다.</td>
</tr>
<tr>
<td>@PostMapping</td>
<td>POST 요청을 처리하는 메서드를 지정한다. 데이터 생성이나 수정 시 사용된다.</td>
</tr>
<tr>
<td>Model</td>
<td>Controller에서 View(JSP)로 데이터를 전달하는 객체이다.</td>
</tr>
<tr>
<td>Redirect</td>
<td>작업 완료 후 새로운 요청을 발생시키기 위해 사용하는 방식이다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">@Controller
@RequestMapping(&quot;/&quot;)
@RequiredArgsConstructor
public class MainController {

    private final BankService bankService;

    @GetMapping(&quot;/&quot;)
    public String index(Model model){

        model.addAttribute(
                &quot;accounts&quot;,
                bankService.getAccounts()
        );

        return &quot;index&quot;;

    }

}</code></pre>
<p>이번 실습에서는 사용자가 브라우저에서</p>
<pre><code>http://localhost:8080/</code></pre><p>를 요청하면</p>
<p>가장 먼저 DispatcherServlet이 요청을 받는다.</p>
<p>DispatcherServlet은</p>
<pre><code>&quot;/&quot;</code></pre><p>URL을 처리할 Controller를 찾는다.</p>
<p>그 결과</p>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)</code></pre>
<p>이 실행된다.</p>
<hr>
<p>Controller에서는</p>
<pre><code class="language-java">bankService.getAccounts();</code></pre>
<p>를 호출하여</p>
<p>Service에게</p>
<blockquote>
<p>&quot;전체 계좌 목록을 가져와.&quot;</p>
</blockquote>
<p>라고 요청한다.</p>
<p>Controller는</p>
<p>SQL을 작성하지 않는다.</p>
<p>DB와 직접 연결하지도 않는다.</p>
<p>오직</p>
<p>사용자의 요청을 받고</p>
<p>Service를 호출한 뒤</p>
<p>결과를 View로 전달하는 역할만 수행한다.</p>
<hr>
<p>조회된 데이터는</p>
<pre><code class="language-java">model.addAttribute()</code></pre>
<p>를 이용하여</p>
<p>JSP에게 전달된다.</p>
<pre><code class="language-java">model.addAttribute(
        &quot;accounts&quot;,
        bankService.getAccounts()
);</code></pre>
<p>Model 객체는</p>
<p>일종의</p>
<pre><code>Map&lt;String,Object&gt;</code></pre><p>처럼 동작한다.</p>
<p>즉,</p>
<pre><code>accounts</code></pre><p>라는 이름으로</p>
<p>JSP에게 데이터를 전달하는 것이다.</p>
<p>JSP에서는</p>
<pre><code class="language-jsp">${accounts}</code></pre>
<p>를 이용하여 사용할 수 있다.</p>
<hr>
<p>마지막으로</p>
<pre><code class="language-java">return &quot;index&quot;;</code></pre>
<p>를 반환하면</p>
<p>ViewResolver가</p>
<pre><code>/WEB-INF/views/index.jsp</code></pre><p>를 찾아 사용자에게 화면을 보여준다.</p>
<hr>
<h3 id="spring-mvc-요청-흐름">Spring MVC 요청 흐름</h3>
<pre><code class="language-text">브라우저

↓

DispatcherServlet

↓

MainController

↓

BankService

↓

Repository

↓

Database

↓

Repository

↓

Service

↓

Controller

↓

Model

↓

index.jsp

↓

브라우저</code></pre>
<p>이번 실습을 통해</p>
<p>Spring MVC는</p>
<p>각 객체가 자신의 역할만 수행하도록 설계되어 있다는 점을 이해하였다.</p>
<hr>
<h3 id="post-요청">POST 요청</h3>
<pre><code class="language-java">@PostMapping(&quot;/&quot;)</code></pre>
<p>에서는</p>
<p>새로운 계좌를 생성한다.</p>
<pre><code class="language-java">bankService.makeAccount(dto);</code></pre>
<p>가 호출되면</p>
<p>Service가 Repository에게</p>
<p>INSERT를 요청한다.</p>
<p>그리고</p>
<pre><code class="language-java">return &quot;redirect:/&quot;;</code></pre>
<p>를 수행한다.</p>
<hr>
<h3 id="redirect를-사용하는-이유">Redirect를 사용하는 이유</h3>
<p>처음에는</p>
<p>왜</p>
<pre><code>return &quot;index&quot;;</code></pre><p>를 하지 않고</p>
<pre><code>redirect:/</code></pre><p>를 사용하는지 이해되지 않았다.</p>
<p>하지만</p>
<p>POST 요청 후</p>
<p>바로 JSP를 반환하면</p>
<p>새로고침(F5)을 했을 때</p>
<p>POST 요청이 다시 실행된다.</p>
<p>즉,</p>
<p>계좌가</p>
<p>두 번 생성될 수 있다.</p>
<p>이를</p>
<p><strong>중복 제출(Double Submit)</strong></p>
<p>문제라고 한다.</p>
<p>그래서</p>
<p>POST가 끝나면</p>
<p>다시</p>
<p>GET 요청을 보내도록</p>
<p>Redirect를 사용하는 것이다.</p>
<p>이를</p>
<p><strong>PRG(Post Redirect Get) 패턴</strong></p>
<p>이라고 한다.</p>
<hr>
<table>
<thead>
<tr>
<th>기능</th>
<th>실무 사용</th>
</tr>
</thead>
<tbody><tr>
<td>Controller</td>
<td>모든 요청의 시작점</td>
</tr>
<tr>
<td>Model</td>
<td>View 데이터 전달</td>
</tr>
<tr>
<td>Redirect</td>
<td>등록, 수정, 삭제 후 반드시 사용</td>
</tr>
<tr>
<td>ViewResolver</td>
<td>Thymeleaf, JSP 등을 찾아준다.</td>
</tr>
<tr>
<td>DispatcherServlet</td>
<td>모든 요청을 관리하는 Front Controller</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-accountcontroller">2) AccountController</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>@PathVariable</td>
<td>URL에 포함된 값을 변수로 받아오는 기능이다.</td>
</tr>
<tr>
<td>@ModelAttribute</td>
<td>HTML Form의 데이터를 객체(DTO)로 자동 변환해준다.</td>
</tr>
<tr>
<td>계좌 수정</td>
<td>수정할 계좌를 조회하고 변경 내용을 저장하였다.</td>
</tr>
<tr>
<td>계좌 삭제</td>
<td>URL을 통해 전달된 id를 이용하여 데이터를 삭제하였다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">@GetMapping(&quot;/{accountId}&quot;)
public String showAccount(
        @PathVariable long accountId,
        Model model
){

    AccountViewDTO accountDto =
            bankService.findAccount(accountId);

    model.addAttribute(
            &quot;account&quot;,
            accountDto
    );

    return &quot;account&quot;;

}</code></pre>
<p>이번 Controller에서는</p>
<p>URL에 포함된 값을</p>
<p>직접 변수로 받아왔다.</p>
<p>예를 들어</p>
<pre><code>/account/3</code></pre><p>이라는 요청이 들어오면</p>
<p>Spring은</p>
<pre><code class="language-java">@PathVariable long accountId</code></pre>
<p>에</p>
<p>자동으로</p>
<pre><code>3</code></pre><p>을 넣어준다.</p>
<p>즉,</p>
<p>개발자가</p>
<p>직접</p>
<pre><code class="language-java">request.getParameter()</code></pre>
<p>를 사용할 필요가 없다.</p>
<p>Spring이 자동으로 처리해준다.</p>
<hr>
<p>수정 기능에서는</p>
<pre><code class="language-java">@ModelAttribute</code></pre>
<p>를 사용하였다.</p>
<pre><code class="language-java">@PostMapping(&quot;/{id}&quot;)</code></pre>
<p>↓</p>
<p>사용자가 입력한</p>
<pre><code>name=홍길동</code></pre><p>↓</p>
<p>Spring</p>
<p>↓</p>
<pre><code class="language-java">AccountFormDTO</code></pre>
<p>자동 생성</p>
<p>↓</p>
<p>Service 호출</p>
<p>이 과정이 자동으로 이루어진다.</p>
<hr>
<p>삭제 기능 역시</p>
<pre><code class="language-java">@GetMapping(&quot;/{id}/delete&quot;)</code></pre>
<p>를 이용하여</p>
<p>Repository에게</p>
<p>삭제를 요청한다.</p>
<p>Controller는</p>
<p>삭제 SQL을 작성하지 않는다.</p>
<p>Service에게</p>
<p>삭제를 요청할 뿐이다.</p>
<hr>
<h3 id="account-수정-흐름">Account 수정 흐름</h3>
<pre><code class="language-text">사용자

↓

account.jsp

↓

POST /account/3

↓

AccountController

↓

BankService

↓

Repository

↓

UPDATE SQL

↓

DB

↓

Redirect

↓

GET /account/3</code></pre>
<hr>
<h3 id="spring-mvc가-편리한-이유">Spring MVC가 편리한 이유</h3>
<p>예전 JDBC에서는</p>
<pre><code class="language-java">Scanner scanner</code></pre>
<p>로 데이터를 입력받았다.</p>
<p>하지만</p>
<p>Spring에서는</p>
<p>HTML Form만 작성하면</p>
<p>자동으로</p>
<p>DTO 객체가 생성된다.</p>
<p>즉,</p>
<p>사용자 입력을</p>
<p>직접 파싱할 필요가 없다.</p>
<hr>
<table>
<thead>
<tr>
<th>기능</th>
<th>사용 이유</th>
</tr>
</thead>
<tbody><tr>
<td>PathVariable</td>
<td>상세조회</td>
</tr>
<tr>
<td>ModelAttribute</td>
<td>Form 처리</td>
</tr>
<tr>
<td>DTO</td>
<td>사용자 입력 검증</td>
</tr>
<tr>
<td>Redirect</td>
<td>중복 제출 방지</td>
</tr>
</tbody></table>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>Spring 자동 바인딩</td>
<td><code>@ModelAttribute</code>를 통해 Form 데이터가 DTO로 자동 변환되는 과정이 매우 편리하다고 느꼈다.</td>
</tr>
<tr>
<td>URL 설계</td>
<td><code>@PathVariable</code>을 사용하여 URL만으로 필요한 데이터를 전달받는 REST 방식의 장점을 이해하였다.</td>
</tr>
<tr>
<td>MVC 역할 분리</td>
<td>Controller는 요청을 연결하는 역할만 수행하고 실제 비즈니스 로직은 Service가 담당한다는 점이 더욱 명확해졌다.</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/16 IL(I Learned) - Step 4]]></title>
            <link>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-4</link>
            <guid>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-4</guid>
            <pubDate>Sun, 26 Jul 2026 11:10:36 GMT</pubDate>
            <description><![CDATA[<h2 id="jdbc-crud-실습을-통한-데이터-관리-과정과-최종-회고">JDBC CRUD 실습을 통한 데이터 관리 과정과 최종 회고</h2>
<p>앞에서 학습한 Driver Loading, Connection 생성, SQL 실행, Repository 패턴을 하나로 연결하여 <strong>CRUD(Create, Read, Update, Delete)</strong> 기능을 직접 수행하였다. 단순히 SQL을 실행하는 것이 아니라 사용자의 입력을 받아 데이터를 저장하고, 조회한 데이터를 수정한 뒤 삭제하는 일련의 과정을 경험하면서 JDBC가 실제 애플리케이션에서 어떻게 사용되는지 이해할 수 있었다.</p>
<p> <strong>데이터베이스와 객체를 연결하는 전체 흐름</strong>을 처음부터 끝까지 직접 구현해 보면서 Spring Boot와 JPA가 내부적으로 수행하는 역할을 더욱 명확하게 이해할 수 있었다.</p>
<hr>
<h3 id="1-mycrud">1) MyCRUD</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>CRUD 실습</td>
<td>Create, Read, Update, Delete를 하나의 프로그램에서 모두 수행하였다.</td>
</tr>
<tr>
<td>Repository 활용</td>
<td>SQL을 직접 작성하지 않고 Repository를 호출하여 데이터를 관리하였다.</td>
</tr>
<tr>
<td>Scanner</td>
<td>사용자 입력을 받아 데이터베이스에 저장하였다.</td>
</tr>
<tr>
<td>Record 활용</td>
<td>조회한 데이터를 객체로 저장하여 수정 작업에 활용하였다.</td>
</tr>
<tr>
<td>전체 JDBC 흐름</td>
<td>JDBC가 실제 서비스에서 어떻게 동작하는지 하나의 프로그램으로 확인하였다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class CrudExample {

    public static void main(String[] args) {

        BanchanRepository repository = new BanchanRepository();

        Scanner inputScanner = new Scanner(System.in);

        System.out.print(&quot;추가할 반찬 이름 : &quot;);

        String menuName = inputScanner.nextLine();

        repository.save(new SideDish(0, menuName));

        List&lt;SideDish&gt; dishes = repository.findAll();

        System.out.println(dishes);

    }

}</code></pre>
<p>이번 프로그램은 CRUD를 하나의 흐름으로 연결하였다.</p>
<p>가장 먼저 Repository 객체를 생성하였다.</p>
<pre><code class="language-java">BanchanRepository repository =
        new BanchanRepository();</code></pre>
<p>Repository는</p>
<ul>
<li>INSERT</li>
<li>SELECT</li>
<li>UPDATE</li>
<li>DELETE</li>
</ul>
<p>모든 SQL을 담당한다.</p>
<p>따라서 Main에서는 SQL을 작성하지 않는다.</p>
<p>객체지향적으로 역할을 분리한 것이다.</p>
<hr>
<p>다음으로 Scanner를 이용하여</p>
<p>사용자의 입력을 받았다.</p>
<pre><code class="language-java">Scanner inputScanner =
        new Scanner(System.in);</code></pre>
<p>입력받은 문자열은</p>
<pre><code class="language-java">repository.save(...)</code></pre>
<p>를 호출하면서 데이터베이스에 저장된다.</p>
<p>Main 클래스는</p>
<p>저장 방법을 모른다.</p>
<p>단지 Repository에게</p>
<p>&quot;저장해줘&quot;</p>
<p>라고 요청만 한다.</p>
<p>이것이 Repository 패턴의 가장 큰 장점이라는 것을 이해하였다.</p>
<hr>
<h3 id="crud-실행-과정">CRUD 실행 과정</h3>
<pre><code class="language-text">사용자 입력

↓

Scanner

↓

Repository.save()

↓

PreparedStatement

↓

INSERT SQL 실행

↓

Database 저장</code></pre>
<p>프로그램은 이 순서대로 동작한다.</p>
<hr>
<h3 id="조회read">조회(Read)</h3>
<p>데이터 저장 후</p>
<pre><code class="language-java">List&lt;SideDish&gt; dishes =
        repository.findAll();</code></pre>
<p>을 호출하였다.</p>
<p>Repository는</p>
<p>SELECT 문을 실행하고</p>
<p>ResultSet을 읽은 뒤</p>
<p>Record 객체를 생성하여</p>
<p>List로 반환한다.</p>
<p>Main에서는</p>
<p>SQL이 어떻게 실행되는지 전혀 알 필요가 없다.</p>
<hr>
<h3 id="수정update">수정(Update)</h3>
<p>수정 과정에서는</p>
<p>먼저</p>
<pre><code class="language-java">repository.findById(...)</code></pre>
<p>를 호출하였다.</p>
<p>조회한 데이터를 이용하여</p>
<p>새로운 Record를 생성하였다.</p>
<pre><code class="language-java">SideDish updatedDish =
        new SideDish(

                oldDish.id(),

                oldDish.name() + &quot;(수정)&quot;

        );</code></pre>
<p>그 이후</p>
<pre><code class="language-java">repository.update(updatedDish);</code></pre>
<p>를 호출하여</p>
<p>데이터를 수정하였다.</p>
<hr>
<h3 id="삭제delete">삭제(Delete)</h3>
<p>삭제는</p>
<pre><code class="language-java">repository.deleteById(id);</code></pre>
<p>한 줄로 수행하였다.</p>
<p>Repository 내부에서는</p>
<p>DELETE SQL이 실행된다.</p>
<p>삭제가 성공하면</p>
<p>executeUpdate()</p>
<p>반환값이</p>
<p>1이 된다.</p>
<p>실패하면</p>
<p>0이 된다.</p>
<p>이를 이용하여</p>
<p>삭제 성공 여부를 확인할 수 있다는 점을 배웠다.</p>
<hr>
<h3 id="crud-전체-흐름">CRUD 전체 흐름</h3>
<pre><code class="language-text">사용자 입력

↓

Main

↓

Repository

↓

DBUtil

↓

Connection

↓

PreparedStatement

↓

Database

↓

ResultSet

↓

Repository

↓

Main

↓

사용자 출력</code></pre>
<p>이번 실습을 통해</p>
<p>JDBC는 단순히 SQL을 실행하는 기술이 아니라</p>
<p>각 객체들이 서로 역할을 분담하며 하나의 흐름으로 동작한다는 점을 이해하였다.</p>
<hr>
<h3 id="객체지향-관점에서-본-구조">객체지향 관점에서 본 구조</h3>
<table>
<thead>
<tr>
<th>클래스</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Main</td>
<td>사용자 요청 처리</td>
</tr>
<tr>
<td>Repository</td>
<td>데이터 접근 담당</td>
</tr>
<tr>
<td>DBUtil</td>
<td>DB 연결 담당</td>
</tr>
<tr>
<td>Record(Banchan)</td>
<td>데이터 저장</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>실제 데이터 저장</td>
</tr>
</tbody></table>
<p>객체마다 역할을 분리하였기 때문에</p>
<p>유지보수가 쉬워지는 구조라는 것을 느꼈다.</p>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>CRUD 전체 흐름</td>
<td>저장, 조회, 수정, 삭제가 하나의 흐름으로 연결된다는 점을 이해하였다.</td>
</tr>
<tr>
<td>객체지향</td>
<td>각 클래스가 자신의 역할만 수행하도록 설계하는 이유를 알게 되었다.</td>
</tr>
<tr>
<td>Repository 패턴</td>
<td>SQL을 분리하는 이유를 직접 경험하였다.</td>
</tr>
<tr>
<td>Spring 준비</td>
<td>앞으로 Spring Data JPA를 배우더라도 내부 동작을 이해하는 데 큰 도움이 될 것 같다.</td>
</tr>
</tbody></table>
<hr>
<p>여러 실습을 반복하면서 <strong>JDBC의 전체 흐름을 하나의 그림으로 정리</strong>하니 각 객체의 역할이 명확하게 구분되었다.</p>
<pre><code class="language-text">Driver Loading

↓

Connection 생성

↓

PreparedStatement 생성

↓

SQL 실행

↓

ResultSet 반환

↓

객체 생성

↓

Repository 반환

↓

Main 출력</code></pre>
<p>이 흐름을 기준으로 코드를 다시 읽어보니, 이전에는 각각 독립적으로 보였던 클래스들이 하나의 과정 안에서 서로 협력하고 있다는 점을 이해할 수 있었다.</p>
<p>또한 <code>PreparedStatement</code>는 SQL과 데이터를 분리하여 SQL Injection을 방지하고, <code>Repository</code>는 데이터 접근을 담당하며, <code>DBUtil</code>은 연결을 생성하는 역할만 수행하는 등 <strong>각 클래스가 하나의 책임만 가지도록 설계</strong>되어 있다는 사실도 배울 수 있었다.</p>
<h1 id="📌-최종-정리">📌 최종 정리</h1>
<p>이번 JDBC 학습은 단순히 SQL을 실행하는 방법을 배우는 것이 아니라, <strong>자바 애플리케이션이 데이터베이스와 어떻게 통신하는지에 대한 전체 흐름을 이해하는 과정</strong>이었다. Driver를 로딩하여 연결을 준비하고, <code>DBUtil</code>을 통해 <code>Connection</code>을 생성하며, <code>PreparedStatement</code>로 안전하게 SQL을 실행하고, <code>ResultSet</code>을 객체로 변환하여 <code>Repository</code>를 통해 반환하는 일련의 과정을 직접 구현하면서 데이터 접근 계층의 역할을 명확하게 이해할 수 있었다.</p>
<p>또한 CRUD 기능을 하나씩 구현하며 객체지향 설계와 Repository 패턴의 장점을 체감하였고, 이러한 구조가 이후 학습할 <strong>Spring Boot, MyBatis, Spring Data JPA</strong>에서도 동일한 개념으로 확장된다는 점을 알게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/16 IL(I Learned) - Step 3]]></title>
            <link>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-3</link>
            <guid>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-3</guid>
            <pubDate>Sun, 26 Jul 2026 10:56:35 GMT</pubDate>
            <description><![CDATA[<h2 id="repository-패턴과-crud-구현을-통한-데이터-관리">Repository 패턴과 CRUD 구현을 통한 데이터 관리</h2>
<p>단순히 SQL을 실행하는 단계에서 더 나아가 <strong>객체를 데이터베이스에 저장하고 관리하는 Repository 패턴</strong>을 구현해 보았다. 이전까지는 SELECT 문을 실행하여 데이터를 조회하는 것에 집중했다면, 이번에는 Create, Read, Update, Delete(CRUD)를 하나의 Repository 클래스에 모아 관리하면서 객체지향적인 데이터 접근 방식을 경험하였다.</p>
<p>또한 <code>record</code>를 이용하여 데이터를 표현하고, <code>DBUtil</code> 클래스로 데이터베이스 연결을 공통화하면서 유지보수성과 재사용성을 높이는 방법도 함께 학습하였다.</p>
<hr>
<h3 id="1-객체-생성-record">1) 객체 생성 (Record)</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Record</td>
<td>Java에서 데이터를 저장하기 위한 불변(Immutable) 객체를 간단하게 생성할 수 있는 문법이다.</td>
</tr>
<tr>
<td>Immutable Object</td>
<td>생성 이후 내부 데이터를 변경할 수 없는 객체이다.</td>
</tr>
<tr>
<td>DTO(Data Transfer Object)</td>
<td>계층 간 데이터를 전달하기 위한 객체로 많이 사용된다.</td>
</tr>
<tr>
<td>자동 생성 기능</td>
<td>Getter, equals(), hashCode(), toString() 등이 자동으로 생성된다.</td>
</tr>
<tr>
<td>코드 간결화</td>
<td>기존 클래스보다 훨씬 적은 코드로 동일한 기능을 구현할 수 있다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public record SideDish(
        long sideDishId,
        String sideDishName
) {
}</code></pre>
<p>이번 실습에서는 Java의 <strong>Record</strong>를 이용하여 반찬 정보를 저장하는 객체를 생성하였다.</p>
<p>기존 클래스를 작성하려면 다음과 같은 코드가 필요하다.</p>
<ul>
<li>필드 선언</li>
<li>생성자</li>
<li>Getter</li>
<li>equals()</li>
<li>hashCode()</li>
<li>toString()</li>
</ul>
<p>하지만 Record는</p>
<pre><code class="language-java">public record SideDish(
        long sideDishId,
        String sideDishName
){}</code></pre>
<p>한 줄만 작성하면 위 기능들이 자동으로 생성된다.</p>
<p>또한 Record는 생성 이후 값을 변경할 수 없기 때문에 데이터가 의도치 않게 수정되는 것을 방지할 수 있다.</p>
<p>특히 데이터베이스에서 조회한 데이터를 표현하는 객체는 대부분 값을 변경할 필요가 없기 때문에 Record가 매우 적합하다는 점을 알게 되었다.</p>
<hr>
<h3 id="기존-클래스와-record-비교">기존 클래스와 Record 비교</h3>
<table>
<thead>
<tr>
<th>기존 클래스</th>
<th>Record</th>
</tr>
</thead>
<tbody><tr>
<td>Getter 작성 필요</td>
<td>자동 생성</td>
</tr>
<tr>
<td>생성자 작성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>equals 작성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>hashCode 작성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>toString 작성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>코드 김</td>
<td>매우 짧음</td>
</tr>
</tbody></table>
<hr>
<h3 id="왜-record를-사용할까">왜 Record를 사용할까?</h3>
<p>데이터를 저장하기 위한 객체는</p>
<ul>
<li>데이터를 계산하는 기능보다</li>
<li>데이터를 전달하는 역할이 훨씬 많다.</li>
</ul>
<p>따라서</p>
<blockquote>
<p>&quot;데이터만 표현하는 객체&quot;</p>
</blockquote>
<p>에는 Record가 가장 적합하다는 것을 배웠다.</p>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>코드 간결성</td>
<td>Record 하나만으로 수십 줄의 코드를 줄일 수 있다는 점이 인상적이었다.</td>
</tr>
<tr>
<td>불변 객체</td>
<td>데이터를 안전하게 관리하는 방법을 이해하였다.</td>
</tr>
<tr>
<td>DTO 활용</td>
<td>앞으로 API 응답 객체를 만들 때 Record를 적극 활용할 수 있을 것 같다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-dbutil">2) DBUtil</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Utility Class</td>
<td>공통 기능을 제공하는 클래스이다.</td>
</tr>
<tr>
<td>static 메서드</td>
<td>객체 생성 없이 사용할 수 있는 메서드이다.</td>
</tr>
<tr>
<td>환경 변수</td>
<td>URL, USER, PASSWORD를 안전하게 관리한다.</td>
</tr>
<tr>
<td>DriverManager</td>
<td>Connection 객체를 생성한다.</td>
</tr>
<tr>
<td>코드 재사용</td>
<td>여러 클래스에서 동일한 Connection 생성 코드를 공유한다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class DBConnectionUtil {

    private DBConnectionUtil(){}

    public static Connection openConnection()
            throws SQLException{

        Class.forName(&quot;org.postgresql.Driver&quot;);

        return DriverManager.getConnection(
                URL,
                USER,
                PASSWORD
        );

    }

}</code></pre>
<p>DB 연결은 모든 Repository에서 공통적으로 수행된다.</p>
<p>만약 DB 연결 코드를</p>
<ul>
<li>회원 Repository</li>
<li>게시판 Repository</li>
<li>주문 Repository</li>
</ul>
<p>마다 작성한다면</p>
<p>동일한 코드가 수십 번 반복된다.</p>
<p>그래서</p>
<pre><code class="language-text">DBUtil</code></pre>
<p>클래스를 만들어</p>
<p>Connection 생성 기능을 하나로 모아두었다.</p>
<p>이후에는</p>
<pre><code class="language-java">Connection conn = DBUtil.getConnection();</code></pre>
<p>한 줄만 작성하면 된다.</p>
<p>이러한 구조는</p>
<p>유지보수성과 재사용성을 크게 높여준다.</p>
<hr>
<h3 id="utility-class의-장점">Utility Class의 장점</h3>
<table>
<thead>
<tr>
<th>장점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>코드 중복 제거</td>
<td>Connection 생성 코드를 한 번만 작성</td>
</tr>
<tr>
<td>유지보수</td>
<td>URL 변경 시 한 곳만 수정</td>
</tr>
<tr>
<td>보안</td>
<td>환경 변수 사용</td>
</tr>
<tr>
<td>재사용</td>
<td>모든 Repository에서 사용 가능</td>
</tr>
</tbody></table>
<hr>
<h3 id="환경-변수를-사용하는-이유">환경 변수를 사용하는 이유</h3>
<p>실습에서는</p>
<pre><code class="language-java">System.getenv()</code></pre>
<p>를 사용하였다.</p>
<p>그 이유는</p>
<p>DB 비밀번호를</p>
<pre><code class="language-java">String PASSWORD=&quot;1234&quot;;</code></pre>
<p>처럼 코드에 작성하면</p>
<p>GitHub에 업로드될 위험이 있기 때문이다.</p>
<p>환경 변수는 운영체제에 저장되므로</p>
<p>소스 코드가 공개되어도 비밀번호는 노출되지 않는다.</p>
<p>실무에서는</p>
<ul>
<li>Spring Boot</li>
<li>application.yml</li>
<li>DataSource</li>
<li>HikariCP</li>
</ul>
<p>를 이용하여 Connection을 관리하지만</p>
<p>결국 내부적으로는</p>
<p>DriverManager를 이용한 Connection 생성 과정을 거친다.</p>
<p>이번 실습은 그 원리를 직접 구현해본 경험이었다.</p>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>코드 재사용</td>
<td>공통 기능을 Utility 클래스로 만드는 이유를 이해하였다.</td>
</tr>
<tr>
<td>보안</td>
<td>환경 변수 관리의 중요성을 알게 되었다.</td>
</tr>
<tr>
<td>Spring 이해</td>
<td>Spring이 내부적으로 하는 일을 직접 구현해보니 프레임워크의 역할이 더욱 명확하게 느껴졌다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-banchanrepository">3) BanchanRepository</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Repository Pattern</td>
<td>데이터 접근을 담당하는 클래스를 별도로 분리하는 설계 방식이다.</td>
</tr>
<tr>
<td>CRUD</td>
<td>Create, Read, Update, Delete 기능을 하나의 Repository에서 관리한다.</td>
</tr>
<tr>
<td>PreparedStatement</td>
<td>모든 SQL 실행에 사용하여 보안성을 높였다.</td>
</tr>
<tr>
<td>Transaction</td>
<td>여러 작업을 하나의 작업 단위로 처리하였다.</td>
</tr>
<tr>
<td>ResultSet</td>
<td>조회 결과를 Record 객체로 변환하였다.</td>
</tr>
</tbody></table>
<h3 id="repository-패턴이란">Repository 패턴이란?</h3>
<p>Repository는</p>
<blockquote>
<p><strong>데이터베이스와 직접 통신하는 역할만 담당하는 클래스</strong></p>
</blockquote>
<p>이다.</p>
<p>즉,</p>
<p>Controller가 SQL을 직접 작성하지 않는다.</p>
<p>Service도 SQL을 작성하지 않는다.</p>
<p>오직 Repository만 SQL을 작성한다.</p>
<h3 id="repository-구조">Repository 구조</h3>
<pre><code class="language-text">Controller

      │

      ▼

Service

      │

      ▼

Repository

      │

      ▼

Database</code></pre>
<p>이 구조를 사용하면</p>
<p>각 클래스의 역할이 명확하게 분리된다.</p>
<h3 id="crud-구현">CRUD 구현</h3>
<p>이번 Repository에서는</p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td>save()</td>
<td>데이터 저장(Create)</td>
</tr>
<tr>
<td>findById()</td>
<td>단건 조회(Read)</td>
</tr>
<tr>
<td>findAll()</td>
<td>전체 조회(Read)</td>
</tr>
<tr>
<td>update()</td>
<td>수정(Update)</td>
</tr>
<tr>
<td>deleteById()</td>
<td>삭제(Delete)</td>
</tr>
</tbody></table>
<p>를 모두 구현하였다. </p>
<hr>
<h3 id="save-구현-과정">save() 구현 과정</h3>
<pre><code class="language-text">객체 생성

↓

Connection 생성

↓

PreparedStatement 생성

↓

executeUpdate()

↓

COMMIT</code></pre>
<p>데이터 저장은</p>
<p>SELECT가 아니라</p>
<p>INSERT 문을 실행하므로</p>
<p>executeUpdate()를 사용하였다.</p>
<hr>
<h3 id="update">update()</h3>
<p>수정은</p>
<p>ID를 기준으로 수행하였다.</p>
<pre><code class="language-sql">UPDATE banchan

SET name=?

WHERE id=?</code></pre>
<p>ID가 동일한 데이터를 찾아</p>
<p>새로운 이름으로 변경하였다.</p>
<p>이 과정에서</p>
<p>Primary Key의 중요성을 이해할 수 있었다.</p>
<hr>
<h3 id="delete">delete()</h3>
<p>삭제 역시</p>
<p>ID를 이용하였다.</p>
<p>삭제 성공 여부는</p>
<pre><code class="language-java">executeUpdate()</code></pre>
<p>가 반환하는</p>
<p>영향 받은 행(Row)의 개수로 확인하였다.</p>
<p>0이면</p>
<p>삭제 실패</p>
<p>1이면</p>
<p>삭제 성공이다.</p>
<hr>
<h3 id="transaction을-사용하는-이유">Transaction을 사용하는 이유</h3>
<p>이번 실습에서는</p>
<pre><code class="language-sql">START TRANSACTION</code></pre>
<p>↓</p>
<p>SQL 실행</p>
<p>↓</p>
<pre><code class="language-sql">COMMIT</code></pre>
<p>순서로 작성하였다.</p>
<p>트랜잭션은</p>
<p>여러 작업을 하나의 작업처럼 처리하기 위한 기능이다.</p>
<p>만약 중간에 오류가 발생하면</p>
<p>ROLLBACK을 수행하여</p>
<p>원래 상태로 되돌릴 수 있다.</p>
<hr>
<h3 id="crud-전체-흐름">CRUD 전체 흐름</h3>
<pre><code class="language-text">사용자 요청

↓

Repository 호출

↓

Connection 생성

↓

PreparedStatement 생성

↓

SQL 실행

↓

ResultSet 또는 실행 결과 반환

↓

Connection 종료</code></pre>
<p>이 흐름이</p>
<p>JDBC CRUD의 핵심이라는 것을 이해하였다.</p>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>객체지향 설계</td>
<td>SQL을 Repository로 분리하는 이유를 이해하였다.</td>
</tr>
<tr>
<td>CRUD 구현</td>
<td>데이터 저장부터 삭제까지 하나의 클래스로 관리하는 구조를 익혔다.</td>
</tr>
<tr>
<td>트랜잭션</td>
<td>여러 작업을 하나로 묶는 이유를 이해하였다.</td>
</tr>
<tr>
<td>실무 연결</td>
<td>Spring Data JPA의 JpaRepository가 이번에 구현한 Repository와 같은 역할이라는 점을 알게 되었다.</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/16 IL(I Learned) - Step 2]]></title>
            <link>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-2</link>
            <guid>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-2</guid>
            <pubDate>Sun, 26 Jul 2026 10:29:34 GMT</pubDate>
            <description><![CDATA[<h2 id="statement-preparedstatement-resultset를-통한-안전한-데이터-조회">Statement, PreparedStatement, ResultSet를 통한 안전한 데이터 조회</h2>
<p>데이터베이스에 연결한 이후 실제 SQL을 실행하는 방법을 학습하였다. 단순히 SQL을 실행하는 것에서 끝나는 것이 아니라 <code>Statement</code>와 <code>PreparedStatement</code>의 차이점, SQL Injection이 발생하는 이유, 그리고 조회 결과를 <code>ResultSet</code>으로 받아 객체로 변환하는 과정까지 직접 구현해보면서 JDBC의 핵심 기능을 이해할 수 있었다.</p>
<hr>
<h3 id="1-usestatement">1) UseStatement</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Statement</td>
<td>SQL 문자열을 그대로 데이터베이스에 전달하여 실행하는 객체이다.</td>
</tr>
<tr>
<td>executeQuery()</td>
<td>SELECT 문을 실행하고 ResultSet을 반환하는 메서드이다.</td>
</tr>
<tr>
<td>SQL 문자열 조합</td>
<td>문자열을 이어 붙여 SQL을 만드는 방식은 매우 위험할 수 있다.</td>
</tr>
<tr>
<td>SQL Injection</td>
<td>사용자의 입력이 SQL 코드의 일부로 실행되는 보안 취약점이다.</td>
</tr>
<tr>
<td>ResultSet</td>
<td>SELECT 결과를 한 행씩 가져오는 객체이다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class StatementExample {

    public static void main(String[] args) {

        String inputId = &quot;1234&#39; OR &#39;1&#39;=&#39;1&quot;;

        try (Connection connection = DBUtil.getConnection()) {

            String sql =
                    &quot;SELECT * FROM users WHERE id = &#39;%s&#39;&quot;
                            .formatted(inputId);

            Statement statement = connection.createStatement();

            ResultSet resultSet = statement.executeQuery(sql);

            while (resultSet.next()) {

                System.out.println(resultSet.getString(&quot;id&quot;));
                System.out.println(resultSet.getString(&quot;name&quot;));
                System.out.println(resultSet.getInt(&quot;age&quot;));

            }

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

}</code></pre>
<p>가장 먼저 사용자의 입력을 SQL 문자열 안에 직접 포함하였다.</p>
<pre><code class="language-java">String sql =
        &quot;SELECT * FROM users WHERE id = &#39;%s&#39;&quot;
                .formatted(inputId);</code></pre>
<p>만약 사용자가</p>
<pre><code class="language-text">admin</code></pre>
<p>을 입력하면 SQL은</p>
<pre><code class="language-sql">SELECT * FROM users
WHERE id=&#39;admin&#39;</code></pre>
<p>이 된다.</p>
<p>하지만 실습에서는</p>
<pre><code class="language-text">1234&#39; OR &#39;1&#39;=&#39;1</code></pre>
<p>을 입력하였다.</p>
<p>그러면 실제 실행되는 SQL은</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE id=&#39;1234&#39;
OR &#39;1&#39;=&#39;1&#39;</code></pre>
<p>가 된다.</p>
<p>여기서</p>
<pre><code class="language-sql">&#39;1&#39;=&#39;1&#39;</code></pre>
<p>은 항상 참(True)이므로 WHERE 조건이 모두 참이 되어 모든 사용자가 조회된다.</p>
<p>즉,</p>
<p><strong>사용자의 입력이 데이터가 아니라 SQL 명령으로 실행되어 버린 것이다.</strong></p>
<p>이것이 SQL Injection이다.</p>
<hr>
<h3 id="sql-injection이-위험한-이유">SQL Injection이 위험한 이유</h3>
<table>
<thead>
<tr>
<th>공격 예시</th>
<th>발생 결과</th>
</tr>
</thead>
<tbody><tr>
<td><code>OR &#39;1&#39;=&#39;1&#39;</code></td>
<td>모든 데이터 조회</td>
</tr>
<tr>
<td><code>DELETE FROM users</code></td>
<td>데이터 삭제 가능</td>
</tr>
<tr>
<td><code>UNION SELECT ...</code></td>
<td>다른 테이블 정보 조회 가능</td>
</tr>
<tr>
<td>관리자 로그인 우회</td>
<td>인증 우회 가능</td>
</tr>
</tbody></table>
<hr>
<h3 id="statement의-동작-과정">Statement의 동작 과정</h3>
<pre><code class="language-text">사용자 입력
      │
      ▼
SQL 문자열 생성
      │
      ▼
Statement
      │
      ▼
DB에서 SQL 그대로 실행
      │
      ▼
ResultSet 반환</code></pre>
<p>Statement는 <strong>사용자가 입력한 문자열을 그대로 SQL로 해석</strong>하기 때문에 보안상 매우 위험하다.</p>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>SQL Injection 체험</td>
<td>실제 문자열 하나만으로 모든 데이터가 조회되는 것을 보며 보안의 중요성을 느꼈다.</td>
</tr>
<tr>
<td>Statement의 한계</td>
<td>단순하지만 매우 위험한 방식이라는 것을 이해하였다.</td>
</tr>
<tr>
<td>사용자 입력 검증</td>
<td>입력값은 절대로 SQL 문자열에 직접 이어 붙이면 안 된다는 점을 배웠다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-usepreparedstatement">2) UsePreparedStatement</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>PreparedStatement</td>
<td>SQL과 데이터를 분리하여 실행하는 객체이다.</td>
</tr>
<tr>
<td>? Placeholder</td>
<td>나중에 값이 들어갈 위치를 의미한다.</td>
</tr>
<tr>
<td>setString()</td>
<td>Placeholder에 문자열을 바인딩한다.</td>
</tr>
<tr>
<td>SQL Injection 방지</td>
<td>입력값을 SQL 코드가 아닌 데이터로 처리한다.</td>
</tr>
<tr>
<td>실행 계획 재사용</td>
<td>동일 SQL은 컴파일 결과를 재사용하여 성능이 향상된다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class PreparedExample {

    public static void main(String[] args) {

        String searchId = &quot;1234&#39; OR &#39;1&#39;=&#39;1&quot;;

        try (Connection connection = DBUtil.getConnection()) {

            String sql =
                    &quot;SELECT * FROM users WHERE id = ?&quot;;

            PreparedStatement preparedStatement =
                    connection.prepareStatement(sql);

            preparedStatement.setString(1, searchId);

            ResultSet resultSet =
                    preparedStatement.executeQuery();

            while (resultSet.next()) {

                System.out.println(resultSet.getString(&quot;id&quot;));
                System.out.println(resultSet.getString(&quot;name&quot;));
                System.out.println(resultSet.getInt(&quot;age&quot;));

            }

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

}</code></pre>
<p>PreparedStatement는 SQL과 데이터를 분리한다.</p>
<pre><code class="language-java">String sql =
        &quot;SELECT * FROM users WHERE id = ?&quot;;</code></pre>
<p>여기서</p>
<pre><code class="language-text">?</code></pre>
<p>는 아직 값이 없는 자리이다.</p>
<p>이후</p>
<pre><code class="language-java">preparedStatement.setString(1, searchId);</code></pre>
<p>가 실행되면</p>
<p>사용자의 입력은 SQL이 아니라</p>
<p><strong>단순 문자열 데이터</strong></p>
<p>로 전달된다.</p>
<p>즉,</p>
<pre><code class="language-text">1234&#39; OR &#39;1&#39;=&#39;1</code></pre>
<p>이라는 문자열은</p>
<p>SQL 코드가 아니라</p>
<p>그냥 하나의 문자열로 인식된다.</p>
<p>따라서 SQL Injection이 발생하지 않는다.</p>
<hr>
<h3 id="statement와-preparedstatement-비교">Statement와 PreparedStatement 비교</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>Statement</th>
<th>PreparedStatement</th>
</tr>
</thead>
<tbody><tr>
<td>SQL 생성</td>
<td>문자열 연결</td>
<td>Placeholder 사용</td>
</tr>
<tr>
<td>SQL Injection</td>
<td>발생 가능</td>
<td>방지 가능</td>
</tr>
<tr>
<td>성능</td>
<td>매번 컴파일</td>
<td>실행 계획 재사용</td>
</tr>
<tr>
<td>사용 빈도</td>
<td>거의 없음</td>
<td>대부분 사용</td>
</tr>
<tr>
<td>실무 사용</td>
<td>거의 X</td>
<td>사실상 표준</td>
</tr>
</tbody></table>
<hr>
<h3 id="내부-동작-과정">내부 동작 과정</h3>
<pre><code class="language-text">SQL 작성
SELECT * FROM users WHERE id = ?
            │
            ▼
SQL 먼저 컴파일
            │
            ▼
?에 값만 전달
            │
            ▼
DB 실행
            │
            ▼
ResultSet 반환</code></pre>
<p>SQL 구조와 데이터가 분리되므로 매우 안전하다.</p>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>보안 향상</td>
<td>SQL Injection을 원천적으로 막을 수 있다는 점이 인상 깊었다.</td>
</tr>
<tr>
<td>성능</td>
<td>실행 계획을 재사용하기 때문에 Statement보다 효율적이라는 것을 배웠다.</td>
</tr>
<tr>
<td>실무 활용</td>
<td>대부분의 프레임워크가 PreparedStatement를 사용하는 이유를 이해하였다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-useresultset">3) UseResultSet</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>ResultSet</td>
<td>SELECT 결과를 저장하는 객체이다.</td>
</tr>
<tr>
<td>rs.next()</td>
<td>다음 행으로 이동한다.</td>
</tr>
<tr>
<td>getString()</td>
<td>문자열 데이터를 읽는다.</td>
</tr>
<tr>
<td>getInt()</td>
<td>정수 데이터를 읽는다.</td>
</tr>
<tr>
<td>Alias</td>
<td>컬럼 이름을 별칭으로 변경할 수 있다.</td>
</tr>
<tr>
<td>Record 활용</td>
<td>조회한 데이터를 객체로 변환하여 관리하였다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">record UserInfo(
        String userId,
        String userName,
        int userAge
) {}

PreparedStatement preparedStatement =
        connection.prepareStatement(sql);

ResultSet resultSet =
        preparedStatement.executeQuery();

while (resultSet.next()) {

    UserInfo userInfo =
            new UserInfo(

                    resultSet.getString(&quot;user_id&quot;),
                    resultSet.getString(&quot;user_name&quot;),
                    resultSet.getInt(&quot;user_age&quot;)

            );

    System.out.println(userInfo);

}</code></pre>
<p>SELECT가 실행되면 ResultSet 객체가 생성된다.</p>
<p>ResultSet은</p>
<p><strong>데이터 여러 개를 저장하는 것이 아니라</strong></p>
<p>현재 한 행(Row)을 가리키는 커서(Cursor)를 가지고 있다.</p>
<p>처음에는</p>
<pre><code class="language-text">Before First</code></pre>
<p>위치에 있다.</p>
<pre><code class="language-java">resultSet.next();</code></pre>
<p>를 호출하면</p>
<p>첫 번째 행으로 이동한다.</p>
<p>다시 next()</p>
<p>↓</p>
<p>두 번째 행</p>
<p>↓</p>
<p>세 번째 행</p>
<p>↓</p>
<p>없으면 false</p>
<p>를 반환한다.</p>
<p>즉,</p>
<pre><code class="language-java">while(resultSet.next())</code></pre>
<p>는</p>
<p>데이터가 존재하는 동안 반복하는 코드이다.</p>
<hr>
<h3 id="resultset-구조">ResultSet 구조</h3>
<pre><code class="language-text">Before First
      │
      ▼
첫 번째 행
      │
      ▼
두 번째 행
      │
      ▼
세 번째 행
      │
      ▼
After Last</code></pre>
<hr>
<h3 id="alias를-사용하는-이유">Alias를 사용하는 이유</h3>
<p>이번 실습에서는</p>
<pre><code class="language-sql">SELECT

id AS user_id,
name AS user_name,
age AS user_age

FROM users</code></pre>
<p>처럼 Alias를 사용하였다.</p>
<p>그 이유는</p>
<ul>
<li>컬럼명이 겹칠 수 있기 때문</li>
<li>JOIN 시 이름을 구분하기 위해</li>
<li>코드 가독성을 높이기 위해</li>
</ul>
<p>이다.</p>
<hr>
<h3 id="record를-사용하는-이유">Record를 사용하는 이유</h3>
<p>조회 결과를</p>
<pre><code class="language-java">UserInfo</code></pre>
<p>객체로 변환하였다.</p>
<p>객체를 사용하면</p>
<ul>
<li>데이터 전달이 편리하다.</li>
<li>여러 값을 하나의 객체로 관리할 수 있다.</li>
<li>가독성이 좋아진다.</li>
</ul>
<p>이는 이후 Repository 패턴에서도 동일하게 활용된다.</p>
<hr>
<h3 id="느낀-점">느낀 점</h3>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>데이터 읽기 이해</td>
<td>ResultSet이 단순한 리스트가 아니라 커서를 기반으로 동작한다는 점을 이해하였다.</td>
</tr>
<tr>
<td>객체 변환</td>
<td>조회 결과를 Record 객체로 변환하면서 객체지향적인 데이터 관리 방법을 배웠다.</td>
</tr>
<tr>
<td>실무 연결</td>
<td>MyBatis와 JPA도 결국 ResultSet을 객체로 변환한다는 점을 알게 되어 프레임워크의 내부 동작을 이해하는 데 도움이 되었다.</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/16 IL(I Learned) - Step 1]]></title>
            <link>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-1</link>
            <guid>https://velog.io/@baeksh_8/260716-ILI-Learned-Step-1</guid>
            <pubDate>Sun, 26 Jul 2026 10:07:10 GMT</pubDate>
            <description><![CDATA[<h2 id="jdbc-기초-이해와-데이터베이스-연결">JDBC 기초 이해와 데이터베이스 연결</h2>
<p>Java 프로그램과 데이터베이스를 연결하는 JDBC(Java Database Connectivity)의 가장 기본적인 동작 과정을 학습하였다. Driver를 직접 로딩하고 Connection 객체를 생성하면서 JDBC가 어떻게 동작하는지 이해할 수 있었다.</p>
<hr>
<h3 id="1-driverloading">1) DriverLoading</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>JDBC(Java Database Connectivity)</td>
<td>Java 프로그램이 데이터베이스와 통신하기 위한 표준 API이다. 데이터베이스 종류(MySQL, PostgreSQL 등)가 달라도 동일한 JDBC 인터페이스를 사용하여 프로그래밍할 수 있다.</td>
</tr>
<tr>
<td>JDBC Driver</td>
<td>데이터베이스 회사에서 제공하는 라이브러리이다. Java에서 작성한 명령을 각 데이터베이스가 이해할 수 있는 형태로 변환하는 역할을 수행한다.</td>
</tr>
<tr>
<td>Driver Loading</td>
<td>Driver를 메모리에 등록하는 과정이다. Driver가 등록되어야 DriverManager가 적절한 Driver를 선택하여 데이터베이스와 연결할 수 있다.</td>
</tr>
<tr>
<td>Class.forName()</td>
<td>문자열로 전달받은 클래스를 JVM 메모리에 로드하는 메서드이다. JDBC Driver 등록 과정에서 많이 사용되었다.</td>
</tr>
<tr>
<td>예외 처리</td>
<td>Driver가 존재하지 않으면 ClassNotFoundException이 발생하므로 반드시 예외 처리를 수행해야 한다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class DriverExample {

    public static void main(String[] args) {

        try {

            Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);

            System.out.println(&quot;Driver Loading Success&quot;);

        } catch (ClassNotFoundException e) {

            System.out.println(&quot;Driver Loading Failed&quot;);
        }

    }

}</code></pre>
<p>데이터베이스에 연결하기 전에 가장 먼저 JDBC Driver를 JVM에 등록하는 과정을 수행하였다.</p>
<p><code>Class.forName()</code> 메서드는 문자열 형태로 전달받은 클래스를 메모리에 로드하는 기능을 수행한다.</p>
<pre><code class="language-java">Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);</code></pre>
<p>가 실행되면 MySQL Driver 클래스가 JVM에 로드되고 DriverManager 내부에 자동으로 등록된다.</p>
<p>이후 프로그램에서</p>
<pre><code class="language-java">DriverManager.getConnection(...)</code></pre>
<p>을 호출하면 DriverManager는 현재 등록되어 있는 Driver들 중 연결 가능한 Driver를 찾아 데이터베이스 연결을 수행한다.</p>
<p>최근 JDBC에서는 Driver가 자동 등록되기 때문에 직접 호출하지 않아도 되는 경우가 많지만, 내부 동작 원리를 이해하기 위해서는 Driver Loading 과정이 매우 중요하다는 것을 알게 되었다.</p>
<p>또한 Driver 클래스가 존재하지 않으면 프로그램은 Driver를 찾을 수 없으므로 <code>ClassNotFoundException</code>이 발생하며, 이를 처리하기 위해 try-catch 문을 사용하였다.</p>
<table>
<thead>
<tr>
<th>내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Spring Boot</td>
<td>Driver를 자동 등록해 주므로 직접 작성하지 않는 경우가 많다.</td>
</tr>
<tr>
<td>순수 JDBC</td>
<td>Driver Loading을 직접 수행하는 경우가 존재한다.</td>
</tr>
<tr>
<td>DriverManager</td>
<td>등록된 Driver를 이용하여 데이터베이스 연결을 생성한다.</td>
</tr>
<tr>
<td>라이브러리 관리</td>
<td>Driver Jar 파일이 프로젝트에 포함되어 있어야 한다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-getconnection">2) GetConnection</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>상세 내용</th>
</tr>
</thead>
<tbody><tr>
<td>Connection</td>
<td>데이터베이스와 실제 연결된 객체이다. SQL을 실행하기 위해 가장 먼저 생성해야 하는 객체이다.</td>
</tr>
<tr>
<td>DriverManager</td>
<td>등록된 Driver 중 적절한 Driver를 선택하여 Connection 객체를 생성한다.</td>
</tr>
<tr>
<td>DBUtil</td>
<td>데이터베이스 연결 코드를 하나의 클래스로 분리하여 재사용성을 높였다.</td>
</tr>
<tr>
<td>환경 변수</td>
<td>URL, USER, PASSWORD를 코드에 직접 작성하지 않고 환경 변수로 관리하였다.</td>
</tr>
<tr>
<td>SQLException</td>
<td>데이터베이스 연결 과정에서 발생하는 예외이다.</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class ConnectionExample {

    public static void main(String[] args) {

        try {

            Connection databaseConnection = DBUtil.getConnection();

            System.out.println(databaseConnection.getMetaData().getURL());

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

}</code></pre>
<p>Driver를 등록한 이후 실제 데이터베이스와 연결을 수행하였다.</p>
<pre><code class="language-java">Connection databaseConnection = DBUtil.getConnection();</code></pre>
<p>을 호출하면 내부적으로 DriverManager가 실행된다.</p>
<p>DBUtil에서는</p>
<ul>
<li>URL</li>
<li>USER</li>
<li>PASSWORD</li>
</ul>
<p>를 환경 변수에서 읽어와 Connection 객체를 생성한다. </p>
<p>이처럼 DB 연결 정보를 별도의 Utility 클래스로 분리하면 여러 클래스에서 동일한 Connection 생성 코드를 반복해서 작성하지 않아도 된다.</p>
<p>또한 민감한 정보(DB 계정과 비밀번호)를 소스 코드에 직접 작성하지 않아 보안 측면에서도 장점이 있다.</p>
<p>Connection 객체가 생성되면 데이터베이스와의 통신이 가능한 상태가 되며, 이후 Statement나 PreparedStatement를 생성하여 SQL을 실행할 수 있다.</p>
<h3 id="jdbc-연결-과정">JDBC 연결 과정</h3>
<p>실습을 통해 JDBC의 연결 과정은 다음과 같은 순서로 이루어진다는 것을 이해하였다.</p>
<pre><code class="language-text">Java Program
      │
      ▼
Class.forName()
      │
      ▼
DriverManager
      │
      ▼
Connection 생성
      │
      ▼
Statement / PreparedStatement 생성
      │
      ▼
SQL 실행
      │
      ▼
ResultSet 반환</code></pre>
<p>단순히 <code>Connection</code>만 생성하는 것이 아니라, JDBC는 위와 같은 흐름으로 데이터베이스와 통신한다는 점을 이해하는 것이 중요하다고 느꼈다.</p>
<table>
<thead>
<tr>
<th>내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>DBUtil</td>
<td>프로젝트 전체에서 하나의 DB 연결 클래스를 공유한다.</td>
</tr>
<tr>
<td>환경 변수</td>
<td>DB 계정과 비밀번호를 코드에 작성하지 않는다.</td>
</tr>
<tr>
<td>Connection Pool</td>
<td>실무에서는 매번 Connection을 생성하지 않고 HikariCP와 같은 Connection Pool을 사용한다.</td>
</tr>
<tr>
<td>Spring Boot</td>
<td>DataSource를 통해 Connection을 자동 관리한다.</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/07 IL(I Learned) - Step 4]]></title>
            <link>https://velog.io/@baeksh_8/260707-ILI-Learned-Step-4</link>
            <guid>https://velog.io/@baeksh_8/260707-ILI-Learned-Step-4</guid>
            <pubDate>Sun, 12 Jul 2026 12:57:04 GMT</pubDate>
            <description><![CDATA[<h2 id="bean-scoperequest-session와-scope의-생명주기-이해">Bean Scope(Request, Session)와 Scope의 생명주기 이해</h2>
<h3 id="1-bean-scope란">1) Bean Scope란?</h3>
<p>Spring에서는 Bean이 <strong>언제 생성되고 언제 소멸되는지</strong>를 Scope를 통해 결정한다.</p>
<p>기본적으로 Spring Bean은 Singleton으로 생성된다.</p>
<pre><code class="language-java">@Component
public class ExampleBean {
}</code></pre>
<p><code>@Scope</code>를 지정하지 않으면 Singleton으로 관리된다.</p>
<p>이번 예제에서는 기존 Singleton 외에</p>
<ul>
<li>Request Scope</li>
<li>Session Scope</li>
</ul>
<p>두 가지 Scope를 학습하였다.</p>
<hr>
<h3 id="2-request-scope">2) Request Scope</h3>
<p>Request Scope Bean은</p>
<pre><code class="language-java">@Component
@RequestScope
public class RequestScopeBean {
}</code></pre>
<p>HTTP 요청이 들어올 때마다 새로운 Bean이 생성된다.</p>
<p>즉,</p>
<pre><code class="language-text">GET /scope

↓

RequestScopeBean 생성

↓

응답 완료

↓

Bean 소멸</code></pre>
<p>요청이 끝나면 Bean도 함께 제거된다.</p>
<p>새로고침(F5)을 하면</p>
<pre><code class="language-text">첫 번째 요청

↓

RequestScopeBean A</code></pre>
<pre><code class="language-text">두 번째 요청

↓

RequestScopeBean B</code></pre>
<p>처럼 항상 새로운 객체가 생성된다.</p>
<hr>
<h3 id="3-requestscopebean에서-uuid를-사용하는-이유">3) RequestScopeBean에서 UUID를 사용하는 이유</h3>
<p>RequestScopeBean에는</p>
<pre><code class="language-java">private final String uuid;</code></pre>
<p>가 존재한다.</p>
<p>생성자에서</p>
<pre><code class="language-java">UUID.randomUUID();</code></pre>
<p>를 호출하여 고유한 UUID를 생성한다.</p>
<p>UUID를 사용하는 이유는</p>
<p>매 요청마다 새로운 Bean이 생성되었는지 확인하기 위해서이다.</p>
<p>예를 들어</p>
<p>첫 번째 요청</p>
<pre><code class="language-text">UUID

A123...</code></pre>
<p>두 번째 요청</p>
<pre><code class="language-text">UUID

F928...</code></pre>
<p>UUID가 계속 바뀌는 것을 통해 새로운 Bean이 생성되었음을 확인할 수 있었다.</p>
<hr>
<h3 id="4-생성-시간을-저장하는-이유">4) 생성 시간을 저장하는 이유</h3>
<p>RequestScopeBean에서는</p>
<pre><code class="language-java">private final ZonedDateTime creationTime;</code></pre>
<p>도 함께 저장한다.</p>
<p>생성자에서</p>
<pre><code class="language-java">creationTime = ZonedDateTime.now();</code></pre>
<p>를 실행한다.</p>
<p>즉,</p>
<pre><code class="language-text">요청

↓

Bean 생성

↓

생성 시간 저장</code></pre>
<p>이 된다.</p>
<p>새로고침할 때마다 생성 시간이 계속 변경되는 것을 확인할 수 있었다.</p>
<hr>
<h3 id="5-session-scope">5) Session Scope</h3>
<p>Session Scope Bean은</p>
<pre><code class="language-java">@Component
@SessionScope
public class SessionScopeBean {
}</code></pre>
<p>으로 선언하였다.</p>
<p>Session Scope는</p>
<p>사용자(Session)마다 하나의 Bean을 생성한다.</p>
<p>즉,</p>
<pre><code class="language-text">브라우저 접속

↓

Session 생성

↓

SessionScopeBean 생성</code></pre>
<p>이후에는</p>
<p>새로고침을 여러 번 해도</p>
<p>같은 Bean을 계속 사용한다.</p>
<hr>
<h3 id="6-sessionscopebean의-count">6) SessionScopeBean의 count</h3>
<p>SessionScopeBean에는</p>
<pre><code class="language-java">private int count = 0;</code></pre>
<p>가 존재한다.</p>
<p>그리고</p>
<pre><code class="language-java">public void increment() {
    count++;
}</code></pre>
<p>를 호출할 때마다</p>
<pre><code class="language-text">0

↓

1

↓

2

↓

3</code></pre>
<p>으로 값이 증가한다.</p>
<p>새로고침을 반복해도 count가 유지되는 이유는</p>
<p>Session Scope Bean이 동일한 객체를 계속 사용하기 때문이다.</p>
<hr>
<h3 id="7-scopecontroller">7) ScopeController</h3>
<p>Controller에서는</p>
<pre><code class="language-java">@RequiredArgsConstructor
@Controller
@RequestMapping(&quot;/scope&quot;)
public class ScopeController {</code></pre>
<p>를 사용하였다.</p>
<p>생성자 주입을 통해</p>
<pre><code class="language-java">private final RequestScopeBean requestScopeBean;

private final SessionScopeBean sessionScopeBean;</code></pre>
<p>을 주입받는다.</p>
<p>그리고</p>
<pre><code class="language-java">sessionScopeBean.increment();</code></pre>
<p>를 호출하여 방문 횟수를 증가시킨다.</p>
<p>이후</p>
<pre><code class="language-java">model.addAttribute(...)</code></pre>
<p>를 이용하여</p>
<p>Request Bean과 Session Bean 정보를 View로 전달한다.</p>
<hr>
<h3 id="8-request-scope와-session-scope-차이">8) Request Scope와 Session Scope 차이</h3>
<p>이번 예제에서 가장 중요한 내용은</p>
<p>두 Scope의 생명주기 차이였다.</p>
<p>새로고침을 했을 때</p>
<p>Request Bean은</p>
<pre><code class="language-text">UUID

A

↓

B

↓

C</code></pre>
<p>처럼 계속 변경된다.</p>
<p>반면</p>
<p>Session Bean은</p>
<pre><code class="language-text">UUID

X

↓

X

↓

X</code></pre>
<p>로 유지된다.</p>
<p>또한 count 역시</p>
<pre><code class="language-text">1

↓

2

↓

3</code></pre>
<p>처럼 계속 증가한다.</p>
<p>이를 통해 Request Scope는 요청마다 새로 생성되고,</p>
<p>Session Scope는 같은 Session 동안 유지된다는 점을 확인하였다.</p>
<hr>
<h1 id="🔄-전체-실행-흐름">🔄 전체 실행 흐름</h1>
<pre><code class="language-text">브라우저

↓

GET /scope

↓

DispatcherServlet

↓

ScopeController

↓

RequestScopeBean 생성

↓

SessionScopeBean 조회

↓

count 증가

↓

Model 저장

↓

scope.jsp

↓

브라우저 응답</code></pre>
<hr>
<h1 id="singleton-·-request-·-session-scope-비교">Singleton · Request · Session Scope 비교</h1>
<table>
<thead>
<tr>
<th>Scope</th>
<th>생성 시점</th>
<th>유지 기간</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>Singleton</td>
<td>서버 시작</td>
<td>서버 종료까지</td>
<td>Service, Repository</td>
</tr>
<tr>
<td>Request</td>
<td>요청마다</td>
<td>요청 종료까지</td>
<td>요청별 임시 데이터</td>
</tr>
<tr>
<td>Session</td>
<td>Session 생성</td>
<td>Session 종료까지</td>
<td>로그인 정보, 장바구니</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/07 IL(I Learned) - Step 3]]></title>
            <link>https://velog.io/@baeksh_8/260707-ILI-Learned-Step3</link>
            <guid>https://velog.io/@baeksh_8/260707-ILI-Learned-Step3</guid>
            <pubDate>Sun, 12 Jul 2026 12:45:50 GMT</pubDate>
            <description><![CDATA[<h2 id="modelattribute를-이용한-데이터-바인딩과-session-활용">@ModelAttribute를 이용한 데이터 바인딩과 Session 활용</h2>
<h3 id="1-get-요청과-post-요청-분리">1) GET 요청과 POST 요청 분리</h3>
<p>이번 예제에서는 화면을 보여주는 역할과 사용자가 입력한 데이터를 처리하는 역할을 분리하였다.</p>
<pre><code class="language-java">@GetMapping
public String page() {
    return &quot;shirt&quot;;
}</code></pre>
<p>GET 요청은 단순히 화면(shirt.jsp)을 반환한다.</p>
<p>실행 흐름</p>
<pre><code class="language-text">브라우저

↓

GET /shirt

↓

DispatcherServlet

↓

ShirtController.page()

↓

shirt.jsp</code></pre>
<hr>
<p>반면,</p>
<pre><code class="language-java">@PostMapping
public String register(...) {</code></pre>
<p>POST 요청은 사용자가 입력한 데이터를 서버에서 처리한다.</p>
<p>즉,</p>
<table>
<thead>
<tr>
<th>GET</th>
<th>POST</th>
</tr>
</thead>
<tbody><tr>
<td>화면 조회</td>
<td>데이터 처리</td>
</tr>
<tr>
<td>View 반환</td>
<td>입력 데이터 저장</td>
</tr>
</tbody></table>
<p>조회와 저장을 서로 분리하여 구현하는 방식을 학습하였다.</p>
<hr>
<h3 id="2-modelattribute와-data-binding">2) @ModelAttribute와 Data Binding</h3>
<p>Servlet에서는 사용자가 입력한 값을 직접 꺼내야 했다.</p>
<pre><code class="language-java">String name = request.getParameter(&quot;name&quot;);

int price =
Integer.parseInt(
request.getParameter(&quot;price&quot;)
);</code></pre>
<p>Spring MVC에서는</p>
<pre><code class="language-java">@PostMapping
public String register(
        @ModelAttribute ShirtDTO shirtDTO
)</code></pre>
<p>한 줄만 작성하면 된다.</p>
<p>Spring이 내부적으로</p>
<pre><code class="language-java">ShirtDTO dto = new ShirtDTO();

dto.setName(...);

dto.setPrice(...);</code></pre>
<p>를 자동으로 수행한다.</p>
<p>이 과정을 <strong>Data Binding</strong>이라고 한다.</p>
<p>즉,</p>
<pre><code class="language-text">브라우저 입력값

↓

Spring

↓

DTO 생성

↓

Setter 호출

↓

Controller 전달</code></pre>
<p>과정이 자동으로 이루어진다.</p>
<hr>
<h3 id="3-lombok을-이용한-dto-작성">3) Lombok을 이용한 DTO 작성</h3>
<p>이번 DTO는 Lombok을 이용하여 작성하였다.</p>
<pre><code class="language-java">@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ShirtDTO</code></pre>
<p>각 어노테이션의 역할은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>Annotation</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>@Getter</td>
<td>Getter 자동 생성</td>
</tr>
<tr>
<td>@Setter</td>
<td>Setter 자동 생성</td>
</tr>
<tr>
<td>@NoArgsConstructor</td>
<td>기본 생성자 생성</td>
</tr>
<tr>
<td>@AllArgsConstructor</td>
<td>모든 필드를 포함하는 생성자 생성</td>
</tr>
<tr>
<td>@ToString</td>
<td>객체 출력 메서드 생성</td>
</tr>
</tbody></table>
<p>특히 Data Binding 과정에서는 Spring이</p>
<pre><code class="language-java">new ShirtDTO()</code></pre>
<p>를 생성한 뒤 Setter를 호출하기 때문에</p>
<p>기본 생성자와 Setter가 반드시 필요하다는 점을 이해하였다.</p>
<hr>
<h3 id="4-httpsession">4) HttpSession</h3>
<p>Controller에서는 Session 객체도 함께 전달받는다.</p>
<pre><code class="language-java">@PostMapping
public String register(
        @ModelAttribute ShirtDTO shirtDTO,
        HttpSession session
)</code></pre>
<p>Session은 사용자마다 독립적으로 유지되는 저장 공간이다.</p>
<p>사용자가 입력한 데이터를</p>
<pre><code class="language-java">session.setAttribute(
        &quot;shirt&quot;,
        shirtDTO
);</code></pre>
<p>를 이용하여 Session에 저장하였다.</p>
<p>동작 과정은 다음과 같다.</p>
<pre><code class="language-text">Controller

↓

HttpSession

↓

사용자 정보 저장

↓

다음 요청에서도 사용 가능</code></pre>
<p>Model은 현재 요청에서만 사용할 수 있지만,</p>
<p>Session은 여러 요청에 걸쳐 데이터를 유지할 수 있다는 차이를 이해하였다.</p>
<hr>
<h3 id="5-redirect">5) Redirect</h3>
<p>데이터 저장 후</p>
<pre><code class="language-java">return &quot;redirect:/shirt&quot;;</code></pre>
<p>를 반환하였다.</p>
<p>Redirect는 JSP를 바로 실행하는 것이 아니라</p>
<p>브라우저에게</p>
<pre><code class="language-text">/shirt로 다시 요청하세요.</code></pre>
<p>라고 응답하는 방식이다.</p>
<p>동작 과정은 다음과 같다.</p>
<pre><code class="language-text">POST /shirt

↓

Controller

↓

redirect:/shirt

↓

브라우저

↓

GET /shirt

↓

shirt.jsp</code></pre>
<p>즉, POST 요청 이후 새로운 GET 요청이 발생한다.</p>
<hr>
<h3 id="6-redirect를-사용하는-이유">6) Redirect를 사용하는 이유</h3>
<p>만약</p>
<pre><code class="language-java">return &quot;shirt&quot;;</code></pre>
<p>를 사용하면</p>
<p>사용자가 새로고침(F5)을 눌렀을 때</p>
<p>POST 요청이 다시 실행된다.</p>
<pre><code class="language-text">POST

↓

상품 등록

↓

새로고침

↓

POST

↓

상품 또 등록</code></pre>
<p>이처럼 같은 데이터가 중복 저장될 수 있다.</p>
<p>Redirect를 사용하면</p>
<pre><code class="language-text">POST

↓

Redirect

↓

GET

↓

새로고침

↓

GET만 실행</code></pre>
<p>이 되어 중복 저장을 방지할 수 있다.</p>
<p>이러한 패턴을</p>
<p><strong>PRG(Post-Redirect-Get)</strong></p>
<p>패턴이라고 한다.</p>
<hr>
<h3 id="7-model과-session의-차이">7) Model과 Session의 차이</h3>
<p>Step2에서는</p>
<pre><code class="language-java">model.addAttribute(...)</code></pre>
<p>를 사용하였다.</p>
<p>Step3에서는</p>
<pre><code class="language-java">session.setAttribute(...)</code></pre>
<p>를 사용하였다.</p>
<p>둘의 차이는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>Model</th>
<th>Session</th>
</tr>
</thead>
<tbody><tr>
<td>현재 요청에서만 사용</td>
<td>여러 요청 동안 유지</td>
</tr>
<tr>
<td>View에 데이터 전달</td>
<td>사용자 정보 저장</td>
</tr>
<tr>
<td>요청 종료 시 삭제</td>
<td>Session 종료 시 삭제</td>
</tr>
</tbody></table>
<p>즉,</p>
<p>Model은 화면을 위한 데이터,</p>
<p>Session은 사용자 상태를 유지하기 위한 데이터라는 점을 이해하였다.</p>
<hr>
<h1 id="🔄-전체-실행-흐름">🔄 전체 실행 흐름</h1>
<pre><code class="language-text">브라우저

↓

GET /shirt

↓

shirt.jsp 출력

↓

사용자 입력

↓

POST /shirt

↓

DispatcherServlet

↓

Spring Data Binding

↓

ShirtDTO 생성

↓

Session 저장

↓

redirect:/shirt

↓

브라우저

↓

GET /shirt

↓

shirt.jsp 출력</code></pre>
<hr>
<h1 id="step2와-step3-비교">Step2와 Step3 비교</h1>
<table>
<thead>
<tr>
<th>Step2</th>
<th>Step3</th>
</tr>
</thead>
<tbody><tr>
<td>서버가 DTO 생성</td>
<td>Spring이 DTO 자동 생성</td>
</tr>
<tr>
<td>Model 사용</td>
<td>Session 사용</td>
</tr>
<tr>
<td>GET 요청만 처리</td>
<td>GET + POST 처리</td>
</tr>
<tr>
<td>화면 출력 중심</td>
<td>사용자 입력 처리</td>
</tr>
<tr>
<td>Forward</td>
<td>Redirect(PRG)</td>
</tr>
</tbody></table>
<p>Step2는 데이터를 화면으로 전달하는 과정이었다면,</p>
<p>Step3는 사용자가 입력한 데이터를 서버가 처리하는 과정을 학습하는 단계였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/07 IL(I Learned) - Step 2]]></title>
            <link>https://velog.io/@baeksh_8/260707-ILI-Learned-Step2</link>
            <guid>https://velog.io/@baeksh_8/260707-ILI-Learned-Step2</guid>
            <pubDate>Sun, 12 Jul 2026 12:30:32 GMT</pubDate>
            <description><![CDATA[<h2 id="controller와-model을-이용한-데이터-전달">Controller와 Model을 이용한 데이터 전달</h2>
<h3 id="1-controller란">1) Controller란?</h3>
<p>Servlet에서는 <code>HttpServlet</code>을 상속받아 <code>doGet()</code>이나 <code>doPost()</code>를 구현했지만,</p>
<p>Spring MVC에서는 <strong>Controller가 요청을 처리</strong>한다.</p>
<pre><code class="language-java">@Controller
@RequestMapping
public class FoodController {</code></pre>
<p><code>@Controller</code>가 붙으면 Spring이 해당 클래스를 Bean으로 등록하고, DispatcherServlet이 요청이 들어왔을 때 적절한 Controller를 찾아 실행한다.</p>
<hr>
<h3 id="2-url-mapping">2) URL Mapping</h3>
<p>Controller 내부에서는 <code>@GetMapping</code>을 이용하여 GET 요청을 처리한다.</p>
<pre><code class="language-java">@GetMapping
public String index(Model model) {</code></pre>
<p>브라우저에서</p>
<pre><code class="language-text">GET /</code></pre>
<p>요청이 들어오면 DispatcherServlet이 해당 메서드를 실행한다.</p>
<p>실행 흐름</p>
<pre><code class="language-text">브라우저

↓

DispatcherServlet

↓

FoodController.index()</code></pre>
<hr>
<h3 id="3-dtodata-transfer-object">3) DTO(Data Transfer Object)</h3>
<p>이번 예제에서는 화면으로 전달할 데이터를 DTO에 담아 전달하였다.</p>
<pre><code class="language-java">public record FoodDTO(
        String name,
        int price
) {
}</code></pre>
<p>DTO는 여러 데이터를 하나의 객체로 묶어서 전달하기 위한 객체이다.</p>
<p>예를 들어</p>
<pre><code class="language-java">FoodDTO food =
        new FoodDTO(&quot;미소라멘&quot;, 12000);</code></pre>
<p>객체 안에는</p>
<pre><code class="language-text">FoodDTO

name = 미소라멘

price = 12000</code></pre>
<p>가 저장된다.</p>
<hr>
<h3 id="4-model을-이용한-데이터-전달">4) Model을 이용한 데이터 전달</h3>
<p>Controller에서는 Model 객체를 이용하여 View(JSP)로 데이터를 전달한다.</p>
<pre><code class="language-java">model.addAttribute(
        &quot;food&quot;,
        new FoodDTO(&quot;미소라멘&quot;, 12000)
);</code></pre>
<p>Model은 Controller와 View 사이에서 데이터를 전달하는 저장 공간이다.</p>
<p>동작 과정은 다음과 같다.</p>
<pre><code class="language-text">Controller

↓

Model

↓

JSP(View)</code></pre>
<p>JSP에서는</p>
<pre><code class="language-jsp">${food.name}

${food.price}</code></pre>
<p>처럼 사용할 수 있다.</p>
<hr>
<h3 id="5-fooddto를-model에-저장하는-이유">5) FoodDTO를 Model에 저장하는 이유</h3>
<p>데이터를 하나씩 전달하는 것도 가능하다.</p>
<pre><code class="language-java">model.addAttribute(&quot;name&quot;, &quot;미소라멘&quot;);
model.addAttribute(&quot;price&quot;, 12000);</code></pre>
<p>하지만 전달해야 하는 데이터가 많아질수록 코드가 복잡해진다.</p>
<p>DTO를 사용하면</p>
<pre><code class="language-java">model.addAttribute(&quot;food&quot;, foodDTO);</code></pre>
<p>한 줄만으로 여러 데이터를 전달할 수 있어 유지보수가 쉬워진다.</p>
<hr>
<h3 id="6-viewresolver-동작-과정">6) ViewResolver 동작 과정</h3>
<p>Controller는 JSP의 전체 경로를 반환하지 않는다.</p>
<pre><code class="language-java">return &quot;index&quot;;</code></pre>
<p>ViewResolver가 자동으로</p>
<pre><code class="language-text">prefix

↓

/WEB-INF/views/

↓

index

↓

suffix

↓

.jsp</code></pre>
<p>를 붙여</p>
<pre><code class="language-text">/WEB-INF/views/index.jsp</code></pre>
<p>를 찾아 이동한다.</p>
<p>따라서 Controller는 JSP 위치를 알 필요 없이 View 이름만 반환하면 된다.</p>
<hr>
<h3 id="7-record와-getter">7) record와 Getter</h3>
<p>FoodDTO는 record를 사용하였다.</p>
<pre><code class="language-java">public record FoodDTO(
        String name,
        int price
)</code></pre>
<p>record는</p>
<pre><code class="language-java">food.name();
food.price();</code></pre>
<p>와 같이 접근할 수 있지만,</p>
<p>JSP의 EL(Expression Language)는</p>
<pre><code class="language-jsp">${food.name}</code></pre>
<p>형태로 값을 조회하기 때문에 Getter가 필요하다.</p>
<p>그래서</p>
<pre><code class="language-java">public String getName() {
    return name;
}

public int getPrice() {
    return price;
}</code></pre>
<p>를 직접 작성하였다.</p>
<hr>
<h1 id="🔄-전체-실행-흐름">🔄 전체 실행 흐름</h1>
<pre><code class="language-text">브라우저

↓

GET /

↓

DispatcherServlet

↓

FoodController.index()

↓

FoodDTO 생성

↓

Model 저장

↓

return &quot;index&quot;

↓

ViewResolver

↓

/WEB-INF/views/index.jsp

↓

JSP 렌더링

↓

브라우저 응답</code></pre>
<hr>
<h1 id="servlet과-spring-mvc-비교">Servlet과 Spring MVC 비교</h1>
<table>
<thead>
<tr>
<th>Servlet</th>
<th>Spring MVC</th>
</tr>
</thead>
<tbody><tr>
<td>HttpServlet 상속</td>
<td>@Controller</td>
</tr>
<tr>
<td>doGet()</td>
<td>@GetMapping</td>
</tr>
<tr>
<td>request.setAttribute()</td>
<td>model.addAttribute()</td>
</tr>
<tr>
<td>RequestDispatcher.forward()</td>
<td>return &quot;viewName&quot;</td>
</tr>
<tr>
<td>URL 직접 처리</td>
<td>@RequestMapping</td>
</tr>
</tbody></table>
<p>Spring MVC는 Servlet에서 직접 처리하던 대부분의 작업을 어노테이션 기반으로 자동 처리해주기 때문에 코드가 훨씬 간결해졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/07 IL(I Learned) - Step 1]]></title>
            <link>https://velog.io/@baeksh_8/260707-ILI-Learned-Step1</link>
            <guid>https://velog.io/@baeksh_8/260707-ILI-Learned-Step1</guid>
            <pubDate>Sun, 12 Jul 2026 12:15:20 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-web-mvc-시작-구조-이해">Spring Web MVC 시작 구조 이해</h2>
<h3 id="1-webapplicationinitializer">1) WebApplicationInitializer</h3>
<p>기존 Java 프로그램은 <code>main()</code> 메서드에서 시작하지만,</p>
<p>Spring Web MVC는 <strong>Tomcat이 애플리케이션을 실행</strong>하며 <code>WebApplicationInitializer</code>가 시작점 역할을 한다.</p>
<pre><code class="language-java">public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {</code></pre>
<h3 id="실행-흐름">실행 흐름</h3>
<pre><code class="language-text">Tomcat 시작
    ↓
WebApplicationInitializer 실행
    ↓
Spring Container 생성
    ↓
DispatcherServlet 등록</code></pre>
<hr>
<h3 id="2-spring-container-생성">2) Spring Container 생성</h3>
<pre><code class="language-java">AnnotationConfigWebApplicationContext context =
        new AnnotationConfigWebApplicationContext();

context.register(WebConfig.class);</code></pre>
<p>여기서 Spring Container가 생성되고, <code>WebConfig</code>를 읽어 Bean을 생성한다.</p>
<hr>
<h3 id="3-dispatcherservlet-등록">3) DispatcherServlet 등록</h3>
<pre><code class="language-java">DispatcherServlet dispatcherServlet =
        new DispatcherServlet(context);</code></pre>
<p>DispatcherServlet은 모든 HTTP 요청을 가장 먼저 받아 적절한 Controller에게 전달한다.</p>
<pre><code class="language-text">브라우저
    ↓
DispatcherServlet
    ↓
Controller
    ↓
View</code></pre>
<hr>
<h3 id="4-url-mapping">4) URL Mapping</h3>
<pre><code class="language-java">registration.addMapping(&quot;/&quot;);</code></pre>
<p>&quot;/&quot;로 매핑했기 때문에 모든 요청은 DispatcherServlet을 거친다.</p>
<pre><code class="language-text">/
/food
/shirt
/login

↓

DispatcherServlet</code></pre>
<hr>
<h3 id="5-webconfig">5) WebConfig</h3>
<pre><code class="language-java">@Configuration
@EnableWebMvc
@ComponentScan(&quot;org.example.webmvc&quot;)</code></pre>
<p>각 어노테이션의 역할</p>
<table>
<thead>
<tr>
<th>Annotation</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>@Configuration</td>
<td>Spring 설정 클래스</td>
</tr>
<tr>
<td>@EnableWebMvc</td>
<td>Spring MVC 기능 활성화</td>
</tr>
<tr>
<td>@ComponentScan</td>
<td>Controller, Service 등을 Bean으로 등록</td>
</tr>
</tbody></table>
<hr>
<h3 id="6-viewresolver">6) ViewResolver</h3>
<pre><code class="language-java">return &quot;index&quot;;</code></pre>
<p>Controller에서는 View 이름만 반환한다.</p>
<p>ViewResolver가</p>
<pre><code class="language-text">return &quot;index&quot;

↓

/WEB-INF/views/index.jsp</code></pre>
<p>로 변환한다.</p>
<hr>
<h3 id="7-resourcehandler">7) ResourceHandler</h3>
<p>정적 리소스(CSS, JS, Image)의 경로를 등록한다.</p>
<pre><code class="language-java">registry
    .addResourceHandler(&quot;/resources/**&quot;)
    .addResourceLocations(&quot;/resources/&quot;);</code></pre>
<hr>
<h1 id="🔄-전체-실행-흐름">🔄 전체 실행 흐름</h1>
<pre><code class="language-text">Tomcat 실행

↓

WebApplicationInitializer

↓

Spring Container 생성

↓

WebConfig 읽기

↓

DispatcherServlet 등록

↓

브라우저 요청

↓

DispatcherServlet

↓

Controller

↓

ViewResolver

↓

JSP

↓

브라우저</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/07/06 IL(I Learned)]]></title>
            <link>https://velog.io/@baeksh_8/260706-ILI-Learned</link>
            <guid>https://velog.io/@baeksh_8/260706-ILI-Learned</guid>
            <pubDate>Sat, 11 Jul 2026 13:47:41 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-객체지향-설계와-의존성-주입">Spring 객체지향 설계와 의존성 주입</h2>
<h3 id="1-계층-분리layered-architecture">1) 계층 분리(Layered Architecture)</h3>
<p>가장 먼저 도메인 객체(Book), 서비스(Service), 저장소(Repository)를 분리하여 객체지향적인 구조를 설계하는 방법을 학습하였다.</p>
<h3 id="각-계층의-역할">각 계층의 역할</h3>
<table>
<thead>
<tr>
<th>계층</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Book</td>
<td>데이터를 표현하는 객체(Domain)</td>
</tr>
<tr>
<td>Repository</td>
<td>데이터를 저장하고 조회하는 역할</td>
</tr>
<tr>
<td>Service</td>
<td>프로그램의 비즈니스 로직 처리</td>
</tr>
<tr>
<td>Main</td>
<td>프로그램 실행 및 서비스 호출</td>
</tr>
</tbody></table>
<p>Service는 데이터를 직접 저장하지 않고 Repository에게 저장을 요청하며, Main은 Service만 호출하도록 역할이 분리되어 있다.</p>
<p>이를 통해 각 클래스가 하나의 책임만 가지도록 설계하는 것이 객체지향 설계의 기본이라는 것을 이해하였다.</p>
<hr>
<h3 id="2-interface를-사용하는-이유">2) Interface를 사용하는 이유</h3>
<p>BookRepository는 저장 기능만 정의하고 실제 저장 방법은 구현하지 않는다.</p>
<pre><code class="language-text">BookRepository
        ▲
        │
MemoryBookRepository</code></pre>
<p>이처럼 인터페이스를 사용하면 구현체를 자유롭게 변경할 수 있으며, Memory 저장 방식에서 Database 저장 방식으로 변경하더라도 Service는 수정하지 않아도 된다.</p>
<p>즉, 구현이 아닌 추상화에 의존하도록 설계하는 것이 객체지향 설계의 중요한 원칙임을 이해하였다.</p>
<hr>
<h3 id="3-repository-구현">3) Repository 구현</h3>
<p>MemoryBookRepository에서는 HashMap을 이용하여 간단한 메모리 저장소를 구현하였다.</p>
<pre><code class="language-text">HashMap

1 → Book

2 → Book

3 → Book</code></pre>
<p>save()는 HashMap의 put()을 이용하여 저장하고,</p>
<p>findBook()은 get()을 이용하여 조회한다.</p>
<p>실제 데이터베이스 대신 HashMap을 사용하지만 Repository의 역할 자체는 동일하다는 점을 이해하였다.</p>
<hr>
<h3 id="4-생성자-주입dependency-injection">4) 생성자 주입(Dependency Injection)</h3>
<p>Step2에서는 Service가 Repository를 직접 생성하지 않고 생성자를 통해 전달받도록 변경하였다.</p>
<p>기존 방식</p>
<pre><code class="language-java">private BookRepository repository = new MemoryBookRepository();</code></pre>
<p>변경 후</p>
<pre><code class="language-java">public BookServiceImpl(BookRepository repository)</code></pre>
<p>이 구조에서는 BookServiceImpl이 어떤 Repository를 사용할지 알 필요가 없으며, 외부에서 필요한 구현체를 전달받기만 한다.</p>
<p>이를 <strong>생성자 주입(Constructor Injection)</strong> 이라고 하며, 객체 간의 결합도를 낮추는 중요한 방법이라는 것을 학습하였다.</p>
<hr>
<h3 id="5-spring-bean과-applicationcontext">5) Spring Bean과 ApplicationContext</h3>
<p>Spring에서는 객체를 직접 생성하지 않고 Spring Container가 생성 및 관리한다.</p>
<p>ApplicationContext를 생성하면 Spring은 설정 클래스를 읽고 Bean을 생성한다.</p>
<pre><code class="language-java">ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);</code></pre>
<p>이후에는</p>
<pre><code class="language-java">BookService service =
context.getBean(BookService.class);</code></pre>
<p>처럼 필요한 객체를 Spring Container에게 요청하여 사용할 수 있다.</p>
<p>이를 통해 객체 생성 책임이 개발자에서 Spring으로 이동한다는 점을 이해하였다.</p>
<hr>
<h3 id="6-configuration과-bean">6) @Configuration과 @Bean</h3>
<p>AppConfig 클래스에서는 Spring에게 어떤 객체를 Bean으로 등록할 것인지 알려준다.</p>
<pre><code class="language-java">@Configuration</code></pre>
<p>은 설정 클래스라는 의미이며,</p>
<pre><code class="language-java">@Bean</code></pre>
<p>은 Spring이 관리할 객체를 등록하는 역할을 한다.</p>
<p>BookRepository Bean을 생성한 뒤,</p>
<p>BookService Bean 생성 시 Repository를 전달하여 객체 간의 의존성을 연결하는 과정을 확인하였다.</p>
<hr>
<h3 id="7-component와-component-scan">7) @Component와 Component Scan</h3>
<p>Step3부터는 @Bean 대신 @Component를 이용한 자동 Bean 등록 방식을 학습하였다.</p>
<pre><code class="language-java">@Component</code></pre>
<p>가 붙은 클래스를</p>
<pre><code class="language-java">@ComponentScan</code></pre>
<p>이 자동으로 탐색하여 Bean으로 등록한다.</p>
<p>즉,</p>
<pre><code class="language-text">@Component

↓

Component Scan

↓

Spring Bean 등록</code></pre>
<p>이라는 흐름으로 동작한다는 것을 이해하였다.</p>
<p>실무에서는 @ComponentScan을 사용하는 방식이 가장 일반적이다.</p>
<hr>
<h3 id="8-bean-scope">8) Bean Scope</h3>
<p>Bean이 몇 개 생성되는지를 결정하는 Scope에 대해서도 학습하였다.</p>
<h4 id="singleton">Singleton</h4>
<p>Spring의 기본 Scope이며 객체를 하나만 생성한다.</p>
<pre><code class="language-text">getBean()

↓

같은 객체 반환</code></pre>
<p>따라서</p>
<pre><code class="language-java">bean1 == bean2</code></pre>
<p>는 true가 된다.</p>
<hr>
<h4 id="prototype">Prototype</h4>
<p>Prototype Scope는 getBean()을 호출할 때마다 새로운 객체를 생성한다.</p>
<pre><code class="language-text">getBean()

↓

새 객체 생성</code></pre>
<p>따라서</p>
<pre><code class="language-java">bean1 == bean2</code></pre>
<p>는 false가 된다.</p>
<p>Singleton은 프로그램 전체에서 하나의 객체를 공유하며, Prototype은 독립적인 객체가 필요할 때 사용된다는 차이를 이해하였다.</p>
<hr>
<h3 id="9-bean-생명주기lifecycle">9) Bean 생명주기(Lifecycle)</h3>
<p>Bean이 생성되고 소멸되는 과정도 학습하였다.</p>
<pre><code class="language-text">객체 생성

↓

@PostConstruct

↓

객체 사용

↓

@PreDestroy</code></pre>
<p>Singleton Bean은 생성과 소멸 모두 Spring이 관리하지만,</p>
<p>Prototype Bean은 생성만 Spring이 담당하며 소멸은 직접 관리해야 한다는 차이를 알게 되었다.</p>
<hr>
<h3 id="10-의존성-주입-방식-비교">10) 의존성 주입 방식 비교</h3>
<p>Spring에서 사용하는 세 가지 의존성 주입 방식을 비교하였다.</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>생성자 주입</td>
<td>가장 권장되는 방식</td>
</tr>
<tr>
<td>필드 주입</td>
<td>테스트 어려움, final 사용 불가</td>
</tr>
<tr>
<td>Setter 주입</td>
<td>선택적인 의존성에 적합</td>
</tr>
</tbody></table>
<p>생성자 주입은 객체 생성 시 반드시 필요한 의존성이 모두 주입되므로 가장 안전한 방식이라는 점을 이해하였다.</p>
<p>또한 Lombok의</p>
<pre><code class="language-java">@RequiredArgsConstructor</code></pre>
<p>를 사용하면 생성자를 직접 작성하지 않아도 final 필드를 포함한 생성자가 자동 생성된다는 것도 학습하였다.</p>
<hr>
<h3 id="11-순환-참조circular-dependency">11) 순환 참조(Circular Dependency)</h3>
<p>객체가 서로를 참조하는 순환 참조 문제도 실습하였다.</p>
<pre><code class="language-text">A

↓

B

↓

A</code></pre>
<p>생성자 주입에서는 Spring이 Bean 생성 단계에서 순환 참조를 감지하여 예외를 발생시키기 때문에 문제를 빠르게 발견할 수 있다.</p>
<p>반면 필드 주입이나 Setter 주입에서는 객체 생성은 가능하지만, 서로의 메서드를 계속 호출하여 StackOverflowError가 발생할 수 있다는 점을 확인하였다.</p>
<p>이를 통해 생성자 주입이 권장되는 또 하나의 이유를 이해하였다.</p>
<hr>
<h1 id="직면한-문제">직면한 문제</h1>
<p>학습 초기에는 Spring이 객체를 어떻게 생성하는지, Bean과 일반 객체의 차이가 무엇인지 명확하게 이해되지 않았다.</p>
<p>의존성 주입 역시 단순히 &quot;객체를 넣어준다&quot; 정도로만 이해하고 있었기 때문에 생성자 주입과 필드 주입의 차이가 잘 와닿지 않았다.</p>
<p>특히 순환 참조 예제에서는 생성자 주입에서는 애플리케이션 시작 단계에서 예외가 발생하는 반면, 필드 주입에서는 실행 중 StackOverflowError가 발생하는 이유를 이해하는 데 어려움을 겪었다.</p>
<hr>
<h1 id="해결을-위해-시도한-방법">해결을 위해 시도한 방법</h1>
<p>Main → Service → Repository → HashMap으로 이어지는 호출 흐름을 단계별로 추적하면서 객체가 생성되는 순서와 메서드가 호출되는 과정을 분석하였다.</p>
<p>Step1부터 Step4까지 변경된 부분만 비교하면서 왜 코드가 수정되었는지 확인하였고,</p>
<p>특히 생성자 주입, 필드 주입, Setter 주입을 각각 비교하여 장단점을 정리하였다.</p>
<p>Singleton과 Prototype은 getBean()을 여러 번 호출했을 때 반환되는 객체를 비교하면서 Scope의 차이를 확인하였다.</p>
<p>마지막으로 순환 참조 예제를 통해 생성자 주입과 필드 주입에서 발생하는 문제를 직접 확인하면서 Spring이 생성자 주입을 권장하는 이유를 이해할 수 있었다.</p>
<hr>
<h1 id="느낀-점">느낀 점</h1>
<p>이번 학습을 통해 Spring은 단순히 객체를 대신 생성해주는 프레임워크가 아니라, 객체 간의 관계를 관리하고 의존성을 연결해주는 컨테이너라는 것을 이해하게 되었다.</p>
<p>또한 객체지향 설계에서는 구현보다 추상화에 의존하도록 설계하는 것이 유지보수성과 확장성을 높인다는 점도 알게 되었다.</p>
<p>특히 생성자 주입이 단순히 권장되는 코딩 스타일이 아니라 테스트 용이성, 객체 불변성 보장, 순환 참조 방지 등 다양한 장점을 가진 설계 방식이라는 것을 실습을 통해 체감할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/06/24 TIL (Today I Learned)]]></title>
            <link>https://velog.io/@baeksh_8/260624-TIL-Today-I-Learned</link>
            <guid>https://velog.io/@baeksh_8/260624-TIL-Today-I-Learned</guid>
            <pubDate>Wed, 24 Jun 2026 12:53:40 GMT</pubDate>
            <description><![CDATA[<h2 id="java-객체지향-심화---상속-다형성-인터페이스-제너릭">Java 객체지향 심화 - 상속, 다형성, 인터페이스, 제너릭</h2>
<hr>
<h1 id="📚-오늘-배운-내용-요약">📚 오늘 배운 내용 요약</h1>
<table>
<thead>
<tr>
<th>주제</th>
<th>핵심 내용</th>
</tr>
</thead>
<tbody><tr>
<td>상속(Inheritance)</td>
<td>기존 클래스의 기능을 물려받아 확장</td>
</tr>
<tr>
<td>오버라이딩(Override)</td>
<td>부모 메서드를 재정의</td>
</tr>
<tr>
<td>동적 바인딩</td>
<td>실제 객체 기준으로 메서드 호출</td>
</tr>
<tr>
<td>업캐스팅</td>
<td>자식 객체를 부모 타입으로 참조</td>
</tr>
<tr>
<td>다운캐스팅</td>
<td>부모 타입을 자식 타입으로 변환</td>
</tr>
<tr>
<td>추상 클래스</td>
<td>공통 기능 정의 및 구현 강제</td>
</tr>
<tr>
<td>인터페이스</td>
<td>다중 구현을 위한 계약</td>
</tr>
<tr>
<td>Wrapper Class</td>
<td>기본형을 객체 형태로 사용</td>
</tr>
<tr>
<td>Generic</td>
<td>타입을 일반화하여 재사용성 향상</td>
</tr>
</tbody></table>
<hr>
<h1 id="1-상속과-오버라이딩">1) 상속과 오버라이딩</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>extends</td>
<td>부모 클래스를 상속</td>
</tr>
<tr>
<td>super()</td>
<td>부모 생성자 호출</td>
</tr>
<tr>
<td>오버라이딩</td>
<td>부모 메서드를 재정의</td>
</tr>
<tr>
<td>toString()</td>
<td>객체 출력 형식 변경</td>
</tr>
<tr>
<td>동적 바인딩</td>
<td>실제 객체 기준 메서드 실행</td>
</tr>
<tr>
<td>정적 바인딩</td>
<td>변수 타입 기준 필드 접근</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class Example01 {

    public static void main(String[] args) {

        Developer dev = new Developer(&quot;Tom&quot;, &quot;Java&quot;);

        BackendDeveloper backend =
                new BackendDeveloper(&quot;Jane&quot;, &quot;Spring&quot;);

        dev.work();
        backend.work();
    }
}

class BackendDeveloper extends Developer {

    String version = &quot;2.0&quot;;

    BackendDeveloper(String name, String skill) {
        super(name, skill);
    }

    @Override
    void work() {
        System.out.println(name + &quot;이 백엔드 개발을 수행합니다.&quot;);
    }
}

class Developer {

    String version = &quot;1.0&quot;;
    String name;
    String skill;

    Developer(String name, String skill) {
        this.name = name;
        this.skill = skill;
    }

    void work() {
        System.out.println(name + &quot;이 개발을 수행합니다.&quot;);
    }
}</code></pre>
<ul>
<li>BackendDeveloper가 Developer 상속</li>
<li>부모 생성자는 super()로 호출</li>
<li>work() 메서드를 재정의하여 기능 확장</li>
<li>같은 메서드 호출이지만 실제 객체 타입에 따라 다르게 실행</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>상속은 확장</td>
<td>기존 기능을 재사용하면서 기능 추가 가능</td>
</tr>
<tr>
<td>오버라이딩 중요</td>
<td>다형성의 핵심</td>
</tr>
<tr>
<td>동적 바인딩 이해</td>
<td>변수 타입보다 실제 객체 타입이 중요</td>
</tr>
</tbody></table>
<hr>
<h1 id="2-접근-범위와-상속">2) 접근 범위와 상속</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>protected</td>
<td>상속 관계에서 접근 가능</td>
</tr>
<tr>
<td>private</td>
<td>해당 클래스 내부만 접근 가능</td>
</tr>
<tr>
<td>super</td>
<td>부모 메서드 호출</td>
</tr>
<tr>
<td>오버라이딩 제한</td>
<td>접근 범위를 더 좁게 변경 불가</td>
</tr>
</tbody></table>
<pre><code class="language-java">class Engineer {

    protected String secret = &quot;SECRET&quot;;

    void work() {
        System.out.println(&quot;작업 수행&quot;);
    }
}

class SoftwareEngineer extends Engineer {

    @Override
    public void work() {

        super.work();

        System.out.println(&quot;추가 작업 수행&quot;);
    }
}</code></pre>
<ul>
<li>protected 필드는 자식 클래스 접근 가능</li>
<li>super를 통해 부모 기능 재사용</li>
<li>오버라이딩 시 접근 범위를 줄일 수 없음</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>protected 활용 중요</td>
<td>상속 구조에서 자주 사용</td>
</tr>
<tr>
<td>부모 기능 재사용 가능</td>
<td>super 활용</td>
</tr>
<tr>
<td>접근제어 설계 필요</td>
<td>무조건 public 사용 지양</td>
</tr>
</tbody></table>
<hr>
<h1 id="3-업캐스팅과-다운캐스팅">3) 업캐스팅과 다운캐스팅</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>업캐스팅</td>
<td>자식 객체 → 부모 타입</td>
</tr>
<tr>
<td>다운캐스팅</td>
<td>부모 타입 → 자식 타입</td>
</tr>
<tr>
<td>instanceof</td>
<td>타입 확인</td>
</tr>
<tr>
<td>패턴 매칭</td>
<td>다운캐스팅 간소화</td>
</tr>
</tbody></table>
<pre><code class="language-java">Animal animal = new Dog();

animal.eat();

if(animal instanceof Dog dog){
    dog.eat();
}</code></pre>
<ul>
<li>업캐스팅은 자동 수행</li>
<li>다운캐스팅은 명시적으로 수행</li>
<li>instanceof로 안전성 확보</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>업캐스팅 활용도 높음</td>
<td>다형성 구현 가능</td>
</tr>
<tr>
<td>다운캐스팅 위험성 존재</td>
<td>타입 확인 필수</td>
</tr>
<tr>
<td>instanceof 중요</td>
<td>런타임 오류 예방</td>
</tr>
</tbody></table>
<hr>
<h1 id="4-추상-클래스">4) 추상 클래스</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>abstract class</td>
<td>객체 생성 불가</td>
</tr>
<tr>
<td>추상 메서드</td>
<td>구현 강제</td>
</tr>
<tr>
<td>공통 기능 정의</td>
<td>코드 중복 감소</td>
</tr>
</tbody></table>
<pre><code class="language-java">abstract class Fruit {

    abstract int getPrice();

    void info() {
        System.out.println(&quot;과일 정보&quot;);
    }
}

class Apple extends Fruit {

    @Override
    int getPrice() {
        return 1000;
    }
}</code></pre>
<ul>
<li>Fruit는 직접 생성 불가</li>
<li>하위 클래스가 반드시 구현</li>
<li>공통 기능은 부모에서 제공</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>설계 강제 가능</td>
<td>구현 규칙 제공</td>
</tr>
<tr>
<td>코드 중복 감소</td>
<td>공통 기능 관리</td>
</tr>
<tr>
<td>다형성과 궁합 좋음</td>
<td>부모 타입 활용 가능</td>
</tr>
</tbody></table>
<hr>
<h1 id="5-인터페이스">5) 인터페이스</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>interface</td>
<td>구현 계약</td>
</tr>
<tr>
<td>implements</td>
<td>인터페이스 구현</td>
</tr>
<tr>
<td>다중 구현</td>
<td>여러 인터페이스 구현 가능</td>
</tr>
<tr>
<td>다형성</td>
<td>인터페이스 타입 활용</td>
</tr>
</tbody></table>
<pre><code class="language-java">interface Playable {
    void play();
}

interface Streamable {
    void stream();
}

class SmartTV implements Playable, Streamable {

    @Override
    public void play() {
        System.out.println(&quot;재생&quot;);
    }

    @Override
    public void stream() {
        System.out.println(&quot;스트리밍&quot;);
    }
}</code></pre>
<ul>
<li>인터페이스는 기능 명세 제공</li>
<li>클래스는 여러 인터페이스 구현 가능</li>
<li>다중 상속 문제 해결</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>느슨한 결합 가능</td>
<td>유지보수 유리</td>
</tr>
<tr>
<td>다형성 극대화</td>
<td>인터페이스 기반 설계</td>
</tr>
<tr>
<td>실무 활용도 매우 높음</td>
<td>Spring에서도 자주 사용</td>
</tr>
</tbody></table>
<hr>
<h1 id="6-wrapper-class">6) Wrapper Class</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Wrapper</td>
<td>기본형을 객체로 변환</td>
</tr>
<tr>
<td>Boxing</td>
<td>기본형 → 객체</td>
</tr>
<tr>
<td>Unboxing</td>
<td>객체 → 기본형</td>
</tr>
<tr>
<td>Auto Boxing</td>
<td>자동 변환</td>
</tr>
</tbody></table>
<pre><code class="language-java">List&lt;Integer&gt; scoreList =
        new ArrayList&lt;&gt;();

Integer value = 100;

int number = value;</code></pre>
<ul>
<li>Generic에는 기본형 사용 불가</li>
<li>Wrapper 클래스 사용 필요</li>
<li>자동 Boxing/Unboxing 지원</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>Generic 사용 시 필수</td>
<td>Integer 자주 사용</td>
</tr>
<tr>
<td>내부 동작 이해 필요</td>
<td>성능 영향 가능</td>
</tr>
<tr>
<td>기본형과 객체형 구분 중요</td>
<td>메모리 사용 차이 존재</td>
</tr>
</tbody></table>
<hr>
<h1 id="7-generic">7) Generic</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Generic</td>
<td>타입을 일반화</td>
</tr>
<tr>
<td>타입 안정성</td>
<td>컴파일 시 오류 확인</td>
</tr>
<tr>
<td>재사용성</td>
<td>다양한 타입 지원</td>
</tr>
<tr>
<td>Generic Method</td>
<td>메서드 단위 타입 선언</td>
</tr>
</tbody></table>
<pre><code class="language-java">class Storage&lt;T&gt; {

    private T data;

    public void setData(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public &lt;K&gt; K echo(K value) {
        return value;
    }
}</code></pre>
<ul>
<li>T는 클래스 전체에서 사용</li>
<li>자료형을 외부에서 결정</li>
<li>String, Integer 등 다양한 타입 활용 가능</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>코드 재사용성 증가</td>
<td>타입별 클래스 생성 불필요</td>
</tr>
<tr>
<td>컴파일 단계 검증 가능</td>
<td>안정성 향상</td>
</tr>
<tr>
<td>컬렉션 프레임워크 핵심</td>
<td>List, Map 모두 Generic 사용</td>
</tr>
</tbody></table>
<hr>
<h1 id="🚨-직면한-문제--generic-이해-부족">🚨 직면한 문제 : Generic 이해 부족</h1>
<h2 id="문제-상황">문제 상황</h2>
<p>수업 중 다음 코드가 이해되지 않았다.</p>
<pre><code class="language-java">Box&lt;String&gt; box1 = new Box&lt;&gt;();

Box&lt;Integer&gt; box2 = new Box&lt;&gt;();</code></pre>
<p>특히 T가 무엇인지, 왜 String이나 Integer를 넣을 수 있는지 혼란스러웠다.</p>
<hr>
<h2 id="해결-과정">해결 과정</h2>
<h3 id="generic이-없는-경우">Generic이 없는 경우</h3>
<pre><code class="language-java">class StringBox {

    private String value;
}</code></pre>
<pre><code class="language-java">class IntegerBox {

    private Integer value;
}</code></pre>
<p>타입마다 클래스를 새로 만들어야 한다.</p>
<hr>
<h3 id="generic-사용">Generic 사용</h3>
<pre><code class="language-java">class Box&lt;T&gt; {

    private T value;
}</code></pre>
<p>사용 시</p>
<pre><code class="language-java">Box&lt;String&gt; textBox = new Box&lt;&gt;();

Box&lt;Integer&gt; numberBox = new Box&lt;&gt;();</code></pre>
<p>컴파일러가 자동으로 타입을 결정한다.</p>
<hr>
<h2 id="최종적으로-이해한-내용">최종적으로 이해한 내용</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>T</td>
<td>Type</td>
</tr>
<tr>
<td>E</td>
<td>Element</td>
</tr>
<tr>
<td>K</td>
<td>Key</td>
</tr>
<tr>
<td>V</td>
<td>Value</td>
</tr>
<tr>
<td>?</td>
<td>Wildcard</td>
</tr>
</tbody></table>
<hr>
<h2 id="generic의-장점">Generic의 장점</h2>
<table>
<thead>
<tr>
<th>장점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>타입 안정성</td>
<td>컴파일 시 오류 확인</td>
</tr>
<tr>
<td>캐스팅 감소</td>
<td>형변환 불필요</td>
</tr>
<tr>
<td>재사용성</td>
<td>여러 타입 지원</td>
</tr>
<tr>
<td>가독성 향상</td>
<td>의도 명확</td>
</tr>
</tbody></table>
<hr>
<h2 id="오늘-배운-점">오늘 배운 점</h2>
<p>Generic은 &quot;여러 타입을 받는다&quot;가 아니라</p>
<p>&quot;클래스나 메서드를 정의할 때 타입을 나중에 결정하도록 미루는 기술&quot;</p>
<p>이라는 것을 이해하게 되었다.</p>
<p>특히 List<String>, List<Integer>, Map&lt;String,Integer&gt; 같은 컬렉션들이 모두 Generic을 기반으로 동작한다는 점을 알게 되었고, 앞으로 Spring과 Java 컬렉션을 사용할 때 Generic 문법을 훨씬 자연스럽게 읽을 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/06/23 TIL (Today I Learned)]]></title>
            <link>https://velog.io/@baeksh_8/260528-TIL-Today-I-Learned</link>
            <guid>https://velog.io/@baeksh_8/260528-TIL-Today-I-Learned</guid>
            <pubDate>Tue, 23 Jun 2026 23:41:07 GMT</pubDate>
            <description><![CDATA[<h2 id="객체지향-프로그래밍-기초와-클래스-설계">객체지향 프로그래밍 기초와 클래스 설계</h2>
<hr>
<h1 id="📚-오늘-배운-내용-요약">📚 오늘 배운 내용 요약</h1>
<table>
<thead>
<tr>
<th>주제</th>
<th>핵심 내용</th>
</tr>
</thead>
<tbody><tr>
<td>클래스(Class)</td>
<td>객체를 만들기 위한 설계도</td>
</tr>
<tr>
<td>객체(Object)</td>
<td>클래스를 기반으로 생성된 실체</td>
</tr>
<tr>
<td>인스턴스(Instance)</td>
<td>메모리에 생성된 객체</td>
</tr>
<tr>
<td>필드(Field)</td>
<td>객체가 가지는 속성(데이터)</td>
</tr>
<tr>
<td>메서드(Method)</td>
<td>객체가 수행하는 기능</td>
</tr>
<tr>
<td>생성자(Constructor)</td>
<td>객체 생성 시 호출되는 특별한 메서드</td>
</tr>
<tr>
<td>this</td>
<td>현재 인스턴스 자신을 가리키는 키워드</td>
</tr>
<tr>
<td>static</td>
<td>객체 생성 없이 클래스 차원에서 공유되는 멤버</td>
</tr>
<tr>
<td>오버로딩(Overloading)</td>
<td>같은 이름의 메서드를 여러 개 정의</td>
</tr>
<tr>
<td>접근제어자</td>
<td>외부 접근 범위를 제어</td>
</tr>
<tr>
<td>캡슐화</td>
<td>데이터를 보호하고 검증하는 객체지향 원칙</td>
</tr>
</tbody></table>
<hr>
<h1 id="1-객체-생성과-메모리-구조">1) 객체 생성과 메모리 구조</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>객체 생성</td>
<td>new 키워드로 객체 생성</td>
</tr>
<tr>
<td>참조 변수</td>
<td>객체의 주소를 저장</td>
</tr>
<tr>
<td>null</td>
<td>객체와 연결되지 않은 상태</td>
</tr>
<tr>
<td>기본값 초기화</td>
<td>객체 생성 시 기본값 자동 할당</td>
</tr>
<tr>
<td>this</td>
<td>현재 객체 자신의 필드 접근</td>
</tr>
<tr>
<td>static 변수</td>
<td>모든 객체가 공유</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class Example01 {

    public static int totalCount = 0;

    public static void main(String[] args) {

        Instructor instructor = new Instructor();

        System.out.println(instructor.name);

        Instructor copiedInstructor = instructor;

        copiedInstructor.name = &quot;김강사&quot;;

        instructor.speak(&quot;학생&quot;, 10);

        System.out.println(instructor.name);
    }
}

class Instructor {

    String name;
    int age;

    static int count;

    void speak(String target, int number) {

        System.out.println(target);
        System.out.println(this.name);

        Example01.totalCount++;
    }
}</code></pre>
<ul>
<li>new를 통해 힙 메모리에 객체 생성</li>
<li>참조 변수는 객체 주소 저장</li>
<li>동일한 객체를 여러 참조 변수가 가리킬 수 있음</li>
<li>this를 통해 현재 객체의 필드 접근</li>
<li>static 변수는 객체가 아닌 클래스에 소속</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>참조 개념 중요</td>
<td>변수 자체가 객체가 아니라 객체 주소를 저장</td>
</tr>
<tr>
<td>static 이해 필요</td>
<td>객체마다 생성되는 것이 아니라 공유됨</td>
</tr>
<tr>
<td>this 역할 이해</td>
<td>지역변수와 필드 구분 가능</td>
</tr>
</tbody></table>
<hr>
<h1 id="2-클래스와-인스턴스">2) 클래스와 인스턴스</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>클래스</td>
<td>객체 설계도</td>
</tr>
<tr>
<td>인스턴스</td>
<td>실제 생성된 객체</td>
</tr>
<tr>
<td>멤버 변수</td>
<td>객체마다 별도 보유</td>
</tr>
<tr>
<td>참조 비교</td>
<td>== 연산자 사용</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class Example02 {

    public static void main(String[] args) {

        Vehicle vehicleA = new Vehicle();
        vehicleA.model = &quot;소나타&quot;;

        Vehicle vehicleB = new Vehicle();
        vehicleB.model = &quot;아반떼&quot;;

        vehicleA.increaseSpeed();
        vehicleA.increaseSpeed();

        vehicleB.increaseSpeed();
    }
}

class Vehicle {

    String model;
    int speed;

    void increaseSpeed() {
        speed += 10;
    }
}</code></pre>
<ul>
<li>객체마다 별도의 필드 공간 보유</li>
<li>같은 클래스라도 다른 인스턴스는 독립적</li>
<li>== 는 객체 주소 비교</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>객체는 독립적</td>
<td>같은 클래스여도 상태는 서로 다름</td>
</tr>
<tr>
<td>참조 비교 이해</td>
<td>==는 값이 아니라 주소 비교</td>
</tr>
<tr>
<td>인스턴스 개념 정립</td>
<td>클래스와 객체를 구분해야 함</td>
</tr>
</tbody></table>
<hr>
<h1 id="3-메서드와-오버로딩">3) 메서드와 오버로딩</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>메서드</td>
<td>객체의 기능 정의</td>
</tr>
<tr>
<td>반환값</td>
<td>return을 통해 결과 전달</td>
</tr>
<tr>
<td>매개변수</td>
<td>외부 데이터 전달</td>
</tr>
<tr>
<td>오버로딩</td>
<td>같은 이름의 메서드 여러 개 정의</td>
</tr>
<tr>
<td>가변인자</td>
<td>개수가 정해지지 않은 매개변수</td>
</tr>
</tbody></table>
<pre><code class="language-java">class Calculator {

    int sum(int a, int b) {
        return a + b;
    }

    String sum(String a, String b) {
        return a + b;
    }

    int sum(int... numbers) {

        int result = 0;

        for (int value : numbers) {
            result += value;
        }

        return result;
    }
}</code></pre>
<ul>
<li>메서드 이름은 동일</li>
<li>매개변수 타입이나 개수가 다르면 오버로딩 가능</li>
<li>가변인자로 여러 개의 값 처리 가능</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>코드 가독성 향상</td>
<td>같은 기능을 같은 이름으로 표현</td>
</tr>
<tr>
<td>컴파일러 구분 기준 이해</td>
<td>매개변수 타입과 개수</td>
</tr>
<tr>
<td>오버로딩 활용 가능</td>
<td>다양한 입력 처리 가능</td>
</tr>
</tbody></table>
<hr>
<h1 id="4-생성자constructor">4) 생성자(Constructor)</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>생성자</td>
<td>객체 생성 시 호출</td>
</tr>
<tr>
<td>기본 생성자</td>
<td>매개변수가 없는 생성자</td>
</tr>
<tr>
<td>생성자 오버로딩</td>
<td>여러 생성자 정의 가능</td>
</tr>
<tr>
<td>this()</td>
<td>다른 생성자 호출</td>
</tr>
</tbody></table>
<pre><code class="language-java">class User {

    String name;
    int age;
    String address;

    User() {
    }

    User(String name, int age) {

        this.name = name;
        this.age = age;
    }

    User(String name, int age, String address) {

        this(name, age);
        this.address = address;
    }
}</code></pre>
<ul>
<li>생성 시 초기값 설정</li>
<li>생성자끼리 재사용 가능</li>
<li>this()를 이용해 코드 중복 제거</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>객체 초기화 중요</td>
<td>생성 시점에 데이터 설정 가능</td>
</tr>
<tr>
<td>코드 재사용 가능</td>
<td>생성자 연결 활용</td>
</tr>
<tr>
<td>this 활용 숙지</td>
<td>필드와 매개변수 구분</td>
</tr>
</tbody></table>
<hr>
<h1 id="5-static">5) static</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>static 변수</td>
<td>클래스가 공유</td>
</tr>
<tr>
<td>static 메서드</td>
<td>객체 생성 없이 호출</td>
</tr>
<tr>
<td>final static</td>
<td>상수 선언</td>
</tr>
<tr>
<td>클래스 영역</td>
<td>프로그램 시작 시 생성</td>
</tr>
</tbody></table>
<pre><code class="language-java">class CounterUtil {

    static int count = 0;

    static void increase() {
        count++;
    }

    static void increase(int amount) {
        count += amount;
    }
}</code></pre>
<ul>
<li>객체 없이 사용 가능</li>
<li>모든 객체가 같은 값 공유</li>
<li>유틸리티 기능 구현에 적합</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>객체와 클래스 구분</td>
<td>static은 클래스 소속</td>
</tr>
<tr>
<td>메모리 절약</td>
<td>객체마다 생성되지 않음</td>
</tr>
<tr>
<td>활용도 높음</td>
<td>유틸 클래스에 자주 사용</td>
</tr>
</tbody></table>
<hr>
<h1 id="6-nested-class와-static-내부-클래스">6) Nested Class와 static 내부 클래스</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Nested Class</td>
<td>클래스 내부 클래스</td>
</tr>
<tr>
<td>static 내부 클래스</td>
<td>외부 객체 없이 사용</td>
</tr>
<tr>
<td>record</td>
<td>데이터 저장 전용 객체</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class Example06 {

    public static class Helper {

        static int count;

        public static void increase() {
            count++;
        }
    }
}</code></pre>
<ul>
<li>특정 클래스 내부에 정의</li>
<li>관련 기능을 묶어서 관리 가능</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>코드 구조화 가능</td>
<td>관련 클래스 묶기</td>
</tr>
<tr>
<td>유지보수 향상</td>
<td>응집도 증가</td>
</tr>
<tr>
<td>record 활용 가능</td>
<td>DTO 대체 가능</td>
</tr>
</tbody></table>
<hr>
<h1 id="7-접근-제어자">7) 접근 제어자</h1>
<table>
<thead>
<tr>
<th>접근제어자</th>
<th>같은 클래스</th>
<th>같은 패키지</th>
<th>다른 패키지</th>
<th>상속</th>
</tr>
</thead>
<tbody><tr>
<td>private</td>
<td>O</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td>default</td>
<td>O</td>
<td>O</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td>protected</td>
<td>O</td>
<td>O</td>
<td>X</td>
<td>O</td>
</tr>
<tr>
<td>public</td>
<td>O</td>
<td>O</td>
<td>O</td>
<td>O</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class User {

    private String password;

    public String name;
}</code></pre>
<ul>
<li>private은 외부 접근 차단</li>
<li>public은 모든 곳에서 접근 가능</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>정보 은닉 중요</td>
<td>내부 구현 보호</td>
</tr>
<tr>
<td>캡슐화 기반</td>
<td>객체 안정성 증가</td>
</tr>
<tr>
<td>접근 범위 설계 필요</td>
<td>무조건 public 사용 지양</td>
</tr>
</tbody></table>
<hr>
<h1 id="8-캡슐화encapsulation">8) 캡슐화(Encapsulation)</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>캡슐화</td>
<td>데이터 보호</td>
</tr>
<tr>
<td>Getter</td>
<td>데이터 조회</td>
</tr>
<tr>
<td>Setter</td>
<td>데이터 수정</td>
</tr>
<tr>
<td>검증 로직</td>
<td>잘못된 데이터 차단</td>
</tr>
<tr>
<td>방어적 복사</td>
<td>원본 데이터 보호</td>
</tr>
</tbody></table>
<pre><code class="language-java">class ProductManager {

    private String productName;
    private int productPrice;

    public void setProductName(String productName) {

        if(productName.length() != 3){
            return;
        }

        this.productName = productName;
    }

    public void setProductPrice(int productPrice) {

        if(productPrice &lt; 1 || productPrice &gt; 1000){
            return;
        }

        this.productPrice = productPrice;
    }
}</code></pre>
<ul>
<li>직접 수정 불가</li>
<li>Setter를 통한 검증 수행</li>
<li>객체 상태 보호 가능</li>
</ul>
<table>
<thead>
<tr>
<th>느낀 점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>데이터 무결성 확보</td>
<td>잘못된 값 차단</td>
</tr>
<tr>
<td>유지보수성 향상</td>
<td>검증 로직 집중</td>
</tr>
<tr>
<td>객체지향 핵심 원칙 체감</td>
<td>캡슐화의 필요성 이해</td>
</tr>
</tbody></table>
<hr>
<h1 id="🚨-직면한-문제와-해결-과정">🚨 직면한 문제와 해결 과정</h1>
<h2 id="문제-1-static은-언제-사용하는가">문제 1. static은 언제 사용하는가?</h2>
<h3 id="고민한-내용">고민한 내용</h3>
<table>
<thead>
<tr>
<th>의문점</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>객체 없이 사용하는 이유는?</td>
<td>굳이 new를 해야 하나?</td>
</tr>
<tr>
<td>인스턴스 변수와 차이는?</td>
<td>메모리 구조가 다른가?</td>
</tr>
</tbody></table>
<h3 id="시도한-방법">시도한 방법</h3>
<pre><code class="language-java">class User {

    static int totalUserCount;
    int age;
}</code></pre>
<h3 id="해결">해결</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>static 변수</th>
<th>인스턴스 변수</th>
</tr>
</thead>
<tbody><tr>
<td>소속</td>
<td>클래스</td>
<td>객체</td>
</tr>
<tr>
<td>생성 시점</td>
<td>프로그램 실행</td>
<td>객체 생성</td>
</tr>
<tr>
<td>공유 여부</td>
<td>공유</td>
<td>개별 보유</td>
</tr>
<tr>
<td>사용 방식</td>
<td>클래스명.변수</td>
<td>객체명.변수</td>
</tr>
</tbody></table>
<h3 id="배운-점">배운 점</h3>
<ul>
<li>공통 데이터는 static 사용</li>
<li>객체 상태 데이터는 인스턴스 변수 사용</li>
</ul>
<hr>
<h2 id="문제-2-오버로딩은-왜-필요한가">문제 2. 오버로딩은 왜 필요한가?</h2>
<h3 id="고민한-내용-1">고민한 내용</h3>
<pre><code class="language-java">calculator.sum(1, 2);
calculator.sum(&quot;1&quot;, &quot;2&quot;);
calculator.sum(1, 2, 3);</code></pre>
<h3 id="해결-1">해결</h3>
<table>
<thead>
<tr>
<th>장점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>가독성</td>
<td>같은 기능은 같은 이름</td>
</tr>
<tr>
<td>편의성</td>
<td>다양한 입력 처리</td>
</tr>
<tr>
<td>유지보수</td>
<td>메서드 이름 증가 방지</td>
</tr>
</tbody></table>
<h3 id="배운-점-1">배운 점</h3>
<ul>
<li>이름보다 행위가 중요</li>
<li>매개변수로 기능 구분 가능</li>
</ul>
<hr>
<h2 id="문제-3-객체object와-인스턴스instance의-차이">문제 3. 객체(Object)와 인스턴스(Instance)의 차이</h2>
<h3 id="고민한-내용-2">고민한 내용</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>헷갈린 이유</th>
</tr>
</thead>
<tbody><tr>
<td>객체</td>
<td>실제 데이터인가?</td>
</tr>
<tr>
<td>인스턴스</td>
<td>객체와 같은 말인가?</td>
</tr>
</tbody></table>
<h3 id="해결-2">해결</h3>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>클래스</td>
<td>설계도</td>
</tr>
<tr>
<td>객체</td>
<td>설계도로 만든 실체</td>
</tr>
<tr>
<td>인스턴스</td>
<td>메모리에 생성된 객체</td>
</tr>
</tbody></table>
<h3 id="예시">예시</h3>
<pre><code class="language-java">Car myCar = new Car();</code></pre>
<ul>
<li>Car → 클래스</li>
<li>new Car() → 객체</li>
<li>myCar → 객체를 참조하는 변수</li>
<li>생성된 Car → 인스턴스</li>
</ul>
<h3 id="배운-점-2">배운 점</h3>
<p>객체와 인스턴스는 거의 같은 의미로 사용되지만,</p>
<ul>
<li>클래스 입장에서 보면 &quot;인스턴스&quot;</li>
<li>실제 존재하는 데이터 입장에서 보면 &quot;객체&quot;</li>
</ul>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="최종-정리">최종 정리</h1>
<table>
<thead>
<tr>
<th>핵심 개념</th>
<th>오늘 이해한 내용</th>
</tr>
</thead>
<tbody><tr>
<td>클래스</td>
<td>객체 설계도</td>
</tr>
<tr>
<td>객체</td>
<td>실제 생성된 데이터</td>
</tr>
<tr>
<td>인스턴스</td>
<td>메모리에 존재하는 객체</td>
</tr>
<tr>
<td>생성자</td>
<td>객체 초기화</td>
</tr>
<tr>
<td>this</td>
<td>현재 객체 참조</td>
</tr>
<tr>
<td>static</td>
<td>클래스 공유 자원</td>
</tr>
<tr>
<td>오버로딩</td>
<td>같은 이름의 메서드 여러 개</td>
</tr>
<tr>
<td>접근제어자</td>
<td>접근 범위 제어</td>
</tr>
<tr>
<td>캡슐화</td>
<td>데이터 보호</td>
</tr>
<tr>
<td>Getter/Setter</td>
<td>안전한 데이터 접근</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/06/18 IL (I Learned)]]></title>
            <link>https://velog.io/@baeksh_8/260618-IL-I-Learned</link>
            <guid>https://velog.io/@baeksh_8/260618-IL-I-Learned</guid>
            <pubDate>Tue, 23 Jun 2026 08:32:03 GMT</pubDate>
            <description><![CDATA[<h2 id="java의-반복문-배열-다차원-배열-향상된-for문-list-map-set-컬렉션-프레임워크">Java의 반복문, 배열, 다차원 배열, 향상된 for문, List, Map, Set 컬렉션 프레임워크</h2>
<h1 id="1-while-반복문과-switch-표현식">1) while 반복문과 switch 표현식</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>while 반복문</td>
<td>조건이 참인 동안 반복 수행</td>
</tr>
<tr>
<td>break</td>
<td>반복문 즉시 종료</td>
</tr>
<tr>
<td>switch 표현식</td>
<td>값을 반환할 수 있는 switch 문</td>
</tr>
<tr>
<td>Scanner</td>
<td>사용자 입력 처리</td>
</tr>
<tr>
<td>isBlank()</td>
<td>문자열이 비어있는지 확인</td>
</tr>
</tbody></table>
<pre><code class="language-java">import java.util.Scanner;

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

        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.println(&quot;포켓몬을 선택하세요.&quot;);
            System.out.println(&quot;1. 이상해씨 2. 꼬부기 3. 파이리 4. 피카츄&quot;);

            int selectedNumber = scanner.nextInt();

            String pokemonName = switch (selectedNumber) {
                case 1 -&gt; &quot;이상해씨&quot;;
                case 2 -&gt; &quot;꼬부기&quot;;
                case 3 -&gt; &quot;파이리&quot;;
                case 4 -&gt; &quot;피카츄&quot;;
                default -&gt; &quot;&quot;;
            };

            if (pokemonName.isBlank()) {
                System.out.println(&quot;잘못된 입력입니다.&quot;);
            } else {
                System.out.println(&quot;선택한 포켓몬 : &quot; + pokemonName);
                break;
            }
        }
    }
}</code></pre>
<ul>
<li>사용자가 올바른 번호를 입력할 때까지 반복</li>
<li>switch 표현식으로 포켓몬 이름 반환</li>
<li>빈 문자열 여부를 검사하여 유효성 체크</li>
<li>정상 입력 시 break로 반복 종료</li>
</ul>
<hr>
<h1 id="2-for문과-while문">2) for문과 while문</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>for문</td>
<td>반복 횟수가 명확할 때 사용</td>
</tr>
<tr>
<td>while문</td>
<td>조건 기반 반복</td>
</tr>
<tr>
<td>증감 연산자</td>
<td>++, --</td>
</tr>
<tr>
<td>중첩 반복문</td>
<td>반복문 안에 반복문</td>
</tr>
<tr>
<td>스코프(scope)</td>
<td>변수의 사용 가능 범위</td>
</tr>
</tbody></table>
<pre><code class="language-java">public class Example02 {
    public static void main(String[] args) {

        for (int number = 1; number &lt;= 9; number++) {
            System.out.println(number);
        }

        int count = 1;
        while (count &lt;= 9) {
            System.out.println(count++);
        }

        for (int number = 9; number &gt;= 1; number--) {
            System.out.println(number);
        }

        for (int dan = 1; dan &lt;= 9; dan++) {
            for (int multiplier = 1; multiplier &lt;= 9; multiplier++) {
                System.out.println(dan + &quot; * &quot; + multiplier +
                        &quot; = &quot; + dan * multiplier);
            }
        }
    }
}</code></pre>
<ul>
<li>정방향 반복과 역방향 반복 구현</li>
<li>이중 for문으로 구구단 출력</li>
<li>내부 반복문이 모두 종료된 후 외부 반복문 진행</li>
</ul>
<hr>
<h1 id="03-배열array">03. 배열(Array)</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>배열 선언</td>
<td>같은 자료형 여러 개 저장</td>
</tr>
<tr>
<td>배열 초기화</td>
<td>new 자료형[크기]</td>
</tr>
<tr>
<td>인덱스</td>
<td>0부터 시작</td>
</tr>
<tr>
<td>length</td>
<td>배열 길이</td>
</tr>
<tr>
<td>Arrays.toString()</td>
<td>배열 출력</td>
</tr>
</tbody></table>
<pre><code class="language-java">import java.util.Arrays;

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

        int[] scores = new int[5];

        scores[0] = 100;

        System.out.println(scores[0]);
        System.out.println(scores.length);

        System.out.println(Arrays.toString(scores));

        int[][] matrix = {
                {1,2,3},
                {4,5,6}
        };

        System.out.println(Arrays.deepToString(matrix));
    }
}</code></pre>
<ul>
<li>배열 생성 후 값 저장</li>
<li>배열 길이 확인</li>
<li>2차원 배열 출력</li>
</ul>
<hr>
<h1 id="04-배열-순회와-2차원-배열">04. 배열 순회와 2차원 배열</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>배열 순회</td>
<td>반복문을 이용한 접근</td>
</tr>
<tr>
<td>역순 탐색</td>
<td>length 활용</td>
</tr>
<tr>
<td>2차원 배열</td>
<td>행(row), 열(column) 구조</td>
</tr>
<tr>
<td>이중 반복문</td>
<td>다차원 배열 탐색</td>
</tr>
</tbody></table>
<pre><code class="language-java">String[] fruits = {&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;};

for (int index = 0; index &lt; fruits.length; index++) {
    System.out.println(fruits[index]);
}

for (int index = fruits.length - 1; index &gt;= 0; index--) {
    System.out.println(fruits[index]);
}</code></pre>
<ul>
<li>정방향 탐색</li>
<li>역방향 탐색</li>
<li>2차원 배열은 이중 반복문 사용</li>
</ul>
<hr>
<h1 id="05-향상된-for문-for-each">05. 향상된 for문 (for-each)</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>향상된 for문</td>
<td>배열 요소 순회</td>
</tr>
<tr>
<td>반복 변수</td>
<td>읽기 전용 개념</td>
</tr>
<tr>
<td>다차원 배열 순회</td>
<td>중첩 for-each 가능</td>
</tr>
</tbody></table>
<pre><code class="language-java">char[] alphabetArray = {&#39;a&#39;,&#39;b&#39;,&#39;c&#39;};

for(char alphabet : alphabetArray){
    System.out.println(alphabet);
}</code></pre>
<ul>
<li>배열 요소를 직접 순회</li>
<li>인덱스 없이 접근 가능</li>
<li>읽기 전용으로 사용</li>
</ul>
<hr>
<h1 id="06-list와-arraylist">06. List와 ArrayList</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ArrayList</td>
<td>동적 배열</td>
</tr>
<tr>
<td>List 인터페이스</td>
<td>리스트의 공통 규격</td>
</tr>
<tr>
<td>제네릭</td>
<td>자료형 제한</td>
</tr>
<tr>
<td>add/get/set/remove</td>
<td>주요 메서드</td>
</tr>
</tbody></table>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.List;

public class Example06 {

    public static void main(String[] args) {

        List&lt;Integer&gt; numberList = new ArrayList&lt;&gt;();

        numberList.add(10);
        numberList.add(20);

        System.out.println(numberList.get(0));

        numberList.set(1, 100);

        numberList.remove(0);

        System.out.println(numberList);
    }
}</code></pre>
<ul>
<li>List 타입으로 선언</li>
<li>ArrayList 객체 생성</li>
<li>추가, 조회, 수정, 삭제 수행</li>
</ul>
<hr>
<h1 id="07-map과-hashmap">07. Map과 HashMap</h1>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Map</td>
<td>Key-Value 구조</td>
</tr>
<tr>
<td>HashMap</td>
<td>Map 구현 클래스</td>
</tr>
<tr>
<td>put/get</td>
<td>저장 및 조회</td>
</tr>
<tr>
<td>getOrDefault</td>
<td>기본값 처리</td>
</tr>
<tr>
<td>entrySet</td>
<td>전체 순회</td>
</tr>
</tbody></table>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;

public class Example07 {

    public static void main(String[] args) {

        Map&lt;String, Integer&gt; fruitPriceMap = new HashMap&lt;&gt;();

        fruitPriceMap.put(&quot;사과&quot;, 1000);
        fruitPriceMap.put(&quot;바나나&quot;, 2000);

        System.out.println(fruitPriceMap.get(&quot;사과&quot;));

        fruitPriceMap.put(
                &quot;체리&quot;,
                fruitPriceMap.getOrDefault(&quot;체리&quot;, 0)
        );

        for(Map.Entry&lt;String, Integer&gt; item
                : fruitPriceMap.entrySet()) {

            System.out.println(item.getKey());
            System.out.println(item.getValue());
        }
    }
}</code></pre>
<ul>
<li>Key-Value 구조 사용</li>
<li>getOrDefault로 NullPointerException 예방</li>
<li>entrySet으로 전체 순회</li>
</ul>
<hr>
<h1 id="08-set과-hashset">08. Set과 HashSet</h1>
<h3 id="오늘-배운-내용">오늘 배운 내용</h3>
<table>
<thead>
<tr>
<th>학습 내용</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Set</td>
<td>중복 불가</td>
</tr>
<tr>
<td>HashSet</td>
<td>Set 구현 클래스</td>
</tr>
<tr>
<td>contains</td>
<td>존재 여부 확인</td>
</tr>
<tr>
<td>add/remove</td>
<td>추가 및 삭제</td>
</tr>
</tbody></table>
<h3 id="실습-코드">실습 코드</h3>
<pre><code class="language-java">import java.util.HashSet;
import java.util.Set;

public class Example08 {

    public static void main(String[] args) {

        Set&lt;String&gt; memberSet = new HashSet&lt;&gt;();

        memberSet.add(&quot;철수&quot;);
        memberSet.add(&quot;철수&quot;);
        memberSet.add(&quot;영희&quot;);

        System.out.println(memberSet);

        System.out.println(memberSet.contains(&quot;영희&quot;));

        memberSet.remove(&quot;철수&quot;);
    }
}</code></pre>
<ul>
<li>중복 데이터 저장 불가</li>
<li>contains로 존재 여부 확인</li>
<li>remove로 삭제 가능</li>
</ul>
<hr>
<h1 id="직면한-문제와-해결-과정">직면한 문제와 해결 과정</h1>
<h2 id="문제-1-arraylistinteger-와-listinteger-의-차이">문제 1. ArrayList<Integer> 와 List<Integer> 의 차이</h2>
<h3 id="고민한-내용">고민한 내용</h3>
<pre><code class="language-java">ArrayList&lt;Integer&gt; scoreList = new ArrayList&lt;&gt;();

List&lt;Integer&gt; scoreList = new ArrayList&lt;&gt;();</code></pre>
<table>
<thead>
<tr>
<th>구분</th>
<th>ArrayList 선언</th>
<th>List 선언</th>
</tr>
</thead>
<tbody><tr>
<td>타입</td>
<td>구현 클래스</td>
<td>인터페이스</td>
</tr>
<tr>
<td>사용 가능 메서드</td>
<td>ArrayList 전용 + List 메서드</td>
<td>List가 제공하는 메서드만</td>
</tr>
<tr>
<td>유연성</td>
<td>낮음</td>
<td>높음</td>
</tr>
<tr>
<td>실무 사용</td>
<td>상대적으로 적음</td>
<td>권장</td>
</tr>
</tbody></table>
<h3 id="해결하기-위해-시도한-것">해결하기 위해 시도한 것</h3>
<table>
<thead>
<tr>
<th>시도</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>ArrayList로 선언</td>
<td>정상 동작</td>
</tr>
<tr>
<td>List로 선언</td>
<td>정상 동작</td>
</tr>
<tr>
<td>메서드 사용 범위 비교</td>
<td>인터페이스 기준으로 제한됨 확인</td>
</tr>
</tbody></table>
<h3 id="결론">결론</h3>
<pre><code class="language-java">List&lt;Integer&gt; scoreList = new ArrayList&lt;&gt;();</code></pre>
<p>실무에서는 구현체보다 인터페이스에 의존하는 것이 객체지향 원칙에 더 적합하다.</p>
<hr>
<h2 id="문제-2-hashmapstringinteger-와-mapstringinteger-의-차이">문제 2. HashMap&lt;String,Integer&gt; 와 Map&lt;String,Integer&gt; 의 차이</h2>
<h3 id="고민한-내용-1">고민한 내용</h3>
<pre><code class="language-java">HashMap&lt;String, Integer&gt; productMap = new HashMap&lt;&gt;();

Map&lt;String, Integer&gt; productMap = new HashMap&lt;&gt;();</code></pre>
<h3 id="해결하기-위해-시도한-것-1">해결하기 위해 시도한 것</h3>
<table>
<thead>
<tr>
<th>시도</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>HashMap 타입 선언</td>
<td>사용 가능</td>
</tr>
<tr>
<td>Map 타입 선언</td>
<td>사용 가능</td>
</tr>
<tr>
<td>구현체 변경 가정</td>
<td>Map이 훨씬 유리</td>
</tr>
</tbody></table>
<h3 id="차이점-정리">차이점 정리</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>HashMap</th>
<th>Map</th>
</tr>
</thead>
<tbody><tr>
<td>종류</td>
<td>구현 클래스</td>
<td>인터페이스</td>
</tr>
<tr>
<td>역할</td>
<td>실제 동작 구현</td>
<td>규격 정의</td>
</tr>
<tr>
<td>유연성</td>
<td>낮음</td>
<td>높음</td>
</tr>
<tr>
<td>실무 권장</td>
<td>△</td>
<td>◎</td>
</tr>
</tbody></table>
<h3 id="이를-통해-배운-점">이를 통해 배운 점</h3>
<p>객체지향 프로그래밍에서는 <strong>구현체(HashMap, ArrayList)가 아니라 인터페이스(Map, List)에 의존하도록 설계하는 것이 유지보수와 확장성 측면에서 유리하다.</strong></p>
<pre><code class="language-java">Map&lt;String, Integer&gt; itemMap = new HashMap&lt;&gt;();
List&lt;Integer&gt; scoreList = new ArrayList&lt;&gt;();</code></pre>
<p>앞으로 컬렉션을 선언할 때는 특별한 이유가 없다면 인터페이스 타입(List, Map, Set)을 우선 사용하는 습관을 가져야겠다고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/05/28 IL (I Learned)]]></title>
            <link>https://velog.io/@baeksh_8/260528-ILI-Learned</link>
            <guid>https://velog.io/@baeksh_8/260528-ILI-Learned</guid>
            <pubDate>Wed, 03 Jun 2026 13:35:19 GMT</pubDate>
            <description><![CDATA[<h2 id="1-🌐-window">1. 🌐 window</h2>
<p>자바스크립트 최상위 전역 객체인 <code>window</code>
우리가 쓰는 <code>alert()</code>, <code>setTimeout()</code>, 그리고 HTML 문서를 다루는 <code>document</code>까지 전부 이 <code>window</code>라는 객체의 주머니 속에 들어있다.</p>
<pre><code class="language-javascript">console.log(this); // 브라우저 환경에선 이게 바로 window 객체
window.alert(&quot;안녕&quot;); // 원래는 이렇게 써야 하지만
alert(&quot;안녕&quot;); // 전역 객체라서 window. 을 생략해도 브라우저가 다 알아듣는다.
</code></pre>
<hr>
<h2 id="2-dom-document-object-model-html을-객체-트리로-바꾼-지도">2. DOM (Document Object Model): HTML을 객체 트리로 바꾼 지도</h2>
<p>브라우저는 텍스트로 된 HTML 파일을 읽으면, 자바스크립트가 알아들을 수 있는 <strong>객체(Object)들의 거대한 나무 트리 구조</strong>로 메모리에 다시 그린다. 이게 바로 DOM이다.</p>
<p>과거에 쓰던 <code>getElementsByClassName</code> 같은 구식 API는 브라우저 상태가 바뀔 때마다 값이 실시간으로 변해서 버그를 만들기 쉬웠다. 이제는 CSS 선택자 문법을 그대로 쓰는 <code>querySelector</code> 시리즈가 표준이다.</p>
<pre><code class="language-javascript">const p1 = document.querySelector(&quot;p&quot;); 
// -&gt; 문서 전체에서 &#39;가장 먼저 나오는 딱 하나의 p 태그&#39;만 가져옴

const p4 = document.querySelectorAll(&quot;.job&quot;); 
// -&gt; 클래스명이 &#39;job&#39;인 모든 p 태그들을 묶어서 NodeList라는 주머니에 담아줘
// 주머니 통째로는 다루기 힘드니 p4[0], p4[1]처럼 인덱스로 꺼내 쓰거나 
// ...p4 (스프레드 연산자)로 알맹이만 풀어서 쓴다.
</code></pre>
<h3 id="textcontent-vs-innerhtml-보안의-핵심"><code>textContent</code> vs <code>innerHTML</code> (보안의 핵심)</h3>
<p>자바스크립트로 화면에 글자를 집어넣을 때, 무심코 <code>innerHTML</code>을 쓰면 대형 사고가 날 수 있다.</p>
<pre><code class="language-javascript">// ❌ 위험한 코드
box.innerHTML = `&lt;button onclick=&quot;alert(&#39;해킹!&#39;)&quot;&gt;클릭&lt;/button&gt;`;
</code></pre>
<p><code>innerHTML</code>은 사용자가 입력한 문자열에 <code>&lt;script&gt;</code>나 <code>onclick</code> 같은 위험한 HTML 마크업이 섞여 있어도 그걸 그대로 브라우저에 실행시켜 버린다. (이걸 <strong>XSS 공격</strong>이라고 한다.)</p>
<pre><code class="language-javascript">box.textContent = content;
</code></pre>
<p>반면 <code>textContent</code>는 주입된 문자열을 단순 &#39;텍스트&#39;로만 취급한다. 사용자가 아무리 악성 스크립트를 밀어 넣어도 화면에는 그냥 글자 그대로 보여주기 때문에 훨씬 안전하다. 게다가 브라우저가 화면을 새로 계산(렌더링)할 필요가 없어서 속도도 훨씬 빠르다.</p>
<hr>
<h2 id="3-event-유저의-행동에-반응하는-신호">3. Event: 유저의 행동에 반응하는 신호</h2>
<p>이벤트는 유저가 클릭을 하거나, 키보드를 누르는 등의 &#39;사건&#39;을 자바스크립트에게 알려주는 신호다.</p>
<h3 id="이벤트-연결-방식의-진화-03_event-listenerhtml">이벤트 연결 방식의 진화 (<code>03_event-listener.html</code>)</h3>
<p>이벤트를 연결하는 방식은 크게 세 단계를 거쳐 발전했다.</p>
<ol>
<li><strong>인라인 방식:</strong> <code>&lt;button onclick=&quot;...&quot;&gt;</code> (HTML과 JS가 뒤섞여 유지보수가 최악이라 절대 금지)</li>
<li><strong>DOM 프로퍼티 방식:</strong> <code>btn.onclick = function() { ... }</code></li>
</ol>
<ul>
<li>치명적인 약점이 있다. 변수에 값을 대입하는 형식이라 새로운 함수를 등록하면 <strong>이전 함수가 지워지고 덮어쓰기</strong>가 된다.</li>
</ul>
<ol start="3">
<li><strong><code>addEventListener</code> (현대 표준):</strong></li>
</ol>
<pre><code class="language-javascript">btn3.addEventListener(&quot;click&quot;, () =&gt; { console.log(&quot;첫 번째 할 일&quot;); });
btn3.addEventListener(&quot;click&quot;, () =&gt; { console.log(&quot;두 번째 할 일&quot;); });
// 덮어쓰지 않고 대기열에 쌓여서 두 작업이 모두 안전하게 실행된다.
</code></pre>
<h3 id="폼-제출을-멈추는-마법">폼 제출을 멈추는 마법</h3>
<pre><code class="language-javascript">form.addEventListener(&quot;submit&quot;, (event) =&gt; {
  event.preventDefault(); // 브라우저의 기본 행동을 막는다.
});
</code></pre>
<p>HTML의 <code>&lt;form&gt;</code> 태그는 제출(<code>submit</code>) 버튼을 누르면 무조건 페이지를 새로고침하면서 서버로 날아가려는 기본 동작이 있다. 현대 웹(SPA)에서는 페이지 새로고침 없이 자바스크립트가 부드럽게 데이터를 처리해야 하므로, <code>event.preventDefault()</code>를 최상단에 적어 브라우저의 기본 동작을 억제해야 한다.</p>
<hr>
<h2 id="4-bom--web-storage">4. BOM &amp; Web Storage</h2>
<p>BOM은 주소창, 타이머, 방문 기록 등 HTML 문서 바깥의 &#39;브라우저 자체&#39;를 제어하는 도구 모음이다. 오늘 가장 흥미로웠던 부분은 데이터를 브라우저에 저장하는 <strong>Web Storage</strong>와 <strong>클립보드 복사 API</strong>다.</p>
<h3 id="sessionstorage-vs-localstorage">sessionStorage vs localStorage</h3>
<p>두 저장소 모두 서버를 거치지 않고 내 브라우저에 키-값 형태로 데이터를 쓱 저장해 두는 공간이다.</p>
<ul>
<li><strong><code>sessionStorage</code>:</strong> 현재 열려 있는 탭(세션) 안에서만 살아있다. <strong>탭을 닫으면 데이터가 사라진다.</strong> (임시 입력 폼 저장)</li>
<li><strong><code>localStorage</code>:</strong> 컴퓨터를 끄든 브라우저를 닫든 <strong>지우기 전까지 영원히 살아있다.</strong> (다크모드 설정값 유지)</li>
</ul>
<h3 id="스토리지-쓸-때-주의점">스토리지 쓸 때 주의점</h3>
<p>웹 스토리지는 무조건 문자열(String)만 저장한다. 자바스크립트 객체(<code>{}</code>)를 그대로 넣으면 브라우저가 강제로 문자로 바꾸면서 데이터가 깨져버린다.</p>
<pre><code class="language-javascript">const o = { name: &quot;윌&quot;, job: &quot;백반집 사장&quot; };

// ❌ 나쁜 예시
localStorage.setItem(&quot;o&quot;, o); 
console.log(localStorage.getItem(&quot;o&quot;)); // 출력: &quot;[object Object]&quot; (데이터가 다 깨져서 복구 불가)

//  좋은 예시 (JSON)
localStorage.setItem(&quot;o2&quot;, JSON.stringify(o)); 
// -&gt; 객체를 문자열 형태(&#39;{&quot;name&quot;:&quot;윌&quot;,...}&#39;)로 변환(직렬화)해서 안전하게 보관

const savedO2 = JSON.parse(localStorage.getItem(&quot;o2&quot;));
// -&gt; 꺼낼 때는 다시 진짜 자바스크립트 객체로 조립(역직렬화)
console.log(savedO2.name); // &quot;윌&quot;
</code></pre>
<hr>
<h2 id="5-clipboard-api">5. Clipboard API</h2>
<p>웹 페이지에서 흔히 보는 &quot;링크 복사하기&quot; 버튼의 원리도 직접 구현해 보았다. 최신 브라우저의 <code>navigator.clipboard</code> API를 쓴다.</p>
<pre><code class="language-javascript">document.querySelector(&quot;#copy&quot;).addEventListener(&quot;click&quot;, async () =&gt; {
  try {
    // OS의 클립보드 메모리를 건드리는 건 시간이 걸리는 작업이라 &#39;비동기(async/await)&#39;로 처리한다
    await navigator.clipboard.writeText(&quot;내가 복사하고 싶은 텍스트!&quot;);
    alert(&quot;클립보드에 복사 완료&quot;);
  } catch (error) {
    console.error(&quot;복사 실패:&quot;, error);
  }
});
</code></pre>
<h3 id="브라우저가-클립보드를-보호하는-이유">브라우저가 클립보드를 보호하는 이유</h3>
<p>만약 아무 제약이 없다면, 나쁜 웹사이트가 유저 몰래 자바스크립트로 내 클립보드에 악성 코드를 심거나 내 클립보드에 있던 비밀번호를 훔쳐 갈 수 있을 것이다.
그래서 브라우저는 두 가지 강력한 자물쇠를 채워놨다.</p>
<ol>
<li>반드시 사용자가 버튼을 직접 클릭(<code>click</code> 이벤트)했을 때만 작동할 것</li>
<li>보안이 철저한 <strong><code>https://</code> 환경</strong>에서만 작동할 것</li>
</ol>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[26/05/26 TIL (Today I Learned)]]></title>
            <link>https://velog.io/@baeksh_8/260526-TIL-Today-I-Learned</link>
            <guid>https://velog.io/@baeksh_8/260526-TIL-Today-I-Learned</guid>
            <pubDate>Wed, 27 May 2026 00:42:36 GMT</pubDate>
            <description><![CDATA[<h2 id="javascript-심화-기초-map-set-고차함수-복사-객체지향-클래스-상속-예외처리">JavaScript 심화 기초 (Map, Set, 고차함수, 복사, 객체지향, 클래스, 상속, 예외처리)</h2>
<p>오늘은 자바스크립트에서 단순 문법 수준을 넘어 실제 언어의 동작 원리와 자료 처리 방식, 객체지향 개념까지 폭넓게 학습했다.
처음에는 하나의 개념만 익히는 것이 아니라 너무 많은 개념들이 한 번에 등장해서 내용을 따라가는 것 자체가 쉽지 않았다. 하지만 각 개념이 어떤 문제를 해결하기 위해 등장했는지 이해하면서 조금씩 흐름이 보이기 시작했다.</p>
<p>오늘 학습한 내용은 크게 다음과 같다.</p>
<ul>
<li>Map / Set 자료구조</li>
<li>고차함수(sort, map, filter, reduce)</li>
<li>객체 복사(얕은 복사 / 깊은 복사)</li>
<li>객체지향 프로그래밍 기초</li>
<li>생성자 함수와 class</li>
<li>private field / getter / setter</li>
<li>상속 / static / super</li>
<li>예외 처리(try-catch)</li>
</ul>
<hr>
<h1 id="1-map">1) Map</h1>
<p>기존 객체(Object)도 key-value 구조를 저장할 수 있지만, Map은 key-value 저장을 위해 더 명확하게 설계된 자료구조라는 것을 배웠다.</p>
<pre><code class="language-javascript">const userInfo = {};
userInfo.name = &quot;철수&quot;;
userInfo[&quot;user age&quot;] = 20;</code></pre>
<p>Object는 데이터를 저장할 수 있지만 반복문 사용이 불편하다.</p>
<pre><code class="language-javascript">for (const item of userInfo) {
    console.log(item);
}</code></pre>
<p>이 코드는 실행되지 않는다.</p>
<p>Object는 iterable 객체가 아니기 때문이다.</p>
<p>그래서 Object는 데이터를 꺼내려면:</p>
<pre><code class="language-javascript">Object.keys(userInfo);
Object.values(userInfo);
Object.entries(userInfo);</code></pre>
<p>같은 별도 변환이 필요하다.</p>
<hr>
<p>Map은 더 명확하다.</p>
<pre><code class="language-javascript">const studentMap = new Map();

studentMap.set(&quot;name&quot;, &quot;영희&quot;);
studentMap.set(&quot;age&quot;, 22);</code></pre>
<pre><code class="language-javascript">studentMap.get(&quot;name&quot;);</code></pre>
<pre><code class="language-javascript">studentMap.has(&quot;age&quot;);</code></pre>
<pre><code class="language-javascript">for (const [key, value] of studentMap) {
    console.log(key, value);
}</code></pre>
<p>Map은 단순히 Object와 비슷한 구조가 아니라 <strong>key-value 저장 목적에 특화된 자료구조</strong>이다.</p>
<hr>
<h1 id="2-set">2) Set</h1>
<p>Set은 중복을 허용하지 않는 자료구조다.</p>
<pre><code class="language-javascript">const uniqueNumbers = new Set();

uniqueNumbers.add(100);
uniqueNumbers.add(100);
uniqueNumbers.add(200);</code></pre>
<p>결과:</p>
<pre><code class="language-javascript">Set {100, 200}</code></pre>
<p>같은 값은 자동 제거된다.</p>
<hr>
<p>배열 중복 제거에도 활용 가능하다.</p>
<pre><code class="language-javascript">const duplicatedList = [1, 1, 2, 2, 3, 3];

const cleanedList = [...new Set(duplicatedList)];</code></pre>
<p>결과:</p>
<pre><code class="language-javascript">[1, 2, 3]</code></pre>
<p>Set은 단순한 저장 구조가 아니라 <strong>중복 데이터 정리라는 매우 실용적인 문제를 빠르게 해결하는 자료구조</strong>이다.</p>
<hr>
<h1 id="3-고차함수">3) 고차함수</h1>
<p>고차함수는:</p>
<blockquote>
<p>함수를 인자로 받거나 함수를 반환하는 함수</p>
</blockquote>
<h2 id="sort">sort</h2>
<p>기본 정렬:</p>
<pre><code class="language-javascript">const scoreList = [1, 13, 5, 8];

console.log(scoreList.sort());</code></pre>
<p>결과:</p>
<pre><code class="language-javascript">[1, 13, 5, 8]</code></pre>
<p>예상과 다르게 동작했다.</p>
<p>이유:</p>
<p>기본 sort는 숫자 비교가 아니라 문자열 비교를 하기 때문이다.</p>
<p>그래서 비교 함수를 직접 전달해야 한다.</p>
<pre><code class="language-javascript">scoreList.sort((left, right) =&gt; left - right);</code></pre>
<p>오름차순 정렬.</p>
<p>내림차순</p>
<pre><code class="language-javascript">scoreList.sort((left, right) =&gt; right - left);</code></pre>
<hr>
<h2 id="map">map</h2>
<p>배열의 각 요소를 변환한다.</p>
<pre><code class="language-javascript">const rawNumbers = [1, -2, 3, -4];

const absoluteNumbers = rawNumbers.map((value) =&gt; Math.abs(value));</code></pre>
<p>결과:</p>
<pre><code class="language-javascript">[1, 2, 3, 4]</code></pre>
<p>map은 기존 배열을 수정하지 않고 새로운 배열을 만든다.</p>
<p><strong>원본 보존 + 변환</strong> 구조.</p>
<hr>
<h2 id="filter">filter</h2>
<p>조건에 맞는 데이터만 남긴다.</p>
<pre><code class="language-javascript">const numberPool = [1, 2, 3, 4, 5, 6];

const evenNumbers = numberPool.filter((value) =&gt; value % 2 === 0);</code></pre>
<p>결과:</p>
<pre><code class="language-javascript">[2, 4, 6]</code></pre>
<p>filter는 단순 반복문이 아니라 <strong>조건 기반 데이터 선별 도구</strong>이다.</p>
<hr>
<h2 id="reduce">reduce</h2>
<p>배열을 하나의 값으로 줄인다.</p>
<pre><code class="language-javascript">const basket = [10, 20, 30];

const totalPrice = basket.reduce((prev, current) =&gt; prev + current);</code></pre>
<p>과정:</p>
<pre><code class="language-javascript">10 + 20 = 30
30 + 30 = 60</code></pre>
<p>결과:</p>
<pre><code class="language-javascript">60</code></pre>
<p>reduce는 처음에는 어렵게 느껴졌지만, 결국 &quot;누적 계산&quot; 구조라는 점을 이해했다.</p>
<hr>
<h1 id="4-객체-복사">4) 객체 복사</h1>
<p>오늘 매우 중요하게 배운 개념.</p>
<p>처음에는:</p>
<pre><code class="language-javascript">const originalData = {
    profile: {
        age: 20
    }
};

const copiedData = originalData;</code></pre>
<p>이렇게 하면 복사된다고 생각할 수 있지만, 실제로 같은 객체를 가리킨다.</p>
<hr>
<p>즉:</p>
<pre><code class="language-javascript">originalData.profile.age = 30;</code></pre>
<p>하면</p>
<pre><code class="language-javascript">copiedData.profile.age</code></pre>
<p>도 같이 바뀐다.</p>
<p>이유:</p>
<p>객체는 값이 아니라 메모리 주소를 저장하기 때문이다.</p>
<hr>
<h1 id="얕은-복사">얕은 복사</h1>
<pre><code class="language-javascript">const shallowCopy = { ...originalData };</code></pre>
<p>겉 객체만 복사.</p>
<p>하지만 내부 중첩 객체는 공유.</p>
<hr>
<h1 id="깊은-복사">깊은 복사</h1>
<pre><code class="language-javascript">const deepCopy = structuredClone(originalData);</code></pre>
<p>중첩 객체까지 완전 복사.</p>
<p>복사는 단순히 &quot;같은 값 만들기&quot;가 아니라 <strong>메모리 참조 구조를 이해해야 하는 문제</strong>라는 것을 배웠다.</p>
<hr>
<h1 id="5-객체지향과-생성자-함수">5) 객체지향과 생성자 함수</h1>
<p>객체 리터럴:</p>
<pre><code class="language-javascript">const playerInfo = {
    nickname: &quot;전사&quot;,
    hp: 100
};</code></pre>
<hr>
<p>생성자 함수:</p>
<pre><code class="language-javascript">function UserProfile(userName, department) {
    this.userName = userName;
    this.department = department;
}</code></pre>
<p>생성:</p>
<pre><code class="language-javascript">const member = new UserProfile(&quot;민수&quot;, &quot;개발&quot;);</code></pre>
<p>생성자 함수는 같은 구조의 객체를 여러 개 만들기 위한 템플릿 역할을 한다는 점을 이해했다.</p>
<hr>
<h1 id="6-class">6) class</h1>
<p>현대 객체지향 방식.</p>
<pre><code class="language-javascript">class Member {
    constructor(memberName) {
        this.memberName = memberName;
    }

    sayHello() {
        console.log(`${this.memberName}입니다.`);
    }
}</code></pre>
<p>생성:</p>
<pre><code class="language-javascript">const userA = new Member(&quot;철수&quot;);</code></pre>
<p>class는 생성자 함수보다 훨씬 읽기 쉽고 구조적으로 객체 설계가 명확했다.</p>
<hr>
<h1 id="7-private--getter--setter">7) private / getter / setter</h1>
<p>private:</p>
<pre><code class="language-javascript">class BankAccount {
    #balance = 0;

    get balance() {
        return this.#balance;
    }

    set balance(amount) {
        this.#balance = amount;
    }
}</code></pre>
<p>private은 내부 데이터 보호.</p>
<p>getter/setter는 직접 접근 대신 통제된 접근 방식.</p>
<p>즉 <strong>객체 내부 상태 보호 구조</strong>라는 것을 이해했다.</p>
<hr>
<h1 id="8-상속">8) 상속</h1>
<pre><code class="language-javascript">class Animal {
    speak() {
        console.log(&quot;동물 소리&quot;);
    }
}

class Dog extends Animal {
    speak() {
        super.speak();
        console.log(&quot;멍멍&quot;);
    }
}</code></pre>
<p>상속은 기존 기능 재사용.</p>
<p>super는 부모 기능 호출.</p>
<hr>
<h1 id="9-static">9) static</h1>
<pre><code class="language-javascript">class Counter {
    static totalCount = 0;
}</code></pre>
<p>instance마다 가지는 값이 아니라 클래스 전체가 공유하는 값이라는 점을 이해했다.</p>
<hr>
<h1 id="10-예외-처리">10) 예외 처리</h1>
<p>기본 구조</p>
<pre><code class="language-javascript">try {
    const user = null;
    user.name = &quot;철수&quot;;
} catch (error) {
    console.log(error);
}</code></pre>
<p>에러 발생 시 프로그램 종료 방지.</p>
<hr>
<p>에러 종류 구분:</p>
<pre><code class="language-javascript">if (error instanceof TypeError) {
    console.log(&quot;타입 에러&quot;);
}</code></pre>
<p>예외 처리는 단순 에러 숨기기가 아니라 <strong>프로그램 안정성을 위한 필수 처리</strong>라는 점을 배웠다.</p>
<hr>
<h1 id="직면한-문제">직면한 문제</h1>
<p>오늘 가장 큰 문제는 <strong>너무 많은 개념을 한 번에 학습했다는 점</strong>이었다.</p>
<p>이번 학습에서는 단순 문법 몇 개만 배운 것이 아니라:</p>
<ul>
<li>자료구조</li>
<li>함수형 프로그래밍</li>
<li>메모리 구조</li>
<li>객체지향</li>
<li>클래스</li>
<li>상속</li>
<li>예외 처리</li>
</ul>
<p>까지 한 번에 등장했다.</p>
<p>그래서 개념이 서로 섞여서 처음에는 머릿속에서 정리가 잘 되지 않았다.</p>
<p>특히 어려웠던 부분은:</p>
<ul>
<li>Map과 Object 차이</li>
<li>얕은 복사 / 깊은 복사</li>
<li>this</li>
<li>private field</li>
<li>static</li>
<li>super</li>
<li>reduce</li>
<li>instanceof</li>
</ul>
<p>같은 개념들이었다.</p>
<p>처음에는 각각 따로 보였고 왜 필요한지도 잘 이해되지 않았다.</p>
<p>처음에는 내용을 처음부터 끝까지 한 번에 이해하려고 했다.</p>
<p>하지만 이 방법은 효과적이지 않았다.</p>
<p>새 개념이 계속 추가되면서 이전 개념까지 헷갈리기 시작했기 때문이다.</p>
<p>class를 이해하려고 하면 this가 필요했고,
상속을 이해하려면 class 구조가 먼저 이해되어야 했고,
예외 처리를 보려면 객체 개념도 어느 정도 이해가 필요했다.</p>
<hr>
<p>그래서 방식을 바꿨다.</p>
<h3 id="1-큰-범위를-작은-단위로-나눴다">1. 큰 범위를 작은 단위로 나눴다</h3>
<p>한 번에 전체 이해하려 하지 않고:</p>
<p>PART 1</p>
<ul>
<li>Map</li>
<li>Set</li>
<li>고차함수</li>
</ul>
<p>PART 2</p>
<ul>
<li>복사</li>
<li>객체지향</li>
</ul>
<p>PART 3</p>
<ul>
<li>class</li>
<li>상속</li>
</ul>
<p>PART 4</p>
<ul>
<li>예외처리</li>
</ul>
<p>이렇게 분리해서 학습했다.</p>
<hr>
<h3 id="2-코드-한-줄씩-해석했다">2. 코드 한 줄씩 해석했다</h3>
<p>단순히 결과만 보지 않고:</p>
<pre><code class="language-javascript">const copied = original;</code></pre>
<p>이 코드가</p>
<p>&quot;복사인지&quot;</p>
<p>&quot;주소 공유인지&quot;</p>
<p>직접 해석했다.</p>
<hr>
<h3 id="3-개념보다-동작-원리를-먼저-이해하려고-했다">3. 개념보다 동작 원리를 먼저 이해하려고 했다</h3>
<p>처음엔 reduce 문법이 어렵게 느껴졌지만</p>
<pre><code class="language-javascript">prev + current</code></pre>
<p>흐름을 직접 따라가면서 이해했다.</p>
<hr>
<h3 id="4-chatgpt에게-계속-질문했다">4. ChatGPT에게 계속 질문했다</h3>
<p>헷갈리는 개념을 바로 질문했다.</p>
<ul>
<li>왜 객체 복사가 같이 바뀌지?</li>
<li>왜 super를 먼저 호출해야 하지?</li>
<li>왜 private은 밖에서 못 쓰지?</li>
</ul>
<p>질문하면서 개념을 정리했다.</p>
<hr>
<h3 id="방대한-내용을-작은-개념-단위로-쪼개서-코드-중심으로-이해하기">방대한 내용을 작은 개념 단위로 쪼개서 코드 중심으로 이해하기</h3>
<p>한 번에 암기하려고 하면 실패했지만,</p>
<p>&quot;이 코드가 왜 이렇게 동작하지?&quot;</p>
<p>이 관점으로 접근하니 이해가 훨씬 쉬워졌다.</p>
<p><strong>복잡한 개념일수록 암기보다 구조적인 이해가 중요하다는 점</strong>이었다.</p>
<p>처음에는 내용이 너무 많아서 압도되는 느낌이 있었다.</p>
<p>하지만 자세히 보면 모든 개념은 연결되어 있었다.</p>
<p>각각 따로 떨어진 개념이 아니라 하나의 흐름이었다.</p>
<p>또 하나 배운 점은</p>
<p><strong>이해되지 않는 상태에서 그냥 넘어가면 뒤에서 더 크게 막힌다</strong>는 점이었다.</p>
<p>특히 객체지향 부분은 앞 개념 이해가 부족하면 계속 어려워진다.</p>
<p>그래서 막히는 부분을 바로 정리하는 습관이 중요하다고 느꼈다.</p>
<p>그리고 코드 학습에서 중요한 것은:</p>
<p>단순히 &quot;이 문법을 외우는 것&quot;이 아니라</p>
<p><strong>&quot;왜 이 문법이 필요한가?&quot;</strong></p>
<p>를 이해하는 것이라는 점도 느꼈다.</p>
<hr>
<h1 id="한-줄-회고">한 줄 회고</h1>
<blockquote>
<p>방대한 내용을 한 번에 이해하려 하기보다, 코드를 중심으로 작은 개념 단위로 나누어 동작 원리를 이해하는 방식이 훨씬 효과적이라는 것을 배웠다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>