<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ttt-1-2.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 03 May 2026 06:35:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ttt-1-2.log</title>
            <url>https://velog.velcdn.com/images/ttt-1-2/profile/5646d045-9f8c-481e-95b4-777fce0691ae/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ttt-1-2.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ttt-1-2" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[7] 고급 매핑]]></title>
            <link>https://velog.io/@ttt-1-2/7-%EA%B3%A0%EA%B8%89-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@ttt-1-2/7-%EA%B3%A0%EA%B8%89-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sun, 03 May 2026 06:35:36 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em> </p>
<p>7장에서 다룰 고급 매핑은 다음과 같다:</p>
<ul>
<li>상속 관계 패핑</li>
<li>MappedSuperClass</li>
<li>복합 키와 식별 관계 매핑</li>
<li>조인 테이블</li>
<li>엔티티 하나에 여러 테이블 매핑</li>
</ul>
<hr>
<h2 id="1-상속-관계-매핑">1. 상속 관계 매핑</h2>
<p>객체지향에서는 상속 구조를 사용하지만 데이터베이스는 테이블 구조를 사용한다. 그래서 JPA에서는 상속 구조를 테이블로 변환하는 전략이 필요하다.</p>
<p>대표적으로 3가지 전략이 있다.</p>
<h3 id="11-조인-전력">1.1 조인 전력</h3>
<p>부모와 자식 각각 테이블을 만든다. 자식 테이블은 부모의 PK를 FK로 같이 사용한다. 조회 시에는 JOIN이 필요하다.</p>
<p>장점</p>
<ul>
<li>정규화 구조 유지</li>
<li>데이터 중복 없음</li>
<li>외래키 제약 사용 가능</li>
</ul>
<p>단점</p>
<ul>
<li>JOIN 많이 발생 → 성능 저하 가능</li>
<li>INSERT 시 여러 번 쿼리 실행</li>
</ul>
<p>핵심 설정</p>
<pre><code class="language-java">@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = &quot;DTYPE&quot;)</code></pre>
<h3 id="12-단일-테이블-전략">1.2 단일 테이블 전략</h3>
<p>모든 데이터를 하나의 테이블에 저장한다. DTYPE 컬럼으로 타입을 구분한다.</p>
<p>장점</p>
<ul>
<li>JOIN 없음 → 조회 성능 가장 빠름</li>
<li>구조 단순</li>
</ul>
<p>단점</p>
<ul>
<li>사용하지 않는 컬럼은 null 허용</li>
<li>테이블이 커질 수 있음</li>
</ul>
<p>핵심 설정</p>
<pre><code class="language-java">@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;DTYPE&quot;)</code></pre>
<h3 id="13-구현-클래스마다-테이블-전략">1.3 구현 클래스마다 테이블 전략</h3>
<p>자식 엔티티마다 테이블을 만든다. 각 테이블에 부모 필드까지 모두 포함된다.</p>
<p>장점</p>
<ul>
<li>서브 타입별로 독립적 저장</li>
<li>NOT NULL 제약 사용 가능</li>
</ul>
<p>단점</p>
<ul>
<li>UNION 쿼리 필요 → 성능 안 좋음</li>
<li>비추천 전략</li>
</ul>
<p>핵심 설정</p>
<pre><code class="language-java">@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)</code></pre>
<h2 id="2-mappedsuperclass">2. @MappedSuperClass</h2>
<p><code>@MappedSuperclass</code>는 테이블과 매핑되지 않는 부모 클래스이다. 단순히 공통 필드를 자식 엔티티에 상속하기 위한 용도이다. 데이터베이스에는 테이블이 생성되지 않는다.</p>
<ul>
<li><p>왜 사용하는가: 여러 엔티티에서 공통으로 사용하는 필드가 있다. (ex: id, name 같은 기본 정보. 이런 필드를 부모 클래스에 모아두고 재사용한다)</p>
</li>
<li><p>구조:</p>
<ul>
<li><p>부모 클래스는 @MappedSuperclass로 선언한다.</p>
</li>
<li><p>자식 클래스만 @Entity로 테이블과 매핑된다.</p>
<pre><code class="language-java">@MappedSuperclass
public abstract class BaseEntity {
  @Id @GeneratedValue
  private Long id;
  private String name;
}

@Entity
public class Member extends BaseEntity {
  private String email;
}

@Entity
public class Seller extends BaseEntity {
  private String shopName;
}</code></pre>
</li>
</ul>
</li>
</ul>
<p>특징:</p>
<ul>
<li>부모 클래스는 테이블이 없다</li>
<li>자식 엔티티만 테이블이 생성된다</li>
<li>공통 컬럼을 코드 레벨에서 재사용한다</li>
</ul>
<p>주의점:</p>
<ul>
<li>@MappedSuperclass는 엔티티가 아니다</li>
<li>em.find()나 JPQL에서 조회할 수 없다</li>
<li>보통 abstract 클래스로 만든다</li>
</ul>
<p><strong><em>→ 정리: @MappedSuperclass는 테이블 매핑이 아닌 공통 필드 상속용 구조이다.</em></strong></p>
<h2 id="3-복합-키와-식별-관계-매핑">3. 복합 키와 식별 관계 매핑</h2>
<h3 id="31-식별-관계-vs-비식별-관계">3.1 식별 관계 vs 비식별 관계</h3>
<p>테이블 관계는 외래 키가 기본 키에 포함되는지에 따라 나뉜다.</p>
<ul>
<li>식별 관계 (Identifying Relationship)<ul>
<li>부모의 기본 키를 자식이 내려받는다. 자식은 부모 PK를 포함해서 복합 키를 만든다.</li>
<li>PK + FK 구조</li>
</ul>
</li>
<li>비식별 관계 (Non-identifying Relationship)<ul>
<li>부모의 기본 키를 자식이 외래 키로만 사용한다.</li>
<li>자식은 별도의 PK를 가진다.</li>
<li>필수적 관계: FK에 NULL 불가</li>
<li>선택적 관계: FK에 NULL 허용</li>
</ul>
</li>
</ul>
<h3 id="32-복합-키-비식별-관계-매핑">3.2 복합 키: 비식별 관계 매핑</h3>
<p>복합 키는 2개 이상의 컬럼으로 구성된 PK이다. JPA에서는 단순히 @Id 여러 개로 처리할 수 없다 →식별자 클래스를 만들어야 한다.</p>
<p>JPA는 두 가지 방법을 제공한다.</p>
<ul>
<li>@IdClass: DB 중심, 단순</li>
<li>@EmbeddedId: 객체지향, 깔끔</li>
</ul>
<blockquote>
<p><strong>@IdClass</strong></p>
</blockquote>
<p>DB 중심 방식이다. 엔티티에 여러 @Id를 직접 선언한다.</p>
<pre><code class="language-java">@IdClass(ParentId.class)
class Parent {
    @Id String id1;
    @Id String id2;
}</code></pre>
<blockquote>
<p><strong>@EmbeddedId</strong></p>
</blockquote>
<p>객체지향 방식이다. 식별자 클래스를 엔티티 안에 포함한다.</p>
<pre><code class="language-java">@EmbeddedId
private ParentId id;</code></pre>
<h3 id="33-복합-키-식별-관계-매핑">3.3 복합 키: 식별 관계 매핑</h3>
<ul>
<li>식별 관계 구조: 식별 관계에서는 부모 → 자식 → 손자까지 기본 키가 계속 전파된다.</li>
</ul>
<p>ex:</p>
<ul>
<li>Parent</li>
<li>Child (PK = parent_id + child_id)</li>
<li>GrandChild (PK = parent_id + child_id + grandchild_id)</li>
</ul>
<blockquote>
<p>@IdClass로 식별 관계 매핑</p>
</blockquote>
<p>식별 관계에서는 @Id + @ManyToOne을 같이 사용한다.</p>
<p>Child 엔티티 핵심</p>
<pre><code class="language-java">@Id
@ManyToOne
@JoinColumn(name = &quot;PARENT_ID&quot;)
private Parent parent;

@Id
@Column(name = &quot;CHILD_ID&quot;)
private String childId;</code></pre>
<p>ChildId (식별자 클래스)</p>
<pre><code class="language-java">class ChildId {
    private String parent;   // Parent.id
    private String childId;
}</code></pre>
<p>GrandChild까지 확장</p>
<pre><code class="language-java">@Id
@ManyToOne
@JoinColumns({
    @JoinColumn(name = &quot;PARENT_ID&quot;),
    @JoinColumn(name = &quot;CHILD_ID&quot;)
})
private Child child;

@Id
@Column(name = &quot;GRANDCHILD_ID&quot;)
private String id;</code></pre>
<blockquote>
<p>@EmbeddedId + 식별 관계</p>
</blockquote>
<p>이 경우는 @MapsId 사용이 핵심이다.</p>
<p>Child 엔티티</p>
<pre><code class="language-java">@EmbeddedId
private ChildId id;

@MapsId(&quot;parentId&quot;)
@ManyToOne
@JoinColumn(name = &quot;PARENT_ID&quot;)
private Parent parent;</code></pre>
<p>ChildId</p>
<pre><code class="language-java">@Embeddable
class ChildId {
    private String parentId;
    private String id;
}</code></pre>
<p>GrandChild (EmbeddedId)</p>
<pre><code class="language-java">@EmbeddedId
private GrandChildId id;

@MapsId(&quot;childId&quot;)
@ManyToOne
@JoinColumns({
    @JoinColumn(name = &quot;PARENT_ID&quot;),
    @JoinColumn(name = &quot;CHILD_ID&quot;)
})
private Child child;</code></pre>
<h3 id="34-비식별-관계로-구현">3.4 비식별 관계로 구현</h3>
<p><strong><em>구조 변화</em></strong></p>
<p>Before (식별 관계)</p>
<ul>
<li>PK = 부모 PK 포함</li>
</ul>
<p>After (비식별 관계)</p>
<ul>
<li>PK = 독립적인 값</li>
<li>FK = 따로 존재</li>
</ul>
<pre><code class="language-java">@Id @GeneratedValue
private Long id;

@ManyToOne
@JoinColumn(name = &quot;PARENT_ID&quot;)
private Parent parent;</code></pre>
<h3 id="35-일대일-식별-관계">3.5 일대일 식별 관계</h3>
<p>자식 PK = 부모 PK → 복합 키 필요 없음</p>
<pre><code class="language-java">@Entity
class BoardDetail {

    @Id
    private Long boardId;

    @MapsId
    @OneToOne
    @JoinColumn(name=&quot;BOARD_ID&quot;)
    private Board board;
}</code></pre>
<h3 id="36-식별-vs-비식별-관계-정리">3.6 식별 vs 비식별 관계 정리</h3>
<p><strong><em>식별 관계 단점</em></strong></p>
<ul>
<li>PK가 계속 증가</li>
<li>JOIN 복잡</li>
<li>SQL 복잡</li>
<li>변경 어려움</li>
<li>복합 키 필수</li>
</ul>
<p><strong><em>비식별 관계 장점</em></strong></p>
<ul>
<li>단순한 PK</li>
<li>유지보수 쉬움</li>
<li>변경 유연</li>
<li>JPA 사용 편함</li>
</ul>
<h2 id="4-조인-테이블">4. 조인 테이블</h2>
<p>데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지다.</p>
<ul>
<li>조인 컬럼 사용 (외래 키)</li>
<li>조인 테이블 사용</li>
</ul>
<p>조인 컬럼 vs 조인 테이블</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>조인 컬럼</th>
<th>조인 테이블</th>
</tr>
</thead>
<tbody><tr>
<td>구조</td>
<td>단순</td>
<td>복잡</td>
</tr>
<tr>
<td>테이블 수</td>
<td>2개</td>
<td>3개</td>
</tr>
<tr>
<td>null 처리</td>
<td>필요</td>
<td>불필요</td>
</tr>
<tr>
<td>확장성</td>
<td>낮음</td>
<td>높음</td>
</tr>
</tbody></table>
<p><strong>JPA 매핑</strong></p>
<ul>
<li>조인 컬럼</li>
</ul>
<pre><code class="language-java">@ManyToOne
@JoinColumn(name = &quot;LOCKER_ID&quot;)
private Locker locker;</code></pre>
<ul>
<li>조인 테이블</li>
</ul>
<pre><code class="language-java">@ManyToOne
@JoinTable(
    name = &quot;MEMBER_LOCKER&quot;,
    joinColumns = @JoinColumn(name = &quot;MEMBER_ID&quot;),
    inverseJoinColumns = @JoinColumn(name = &quot;LOCKER_ID&quot;)
)
private Locker locker;</code></pre>
<h2 id="5-엔티티-하나에-여러-테이블-매핑">5. 엔티티 하나에 여러 테이블 매핑</h2>
<p>하나의 엔티티를 여러 테이블에 매핑할 수 있다.</p>
<ul>
<li>@SecondaryTable</li>
</ul>
<pre><code class="language-java">@Entity
@Table(name = &quot;BOARD&quot;)
@SecondaryTable(
    name = &quot;BOARD_DETAIL&quot;,
    pkJoinColumns = @PrimaryKeyJoinColumn(name = &quot;BOARD_DETAIL_ID&quot;)
)
public class Board {

    @Id
    private Long id;

    private String title;

    @Column(table = &quot;BOARD_DETAIL&quot;)
    private String content;
}</code></pre>
<p>동작 방식:</p>
<ul>
<li><p>BOARD → id, title</p>
</li>
<li><p>BOARD_DETAIL → content</p>
</li>
<li><p>기본 키로 조인</p>
</li>
<li><p>여러 테이블 매핑</p>
</li>
</ul>
<pre><code class="language-java">@SecondaryTables({
    @SecondaryTable(name=&quot;BOARD_DETAIL&quot;),
    @SecondaryTable(name=&quot;BOARD_FILE&quot;)
})</code></pre>
<ul>
<li>단점: 항상 조인 발생, 성능 최적화 어려움</li>
<li>권장 방식: 실무에서는: 엔티티 분리 + 연관관계 사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[6] 다양한 연관관계 매핑]]></title>
            <link>https://velog.io/@ttt-1-2/6-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@ttt-1-2/6-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sat, 11 Apr 2026 09:37:11 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em> </p>
<p>6장은 다양한 연관관계를 다룬다 (다대일, 일대다, 일대일, 다대다).</p>
<hr>
<h2 id="1-다대일">1. 다대일</h2>
<ul>
<li>다대일 관계의 반대 방향은 항상 일대다 관계고 일대다 관계의 방향은 항상 다대일 관계다.<ul>
<li>데이터베이스 테이블: 일(1), 다(N) 관계에서 외래 키는 항상 다쪽에 있다.</li>
<li>객체 양방향 관계: 연관관계의 주인은 항상 다쪽이다.</li>
</ul>
</li>
</ul>
<p>ex: 회원(N), 팀(1)이 있으면 회원 쪽이 연관관계의 주인이다.</p>
<h3 id="11-다대일-단방향-n1">1.1 다대일 단방향 [N:1]</h3>
<pre><code class="language-java">// 회원 엔티티

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name=&quot;MEMBER_ID&quot;
    private Long id;

    private String username;

    @ManyToOne
    @JoinColumn(name=&quot;TEAM_ID&quot;)
    private Team team;

    // ...
}</code></pre>
<pre><code class="language-java">// 팀 엔티티

@Entity 
public class Team {

    @Id @GeneratedValue
    @Column(name=&quot;TEAM_ID&quot;)
    private Long id;

    private String name;

    // ...
}</code></pre>
<p>→ 회원은 Member.team으로 팀 엔티티를 참조 할 수 있다. 팀에는 회원을 참조하는 필드가 없다. 회원과 팀은 다대일 <strong><em>단방향 연관관계다.</em></strong></p>
<h3 id="12-다대일-양방향-n1-1n">1.2 다대일 양방향 [N:1, 1:N]</h3>
<pre><code class="language-java">// 회원 엔티티

@ManyToOne
@JoinColumn(name=&quot;TEAM_ID&quot;)
private Team team;</code></pre>
<pre><code class="language-java">// 팀 엔티티

@OneToMany(mappedBy=&quot;team&quot;)
private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;();</code></pre>
<ul>
<li>양방향은 키가 있는 쪽이 연관관계의 주인이다.<ul>
<li><code>Member.team</code> : 외래 키(TEAM_ID)를 관리하는 연관관계의 주인이다.</li>
<li><code>Team.members</code> : <code>mappedBy</code>로 매핑된 주인이 아닌 쪽이다. 조회와 객체 그래프 탐색에만 사용한다.</li>
</ul>
</li>
<li>양방향 연관관계는 항상 서로를 참조해야 한다.</li>
</ul>
<h2 id="2-일대다">2. 일대다</h2>
<p>일대다 관계는 다대일 관계의 반대 반향이다.  일대다 관계는 엔티티를 하나 이상 참조할 수 있어서 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용해야 한다.</p>
<h3 id="21-일대다-단방향-1n">2.1 일대다 단방향 [1:N]</h3>
<p>하나의 팀은 여러 회원을 참조할 수 있는데 이런 관계를 일대다 관계라 한다. 팀은 회원들을 참조하지만 회원은 팀을 참조하지 않으면 둘의 관계는 <strong><em>일대다 단방향</em></strong> 관계다.</p>
<pre><code class="language-java">// 팀 엔티티

@OneToMany
// 일대다 단방향 관계를 매핑할 때는 @JoinColumn을 명시해야 한다.
@JoinColumn(name=&quot;TEAM_ID&quot;) //  MEMBER 테이블의 TEAM_ID (FK)
private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;()&#39;</code></pre>
<pre><code class="language-java">// 회원
@Id @GeneratedValue
@Column(name=&quot;MEMBER_ID&quot;)
private Long id;

private String username;</code></pre>
<ul>
<li>일대다 단방향 매핑의 단점: 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다. Team에서 members를 추가하면 Member 테이블의 <code>team_id</code>를 변경해야 해서 INSERT 이후에 추가 UPDATE 쿼리가 발생한다 → 비효율적</li>
</ul>
<blockquote>
<p><strong>일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자</strong></p>
</blockquote>
<h3 id="22-일대다-양방향-1n-n1">2.2 일대다 양방향 [1:N, N:1]</h3>
<p>일대다 양방향 매핑은 일반적으로 존재하지 않는다. 양방향 매핑에서 연관관계의 주인은 항상 외래 키를 가진 쪽이고, <code>@OneToMany</code>는 주인이 될 수 없고 <code>@ManyToOne</code>에는 <code>mappedBy</code> 속성이 없다.</p>
<p>다만 아래와 같이 일대다를 주인처럼 사용하는 방식으로 “형식적인 양방향” 구현은 가능하다.</p>
<pre><code class="language-java">// 팀 엔티티

@OneToMany
@JoinColumn(name=&quot;TEAM_ID&quot;)
private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;()&#39;</code></pre>
<pre><code class="language-java">// 회원 엔티티

@ManyToOne
@JoinColumn(name=&quot;TEAM_ID&quot;, insertable=false, updatable=false)
private Team team;</code></pre>
<ul>
<li>단점:<ul>
<li>Member 쪽은 읽기 전용이 되고, 연관관계 관리가 Team에만 집중되어 구조가 비정상적이다.</li>
<li>불필요한 UPDATE 쿼리가 발생해 성능상 비효율이 생긴다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>될 수 있으면 다대일 양방향 매핑을 사용하자!</strong></p>
</blockquote>
<h2 id="3-일대일-11">3. 일대일 [1:1]</h2>
<p>일대일 관계는 양쪽이 서로 하나의 관계만 가지는 구조다. (ex: 회원은 하나의 사물함만 사용하고, 사물함도 하나의 회원에 의해서만 사용된다.)</p>
<ul>
<li>일대일 관계는 그 반대 방향에서도 동일하게 일대일 관계가 된다.</li>
<li>외래 키는 주 테이블이나 대상 테이블 둘 중 어느 곳에나 둘 수 있다.<ul>
<li>주 테이블 외래키: 주 테이블에 외래 키를 두고 대상 테이블을 참조한다. 일반적으로 객체지향 설계와 유사해 관리가 편리하다.</li>
<li>대상 테이블 외래키: 대상 테이블에 외래 키를 두는 방식으로, 데이터베이스 설계 중심으로 볼 때 선택할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="31-주-테이블에-외래-키">3.1 주 테이블에 외래 키</h3>
<p>객체지향 개발자들은 주 테이블에 외래 키가 있는 것을 선호한다. JPA도 주 테이블에 외래 키가 있으면 더 직관적이고 편리하게 매핑할 수 있다.</p>
<h4 id="단방향">단방향</h4>
<pre><code class="language-java">// Member
@OneToOne
@JoinColumn(name=&quot;LOCKER_ID&quot;)
private Locker locker;

// Locker
@Id @GeneratedValue
@Column(name=&quot;LOCKER_ID&quot;)
private Long id;</code></pre>
<p>객체 매핑에는 <code>@OneToOne</code>을 사용하고, <code>@JoinColumn</code>으로 외래 키를 지정한다. 이 경우 Member 테이블에 <code>LOCKER_ID</code>가 생성되어 Locker를 참조한다.</p>
<p>이 구조는 다대일 단방향과 거의 동일하게 동작하고 관계의 최대 개수가 1:1이라는 점만 다르다.</p>
<h4 id="양방향">양방향</h4>
<pre><code class="language-java">// Member
@OneToOne
@JoinColumn(name=&quot;LOCKER_ID&quot;)
private Locker locker;

// Locker
@OneToOne(mappedBy=&quot;locker&quot;)
private Member member;
</code></pre>
<p>MEMBER 테이블이 외래 키를 가지고 있어서 Member 엔티티에 있는 Member.locker가 연관관계의 주인이다.</p>
<p>반대 매핑인 사물한의 Locker.member는 mappedBy를 선언해서 연관관계의 주인이 아니라고 설정했다.</p>
<h3 id="32-대상-테이블에-외래-키">3.2 대상 테이블에 외래 키</h3>
<h4 id="단방향-1">단방향</h4>
<p>일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다.</p>
<p>JPA2.0부터 일대다 단방향 관계에서 대상 테이블에 외래 키가 있는 매핑을 허용했다. 하지만 일대일 단방향은 이런 매핑을 허용하지 않는다.</p>
<h4 id="양방향-1">양방향</h4>
<pre><code class="language-java">// Member
@OneToOne(mappedBy=&quot;member&quot;)
private Locker locker;

// Locker
@OneToOne
@JoinColumn(name=&quot;MEMBER_ID&quot;)
private Member member;</code></pre>
<p>이 경우 대상 엔티티인 Locker가 연관관계의 주인이 되며, <code>MEMBER_ID</code> 외래 키를 통해 관계를 관리한다. Member는 <code>mappedBy</code>를 사용해 읽기 전용으로 관계를 조회만 한다.</p>
<h2 id="4-다대다-nn">4. 다대다 [N:N]</h2>
<ul>
<li>관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.</li>
<li>객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다. ex: 회원 객체는 컬렉션을 사용해서 상품들을 참조하면 되고 반대로 상품들도 컬렉션을 사용해서 회원들을 참조하면 된다.</li>
</ul>
<h3 id="41-다대다-단방향">4.1 다대다: 단방향</h3>
<pre><code class="language-java">// Member
@ManyToMany
@JoinTable(name=&quot;MEMBER_PRODUCT&quot;, 
                    joinColumns = @JoinColumn(name=&quot;MEMBER_ID&quot;),
                    inverseJoinColumns = @JoinColumn(name=&quot;PRODUCT_ID&quot;)
private List&lt;Product&gt; products = new ArrayList&lt;Product&gt;();</code></pre>
<pre><code class="language-java">// Product
@Id @Column(name=&quot;PRODUCT_ID&quot;)
private String id;</code></pre>
<p>회원 엔티티와 상품 엔티티를 <code>@ManyToMany</code> 로 매핑했다. 중요한 포인트: <code>@ManyToMany</code> 와 <code>@JoinTable</code> 을 사용해서 연결 테이블을 바로 매핑했다.</p>
<p>속성을 정리하면:</p>
<ul>
<li><code>@JoinTable.name</code> : 연결 테이블 지정</li>
<li><code>@JoinTable.joinColumns</code> : 현재 방향인 회원과 매핑할 조인 컬럼 정보 지정</li>
<li><code>@JoinTable.inverseJoinColumns</code> : 반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정</li>
</ul>
<h3 id="42-다대다-양방향">4.2 다대다: 양방향</h3>
<p>다대다 매핑이므로 역방향도 <code>@ManyToMany</code> 를 사용한다. 양쪽 중 원하는 곳에 mappedBy로 연관관계의 주인을 지정한다.</p>
<pre><code class="language-java">// 역방향 추가
// Product

@ManyToMany(mappedBy=&quot;products&quot;) 
private List&lt;Member&gt; members;</code></pre>
<h3 id="43-다대다-매핑의-한계와-극복-연결-엔티티-사용">4.3 다대다: 매핑의 한계와 극복, 연결 엔티티 사용</h3>
<p><code>@ManyToMany</code></p>
<ul>
<li>장점: 연결 테이블을 자동으로 처리해주므로 도메인 모델이 단순해지고 여러 가지로 편리하다.</li>
<li>한계: 예를 들어 회원이 상품을 주문하면 단순히 회원 ID와 상품 ID만 저장하는 것이 아니라 주문 수량, 주문 날짜와 같은 추가 정보가 필요하다. 하지만 <code>@ManyToMany</code>에서는 연결 테이블이 엔티티로 존재하지 않기 때문에 이러한 컬럼을 매핑할 수 없다.</li>
</ul>
<p>→ 이를 해결하기 위해 연결 테이블을 별도의 엔티티로 만들어 다대다 관계를 일대다, 다대일 관계로 풀어야 한다.</p>
<pre><code class="language-java">// Member
@Id @Column(name=&quot;MEMBER_ID&quot;)
private String id;

@OneToMany(mappedBy = &quot;member&quot;)
private List&lt;MemberProduct&gt; memberProducts;</code></pre>
<pre><code class="language-java">// Product
@Id @Column(name=&quot;PRODUCT_ID&quot;)
private String id;

private String name;</code></pre>
<pre><code class="language-java">// MemberProduct (회원상품 엔티티)

@Id
@ManyToOne
@JoinColumn(name=&quot;MEMBER_ID&quot;)
private Member member; // MemberProductId.member와 연결

@Id
@ManyToOne
@JoinColumn(name=&quot;PRODUCT_ID&quot;)
private Product product; // MemberProductId.product와 연결</code></pre>
<p>위 예제에서 <code>MemberProduct</code> 엔티티는 <code>member</code>와 <code>product</code>를 함께 기본 키로 사용하는 복합 키 구조를 가진다. 즉, <code>MEMBER_ID</code>와 <code>PRODUCT_ID</code>를 조합해 하나의 식별자로 사용하며, 같은 회원과 상품의 조합은 하나만 존재할 수 있다.</p>
<p>회원상품은 회원과 상품의 기본 키를 받아서 자신의 기본 키로 사용한다. 이처럼 부모 엔티티의 기본 키를 자식 엔티티의 기본 키로 함께 사용하는 관계를 식별관계(Identifying Relationship) 라고 한다. </p>
<p>→ 복합 키를 사용하는 방법이 복잡하다.</p>
<h3 id="44-다대다-새로운-기본키-사용">4.4 다대다: 새로운 기본키 사용</h3>
<p>추천하는 방식은 데이터베이스에서 자동으로 생성되는 대리 키(Long 타입)를 기본 키로 사용하는 것이다. 기존의 복합 키 대신 <code>ORDER_ID</code>와 같은 단일 기본 키를 두고, <code>MEMBER_ID</code>, <code>PRODUCT_ID</code>는 외래 키로만 사용한다.</p>
<pre><code class="language-java">// Order
@Id @GeneratedValue
@Column(name=&quot;ORDER_ID&quot;)
private Long id;

@ManyToOne
@JoinColumn(name=&quot;MEMBER_ID&quot;)
private Member member;

@ManyToOne
@JoinColumn(name=&quot;PRODUCT_ID&quot;)
private Product product;</code></pre>
<pre><code class="language-java">// Member
@OneToMany(mappedBy=&quot;member&quot;)
private List&lt;Order&gt; orders = new ArrayList&lt;Order&gt;();

// Product
@Id @Column(name=&quot;PRODUCT_ID&quot;)
private String id;
private String name;</code></pre>
<p>이 방식은 엔티티가 독립적인 식별자를 가지게 되어 식별관계가 아닌 비식별관계로 구성된다. 그 결과 매핑이 단순해지고, 코드 작성과 유지보수가 훨씬 수월해진다.</p>
<blockquote>
<p><strong>다대다 연관관계 정리</strong></p>
</blockquote>
<p>다대다 관계를 일대다 다대일 관계로 풀어내기 위해 연결 테이블을 만들 때 식별자를 어떻게 구성할지 선택해야 한다.</p>
<ul>
<li><strong>식별 관계:</strong> 받아온 식별자를 기본 키 + 외래 키로 사용한다.</li>
<li><strong>비식별 관계:</strong> 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[5] 연관관계 매핑 기초]]></title>
            <link>https://velog.io/@ttt-1-2/5-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@ttt-1-2/5-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sun, 05 Apr 2026 06:06:18 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em></p>
<p>5장의 목표는 객체의 참조와 테이블의 외래 키를 매핑하는 것이다.</p>
<p>5장의 핵심 키워드:</p>
<ul>
<li>방향 (direction): 단방향, 양방향이 있다. 방향은 객체관계에만 존재하고 테으블 관계는 항상 양방향이다.</li>
<li>다중성 (multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 다중성이 있다.</li>
<li>연관관계 주인 (owner): 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.</li>
</ul>
<hr>
<h2 id="1-단방향-연관관계">1. 단방향 연관관계</h2>
<blockquote>
<p><strong>ex: 회원과 팀의 관계를 알아보자</strong></p>
</blockquote>
<ul>
<li>회원과 팀이 잇다</li>
<li>회원은 하나의 팀에만 소속될 수 있다</li>
<li>회원과 팀은 다대일 관계다</li>
</ul>
<blockquote>
<p><strong>객체 연관관계</strong></p>
</blockquote>
<ul>
<li>회원 객체는 Member.team 필드(멤버 볌수)로 팀 객체와 연관관계를 맺는다</li>
<li>회원 객체와 팀 객체: 단방향 관계다.<ul>
<li>회원: Member.team 필드를 통해 팀을 알 수 있다 (member.getTeam()으로 조회)</li>
<li>팀: 회원을 알 수 없다 (team → member 접근 필드 없음)</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>테이블 연관관계</strong></p>
</blockquote>
<ul>
<li>회원 테이블은: TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.</li>
<li>회원 테이블과 팀 테이블: 양방향 관계.<ul>
<li>회원: TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있다</li>
<li>팀: 회원도 조인할 수 있다</li>
</ul>
</li>
</ul>
<pre><code class="language-sql">-- 회원과 팀을 조인하는 SQL
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.ID

-- 팀과 회원을 조인하는 SQL
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.ID = M.TEAM_ID</code></pre>
<h3 id="11-순수한-객체-연관관계">1.1 순수한 객체 연관관계</h3>
<ul>
<li>객체는 참조를 사용해서 연관관계를 탐색하는 것이 객체 그래프 탐색이라 한다.</li>
</ul>
<pre><code class="language-java">member1.setTeam(team1);
member2.setTeam(team1);

Team findTeam = member1.getTeam();</code></pre>
<h3 id="12-테이블-연관관계">1.2 테이블 연관관계</h3>
<p>데이터베이스는 외래 키를 사용해서 연관관계를 탐색하는 것이 조인이라 한다.</p>
<pre><code class="language-java">SELECT T.*
FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE M.MEMBER_ID = &#39;member1&#39;</code></pre>
<h3 id="13-객체-관계-매핑">1.3 객체 관계 매핑</h3>
<p>ex: 회원 엔티티 매핑</p>
<pre><code class="language-java">@ManyToOne
@JoinColumn(name=&quot;TEAM_ID&quot;)
private Team team;</code></pre>
<ul>
<li>@ManyToOne: 다대일(N:1) 관계. 매핑할 때 이렇게 다중성을 나타내는 어노테이션은 필수다.</li>
<li>@JoinColumn(name=”TEAM_ID”): 조인 컬럼은 외래 키를 매핑할 때 사용한다. name 속성: 매핑할 외래 키 이름. 이 어노테이션은 생략할 수 있다</li>
</ul>
<h3 id="14-joincolumn">1.4 @JoinColumn</h3>
<p>속성:</p>
<ul>
<li>name: 매핑할 외래 키 이름</li>
<li>referencedColumnName: 외래 키가 참조하는 대상 테이블의 컬럼명</li>
<li>foreignKey(DDL): 외래 키 제약조건을 직접 지정. 테이블 생성할 때만 사용</li>
<li>unique, nullable, insertable, updatable, columnDefinition, table: @Column의 속성과 같다</li>
</ul>
<h3 id="15-manytoone">1.5 @ManyToOne</h3>
<p>속성:</p>
<ul>
<li>optional: 연관관계가 필수인지(null 허용 여부)를 설정한다</li>
<li>fetch: 연관 엔티티의 조회 방식을 설정한다 (LAZY 또는 EAGER)</li>
<li>cascade: 연관 엔티티에 대한 영속성 전이 범위를 설정한다</li>
<li>targetEntity: 연관될 엔티티 타입을 지정한다</li>
</ul>
<h2 id="2-연관관계-사용">2. 연관관계 사용</h2>
<h3 id="21-저장">2.1 저장</h3>
<pre><code class="language-java">public void testSave() {

    //팀1 저장
    Team team1 = new Team(&quot;team1&quot;, &quot;팀1&quot;);
    em.persist(team1);

    //회원1 저장
    Member member1 = new Member(&quot;member1&quot;, &quot;회원1&quot;);
    member1.setTeam(team1); // 연관관계 설정: member1 -&gt; team1
    em.persist(member1);

    //회원2 저장
    Member member2 = new Member(&quot;member2&quot;, &quot;회원2&quot;);
    member2.setTeam(team1); // 연관관계 설정: member2 -&gt; team1
    em.persist(member2);
}</code></pre>
<h3 id="22-조회">2.2 조회</h3>
<ul>
<li><p>연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지다.</p>
<ul>
<li><p>객체 그래프 탐색</p>
<pre><code class="language-java">Team team = member.getTeam();</code></pre>
</li>
<li><p>객체지향 쿼리 사용 JPQL</p>
<pre><code class="language-java">List&lt;Member&gt; resultList = em.createQuery(jpql, Member.class)
      .setParameter(&quot;teamName&quot;, &quot;팀1&quot;);
      .getResultList();</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="23-수정">2.3 수정</h3>
<pre><code class="language-java">//회원1에 새로운 팀2 설정
Member member = em.find(Member.class, &quot;member1&quot;);
member.setTeam(team2);</code></pre>
<h3 id="24-연관관계-제거">2.4 연관관계 제거</h3>
<pre><code class="language-java">member1.setTeam(null); //연관관계 제거</code></pre>
<h3 id="25-연관된-엔티티-삭제">2.5 연관된 엔티티 삭제</h3>
<pre><code class="language-java">member1.setTeam(null); //회원1 연관관계 제거
member2.setTeam(null); //회원2 연관관계 제거
em.remove(team); //팀 삭제</code></pre>
<h2 id="3-양방향-연관관계">3. 양방향 연관관계</h2>
<p>회원 → 팀 접근하고 반대 방향 팀 → 회원 접근할 수 있는 양방향 연관관계로 매핑해 볼 것이다.</p>
<h3 id="31-양방향-연관관계-매핑">3.1 양방향 연관관계 매핑</h3>
<p>매핑한 팀 엔티티:</p>
<pre><code class="language-java">@Entity
public class Team {

    @Id
    @Column(name=&quot;TEAM_ID&quot;)
    private Stirng id;

    // 추가
    @OneToMany(mappedBy = &quot;team&quot;)
    private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;();

}</code></pre>
<ul>
<li><code>mappedBy</code>: 양방향 매핑일 때 사용, 반대 쪽 매핑의 필드 이름을 값으로 주면된다.</li>
</ul>
<h3 id="32-일대다-컬렉션-조회">3.2 일대다 컬렉션 조회</h3>
<pre><code class="language-java">List&lt;Member&gt; members = team.getMembers(); // (팀 -&gt; 회원), 객체 그래프 탐색</code></pre>
<h2 id="4-연관관계의-주인">4. 연관관계의 주인</h2>
<p>복습:</p>
<ul>
<li>테이블: 외래 키 하나로 두 테이블의 연관관계를 관리한다</li>
<li>엔티티: 단방향으로 매핑하면 참조를 하나만 사용</li>
</ul>
<p>→ 엔티티를 양방향 관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다.</p>
<blockquote>
<p><strong>연관관계의 주인</strong></p>
</blockquote>
<ul>
<li>두 연관관계 중 하나를 연관관계의 주인으로 정해야한다.<ul>
<li>연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다</li>
<li>주인이 아닌 쪽: 읽기만 할 수 있다</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>연관관계의 주인은 외래 키가 있는 곳</strong></p>
</blockquote>
<p>연관관계의 주인은 외래 키(FK)를 가진 쪽이고 Team–Member 관계에서는 <code>TEAM_ID</code>를 가진 MEMBER가 주인이 된다.</p>
<h2 id="5-양방향-연관관계-저장">5. 양방향 연관관계 저장</h2>
<p>양방향 연관관계는 연관관계의 주인이 외래 키를 관리한다. 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력된다.</p>
<pre><code class="language-java">team1.getMembers().add(member1); // 무시
team1.getMembers().add(member2); // 무시

member1.setTeam(team1); // 연관관계 설정 
member2.setTeam(team1); // 연관관계 설정</code></pre>
<h2 id="6-양방향-연관관계의-주의점">6. 양방향 연관관계의 주의점</h2>
<p>외래 키 값이 저장되지 않는다면 연관관계의 주인이 아닌 쪽에서 값을 설정했는지 먼저 확인해야 한다.</p>
<h3 id="61-순수한-객체까지-고려한-양방향-연관관계">6.1 순수한 객체까지 고려한 양방향 연관관계</h3>
<p>객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.</p>
<pre><code class="language-java">member1.setTeam(team1); // 회원 -&gt; 팀
team1.getMembers().add(member1); // 팀 -&gt; 회원</code></pre>
<h3 id="62-연관관계-편의-메소드">6.2 연관관계 편의 메소드</h3>
<p>양방향 연관관계는 양쪽 다 신경 써야 한다. 각각 호출하다 보면 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있다. 편의 메소드 사용 방법:</p>
<pre><code class="language-java">  private Team team;

  public void setTeam(Team team) {

      if (this.team != null) {
        this.team.getMembers().remove(this);
      }
      this.team = team;
      team.getMembers().add(this);
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[4] 엔티티 매핑]]></title>
            <link>https://velog.io/@ttt-1-2/4-%EC%97%94%ED%8B%B0%ED%8B%B0-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@ttt-1-2/4-%EC%97%94%ED%8B%B0%ED%8B%B0-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sun, 05 Apr 2026 04:21:50 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em> </p>
<p>JPA는 다양한 어노테이션을 지원하는데 크게 4가지로 나눌 수 있다:</p>
<ul>
<li>객체와 테이블 매핑: @Entity, @Table</li>
<li>기본 키 매핑: @Id</li>
<li>필드와 컬럼 매핑: @Column</li>
<li>연관관꼐 패핑: @ManyToOne, @JoinColumn</li>
</ul>
<p>4장에서 객체와 테이블 매핑, 기본 키 매핑, 필드와 컬럼 매핑을 다룰 것이다.</p>
<hr>
<h2 id="1-entity">1. @Entity</h2>
<p>JPA를 사용해서 테이블과 매핑할 클래스는 <code>@Entity</code> 필수로 붙여야 한다.</p>
<ul>
<li>속성: name: 사용할 엔티티 이름 (기본값: 클래스 이름)</li>
<li>주의사항:<ul>
<li>기본 생성자는 필수!</li>
<li>final 클래스, enum, interface, inner 클래스 사용할 수 없다</li>
<li>저장할 필드에 final을 사용하면 안 된다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public Member() {} // 직접 만든 기본 생성자

// 임의 생성자
public Member(String name) {
    this.name = name;
}</code></pre>
<h2 id="2-table">2. @Table</h2>
<p>@Table은 엔티티와 매핑할 테이블을 지정한다. </p>
<ul>
<li>속성:<ul>
<li>name: 매핑할 테이블 이름</li>
<li>catalog: catalog 기능이 있는 데이터베이스에서 catalog를 매핑한다</li>
<li>schema: schema 기능이 있는 데이터베이스에서 schema를 매핑한다</li>
<li>uniqueConstraints: DDL 생성시에 유니크 제약 조건을 만든다</li>
</ul>
</li>
</ul>
<h2 id="3-다양한-매핑-사용">3. 다양한 매핑 사용</h2>
<p>요구사항:</p>
<ul>
<li>회원: 일반회원, 관리자로 구분한다</li>
<li>회원 가입일 + 수정일이 있어야 한다</li>
<li>회원 설명 필드 있어야 한다. 이 필드는 길이 제한이 없다</li>
</ul>
<pre><code class="language-java">package jpabook.start;

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name=&quot;MEMBER&quot;)

public class Member {

    @Id
    @Column(name=&quot;ID&quot;)
    private String id;

    @Column(name=&quot;NAME&quot;)
    private String username;

    private Integer age;

    //===추가===
    @Enumerated(EnumType.String)
    private RoleType roletype;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob
    private String description;

    //Getter, Setter
    ...
}</code></pre>
<pre><code class="language-java">package jpabook.start;

public enum RoleType {
    ADMIN, USER
}</code></pre>
<h2 id="4-데이터베이스-스키마-사용">4. 데이터베이스 스키마 사용</h2>
<p>JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다. 이 기능은 <code>persistence.xml</code> 또는 <code>application.yml</code>에서 설정할 수 있다.</p>
<pre><code class="language-java">&lt;property name=&quot;hibernate.hbm2ddl.auto&quot; value=&quot;create&quot; /&gt;
&lt;property namee=&quot;hibernate.show_sql&quot; value=&quot;true&quot; /&gt;</code></pre>
<ul>
<li>[hibernate.hbm2ddl.auto] 속성:<ul>
<li>create: 기존 테이블을 삭제하고 새로 생성한다</li>
<li>create-drop: 실행 시 생성하고 종료 시 삭제한다</li>
<li>update: 엔티티 변경 사항만 DB에 반영한다</li>
<li>validate: 엔티티와 DB 구조를 비교만 하고 수정하지 않는다</li>
<li>none: 아무 작업도 하지 않는다</li>
</ul>
</li>
</ul>
<h2 id="5-ddl-생성-기능">5. DDL 생성 기능</h2>
<ul>
<li>추가 제약조건이 있다: 회원 이름은 필수로 입력되어야 하고, 10자를 초과하면 안 된다.</li>
</ul>
<pre><code class="language-java">@Column(name=&quot;NAME&quot;, nullable=false, lengeth=10) // 추가 제약조건</code></pre>
<blockquote>
<p>@Column 매핑 정보</p>
</blockquote>
<ul>
<li><p><code>nullable = false</code>
→ DDL에서 <code>NOT NULL</code> 제약 조건을 설정한다.</p>
</li>
<li><p><code>length</code>
→ DDL에서 문자 길이(컬럼 크기)를 설정한다.</p>
</li>
<li><p>추가 제약조건: 유니크</p>
</li>
</ul>
<pre><code class="language-java">@Table(name=&quot;MEMBER&quot;, uniqueConstraints = {@UniqueConstraint( //추가 //**
        name = &quot;NAME_AGE_UNIQUE&quot;,
        columnNames = {&quot;NAME&quot;, &quot;AGE&quot;} )})</code></pre>
<blockquote>
<p>엔티티의 <code>@Column</code> 속성들은 주로 DDL 자동 생성 시에만 사용되고 애플리케이션 실행 로직에는 영향을 주지 않는다. <strong>하지만 엔티티만 보고도 제약조건을 쉽게 파악할 수 있어 코드 가독성과 유지보수에 도움이 된다.</strong></p>
</blockquote>
<h2 id="6-기본-키-매핑">6. 기본 키 매핑</h2>
<p>JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같다</p>
<ul>
<li>직접 할당: 애플리케이션에서 직접 기본 키 값을 설정한다</li>
<li>자동 생성:<ul>
<li>IDENTITY: DB의 auto increment 기능을 사용해 기본 키를 생성한다</li>
<li>SEQUENCE: DB의 시퀀스를 사용해 기본 키를 생성한다</li>
<li>TABLE: 별도의 키 생성 테이블을 사용해 기본 키를 관리한다</li>
</ul>
</li>
</ul>
<h3 id="61-기본-키-직접-할당전략">6.1 기본 키 직접 할당전략</h3>
<p>기본 키를 직접 할당하려면 다음 코드와 같이 <code>@Id</code> 로 매핑하면 된다.</p>
<pre><code class="language-java">@Id
@Column(name=&quot;id&quot;)
private String id;

Board board = new Board();
board.setID(&quot;id&quot;) // 기본 키 직접할당
em.persist(board);</code></pre>
<h3 id="62-identity-전략">6.2 IDENTITY 전략</h3>
<p>IDENTITY는 기본키 생성을 데이터베이스에 위임하는 전략이다.</p>
<p>IDENTITY 전략을 사용하려면 @GeneratedValue의 strategy 속성값을 GenerationType.IDENTITY로 지정하면 된다. 이전략을 사용하면 JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회한다. </p>
<pre><code class="language-java">@Entity
public class Board {

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

}</code></pre>
<pre><code class="language-java">private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    System.out.println(&quot;board.id = &quot; + board.getId);
}
// 출력: board.id=1</code></pre>
<h3 id="63-sequence-전략">6.3 SEQUENCE 전략</h3>
<p>시퀀스: 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다. SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다.</p>
<ul>
<li>시퀀스 DDL:</li>
</ul>
<pre><code class="language-sql">CREATE TABLE BOARD {
    ID INT NOT NULL PRIMARY KEY,
    DATA VARCHAR(255)
 }

 CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;</code></pre>
<ul>
<li>시퀀스 매핑 코드:</li>
</ul>
<pre><code class="language-sql">@Entity
@SequenceGenerator(
    name = &quot;BOARD_SEQ_GENERATOR&quot;,
    sequenceName = &quot;BOARD_SEQ&quot;, //매핑할 db 시퀀스 이름
    initialValue = 1, allocationSize = 1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                        generator = &quot;BOARD_SEQ_GENERATOR&quot;)
    private Long id;

    ..
}</code></pre>
<blockquote>
<p><strong>@SequenceGenerator: 는 시퀀스 생성 방식을 정의하는 설정이고, 실제로는 @GeneratedValue에서 generator 이름을 통해 사용된다. 즉, @SequenceGenerator는 정의, @GeneratedValue는 실행 역할을 한다.</strong></p>
</blockquote>
<ul>
<li>name: 시퀀스 생성기를 식별하기 위한 이름이다</li>
<li>sequenceName: 매핑할 데이터베이스 시퀀스 이름이다</li>
<li>initialValue: 시퀀스의 시작 값을 설정한다</li>
<li>allocationSize: 한 번에 가져올 시퀀스 값의 증가 크기를 설정하여 성능을 최적화한다</li>
<li>catalog, schema: 시퀀스가 속한 데이터베이스의 catalog와 schema를 지정한다</li>
</ul>
<h3 id="64--table-전략">6.4  TABLE 전략</h3>
<p>TABLE 전략: 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉낸다.</p>
<ul>
<li>TABLE 전략 키 생성 DDL</li>
</ul>
<pre><code class="language-sql">CREATE table MY_SEQUENCES {
    sequence_name varchar(255) not null,
    next_val bigint,
    primary key (sequence_name)
 }</code></pre>
<ul>
<li>TABLE 전략 매핑 코드</li>
</ul>
<pre><code class="language-sql">@Entity
@TableGenerator(
    name = &quot;BOARD_SEQ_GENERATOR&quot;,
    table = &quot;MY_SEQUENCES&quot;, 
    pkColumnValue = &quot;BOARD_SEQ&quot;, allocationSize = 1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                        generator = &quot;BOARD_SEQ_GENERATOR&quot;)
    private Long id;

    ..
}</code></pre>
<h3 id="65-auto-전략">6.5 AUTO 전략</h3>
<p><a href="http://GenerationType.AUTO"><code>GenerationType.AUTO</code></a> 는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABL 전략 중 하나를 자동으로 선택한다.</p>
<p>ex: 오라클을 선택하면 SEQUENCE, MySQL을 선택하면 IDENTITY를 사용한다.</p>
<h3 id="66-기본-키-매핑-정리">6.6 기본 키 매핑 정리</h3>
<p><code>em.persist()</code> 를 호출한 직후에 발생하는 일을 식별자 할당 전략별로 정리하면 다음과 같다.</p>
<ul>
<li>직적 할당: persist 전에 애플리케이션에서 직접 id 값을 설정해야 한다</li>
<li>SEQUENCE: 시퀀스를 통해 id를 먼저 조회한 후 엔티티에 할당한다</li>
<li>TABLE: 키 생성 테이블에서 id를 조회하고 값을 증가시킨 후 할당한다</li>
<li>IDENTITY: 엔티티를 먼저 DB에 저장한 후 생성된 id 값을 조회한다</li>
</ul>
<h2 id="7-필드와-컬럼-매핑-레퍼런스">7. 필드와 컬럼 매핑: 레퍼런스</h2>
<h3 id="71-column">7.1 @Column</h3>
<p>@Column의 속성:</p>
<ul>
<li>name: 컬럼 이름을 지정한다</li>
<li>insertable: INSERT SQL에 포함할지 여부를 설정한다</li>
<li>updatable: UPDATE SQL에 포함할지 여부를 설정한다</li>
<li>table: 매핑할 테이블을 지정한다</li>
<li>nullable(DDL): NOT NULL 제약 조건을 설정한다</li>
<li>unique(DDL): UNIQUE 제약 조건을 설정한다</li>
<li>columnDefinition(DDL): 컬럼의 DDL을 직접 정의한다</li>
<li>length(DDL): 문자열 컬럼의 길이를 설정한다</li>
<li>precision, scale(DDL): 숫자의 전체 자릿수와</li>
</ul>
<h3 id="72-enumerated">7.2 @Enumerated</h3>
<p>자바의 enum 타입을 매핑할 때 사용한다</p>
<ul>
<li>EnumType.ORDINAL: enum 순서를 데이터베이스에 저장</li>
<li>EnumType.STRING: enum 이름을 데이터베이스에 저장</li>
</ul>
<h3 id="73-temporal">7.3 @Temporal</h3>
<p>날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용한다.</p>
<ul>
<li>value 속성:<ul>
<li>TemporalType.DATE: 날짜(년,월,일)만 저장한다</li>
<li>TemporalType.TIME: 시간(시,분,초)만 저장한다</li>
<li>TemporalType.TIMESTAMP: 날짜와 시간을 모두 저장한다</li>
</ul>
</li>
</ul>
<h3 id="74-lob">7.4 @Lob</h3>
<p>데이터베이스 BLOB, CLOB 타입과 매핑한다.</p>
<ul>
<li>BLOB: byte[], java.sql.BLOB</li>
<li>CLOB: String, char[], java.sql.CLOB</li>
</ul>
<h3 id="75-transient">7.5 @Transient</h3>
<p>이 필드는 매핑하지 않는다. 데이터베이스에 저장하지 않고 조회하지도 않는다. 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.</p>
<pre><code class="language-sql">@Transient
private Integer temp;</code></pre>
<h3 id="76-access">7.6 @Access</h3>
<p>JPA가 엔티티 데이터에 접근하는 방식을 지정한다:</p>
<ul>
<li>필드 접근: AccessType.FIELD로 지정한다. 필드에 직접 접근한다. 필드 접근 권한이 private이어도 접근할 수 있다.</li>
<li>프로퍼티 접근: AccessType.PROPERTY로 지정한다. 접근자 getter를 사용한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[3] 영속성 관리]]></title>
            <link>https://velog.io/@ttt-1-2/3-%EC%98%81%EC%86%8D%EC%84%B1-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@ttt-1-2/3-%EC%98%81%EC%86%8D%EC%84%B1-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sun, 22 Mar 2026 06:20:56 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em> </p>
<hr>
<h2 id="1-엔티티-매니저-팩토리와-엔티티-매니저">1. 엔티티 매니저 팩토리와 엔티티 매니저</h2>
<p>하나의 데이터베이스만 사용하는 애플리케이션에서는 보통 EntityManagerFactory를 하나만 생성해서 사용한다.</p>
<pre><code class="language-java">// 공장 생성, 초기 비용이 매우 큼
EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;jpabook&quot;);</code></pre>
<p><code>Persistence.createEntityManagerFactory(&quot;jpabook&quot;)</code>를 실행하면 <code>META-INF/persistence.xml</code>에 정의된 설정을 기반으로 EntityManagerFactory가 만들어진다. 이때 데이터베이스 연결 설정을 읽고 커넥션 풀을 준비하며 JPA 관련 초기화 작업이 수행되므로 생성 비용이 상당히 크다.</p>
<p>이후에는 필요할 때마다 EntityManagerFactory를 통해 엔티티 매니저를 생성해서 사용한다.</p>
<pre><code class="language-java">// 엔티티 매니저 생성, 비용이 거의 없음
EntityManager em = emf.createEntityManager();</code></pre>
<p>엔티티 매니저는 실제로 데이터베이스와 CRUD 작업을 수행하는 객체이며 생성 비용이 낮기 때문에 요청 단위로 생성하고 사용 후 종료하는 방식이 일반적이다.</p>
<p>주의: 엔티티 매니저는 여러 스레드에서 동시에 공유해서 사용하면 안 된다.</p>
<h2 id="2-영속성-컨텍스트란">2. 영속성 컨텍스트란?</h2>
<p>영속성 컨텍스트 (persistence context) = 엔티티를 영구 저장하는 환경</p>
<pre><code class="language-java">em.persist(member);</code></pre>
<h2 id="3-엔티티의-생명주기">3. 엔티티의 생명주기</h2>
<p>엔티티는 4가지 상태가 있다:</p>
<p><strong><em>(1) 비영속 (new/transient)</em></strong></p>
<p>엔티티 객체를 생성만 한 상태로, 아직 영속성 컨텍스트와 전혀 관계가 없다. 데이터베이스와 연결되지 않은 순수 객체 상태다.</p>
<pre><code class="language-java">Member member = new Member(); // 비영속 상태
member.setName(&quot;Trang&quot;);</code></pre>
<p><strong><em>(2) 영속 (managed)</em></strong></p>
<p>엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태다. 이 상태에서는 JPA가 엔티티를 관리하고 변경 사항을 자동으로 감지하여 DB에 반영한다.</p>
<pre><code class="language-java">em.persist(member); // 영속 상태로 전환</code></pre>
<p><strong><em>(3) 준영속 (detached)</em></strong></p>
<p>영속 상태였던 엔티티가 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태다. 변경을 해도 DB에 반영되지 않는다.</p>
<pre><code class="language-java">em.detach(member); // 준영속 상태</code></pre>
<p><strong><em>(4) 삭제 (removed): 삭제된 상태</em></strong></p>
<p>엔티티가 삭제 대상으로 지정된 상태다. 이후 트랜잭션을 커밋하면 실제 DB에서도 삭제된다.</p>
<pre><code class="language-java">em.remove(member); // 삭제 상태</code></pre>
<h2 id="4-영속성-컨텍스트의-특징">4. 영속성 컨텍스트의 특징</h2>
<h3 id="41-엔티티-조회">4.1 엔티티 조회</h3>
<p>JPA에서 엔티티를 조회할 때는 1차 캐시(영속성 컨텍스트)를 먼저 사용한다.</p>
<p>1차 캐시는 엔티티 매니저 내부에 존재하는 저장 공간이며 같은 트랜잭션 내에서 엔티티를 효율적으로 관리하기 위해 사용된다. 1차 캐시의 키는 식별자 값(ID)이다. 이 식별자 값은 데이터베이스의 기본 키(PK)와 매핑된다.</p>
<p>조회 과정은 다음과 같이 동작한다.</p>
<ol>
<li>먼저 1차 캐시에서 식별자 값으로 엔티티를 조회한다.</li>
<li>1차 캐시에 해당 엔티티가 없으면 데이터베이스에서 조회한다.</li>
<li>조회한 데이터를 기반으로 엔티티 객체를 생성한다.</li>
<li>생성된 엔티티를 1차 캐시에 저장한 후 반환한다.</li>
</ol>
<pre><code class="language-java">// 엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId(&quot;member1&quot;);
member.setUsername(&quot;회원1&quot;);

// 엔티티를 영속
em.persist(member); // 1차 캐시에 저장됨

// 1차 캐시 에서 조회
Member findMember = em.find(Member.class, &quot;member1&quot;);</code></pre>
<h3 id="42-엔티티-등록">4.2 엔티티 등록</h3>
<p>엔티티 등록은 새로운 객체를 생성하고 데이터베이스에 저장하는 과정이다. JPA에서는 <code>persist()</code> 메서드를 사용해서 엔티티를 영속 상태로 만든다.</p>
<pre><code class="language-java">Member member = new Member();
member.setName(&quot;Trang&quot;);

em.persist(member); // 영속 상태</code></pre>
<p>엔티티를 <code>persist()</code> 하면 바로 SQL이 실행되는 것이 아니라, 쓰기 지연 SQL 저장소에 INSERT 쿼리가 먼저 저장된다. 이후 트랜잭션을 커밋하는 시점에 SQL이 실제 데이터베이스로 전달된다.</p>
<pre><code class="language-java">tx.commit(); // 이때 INSERT SQL 실행</code></pre>
<p>동작 흐름:</p>
<ol>
<li>엔티티 생성 (비영속 상태)</li>
<li><code>persist()</code> 호출 → 영속 상태 전환</li>
<li>INSERT SQL을 쓰기 지연 저장소에 저장</li>
<li>commit 시점에 DB에 반영</li>
</ol>
<h3 id="43-엔티티-수정">4.3 엔티티 수정</h3>
<p>엔티티 수정은 별도의 update 메서드를 호출하지 않고, 변경 감지(dirty checking)를 통해 자동으로 처리된다.</p>
<p><strong><em>변경 감지 (Dirty Checking)</em></strong></p>
<p>JPA는 엔티티의 변경을 자동으로 감지한다.</p>
<pre><code class="language-java">Member member = em.find(Member.class, 1L);
member.setName(&quot;New Name&quot;); // 값 변경</code></pre>
<p>별도의 update 호출 없이도 트랜잭션 commit 시점에 JPA가 변경을 감지해서 UPDATE SQL을 생성한다.</p>
<p>동작 방식:</p>
<ol>
<li>엔티티를 조회하면 초기 상태를 스냅샷으로 저장한다.</li>
<li>트랜잭션 종료 시점에 현재 값과 비교한다.</li>
<li>변경된 경우에만 UPDATE SQL을 실행한다.</li>
</ol>
<p><strong><em>@DynamicUpdate</em></strong></p>
<pre><code class="language-java">@org.hibernate.annotations.DynamicUpdate</code></pre>
<p>이 옵션을 사용하면 변경된 컬럼만 포함한 UPDATE SQL이 생성된다. 불필요한 컬럼 업데이트를 줄이고 성능을 조금 더 최적화할 수 있다.</p>
<h3 id="44-엔티티-삭제">4.4 엔티티 삭제</h3>
<p>엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.</p>
<pre><code class="language-java">Member memberA = em.find(Member.class, &quot;memberA&quot;); // 삭제 대상 조회
em.remove(memberA); // 엔티티 삭제</code></pre>
<h2 id="5-플러시-flush">5. 플러시 (flush)</h2>
<p>flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이다.</p>
<ul>
<li>직접 호출: 개발자가 직접 flush를 호출하고 현재까지의 변경 사항이 즉시 DB에 반영된다.</li>
</ul>
<pre><code class="language-java">em.flush();</code></pre>
<ul>
<li>트랜잭션 커밋 시 자동 호출</li>
</ul>
<pre><code class="language-java">tx.commit();</code></pre>
<p>트랜잭션을 커밋하면 flush가 자동으로 실행된 후 commit이 진행된다. 따라서 대부분의 경우 flush를 직접 호출할 필요는 없다.</p>
<ul>
<li>JPQL 쿼리 실행 시 자동 호출: JPQL을 실행하기 전에 flush가 자동으로 호출된다.</li>
</ul>
<h2 id="6-준영속">6. 준영속</h2>
<p>준영속 상태는 엔티티가 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태다. 이 상태에서는 엔티티를 수정해도 변경 사항이 DB에 반영되지 않는다.</p>
<h3 id=""></h3>
<blockquote>
<p><strong>detach()</strong></p>
</blockquote>
<pre><code class="language-java">em.detach(member);</code></pre>
<p>특정 엔티티를 준영속 상태로 전환한다. 해당 엔티티만 영속성 컨텍스트에서 분리된다.</p>
<blockquote>
<p><strong>clear()</strong></p>
</blockquote>
<pre><code class="language-java">em.clear();</code></pre>
<p>영속성 컨텍스트를 초기화한다. 관리 중이던 모든 엔티티가 한 번에 준영속 상태가 된다.</p>
<blockquote>
<p><strong>close()</strong></p>
</blockquote>
<pre><code class="language-java">em.close();</code></pre>
<p>영속성 컨텍스트를 종료한다. 이후에는 엔티티 매니저를 더 이상 사용할 수 없다.</p>
<blockquote>
<p><strong>병합</strong></p>
</blockquote>
<p>준영속 상태의 엔티티를 다시 영속 상태로 변형하려면 병합을 사용하면 된다.</p>
<pre><code class="language-java">// 정의
public &lt;T&gt; T merge(T entity);

// merge() 사용 예
Member mergeMember = em.merge(member);</code></pre>
<pre><code class="language-java">// 전체 흐름 예시
// 1. 엔티티 조회 (영속 상태)
Member member = em.find(Member.class, 1L);

// 2. 준영속 상태로 변경
em.detach(member);

// 3. 값 변경 (DB 반영 안 됨)
member.setName(&quot;New Name&quot;);

// 4. merge 호출 → 다시 영속 상태
Member mergedMember = em.merge(member);

// 5. commit 시 UPDATE SQL 실행
tx.commit();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2] JPA 시작]]></title>
            <link>https://velog.io/@ttt-1-2/2-JPA-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@ttt-1-2/2-JPA-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Sun, 15 Mar 2026 11:48:21 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em> </p>
<p><em>깃허브:</em> <a href="https://github.com/holyeye/jpabook">https://github.com/holyeye/jpabook</a></p>
<hr>
<p>2026년 3월 기준으로 책 내용을 변경해서 실습을 진행했다: (1) 이클립스 → 인텔리제이 (2) Maven → Gradle</p>
<p>폴더 구조는 다음과 같다:
<img src="https://velog.velcdn.com/images/ttt-1-2/post/2a8a48bd-77af-4bb7-a52c-1b1b5c9e36b6/image.png" alt=""></p>
<h2 id="buildgradle">build.gradle</h2>
<ul>
<li>Gradle: 프로젝트의 의존성과 빌드 설정을 관리하는 파일이다. 어떤 라이브러리를 사용할지, 자바 버전은 무엇인지 등을 여기서 설정한다.</li>
</ul>
<pre><code class="language-java">plugins {
    id &#39;java&#39;
}

group = &#39;hellojpa&#39;
version = &#39;1.0-SNAPSHOT&#39;

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.hibernate:hibernate-core:5.6.15.Final&#39;
    implementation &#39;javax.persistence:javax.persistence-api:2.2&#39;
    runtimeOnly &#39;com.h2database:h2:2.2.224&#39;
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}</code></pre>
<h2 id="persistencexml">persistence.xml</h2>
<p><code>persistence.xml</code>은 JPA 설정 파일이다. 사용할 데이터베이스, 사용할 JPA 구현체, 엔티티 클래스, JPA 동작 옵션 등은 여기에서 설정한다.</p>
<pre><code class="language-java">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;persistence xmlns=&quot;http://xmlns.jcp.org/xml/ns/persistence&quot;
             xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
             xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd&quot;
             version=&quot;2.2&quot;&gt;

        // persistence-unit은 JPA 설정 단위이다
    &lt;persistence-unit name=&quot;jpabook&quot;&gt;
                // JPA가 관리할 엔티티 클래스를 등록한다
        &lt;class&gt;hellojpa.Member&lt;/class&gt;

        &lt;properties&gt;

            &lt;!-- database connection --&gt;
            &lt;property name=&quot;javax.persistence.jdbc.driver&quot; value=&quot;org.h2.Driver&quot;/&gt;
            &lt;property name=&quot;javax.persistence.jdbc.url&quot; value=&quot;jdbc:h2:~/test&quot;/&gt;
            &lt;property name=&quot;javax.persistence.jdbc.user&quot; value=&quot;sa&quot;/&gt;
            &lt;property name=&quot;javax.persistence.jdbc.password&quot; value=&quot;&quot;/&gt;

            &lt;!-- Hibernate --&gt;
            &lt;property name=&quot;hibernate.dialect&quot; value=&quot;org.hibernate.dialect.H2Dialect&quot;/&gt;

            &lt;!-- console SQL --&gt;
            &lt;property name=&quot;hibernate.show_sql&quot; value=&quot;true&quot;/&gt;
            &lt;property name=&quot;hibernate.format_sql&quot; value=&quot;true&quot;/&gt;

            &lt;!-- auto create table --&gt;
            &lt;property name=&quot;hibernate.hbm2ddl.auto&quot; value=&quot;create&quot;/&gt;

        &lt;/properties&gt;

    &lt;/persistence-unit&gt;

&lt;/persistence&gt;</code></pre>
<h2 id="member">Member</h2>
<p>이번 실습에서 Member 클래스는 JPA 엔티티 클래스이다. 데이터베이스 테이블과 매핑된다.</p>
<pre><code class="language-java">package hellojpa;

import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = &quot;MEMBER&quot;)
@Getter
@Setter

public class Member {

    @Id
    @Column(name = &quot;ID&quot;)
    private String id;

    @Column(name = &quot;NAME&quot;)
    private String username;

    // 매핑 정보가 없는 필드
    private Integer age;
}
</code></pre>
<ul>
<li>@Entity: 이 클래스가 JPA 엔티티임을 나타낸다 → 이 객체는 데이터베이스 테이블과 매핑된다</li>
<li>@Table: 엔티티가 매핑될 테이블 이름을 지정한다</li>
<li>@Id: 기본 키를 지정한다</li>
<li>@Column: 필드와 DB 컬럼을 매핑한다</li>
<li>매핑 어노테이션이 없으면 필드 이름 그대로 컬럼이 생성된다</li>
</ul>
<p>참고: JPA 어노테이션의 패키지는 javax.persistence이다.</p>
<h2 id="jpamain">JpaMain</h2>
<ul>
<li>EntityManagerFactory: EntityManager를 생성한다. 애플리케이션 전체에서 하나만 생성한다.</li>
<li>EntityManager: JPA의 핵심 객체이다. (엔티티 저장, 엔티티 조회, 엔티티 수정, 엔티티 삭제)</li>
<li>트랜잭션: JPA의 모든 데이터 변경 작업은 트랜잭션 안에서 실행해야 한다.<pre><code class="language-java">package hellojpa;
</code></pre>
</li>
</ul>
<p>import javax.persistence.*;
import java.util.List;</p>
<p>public class JpaMain {
    public static void main(String[] args) {</p>
<pre><code>    // 엔티티 매니저 팩토리 - 생성
    EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;jpabook&quot;);

    // 엔티티 매니저 - 생성
    EntityManager em = emf.createEntityManager();

    // 트랜잭션 - 획득
    EntityTransaction tx = em.getTransaction();

    try {
        tx.begin();
        logic(em);
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }
    emf.close();
}

// 비지니스 로직
private static void logic(EntityManager em) {
    String id = &quot;id1&quot;;
    Member member = new Member();
    member.setId(id);
    member.setUsername(&quot;유정&quot;);
    member.setAge(2);

    // 등록
    em.persist(member);
    // 수정
    member.setAge(20);
    // 한 건 조회
    Member findMember = em.find(Member.class, id);
    System.out.println(&quot;findMember=&quot; + findMember.getUsername() + &quot;, age=&quot; + findMember.getAge());
    // 목록 조회
    List&lt;Member&gt; members = em.createQuery(&quot;select m from Member m&quot;, Member.class).getResultList();
    System.out.println(&quot;members.size=&quot; + members.size());

    // 삭제
    em.remove(member);
}</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[1] JPA 소개]]></title>
            <link>https://velog.io/@ttt-1-2/1-JPA-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@ttt-1-2/1-JPA-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Sun, 15 Mar 2026 11:45:06 GMT</pubDate>
            <description><![CDATA[<p><em>교재: 자바 ORM 표준 JPA 프로그래밍</em> </p>
<p>JPA를 사용하면 SQL 자체보다 내가 만든 객체에 더 집중할 수 있다. 데이터베이스가 바뀌더라도(MySQL → Oracle 등) 애플리케이션 코드를 크게 바꾸지 않고 유지보수하기 쉬워진다.</p>
<p>예전에는 보통 다음과 같은 흐름으로 개발했다:</p>
<ul>
<li><p>ORM 이전: Java → JDBC → SQL → DB</p>
<p>  개발자가 직접 SQL을 작성하고, 조회 결과를 다시 자바 객체로 하나하나 매핑해야 했다.</p>
</li>
<li><p>ORM 이후: Java 객체 → ORM → SQL → DB</p>
<p>  개발자는 객체 중심으로 개발하고, ORM이 중간에서 SQL 생성과 매핑을 도와준다.</p>
</li>
</ul>
<p>즉, ORM은 <strong>객체와 관계형 데이터베이스 사이의 번역기</strong> 같은 역할을 한다. 자바는 객체를 사용하고 데이터베이스는 테이블을 사용하므로 둘 사이에는 구조 차이가 있다. ORM은 이 차이를 줄여 주어서 개발자가 조금 더 자연스럽게 객체지향적으로 개발할 수 있게 해준다.</p>
<hr>
<h2 id="1-sql을-직접-다룰-때-발생하는-문제점">1. SQL을 직접 다룰 때 발생하는 문제점</h2>
<blockquote>
<p><strong>반복</strong></p>
</blockquote>
<p>ex: 회원 정보를 조회한다고 해보자.</p>
<ol>
<li>조회용 SQL을 직접 작성한다.</li>
<li>JDBC API를 사용해서 SQL을 실행한다.</li>
<li>결과(ResultSet)를 꺼내서 자바 객체에 하나씩 넣는다.</li>
</ol>
<p>아래와 같이 <code>Member</code> 객체와 <code>MemberDAO</code>가 있다고 가정해보자.</p>
<pre><code class="language-java">public class Member {
    private String memberId;
    private String name;
}</code></pre>
<pre><code class="language-java">public class MemberDAO {
    public Member find(String memberId) {
        // DB에서 회원을 조회하는 로직이 들어간다
        return null;
    }
}</code></pre>
<p>겉으로 보면 <code>find()</code> 메서드 하나만 있으면 될 것 같지만 실제 내부에서는 꽤 많은 코드가 필요하다.</p>
<p>ex:  DB 연결 얻기, SQL 문자열 작성, PreparedStatement 생성, 파라미터 바인딩, SQL 실행, ResultSet 조회, 컬럼 값을 꺼내서 <code>Member</code> 객체에 저장, 자원 반납(Connection, Statement, ResultSet 닫기)</p>
<p>→ 비즈니스 로직보다 JDBC 처리 코드가 더 많이 보이는 경우가 많다.</p>
<blockquote>
<p><strong>SQL에 의존적인 개발</strong></p>
</blockquote>
<p>SQL을 직접 다루면 자바 코드가 데이터베이스 구조에 강하게 묶이게 된다.</p>
<p>ex: 회원 연락처를 추가할 때 겉보기에는 필드 하나 추가한 것뿐이지만 실제로는 수정할 것이 많다.</p>
<pre><code class="language-java">// 회원 클래스에 연락처 필드 추가
pulic class Member {
    private String memberId;
    private String name;
    private String tel;
}</code></pre>
<p><em>수정해야 하는 부분</em></p>
<ul>
<li>자바 객체에 필드 추가</li>
<li>DB 테이블에 컬럼 추가</li>
<li>INSERT SQL 수정</li>
<li>UPDATE SQL 수정</li>
<li>SELECT SQL 수정</li>
<li>ResultSet 매핑 코드 수정</li>
</ul>
<blockquote>
<p><strong>계층 분할이 어려워진다</strong></p>
</blockquote>
<p>객체지향적으로 설계하면 보통 다음처럼 생각한다.</p>
<ul>
<li><code>Member</code>는 회원 객체다</li>
<li><code>Team</code>은 팀 객체다</li>
<li>회원은 한 팀에 속할 수 있다</li>
</ul>
<p>자바 코드에서는 자연스럽게 이렇게 표현하고 싶어진다.</p>
<pre><code>member.getTeam();</code></pre><p>하지만 SQL 중심으로 개발하면 그렇게 단순하지 않다.</p>
<p><code>member.getTeam()</code>을 호출한다고 자동으로 팀 정보가 나오는 것이 아니다. 개발자는 먼저 DAO 안을 열어 보고 어떤 SQL이 실행되고 있는지 확인해야 한다. (회원만 조회하는 SQL인지, 팀까지 조인해서 조회하는 SQL인지, 팀은 따로 다시 조회해야 하는지..)</p>
<p>→ 객체를 사용하는 입장에서는 그냥 <code>member.getTeam()</code> 하면 될 것 같지만, 실제로는 DAO 내부 SQL 구현을 알아야만 동작을 예측할 수 있다.</p>
<blockquote>
<p><strong>JPA와 문제 해결</strong></p>
</blockquote>
<p>JPA를 사용하면 개발자가 SQL을 직접 다루기보다 엔티티 객체를 중심으로 개발하게 된다.</p>
<pre><code class="language-java">// 저장 기능
jpa.persist(member);

// 조회 기능
String memberId = &quot;helloId&quot;;
Member member = jpa.find(Member.class, memberId);

// 수정 기능
Member member = jpa.find(Member.class, memberId);
member.setName(&quot;이름변경&quot;)

// 연관된 객체 조회
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();</code></pre>
<p>필요한 시점에 JPA가 적절한 SELECT SQL을 실행해서 팀 정보를 가져온다 → 개발자는 “이 시점에 조인 SQL을 직접 날려야 하나?”를 덜 고민하게 된다.</p>
<h2 id="2-패러다임의-불일치">2. 패러다임의 불일치</h2>
<blockquote>
<p><strong>상속</strong></p>
</blockquote>
<ul>
<li>문제점: 객체는 상속 구조를 사용할 수 있지만 데이터베이스에는 상속 개념이 없다. JDBC를 사용하면 insert 시 여러 SQL을 작성해야 하고, 조회할 때는 여러 테이블을 JOIN 해야 한다.</li>
<li>JPA 해결: JPA는 객체 상속 구조를 그대로 저장할 수 있게 해 준다.</li>
</ul>
<blockquote>
<p><strong>연관관계</strong></p>
</blockquote>
<ul>
<li>문제점: 객체는 참조로 관계를 표현하지만 데이터베이스는 외래 키로 관계를 표현한다.</li>
<li>JPA 해결: 객체 참조만 설정하면 JPA가 외래 키를 자동으로 관리한다.</li>
</ul>
<pre><code class="language-java">member.setTeam(team);
em.persist(member);</code></pre>
<blockquote>
<p><strong>객체 그래프 탐색</strong></p>
</blockquote>
<ul>
<li>문제점: JDBC에서는 객체처럼 자유롭게 탐색하기 어렵고 필요한 SQL을 직접 작성해야 한다.</li>
<li>JPA 해결: 객체처럼 자연스럽게 탐색할 수 있다.</li>
</ul>
<pre><code class="language-java">Member member = em.find(Member.class, 1L);
Team team = member.getTeam();</code></pre>
<blockquote>
<p><strong>비교</strong></p>
</blockquote>
<ul>
<li>문제점: DB는 PK 값으로 데이터를 비교하고 객체는 equals()로 객체를 비교한다.</li>
<li>JPA 해결: JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.</li>
</ul>
<h2 id="3-jpa란-무엇일까">3. JPA란 무엇일까?</h2>
<ul>
<li>ORM (Object-Relational Mapping): 객체와 관계형 데이터베이스를 매핑한다.</li>
<li>JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준이다. (JPA는 실제 ORM 엔진이 아니라 ORM 기술의 규격이다. 실제 동작은 Hibernate, EclipseLink 같은 구현체가 수행한다.)</li>
</ul>
<p>개발자는 JPA API를 사용하여 객체 중심으로 코드를 작성한다. 그러면 JPA 구현체가 내부적으로 SQL을 생성하여 데이터베이스와 통신한다.</p>
<pre><code class="language-java">개발자 코드
     |
     v
JPA API
(EntityManager, @Entity, @Id ...)
     |
     v
JPA 구현체 (Implementation)
------------------------------
| Hibernate                  |
| EclipseLink                |
| DataNucleus                |
------------------------------
     |
     v
SQL 생성
     |
     v
Database</code></pre>
<p>JPA를 사용하면 생산성 향상, 유지보수 용이성, 객체와 관계형 데이터베이스 사이의 패러다임 불일치 해결 그리고 데이터 접근 추상화를 통한 벤더 독립성 등의 장점을 얻을 수 있다.</p>
<p><strong><em>JPA를 사용하면 SQL 중심 개발에서 벗어나 객체 중심 개발이 가능해진다.</em></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[7] MySQL 마무리]]></title>
            <link>https://velog.io/@ttt-1-2/7-MySQL-%EB%A7%88%EB%AC%B4%EB%A6%AC</link>
            <guid>https://velog.io/@ttt-1-2/7-MySQL-%EB%A7%88%EB%AC%B4%EB%A6%AC</guid>
            <pubDate>Sat, 07 Mar 2026 08:24:12 GMT</pubDate>
            <description><![CDATA[<p>강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<p>관계형 데이터베이스(Relational Database)를 제대로 사용하려면 단순히 데이터를 저장하는 것만으로는 부족하다. 실제로 서비스를 운영하다 보면 데이터 조회, 성능, 설계, 안정성까지 모두 고려해야 한다.</p>
<p>대표적으로 중요한 요소는 다음 네 가지다:</p>
<ul>
<li>SQL</li>
<li>Index</li>
<li>Modeling</li>
<li>Backup</li>
</ul>
<hr>
<h2 id="1-sql---데이터베이스의-기본-언어">(1) SQL - 데이터베이스의 기본 언어</h2>
<p>관계형 데이터베이스를 다루기 위해서는 SQL을 이해하고 잘 사용할 수 있어야 한다. SQL은 데이터베이스와 대화하는 언어다.</p>
<p>SQL에는 여러 종류의 명령이 있지만 그 중에서도 가장 중요한 것은 <code>SELECT</code>이다. 실제 서비스에서는 데이터를 저장하는 것보다 데이터를 조회하는 일이 훨씬 많기 때문이다. (ex: 유튜브 영상 업로드 1번에 조회수 수백만 번, 쇼핑몰 상품 등록 1번에 상품 조회 수천 번…)</p>
<p>그래서 데이터베이스에서는</p>
<blockquote>
<p>데이터를 얼마나 빠르고 정확하게 조회할 수 있는지가 매우 중요하다.</p>
</blockquote>
<h2 id="2-index---데이터를-빠르게-찾는-방법">(2) Index - 데이터를 빠르게 찾는 방법</h2>
<p>데이터가 적을 때는 문제가 없지만 데이터가 많아지면 검색 속도가 느려질 수 있다. 이때 사용하는 것이 인덱스이다.</p>
<p>ex: 책에서 특정 내용을 찾는다고 생각해보자</p>
<ul>
<li>인덱스가 없는 경우 처음 페이지부터 끝까지 다 읽어야 한다.</li>
</ul>
<pre><code>page 1 → page 2 → page 3 → ... → page 500</code></pre><ul>
<li>인덱스가 있는 경우 목차에서 바로 찾을 수 있다</li>
</ul>
<pre><code>index → page 327</code></pre><p>데이터베이스에서도 같은 원리가 적용된다. 예를 들어 <code>title</code> 컬럼에 index가 있다면</p>
<pre><code>SELECT * FROM topic WHERE title=&#39;MySQL&#39;;</code></pre><p>이 검색을 훨씬 빠르게 수행할 수 있다.</p>
<h2 id="3-modeling--좋은-데이터-구조-설계">(3) Modeling – 좋은 데이터 구조 설계</h2>
<p>데이터가 많아질수록 테이블 구조를 잘 설계하는 것이 매우 중요하다. 이 과정을 데이터 모델링(Data Modeling)이라고 한다.</p>
<p>ex: 다음과 같은 테이블이 있다고 생각해보자.</p>
<pre><code>user
-------------------
id
name
email
phone
address</code></pre><p>그리고 게시글을 저장하는 테이블이 있다.</p>
<pre><code>post
-------------------
id
title
content
user_name
user_email
user_phone</code></pre><p>이 구조에는 같은 정보가 여러 번 저장되어 있다. 사용자가 전화번호를 바꾸면 user 테이블, post 테이블 둘 다 수정해야 한다. 그래서 보통 다음처럼 설계한다:</p>
<pre><code>user
-------------------
id
name
email
phone</code></pre><pre><code>post
-------------------
id
title
content
user_id</code></pre><p>이렇게 하면 데이터 중복이 줄어들고 관리가 쉬워진다. 이 과정을 정규화(Normalization)라고 한다. 좋은 데이터 모델링은 데이터 중복 감소, 데이터 일관성 유지, 유지보수 편리 같은 장점을 만든다.</p>
<h2 id="4-backup--데이터-보호">(4) Backup – 데이터 보호</h2>
<p>데이터베이스에서 가장 중요한 것 중 하나가 백업이다. 왜냐하면 데이터는 언제든지 사라질 수 있기 때문이다.</p>
<p>ex: 하드디스크 고장, 서버 오류, 실수로 데이터 삭제, 해킹,…</p>
<p>그래서 실제 서비스에서는 정기적으로 백업을 수행한다.</p>
<p>최근에는 <strong>클라우드</strong>를 많이 사용한다. 대표적인 클라우드 서비스: AWS (Amazon Web Services), Google Cloud, Microsoft Azure… 클라우드를 사용하면 자동 백업, 데이터 복구, 안정적인 저장 같은 기능을 쉽게 사용할 수 있다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/dffa8fb6-ebf0-437f-829a-4f5d6b65e82b/image.png" alt=""></p>
<hr>
<h2 id="마무리">마무리</h2>
<p>웹 애플리케이션을 개발하다 보면 자연스럽게 느끼게 되는 것이 하나 있다. 웹 서비스는 결국 데이터와 함께 돌아간다는 것이다. (ex: 회원가입을 하면 사용자 정보가 저장된다, 게시글을 작성하면 글 내용이 저장된다, 로그인하면 저장된 정보와 비교해서 인증을 한다)</p>
<p>프로그래밍 언어마다 데이터베이스에 연결하기 위한 라이브러리나 API가 존재한다. ex: Python MySQL API, PHP MySQL API, Java MySQL API. 이러한 API를 통해 웹 애플리케이션은 데이터베이스와 연결되어 데이터를 저장하고 조회하며 실제 서비스가 동작하게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[6] 더 배워보기]]></title>
            <link>https://velog.io/@ttt-1-2/6-%EB%8D%94-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@ttt-1-2/6-%EB%8D%94-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 07 Mar 2026 08:19:51 GMT</pubDate>
            <description><![CDATA[<p>강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<p>이번 강의에서는 관계형 데이터베이스가 왜 필요한지 살펴보고, JOIN을 통해 데이터를 연결하는 방법과 MySQL을 사용하는 기본 환경을 간단히 알아본다.</p>
<hr>
<h2 id="관계형-데이터베이스의-필요성">관계형 데이터베이스의 필요성</h2>
<ul>
<li>유지 보수가 쉽다</li>
<li>중복된 데이터 제거가 쉽다</li>
<li>서로 다른 데이터를 연결할 수 있다 (관계형 데이터베이스의 꽃!)</li>
</ul>
<p>ex: 만약 파일이나 하나의 테이블에 모든 정보를 같이 저장하면 다음과 같이 된다.</p>
<table>
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>content</th>
<th>author</th>
<th>author_email</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>데이터베이스</td>
<td>...</td>
<td>김</td>
<td><a href="mailto:kim@gmail.com">kim@gmail.com</a></td>
</tr>
<tr>
<td>2</td>
<td>자료구조</td>
<td>...</td>
<td>이</td>
<td><a href="mailto:lee@gmail.com">lee@gmail.com</a></td>
</tr>
<tr>
<td>3</td>
<td>정보통신공학</td>
<td>...</td>
<td>이</td>
<td><a href="mailto:lee@gmail.com">lee@gmail.com</a></td>
</tr>
</tbody></table>
<p>이 경우 같은 작성자가 여러 글을 쓰면 작성자 정보가 계속 반복 저장된다.</p>
<p>→ 이메일이 바뀌면 모든 row를 수정해야 한다. 데이터 중복이 많아지고 유지 보수가 어려워진다.</p>
<blockquote>
<p>관계형 데이터베이스에서는 데이터를 테이블로 나누고 관계를 만든다.</p>
</blockquote>
<p><strong>(1) author 테이블</strong></p>
<table>
<thead>
<tr>
<th>author_id</th>
<th>name</th>
<th>email</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>김</td>
<td><a href="mailto:kim@gmail.com">kim@gmail.com</a></td>
</tr>
<tr>
<td>2</td>
<td>이</td>
<td><a href="mailto:lee@gmail.com">lee@gmail.com</a></td>
</tr>
</tbody></table>
<p><strong>(2) post 테이블</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>author_id</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>데이터베이스</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>자료구조</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>정보통신공학</td>
<td>2</td>
</tr>
</tbody></table>
<p>이렇게 하면 작성자 정보는 한 번만 저장하고 게시글에서는 author_id로 연결한다. 데이터 중복이 줄어들고 관리가 훨씬 쉬워진다.</p>
<p>이처럼 서로 다른 데이터를 연결해 구조적으로 관리할 수 있는 것이 관계형 데이터베이스의 핵심이다.</p>
<h2 id="join---관계형-데이터의-꽃">JOIN - 관계형 데이터의 꽃</h2>
<p>관계형 데이터베이스의 가장 큰 특징은 여러 테이블에 저장된 데이터를 서로 연결해서 조회할 수 있다는 것이다. 이때 사용하는 SQL 구문이 바로 <code>JOIN</code>이다.</p>
<p>ex: 다음과 같이 두 개의 테이블이 있다.</p>
<ul>
<li><p><strong>author 테이블</strong>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/063ee393-0e7b-4aa8-814b-043b8ee34e8e/image.png" alt=""></p>
</li>
<li><p><strong>topic 테이블</strong>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/1b352681-e2d6-4ae0-89fc-d453bfda3e3a/image.png" alt=""></p>
</li>
</ul>
<p>여기서 <code>topic</code> 테이블의 <code>author_id</code>는 작성자 테이블(author)의 id를 참조하는 값이다. 즉 <code>topic.author_id = [author.id](http://author.id/)</code> </p>
<p>→ 이 관계를 이용하면 게시글 정보와 작성자 정보를 함께 조회할 수 있다.</p>
<p>JOIN을 사용하면 여러 테이블에 나누어 저장된 데이터를 연결하여 조회할 수 있다. 대표적인 JOIN의 종류는 다음과 같다:</p>
<ul>
<li>INNER JOIN: 두 테이블 모두에 존재하는 데이터만 조회</li>
<li>LEFT JOIN : 왼쪽 테이블의 모든 데이터 유지</li>
<li>RIGHT JOIN: 오른쪽 테이블의 모든 데이터 유지</li>
</ul>
<pre><code class="language-sql">-- topic 테이블과 author 테이블을 LEFT JOIN으로 연결
SELECT * FROM topic LEFT JOIN author ON topic.author_id=author.id;

-- 필요한 컬럼만 선택해서 조회
SELECT topic.id,title,description,created,name,profile FROM topic LEFT JOIN author ON topic.author_id=author.id;</code></pre>
<p><img src="https://velog.velcdn.com/images/ttt-1-2/post/327b3aa8-a2fe-455a-b284-11e7cd59ff3b/image.png" alt=""></p>
<p>→ 게시글 정보와 작성자 정보들은 다른 테이블에 저장되지만 <code>JOIN</code> 통해 한눈에 확인할 수 있다.</p>
<h2 id="인터넷과-데이터베이스">인터넷과 데이터베이스</h2>
<p>인터넷은 여러 컴퓨터가 서로 통신할 수 있도록 연결해 주는 네트워크이다. 컴퓨터들은 인터넷을 통해 서로 요청(Request) 과 응답(Response) 을 주고받는다. 대표적인 구조가 바로 Client – Server 구조이다.</p>
<pre><code class="language-sql">Client   ── 요청 ──▶  Server
Client   ◀─ 응답 ──   Server</code></pre>
<p>데이터베이스도 같은 구조로 동작한다.</p>
<pre><code class="language-sql">Database Client ── SQL Query ──▶ Database Server ── 데이터 처리 ──▶ 결과 반환 ──▶ Database Client</code></pre>
<ul>
<li>MySQL Server: 데이터를 저장하고 관리하는 프로그램</li>
<li>MySQL Client: 서버에 접속해서 SQL을 실행하는 프로그램</li>
</ul>
<p>현재 실습에서 사용하고 있는 프로그램은 MySQL Client이다. <code>mysql -uroot -p</code> 로 MySQL 접속하면 영문으로 “Welcome to the MySQL monitor” 볼 수 있다. 여기서 말하는 MySQL monitor는 터미널에서 사용하는MySQL command line client를 의미한다. 즉, 우리가 SQL을 입력하고 결과를 확인하는 CLI(Command Line Interface) 기반 MySQL Client이다.</p>
<p><img src="https://velog.velcdn.com/images/ttt-1-2/post/19ed758f-e537-4415-a41f-0577c5bd1e94/image.png" alt="">
또 다른 MySQL Client로 <strong>MySQL Workbench</strong>가 있다. MySQL Workbench는 GUI기반 MySQL Client이다. 즉, 터미널 대신 그래픽 화면을 통해 데이터베이스를 관리할 수 있는 프로그램이다.</p>
<h2 id="mysql-workbench">MySQL Workbench</h2>
<p>다운로드: <a href="https://www.mysql.com/products/workbench/">https://www.mysql.com/products/workbench/</a></p>
<p>앞에서 사용한 MySQL monitor는 터미널에서 SQL을 직접 입력해야 하는 CLI 방식이었다. 반면 MySQL Workbench는 GUI를 통해 데이터베이스를 관리할 수 있다.</p>
<p>ex: SQL 쿼리를 입력하고 실행하거나 데이터 조회 결과를 표 형태로 바로 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ttt-1-2/post/e28f2054-db52-41d1-b1a7-96385e11fd9f/image.png" alt="">
<img src="https://velog.velcdn.com/images/ttt-1-2/post/0fc93ef8-dcc4-4f79-8a9e-62312e23b82b/image.png" alt="">
MySQL Workbench를 사용하면 SQL 실행뿐 아니라 데이터베이스 서버의 상태, 성능, 사용자 관리 등 다양한 작업을 한 곳에서 관리할 수 있다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/369e29f3-0a31-4098-819a-eabb938637e0/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[5] MySQL CRUD]]></title>
            <link>https://velog.io/@ttt-1-2/5-MySQL-CRUD</link>
            <guid>https://velog.io/@ttt-1-2/5-MySQL-CRUD</guid>
            <pubDate>Sat, 07 Mar 2026 07:22:41 GMT</pubDate>
            <description><![CDATA[<p>강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<p>이번 강의에서는 데이터베이스에서 가장 기본적인 작업인 CRUD(Create, Read, Update, Delete) 개념을 살펴본다.</p>
<hr>
<p>지난 실습에서 생성한 topic 테이블의 구조를 확인하기 위해 <code>DESC topic</code> 명령어를 사용한다. 실행 결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/4aa4a07b-3f36-4528-9d32-197395ded409/image.png" alt=""></p>
<h2 id="insert-구문-create">INSERT 구문 (Create)</h2>
<p>새로운 데이터를 테이블에 추가할 때 <code>INSERT</code> 구문을 사용한다.</p>
<pre><code class="language-sql">INSERT INTO topic (title, description, created, author, profile)
VALUES(&#39;MySQL&#39;, &#39;MySQL is ...&#39;, NOW(), &#39;trang&#39;, &#39;developer&#39;);
-- topic 테이블에 새로운 게시글 데이터를 추가한다
-- NOW() : 현재 날짜와 시간을 자동으로 입력한다</code></pre>
<h2 id="select-구문-read">SELECT 구문 (Read)</h2>
<p>데이터를 조회할 때는 <code>SELECT</code> 구문을 사용한다.</p>
<pre><code class="language-sql">-- topic 테이블의 모든 컬럼과 모든 데이터를 조회
SELECT * FROM topic;

-- 필요한 컬럼만 선택해서 조회
SELECT id,title,created,author FROM topic;

-- author가 &#39;trang&#39;인 데이터만 조회
SELECT id,title,created,author FROM topic WHERE author=&#39;trang&#39;;

-- id 기준으로 내림차순 정렬 (최신 글이 먼저 나오게)
SELECT id,title,created,author FROM topic WHERE author=&#39;trang&#39; ORDER BY id DESC;

-- 결과를 3개 row까지만 조회
SELECT * FROM topic LIMIT 3;</code></pre>
<h2 id="update-구문">UPDATE 구문</h2>
<p>기존 데이터를 수정할 때 <code>UPDATE</code> 구문을 사용한다.</p>
<pre><code class="language-sql">-- id가 2인 row의 title과 description을 수정
UPDATE topic SET description=&#39;Oracle is ...&#39;, title=&#39;Oracle&#39; WHERE id=2;</code></pre>
<ul>
<li>주의:  <code>WHERE</code> 조건을 넣지 않으면 테이블의 모든 데이터가 수정될 수 있다.</li>
</ul>
<h2 id="delete-구문">DELETE 구문</h2>
<p>데이터를 삭제할 때는 <code>DELETE</code> 구문을 사용한다.</p>
<pre><code class="language-sql">-- id가 5인 데이터를 삭제
DELETE FROM topic WHERE id=5;</code></pre>
<ul>
<li>주의: <code>WHERE</code> 조건이 없으면 테이블의 모든 데이터가 삭제된다.</li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<p>데이터베이스에서 데이터를 다룰 때 가장 기본이 되는 작업은 CRUD이다.</p>
<pre><code>CREATE → INSERT
READ   → SELECT
UPDATE → UPDATE
DELETE → DELETE</code></pre><p>각각의 SQL 구문을 통해 데이터를 추가하고, 조회하고, 수정하고, 삭제할 수 있다.</p>
<p>또한 <code>UPDATE</code>나 <code>DELETE</code>를 사용할 때는 반드시 <code>WHERE</code> 조건을 확인해야 한다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[4] MySQL 테이블의 생성]]></title>
            <link>https://velog.io/@ttt-1-2/4-MySQL-%ED%85%8C%EC%9D%B4%EB%B8%94%EC%9D%98-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@ttt-1-2/4-MySQL-%ED%85%8C%EC%9D%B4%EB%B8%94%EC%9D%98-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Sat, 07 Mar 2026 07:15:11 GMT</pubDate>
            <description><![CDATA[<p>강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<p>실습 tips: MySQL cheating sheet 검색하면 주요 SQL 문법과 예시를 빠르게 확인할 수 있다.</p>
<hr>
<h2 id="실습-테이블의-생성"><strong>실습: 테이블의 생성</strong></h2>
<p>먼저 사용할 데이터베이스를 선택한다.</p>
<pre><code class="language-sql">USE opentutorials</code></pre>
<p>다음으로 <code>topic</code>이라는 테이블을 생성한다.</p>
<pre><code class="language-sql">CREATE TABLE topic (
  id INT(11) NOT NULL AUTO_INCREMENT,
  title VARCHAR(100) NOT NULL,
  description TEXT NULL,
  created DATETIME NOT NULL,
  author VARCHAR(15) NULL,
  profile VARCHAR(200) NULL,
  PRIMARY KEY(id)
);</code></pre>
<p>이 테이블은 게시글 정보를 저장하는 간단한 구조이다. 각 컬럼의 의미는 다음과 같다:</p>
<p><strong>id</strong></p>
<pre><code class="language-sql">id INT(11) NOT NULL AUTO_INCREMENT,</code></pre>
<ul>
<li><code>INT</code> : 정수형 데이터 타입</li>
<li><code>NOT NULL</code> : 값이 반드시 있어야 한다</li>
<li><code>AUTO_INCREMENT</code> : 새로운 데이터가 추가될 때마다 값이 자동으로 1씩 증가한다. (게시글마다 고유한 id 위해 꼭 넣어 줘야 함!)</li>
</ul>
<p><strong>title</strong></p>
<pre><code class="language-sql">title VARCHAR(100) NOT NULL</code></pre>
<ul>
<li><code>VARCHAR(100)</code> : 최대 100자의 문자열을 저장할 수 있다</li>
<li><code>NOT NULL</code> : 제목은 반드시 있어야 하므로 NULL을 허용하지 않는다</li>
</ul>
<p><strong>description</strong></p>
<pre><code class="language-sql">description TEXT NULL</code></pre>
<ul>
<li><code>TEXT</code> : 긴 문자열을 저장할 수 있는 타입</li>
<li><code>NULL</code> : 값이 없어도 허용한다. 본문은 길어질 수 있기 때문에 <code>VARCHAR</code> 대신 <code>TEXT</code> 타입을 사용한다.</li>
</ul>
<p><strong>created</strong></p>
<pre><code class="language-sql">created DATETIME NOT NULL</code></pre>
<ul>
<li><code>DATETIME</code> : 날짜와 시간을 함께 저장하는 데이터 타입</li>
<li><code>NOT NULL</code> : 게시글이 생성된 시간은 반드시 기록되어야 한다.</li>
</ul>
<p><strong>author</strong></p>
<pre><code class="language-sql">author VARCHAR(15) NULL</code></pre>
<ul>
<li><code>VARCHAR(15)</code> : 최대 15자의 문자열을 저장할 수 있다</li>
<li><code>NULL</code> : 작성자가 없는 경우도 허용한다</li>
</ul>
<p><strong>profile</strong></p>
<pre><code class="language-sql">profile VARCHAR(200) NULL</code></pre>
<ul>
<li><code>VARCHAR(200)</code> : 최대 200자의 문자열을 저장할 수 있다.</li>
</ul>
<p><strong>PRIMARY KEY</strong></p>
<pre><code class="language-sql">PRIMARY KEY(id)</code></pre>
<p><code>PRIMARY KEY</code>는 각 행을 구분하는 유일한 값을 의미한다. 이 테이블에서는 <code>id</code> 컬럼을 기본 키로 사용한다.</p>
<blockquote>
<p><strong>주의점: AUTO_INCREMENT와 PRIMARY KEY</strong></p>
</blockquote>
<p><code>id</code> 컬럼을 보면 <code>AUTO_INCREMENT</code> 옵션이 붙어 있다. <code>AUTO_INCREMENT</code>는 데이터가 추가될 때 값이 자동으로 1씩 증가하도록 하는 기능이다. </p>
<p>→ AUTO_INCREMENT를 사용하는 컬럼은 반드시 KEY로 정의되어야 한다. 보통은 <code>PRIMARY KEY</code> 로 설정한다.</p>
<p>테이블이 잘 생성되었는지 확인하려면 <code>SHOW TABLES</code>  명령어 사용한다. 여기에서 topic 테이블이 보이면 정상적으로 생성된 것이다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/951acfc2-6799-4fce-b31b-4409fd8ddb57/image.png" alt=""></p>
<hr>
<h2 id="정리">정리</h2>
<p>이번 실습 에서는 다음 내용을 확인했다.</p>
<ul>
<li><code>USE</code> : 사용할 데이터베이스 선택</li>
<li><code>CREATE TABLE</code> : 새로운 테이블 생성</li>
<li><code>VARCHAR</code>, <code>TEXT</code>, <code>DATETIME</code> ,… : 컬럼에 사용할 데이터 타입 지정</li>
<li><code>AUTO_INCREMENT</code> : 데이터 추가 시 값 자동 증가</li>
<li><code>PRIMARY KEY</code> : 각 row를 구분하는 고유 키 설정</li>
<li><code>SHOW TABLES</code> : 생성된 테이블 확인</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[3] MySQL 기본]]></title>
            <link>https://velog.io/@ttt-1-2/3-MySQL-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@ttt-1-2/3-MySQL-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Fri, 06 Mar 2026 08:00:41 GMT</pubDate>
            <description><![CDATA[<p>강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<h2 id="mysql의-구조">MySQL의 구조</h2>
<p>데이터베이스의 구조를 간단하게 보면 다음과 같다:</p>
<pre><code class="language-bash">Database Server
┌───────────────────────────────────────────────────────────────┐
│                                                               │
│   Database                                                    │
│   ┌───────────────────────────────────────────────┐           │
│   │                                               │           │
│   │   Table            Table            Table     │           │
│   │  ┌───────┐        ┌───────┐        ┌───────┐  │           │
│   │  │ row   │        │ row   │        │ row   │  │           │
│   │  │ row   │        │ row   │        │ row   │  │           │
│   │  │ row   │        │ row   │        │ row   │  │           │
│   │  └───────┘        └───────┘        └───────┘  │           │
│   │                                               │           │
│   └───────────────────────────────────────────────┘           │
│                                                               │
│   Database                                                    │
│   ┌───────────────────────────────────────────────┐           │
│   │   Table        Table        Table             │           │
│   └───────────────────────────────────────────────┘           │
│                                                               │
└───────────────────────────────────────────────────────────────┘</code></pre>
<p>(1) Database Server: 여러 개의 데이터베이스를 관리하는 시스템이다. MySQL 프로그램이 실행되는 환경이라고 볼 수 있다.</p>
<p>(2) Database: 여러 개의 테이블을 포함하는 공간이다.</p>
<p>(2) Table: 실제 데이터가 저장되는 구조다.</p>
<h2 id="mysql-서버-접속">MySQL 서버 접속</h2>
<p>MySQL 서버에 접속하려면 터미널에서 다음 명령어를 사용한다: <code>mysql -u root -p</code></p>
<h2 id="mysql-스키마의-사용">MySQL 스키마의 사용</h2>
<ul>
<li><p>스키마 생성, 삭제:
<img src="https://velog.velcdn.com/images/ttt-1-2/post/8ac21b66-0754-4df1-a699-860216e52d85/image.png" alt=""></p>
</li>
<li><p>스키마 목록 확인:
<img src="https://velog.velcdn.com/images/ttt-1-2/post/f17a1fb3-b412-49ae-9475-e5523d45fa71/image.png" alt=""></p>
</li>
<li><p>스키마 선택해서 사용: 
<img src="https://velog.velcdn.com/images/ttt-1-2/post/e2fe3324-ecf6-40d9-95e6-161d839d58bf/image.png" alt=""></p>
</li>
</ul>
<h2 id="sql과-테이블-구조">SQL과 테이블 구조</h2>
<ul>
<li>SQL: <strong>S</strong>tructured <strong>Q</strong>uery <strong>L</strong>anguage</li>
<li>예를 들어 하나의 테이블은 다음과 같은 구조를 가진다.<pre><code class="language-sql">table
├─ row
│   ├─ column
│   ├─ column
│   └─ column
└─ row</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2] MySQL 서론]]></title>
            <link>https://velog.io/@ttt-1-2/2-MySQL-%EC%84%9C%EB%A1%A0</link>
            <guid>https://velog.io/@ttt-1-2/2-MySQL-%EC%84%9C%EB%A1%A0</guid>
            <pubDate>Fri, 06 Mar 2026 07:06:54 GMT</pubDate>
            <description><![CDATA[<p>강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<p>파일 기반으로 데이터를 관리할 때 여러 가지 한계가 있었다. 이 문제를 해결하기 위해 1960년대부터 데이터베이스 연구가 시작된다. 1970년에는 Edgar F. Codd가 IBM에서 관계형 데이터베이스(Relational Database) 개념을 제안한다.</p>
<p>이후 수십 년이 지난 지금까지도 관계형 데이터베이스는 가장 널리 사용되는 데이터베이스 모델이다. (ex: MySQL, Oracle, SQL Server, PostgreSQL, Db2, Access)</p>
<p>MySQL은 무료로 사용할 수 있는 오픈소스 소프트웨어이며 성능도 준수한 관계형 데이터베이스다. 그래서 웹 개발에서 많이 사용되는 데이터베이스 중 하나이자 좋은 대안으로 자주 언급된다.</p>
<h2 id="데이터베이스의-목적">데이터베이스의 목적</h2>
<p>데이터베이스를 이해할 때 스프레드시트와 비교해 보면 이해하기 쉽다.</p>
<p><strong>공통점</strong></p>
<p>(1) 데이터를 표 형태로 저장한다</p>
<p>(2) 필터나 정렬 기능으로 데이터를 정리할 수 있다</p>
<p><strong>차이점</strong></p>
<p>스프레드시트:  사용자가 직접 화면에서 클릭하면서 데이터를 조회하고 수정한다.</p>
<p>데이터베이스 (MySQL): 프로그래밍 언어를 통해 데이터를 조작할 수 있다. 프로그램에서 자동으로 데이터를 처리할 수 있다.</p>
<p><em>→ 스프레드시트는 사람이 직접 다루는 도구 vs 데이터베이스는 프로그램이 데이터를 다루기 위한 시스템이다.</em></p>
<blockquote>
<p>웹 서비스에서는 데이터베이스가 핵심 역할을 한다.
예를 들어 회원가입을 하면 사용자 정보가 데이터베이스에 저장된다. 로그인을 하면 서버가 데이터베이스에서 사용자 정보를 조회한다.
→ 그래서 <strong>데이터베이스는 웹 서비스의 데이터를 관리하는 기반 시스템</strong>이라고 볼 수 있다.</p>
</blockquote>
<h2 id="mysql-설치-macos-환경-기준">MySQL 설치 (macOS 환경 기준)</h2>
<p>아래 링크에 접속하면 다운로드할 수 있다:</p>
<p><a href="https://dev.mysql.com/downloads/mysql/">https://dev.mysql.com/downloads/mysql/</a></p>
<p><strong>설치</strong></p>
<ul>
<li>다운로드한 파일을 실행하면 설치가 시작된다.</li>
<li>주의: Accounts and Roles 화면에서 사용자 비밀번호를 입력해야 한다.</li>
</ul>
<p><strong>설정</strong></p>
<ul>
<li>MySQL 실행 파일이 다음 경로에 설치된다. /usr/local/mysql/bin</li>
<li>터미널에서 MySQL 명령어를 바로 사용하려면 PATH 환경변수에 이 경로를 추가해야 한다.</li>
</ul>
<pre><code class="language-bash"># step 1: 터미널에서 설정 파일을 연다.
nano ~/.zshrc

# step 2: 다음 내용을 추가한다.
export PATH=$PATH:/usr/local/mysql/bin

# step 3: 설정을 적용한다.
source ~/.zshrc

# 이제 터미널에서 다음 명령어로 MySQL에 접속할 수 있다.
mysql -u root -p</code></pre>
<p>이제 <code>Enter password:</code> 에 사용자 지정한 비밀번호를 입력하면 된다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/1a7aad1c-a78a-4524-9dd0-b86a960bad86/image.png" alt=""></p>
<ul>
<li>mysql 클라이언트 종료하기: mysql&gt; exit</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[1] DATABASE 소개 및 본질 알아보기]]></title>
            <link>https://velog.io/@ttt-1-2/1-DATABASE-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EB%B3%B8%EC%A7%88-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@ttt-1-2/1-DATABASE-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EB%B3%B8%EC%A7%88-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 06 Mar 2026 00:58:56 GMT</pubDate>
            <description><![CDATA[<p>강의: 강의: <a href="https://www.inflearn.com/course/database-2-mysql-%EA%B0%95%EC%A2%8C?cid=119293">https://www.inflearn.com/course/database-2-mysql-강좌?cid=119293</a></p>
<p>첫번째 강의에서는 데이터베이스의 기본 개념을 먼저 잡는다.</p>
<h2 id="데이터베이스의-본질">데이터베이스의 본질</h2>
<p>데이터베이스의 핵심은 결국 <strong>CRUD</strong>다.  </p>
<p>입력:</p>
<ul>
<li>생성 → Create</li>
<li>수정 → Update</li>
<li>삭제 → Delete</li>
</ul>
<p>출력:</p>
<ul>
<li>조회 → Read</li>
</ul>
<p>데이터를 생성하고, 읽고, 수정하고, 삭제하는 것이 데이터베이스가 하는 가장 기본적인 작업이다.</p>
<h2 id="file-vs-database">File vs Database</h2>
<p><strong>File</strong></p>
<p>파일로 데이터를 관리하면 구조적으로 다루기 어렵다. (ex: 텍스트 파일을 만들면 문장 순서를 바꾸거나 특정 정보만 따로 보기가 쉽지 않다. 제목, 본문 같은 데이터도 원하는 방식으로 조회하기 어렵다…)</p>
<p><strong>Spreadsheet</strong></p>
<p>엑셀처럼 스프레드시트를 사용하면 조금 나아진다. 필터나 정렬 기능을 통해 데이터를 어느 정도 구조적으로 볼 수 있다.</p>
<p><strong>Database</strong></p>
<p>데이터베이스를 사용하면 데이터를 구조적으로 저장하고 관리할 수 있다. 필요한 데이터만 조회하거나 정렬, 필터링하는 것도 훨씬 쉽게 할 수 있다. 데이터가 많아지고 여러 사람이 함께 사용해야 할 때 특히 유용하다.</p>
<h2 id="database-1을-마치며">Database 1을 마치며</h2>
<p>통계를 보면 2026년 기준으로 Oracle이 1위, MySQL이 2위다. (출처: <a href="https://db-engines.com/en/ranking">https://db-engines.com/en/ranking</a>)
<img src="https://velog.velcdn.com/images/ttt-1-2/post/98c9ba17-e124-4241-8992-473292c05b01/image.png" alt="">
데이터베이스도 종류가 여러 가지 있다. Oracle, MySQL, PostgreSQL 같은 관계형 데이터베이스 있고 MongoDB처럼 관계형이 아닌 데이터베이스도 있다. </p>
<p>그래서 보통은 관계형 데이터베이스 하나 + 비관계형 데이터베이스 하나 이렇게 같이 배워두면 좋다고 한다. <strong>MySQL</strong>은 비교적 배우기 쉬워서 입문용 데이터베이스로 많이 추천된다고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Part 3 REST API와 테스트 코드 작성하기 (10-13장)]]></title>
            <link>https://velog.io/@ttt-1-2/Part-3-REST-API%EC%99%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0-10-13%EC%9E%A5</link>
            <guid>https://velog.io/@ttt-1-2/Part-3-REST-API%EC%99%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0-10-13%EC%9E%A5</guid>
            <pubDate>Fri, 03 Oct 2025 15:42:45 GMT</pubDate>
            <description><![CDATA[<h1 id="10장---rest-api와-json">10장 - REST API와 JSON</h1>
<h2 id="101-rest-api와-json">10.1 REST API와 JSON</h2>
<blockquote>
<p><strong>REST API</strong></p>
</blockquote>
<ul>
<li>REST API (Representational State Transfer API)는 서버의 자원을 클라이언트에 구애받지 않고 사용할 수 있게하는 설계 방식이다</li>
<li>다음과 같이 REST API의 동작을 이해할 수 있다:</li>
</ul>
<pre><code class="language-bash">[클라이언트들]
 PC / 모바일 / 태블릿 / IoT ...
        │
        │  HTTP 요청
        ▼
+------------------+
|   REST API 서버  |
+------------------+
        │  JSON 응답(뷰X, 데이터O) //서버는 화면이 아닌 데이터(JSON) 를 돌려준다.
        ▼
자원(RESOURCE): /articles
   ├─ GET    /articles          → 글 목록 조회
   ├─ GET    /articles/{id}     → 글 단건 조회
   ├─ POST   /articles          → 글 생성
   ├─ PATCH  /articles/{id}     → 글 부분 수정
   └─ DELETE /articles/{id}     → 글 삭제
</code></pre>
<blockquote>
<p><strong>JSON</strong></p>
</blockquote>
<ul>
<li>JSON 데이터는 키(key)와 값(value)으로 구성된 정렬되지 않는 속성(properties)의 집합이다.<ul>
<li>키: 큰따옴표 (””)로 감싼다</li>
<li>값: 문자열인 경우만 큰따옴표 (””)로 감싼다</li>
</ul>
</li>
</ul>
<pre><code class="language-json">{
    &quot;name&quot;: &quot;망고&quot;,
    &quot;breeds&quot;: &quot;골든리트리버&quot;,
    &quot;age&quot;: 2
}</code></pre>
<h2 id="102-rest-api-동작-살펴보기">10.2 REST API 동작 살펴보기</h2>
<p>연습용으로 Talend API Tester 이용해 HTTP 요청을 보내고 돌아온 응답을 확인하겠습니다. {JSON} Placeholder 사이트에서 자원들을 JSON 형식으로 받아 실습해 보겠습니다!</p>
<ul>
<li><a href="https://jsonplaceholder.typicode.com/">{JSON} Placeholder 사이트</a></li>
<li><a href="https://chromewebstore.google.com/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=ko">Talend API 확장 프로그램</a> 설치</li>
</ul>
<blockquote>
<p><strong>HTTP 상태 코드</strong></p>
</blockquote>
<p>HTTP 상태 코드는 클라이언트가 보낸 요청이 성공/실패했는지 알려주는 코드다. 코드는 100번대부터 500번대까지 5개 그룹으로 나눠 있다.</p>
<p>자세한 내용: <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Status">MDN 웹 문서</a></p>
<table>
<thead>
<tr>
<th>1XX (정보)</th>
<th>요청이 수신돼, 처리 중</th>
</tr>
</thead>
<tbody><tr>
<td>2XX (성공)</td>
<td>요청이 정상적으로 처리됨</td>
</tr>
<tr>
<td>3XX (리다이렉션)</td>
<td>요청을 완료하려면 추가 행동이 필요함</td>
</tr>
<tr>
<td>4XX (클라이언트 요청 오류)</td>
<td>클라이언트의 요청이 잘못됨</td>
</tr>
<tr>
<td>5XX (서버 응답 오류)</td>
<td>서버 내부에 에러가 발생해 클라이언트의 요청에 답할 수 없음</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>GET 요청하고 응답받기</strong></p>
</blockquote>
<ul>
<li>Method: GET</li>
<li>URL: <a href="https://jsonplaceholder.typicode.com/posts">https://jsonplaceholder.typicode.com/posts</a></li>
<li>응답: 200 (OK)
<img src="https://velog.velcdn.com/images/ttt-1-2/post/3375e93c-7791-49b2-a112-a3489a795296/image.png" alt=""></li>
</ul>
<blockquote>
<p><strong>POST 요청하고 응답받기</strong></p>
</blockquote>
<ul>
<li>Method: POST</li>
<li>URL: <a href="https://jsonplaceholder.typicode.com/posts">https://jsonplaceholder.typicode.com/posts</a></li>
<li>응답: 201 (OK)
<img src="https://velog.velcdn.com/images/ttt-1-2/post/5394582d-9108-4dd4-887e-7554db7724d3/image.png" alt=""></li>
</ul>
<blockquote>
<p><strong>PATCH 요청하고 응답받기</strong></p>
</blockquote>
<ul>
<li>Method: PATCH</li>
<li>URL: <a href="https://jsonplaceholder.typicode.com/posts/1">https://jsonplaceholder.typicode.com/posts/1</a></li>
<li>응답: 200 (OK)
<img src="https://velog.velcdn.com/images/ttt-1-2/post/9b7d2942-0117-47dd-ae7c-116ce63af650/image.png" alt=""></li>
</ul>
<blockquote>
<p><strong>DELETE 요청하고 응답받기</strong></p>
</blockquote>
<ul>
<li>Method: DELETE</li>
<li>URL: <a href="https://jsonplaceholder.typicode.com/posts/10">https://jsonplaceholder.typicode.com/posts/10</a></li>
<li>응답: 200 (OK)
<img src="https://velog.velcdn.com/images/ttt-1-2/post/afed8acc-d816-4ecf-8c7e-44002fe8dba2/image.png" alt=""></li>
</ul>
<hr>
<h1 id="11장---http와-rest-컨트롤러">11장 - HTTP와 REST 컨트롤러</h1>
<p>Part 2에서 만든 firstproject를 이용해 데이터를 CRUD하기 위해 REST API를 구현해 보겠습니다!</p>
<ul>
<li>REST API:<ul>
<li>REST: HTTP URL로 서버의 자원을 명시하고, HTTP 메소드로 해당 자원에 대해 CRUD하는 것</li>
<li>API: 클라이언트가 서버의 자원을 요청할 수 있도록 서버에서 제공하는 인터페이스</li>
</ul>
</li>
</ul>
<h2 id="rest-api-구현">REST API 구현</h2>
<blockquote>
<p><strong>GET 구현하기</strong></p>
</blockquote>
<pre><code class="language-java">    // GET
  @GetMapping(&quot;/api/articles&quot;)
  public List&lt;Article&gt; index() {
      return articleRepository.findAll();
  }

  @GetMapping(&quot;/api/articles/{id}&quot;)
  public Article show(@PathVariable Long id) {
      return articleRepository.findById(id).orElse(null);
  }</code></pre>
<blockquote>
<p><strong>POST 구현하기</strong></p>
</blockquote>
<ul>
<li>주의점: <code>@RequestBody</code> 추가해야 본문(BODY)을 받아 올 수 있다!</li>
</ul>
<pre><code class="language-java">  // POST
  @PostMapping(&quot;/api/articles&quot;)
  public Article create(@RequestBody ArticleForm dto) {
      Article article = dto.toEntity();
      return articleRepository.save(article);
  }</code></pre>
<blockquote>
<p><strong>PATCH 구현하기</strong></p>
</blockquote>
<ul>
<li>주의점: 일부만 수정한 경우: <code>patch</code> 메소드를 만들어야 한다</li>
</ul>
<pre><code class="language-java">    // PATCH
  @PatchMapping(&quot;/api/articles/{id}&quot;)
  public ResponseEntity&lt;Article&gt; update(@PathVariable Long id, @RequestBody ArticleForm dto) {
      // 1. DTO -&gt; 엔티티 변환하기
      Article article = dto.toEntity();
      // 2. 타깃 조회하기
      Article target = articleRepository.findById(id).orElse(null);
      // 3. 잘못된 요청 처리하기
      if(target == null || id != article.getId()) {
          return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
      }
      // 4. 업데이트 및 정상 응답(200)하기
      target.patch(article); // 기존 데이터에 새 데이터 붙이기
      Article updated = articleRepository.save(target); // 수정 내용 DB에 최종 저장
      return ResponseEntity.status(HttpStatus.OK).body(updated);
  }</code></pre>
<blockquote>
<p><strong>DELETE 구현하기</strong></p>
</blockquote>
<pre><code class="language-java">  // DELETE
  @DeleteMapping(&quot;/api/articles/{id}&quot;)
  public ResponseEntity&lt;Article&gt; delete(@PathVariable Long id) {
      // 1. 대상 찾기
      Article target = articleRepository.findById(id).orElse(null);
      // 2. 잘못된 요청 찾기
      if(target == null) {
          return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
      }
      // 3. 대상 삭제하기
      articleRepository.delete(target);
      return ResponseEntity.status(HttpStatus.OK).build();
  }</code></pre>
<hr>
<h1 id="12장---서비스-계층과-트렌잭션">12장 - 서비스 계층과 트렌잭션</h1>
<h2 id="121-개념">12.1 개념</h2>
<ul>
<li>서비스: 컨트롤러와 리포지토리 사이에서 비즈니스 규칙을 수행하고 흐름을 조정한다. 여러 리포지토리 호출을 묶어 트랜잭션 경계를 만든다</li>
<li>트랜잭션: 여러 데이터 작업을 하나의 논리적 단위로 묶어 전부 성공하면 커밋하고 하나라도 실패하면 롤백한다</li>
</ul>
<p>ex:</p>
<ul>
<li>웨이터 = 컨트롤러: 손님(클라이언트) 주문을 받아 전달한다</li>
<li>주방장 = 서비스: 어떤 메뉴(비즈니스 로직)를 어떤 순서로 만들지 결정하고 전체를 지휘한다. 여러 하위 작업을 하나의 주문 단위(트랜잭션) 로 묶는다</li>
<li>보조요리사 = 리포지토리: 주방장이 지시한 대로 재료 창고(DB) 에서 꺼내고 넣는 CRUD 를 수행한다</li>
<li>트랜잭션 = 한 테이블의 코스 전체: 모든 요리가 완성되어야 서빙한다. 하나라도 문제가 나면 전체 취소(롤백) 하고 처음부터 다시 준비한다</li>
</ul>
<h2 id="122-서비스-계층-만들기">12.2 서비스 계층 만들기</h2>
<p>이전 11장까지 만든 코드를 요청해 보겠습니다!</p>
<p>수정한 부분: </p>
<ul>
<li><code>service</code> package 만들기</li>
<li><code>ArticleApiController</code> 수정하기</li>
</ul>
<p>ex:</p>
<ul>
<li>수정하기 전:</li>
</ul>
<pre><code class="language-java">// controller

  // POST
  @PostMapping(&quot;/api/articles&quot;)
  public Article create(ArticleForm dto) {
      Article article = dto.toEntity();
      return articleRepository.save(article);
  }</code></pre>
<ul>
<li>수정한 후:</li>
</ul>
<pre><code class="language-java">// controller

  // POST
  @PostMapping(&quot;/api/articles&quot;)
  public ResponseEntity&lt;Article&gt; create(@RequestBody ArticleForm dto) {
      Article created = articleService.create(dto);
      return (created != null) ?
              ResponseEntity.status(HttpStatus.OK).body(created) :
              ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
  }

// service

    public Article create(ArticleForm dto) {
            Article article = dto.toEntity();
            if (article.getId() != null) {
                return null;
            }
            return articleRepository.save(article);
    }</code></pre>
<h2 id="123-트랜잭션-맛보기">12.3 트랜잭션 맛보기</h2>
<ul>
<li><code>ArticleApiController</code></li>
</ul>
<pre><code class="language-java">  @PostMapping(&quot;/api/transaction-test&quot;)
  public ResponseEntity&lt;List&lt;Article&gt;&gt; transactionTest (@RequestBody List&lt;ArticleForm&gt; dtos) {
      List&lt;Article&gt; createdList = articleService.createArticles(dtos);
      return (createdList != null) ?
              ResponseEntity.status(HttpStatus.OK).body(createdList) :
              ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
  }</code></pre>
<ul>
<li><code>ArticleService</code></li>
</ul>
<pre><code class="language-java">
  @Transactional
  public List&lt;Article&gt; createArticles(List&lt;ArticleForm&gt; dtos) {
      // 1. dto 묶음을 엔티티 묶음으로 변환하기
      List&lt;Article&gt; articleList = dtos.stream()
              .map(dto -&gt; dto.toEntity())
              .collect(Collectors.toList());
      // 2. 엔티티 묶음을 DB에 저장하기
      articleList.stream()
              .forEach(article -&gt; articleRepository.save(article));
      // 3. 강제 예외 발생시키기
      articleRepository.findById(-1L)
              .orElseThrow(() -&gt; new IllegalArgumentException(&quot;결제 실패!&quot;));
      // 4. 결과 값 반환하기
      return articleList;

  }</code></pre>
<p>→ <code>@Transactional</code>로 메서드 전체를 하나의 트랜잭션으로 묶어 DTO 목록을 엔티티로 변환해 저장한 뒤, 의도적으로 예외를 던져 롤백이 일어나는지 확인한다. 예외가 발생하면 이전에 저장된 작업이 모두 취소되어 DB에 반영되지 않는다!</p>
<hr>
<h1 id="13장---테스트-코드-작성하기">13장 - 테스트 코드 작성하기</h1>
<p>테스트 코드는 보통 3단계로 작성한다:</p>
<ol>
<li>예상 데이터 작성하기</li>
<li>실제 데이터 획득하기</li>
<li>예상 데이터와 실제 데이터 비교해 검증하기</li>
</ol>
<h2 id="테스트-코드-작성하기">테스트 코드 작성하기</h2>
<ul>
<li><p>메소드 오른쪽 누르고 Generate → Test 선택. JUnit5로 테스트를 만들겠습니다!
<img src="https://velog.velcdn.com/images/ttt-1-2/post/cd1c24ec-b533-4aff-b73d-7d02081602d6/image.png" alt=""></p>
</li>
<li><p>ArticleSeriveTest 생성 위치를 확인할 수 있다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/2a71f3f6-d4ff-47f7-b89d-c5612c649dc4/image.png" alt=""></p>
</li>
<li><p>테스트 코드 작성하기:</p>
<pre><code class="language-java">@SpringBootTest
class ArticleServiceTest {
  @Autowired
  ArticleService articleService;

  @Test
  void index() {
      // 1. 예상 데이터
      Article a = new Article(1L, &quot;가가가가&quot;, &quot;1111&quot;);
      Article b = new Article(2L, &quot;나나나나&quot;, &quot;2222&quot;);
      Article c = new Article(3L, &quot;다다다다&quot;, &quot;3333&quot;);
      List&lt;Article&gt; expected = new ArrayList&lt;Article&gt;(Arrays.asList(a,b,c));
      // 2. 실제 데이터
      List&lt;Article&gt; articles = articleService.index();
      // 3. 비교 및 검증
      assertEquals(expected.toString(), articles.toString());
  }
  @Test
  void show_성공() {
      // 1. 예상 데이터
      Long id = 1L;
      Article expected = new Article(id, &quot;가가가가&quot;, &quot;1111&quot;);
      // 2. 실제 데이터
      Article article = articleService.show(id);
      // 3. 비교 및 검증
      assertEquals(expected.toString(), article.toString());
  }

  @Test
  void show_실패() {
      // 1. 예상 데이터
      Long id = -1L;
      Article expected = null;
      // 2. 실제 데이터
      Article article = articleService.show(id);
      // 3. 비교 및 검증
      assertEquals(expected, article);
  }
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 | 투 포인터, 슬라이딩 윈도우]]></title>
            <link>https://velog.io/@ttt-1-2/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%88%AC-%ED%8F%AC%EC%9D%B8%ED%84%B0-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%94%A9-%EC%9C%88%EB%8F%84%EC%9A%B0</link>
            <guid>https://velog.io/@ttt-1-2/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%88%AC-%ED%8F%AC%EC%9D%B8%ED%84%B0-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%94%A9-%EC%9C%88%EB%8F%84%EC%9A%B0</guid>
            <pubDate>Thu, 02 Oct 2025 08:46:38 GMT</pubDate>
            <description><![CDATA[<p>이 글의 내용은 다음과 같습니다:</p>
<ol>
<li><p>투포인터</p>
</li>
<li><p>1 백준 1253 좋다</p>
</li>
<li><p>슬라이딩 윈도우</p>
</li>
<li><p>1 백준 12891번: DNA 비밀번호</p>
</li>
</ol>
<hr>
이 두 가지 알고리즘의 공통점은 1차원 배열 반복 탐색할 때 시간 복잡 O(N^2) 에서 O(N) 으로 줄일 수 있습니다.

<p>다른 점은 투 포인터의 경우 두 인덱스 start, end를 조건에 따라 독립적으로 이동시키며 구간을 늘리거나 줄여서 배열을 탐색합니다.</p>
<p>반면 슬라이딩 윈도우는 고정 크기의 윈도우를 유지한 채 한 칸씩 이동하면서 들어오는 원소 &amp;&amp; 빠지는 원소만 O(1)로 반영해 문제를 해결합니다.</p>
<hr>

<h1 id="1-투-포인터">1. 투 포인터</h1>
<ul>
<li>투 포인터는 두 개의 포인터 start(left), end(right) 이용해 값을 탐색하는 알고리즘입니다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/c4b76041-11a2-493d-ae6d-7bd44ff87eee/image.png" alt=""></li>
</ul>
<h2 id="11-1253-좋다">1.1) 1253 좋다</h2>
<p><img src="https://velog.velcdn.com/images/ttt-1-2/post/f973491d-2205-4367-a388-b2bcbad7182d/image.png" alt=""></p>
<ul>
<li><code>sort()</code> 함수를 이용해 배열 정렬해 줍니다</li>
<li>배열 양 끝에서 <code>i(start)=0</code> 와 <code>j(end)=N-1</code> 포인터를 지정해 두 수의 합이 find과 같으면 res 증가 시켰습니다</li>
</ul>
<pre><code class="language-cpp">#include&lt;iostream&gt;
#include&lt;vector&gt;
#include&lt;algorithm&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int N;
    cin &gt;&gt; N;
    vector&lt;int&gt; A(N,0);

    for(int i=0;i&lt;N;i++) {
        cin &gt;&gt; A[i];
    }

    sort(A.begin(), A.end());
    int res = 0;

    for(int k=0; k&lt;N; k++) {
        long find = A[k];
        int i = 0, j = N-1;

        while(i &lt; j) {
            if(A[i] + A[j] == find) {
                if(i != k &amp;&amp; j != k) { //서로 다른 수의 합인지 체크
                    res++;
                    break;
                }
                else if (i==k) i++;
                else if (j==k) j--;
            }
            else if(A[i] + A[j] &lt; find) i++;
            else j--;
        }
    }

    cout &lt;&lt; res;
}</code></pre>
<hr>

<h1 id="2-슬라이딩-윈도우">2. 슬라이딩 윈도우</h1>
<p>슬라이딩 윈도우는 투 포인터와 유사한데 윈도우의 크기는 <strong>고정</strong>됩니다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/9244a807-68a1-450f-9b5f-e0e7503e1ece/image.png" alt=""></p>
<h2 id="21-12891번-dna-비밀번호">2.1) 12891번: DNA 비밀번호</h2>
<ul>
<li><p>문제 요약: 문자열이 {A, C, G, T} 로만 이루어진 DNA 문자열에서 길이 S의 모든 부분문자열을 검사해 각 문자 A, C, G, T가 최소 몇 번 이상 등장해야 한다는 조건을 모두 만족하면 비밀번호로 인정하여 개수를 센다</p>
</li>
<li><p>풀이 과정:</p>
<ul>
<li>Step 1: 체크 배열(checkArr) 저장</li>
<li>Step 2: 윈도우에 포함된 문자로 현재 상태 배열 (myArray) 만든다. myArr &amp;&amp; checkArr 비교</li>
<li>Step 3: 3: 윈도우를 한 칸씩 이동해 myArr 업데이트한다. 계속해서 myArr &amp;&amp; checkArr 비교한다</li>
</ul>
</li>
</ul>
<pre><code class="language-cpp">#include&lt;iostream&gt;
using namespace std;

int checkArr[4];
// myArr[i]: 슬라이딩 윈도우 안에서 각 문자가 나온 현재 개수
int myArr[4];
// check: 4가지 조건(A/C/G/T) 중에서 요구에 도달한 항목 수
int check = 0;
void Add(char c);
void Remove(char c);

void Add(char c) { //새로 들어온 문자를 처리하는 함수
    switch (c) {
        case &#39;A&#39;:
            myArr[0]++;
            if (myArr[0] == checkArr[0]) check++;
            break;
        case &#39;C&#39;:
            myArr[1]++;
            if (myArr[1] == checkArr[1])  check++;
            break;
        case &#39;G&#39;:
            myArr[2]++;
            if (myArr[2] == checkArr[2])  check++;
            break;
        case &#39;T&#39;:
            myArr[3]++;
            if (myArr[3] == checkArr[3])  check++;
            break;
    }
}

void Remove(char c) { //제거할 문자를 처리하는 함수
    switch(c) {
        case &#39;A&#39;:
            if (myArr[0] == checkArr[0]) check--;
            myArr[0]--;
            break;
        case &#39;C&#39;:
            if (myArr[1] == checkArr[1]) check--;
            myArr[1]--;
            break;
        case &#39;G&#39;:
            if (myArr[2] == checkArr[2]) check--;
            myArr[2]--;
            break;
        case &#39;T&#39;:
            if (myArr[3] == checkArr[3]) check--;
            myArr[3]--;
            break;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int S, P;
    cin &gt;&gt; S &gt;&gt; P;
    int Result = 0;
    string A;
    cin &gt;&gt; A;

    for(int i=0;i&lt;4;i++) {
        cin &gt;&gt; checkArr[i];
        if(checkArr[i] == 0) {
            check++;
        }
    }

    // 초기 윈도우: [0, P-1] 구간을 먼저 반영
    for(int i=0; i&lt;P; i++) {
        Add(A[i]);
    }
    // 초기 윈도우가 4가지 조건을 모두 만족하면 결과 +1
    if(check == 4) {
        Result++;
    }

    //슬라이딩 윈도우 처리 부분
    for(int i=P; i&lt;S; i++) {
        int j = i-P; // i: 새로 들어오는 문자의 인덱스, j: 빠져나가는 문자의 인덱스
        Add(A[i]);
        Remove(A[j]);
        if (check == 4) {
            Result++;
        }
    }
    cout &lt;&lt; Result &lt;&lt; &quot;\n&quot;;
}</code></pre>
<hr>
<p><em>*이 글은 교재 &quot;Do it! 알고리즘 코딩 테스트 C++&quot; 기반으로 작성했습니다!</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Part 2 게시판 CRUD 만들기 (6-9장)]]></title>
            <link>https://velog.io/@ttt-1-2/Part-2-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD-%EB%A7%8C%EB%93%A4%EA%B8%B0-6-9%EC%9E%A5</link>
            <guid>https://velog.io/@ttt-1-2/Part-2-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD-%EB%A7%8C%EB%93%A4%EA%B8%B0-6-9%EC%9E%A5</guid>
            <pubDate>Sat, 27 Sep 2025 13:15:25 GMT</pubDate>
            <description><![CDATA[<h1 id="6장---게시판-내-페이지-이동하기">6장 - 게시판 내 페이지 이동하기</h1>
<h2 id="61-링크와-리다이렉트">6.1 링크와 리다이렉트</h2>
<ul>
<li>링크(link): 사용자가 <code>&lt;a&gt;</code>나 <code>&lt;form&gt;</code>을 통해 다른 페이지로 이동하겠다는 요청만 전달하고 그 결과 페이지를 그대로 응답받는 단순 내비게이션 기능이다</li>
<li>리다이렉트(redirect): 서버가 요청을 처리한 뒤 ‘다음으로 갈 주소’를 클라이언트에 지시하여 다시 요청하게 만드는 방식이다</li>
</ul>
<h2 id="62-링크와-리다이렉트를-이용해-페이지-연결하기">6.2 링크와 리다이렉트를 이용해 페이지 연결하기</h2>
<ul>
<li>새 글 작성 링크 만들기</li>
</ul>
<pre><code class="language-java">&lt;a href=&quot;/articles/new&quot;&gt;New Article&lt;/a&gt;</code></pre>
<ul>
<li>&lt;목록 페이지&gt; → &lt;목록 페이지&gt; 돌아가기</li>
</ul>
<pre><code class="language-java">&lt;a href=&quot;/articles&quot;&gt;Back&lt;/a&gt;</code></pre>
<ul>
<li><p>&lt;입력 페이지&gt; → &lt;상세 페이지&gt; 이동하기</p>
<ul>
<li><p>리다이렉드: 클라이언트의 요청를 받아 새로운 URL 주소로 재요청하라고 지시하는 것이다</p>
<pre><code class="language-java">return &quot;redirect:URL_주소&quot;;</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li>&lt;상세 페이지&gt; → &lt;목록 페이지&gt; 돌아가기</li>
</ul>
<pre><code class="language-java">//show.mustache
&lt;a href=&quot;/articles&quot;&gt;Go to Article List&lt;/a&gt;</code></pre>
<ul>
<li>&lt;목록 페이지&gt; → &lt;상세 페이지&gt; 이동하기</li>
</ul>
<pre><code class="language-java"> &lt;td colspan=&quot;2&quot;&gt;&lt;a href=&quot;/articles/{{id}}&quot;&gt;{{title}}&lt;/td&gt;</code></pre>
<h1 id="7장---데이터-수정-과정">7장 - 데이터 수정 과정</h1>
<p><strong>- 1단계: &lt;수정 페이지&gt; 만들고 기존 데이터 불러오기</strong></p>
<pre><code class="language-java">[목록 페이지] 
    │ (항목 클릭)
    v
[상세 페이지]
    │ (Edit 버튼)
    v
[컨트롤러] ── DB에서 기존 데이터 조회 ──&gt; [모델에 담음]
    │
    v
[수정 폼 페이지 렌더]</code></pre>
<p><strong>- 2단계: 데이터를 수정해 DB에 반영한 후 결과를 볼 수 있게 &lt;상세 페이지&gt;로 리다이렉트하기</strong></p>
<pre><code class="language-java">[수정 폼 페이지] --(Submit: 폼데이터)--&gt; [컨트롤러]
    │                                             
    └─ DTO 생성 → 엔티티로 변환/병합 → 리포지토리.save() → DB 갱신
                                                    │
                                                    v
                                리다이렉트(결과 확인용 페이지 또는 목록)
</code></pre>
<blockquote>
<p>HTTP 메소드</p>
</blockquote>
<ul>
<li>HTTP(HyperText Transfer Protocol)는 웹 서비스에 사용하는 프로토콜이다</li>
<li>HTTP의 대표적인 메서드:<ul>
<li>POST</li>
<li>GET</li>
<li>PATCH(PUT)</li>
<li>DELETE</li>
</ul>
</li>
</ul>
<p><strong><em>CRUD를 위한 SQL 문과 HTTP 메서드</em></strong></p>
<table>
<thead>
<tr>
<th>데이터 관리</th>
<th>SQL</th>
<th>HTTP</th>
</tr>
</thead>
<tbody><tr>
<td>Create</td>
<td>INSERT</td>
<td>POST</td>
</tr>
<tr>
<td>Read</td>
<td>SELECT</td>
<td>GET</td>
</tr>
<tr>
<td>Update</td>
<td>UPDATE</td>
<td>PATCH (PUT)</td>
</tr>
<tr>
<td>Delete</td>
<td>DELETE</td>
<td>DELETE</td>
</tr>
</tbody></table>
<h1 id="8장---게시글-삭제하기-delete">8장 - 게시글 삭제하기: DELETE</h1>
<h2 id="데이터-삭제-과정">데이터 삭제 과정</h2>
<ul>
<li>Step 1: 클라이언트가 HTTP 메서드로 게시글 삭제 요청</li>
<li>Step 2: 요청 받은 컨트롤러는 리파지터리를 통해 DB에 저장된 데이터를 찾아 삭제한다</li>
<li>Step 3: 삭제 완료시 클라이언트를 결과 페이지로 리타이렉트한다</li>
</ul>
<pre><code class="language-java">[상세 페이지] (Delete 클릭)
      │  POST/GET /articles/{id}/delete
      v
[컨트롤러] ── findById(id) → 존재 확인
      │
      ├─ 존재 O → repository.delete(엔티티)
      │
      └─ 존재 X → 아무 것도 안 함
      v
redirect:/articles  (목록)</code></pre>
<pre><code class="language-java">[컨트롤러]
  delete 성공 → redirectAttributes.addFlashAttribute(&quot;msg&quot;, &quot;삭제되었습니다!&quot;)
  └─ redirect:/articles
                 │
                 v
        [목록 페이지 index.mustache]
           {{#msg}} 삭제되었습니다! {{/msg}}
</code></pre>
<h2 id="sql-문으로-직접-db-삭제하기">SQL 문으로 직접 DB 삭제하기</h2>
<pre><code class="language-java">DELETE [FROM] 테이블면 WHERE 조건;</code></pre>
<h1 id="9장---crud와-sql-쿼리-종합">9장 - CRUD와 SQL 쿼리 종합</h1>
<h2 id="91-jpa-로깅-설정하기">9.1 JPA 로깅 설정하기</h2>
<ul>
<li>쿼리(query)란 DB에 정보를 요청하는 구문</li>
<li>SQL 쿼리:<ul>
<li>INSERT (생성)</li>
<li>SELECT (조회)</li>
<li>UPDATE (수정)</li>
<li>DELETE (삭제)</li>
</ul>
</li>
<li>로깅(logging): 시스템이 작동할 때 당시의 상태와 작동 정보를 기록하는 것</li>
</ul>
<pre><code class="language-bash"># resources/application.properties
# JPA 로깅 설정
# 디버그 레벨로 쿼리 출력
logging.level.org.hibernate.SQL=DEBUG

# 쿼리 줄바꿈하기
spring.jpa.properties.hibernate.format_sql=true

# 매개변수 값 보여 주기
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# DB URL 설정
# 유니크 URL 생성하지 않기
spring.datasource.generate-unique-name=false
# 고정 URL 설정하기
spring.datasource.url=jdbc:h2:mem:testdb</code></pre>
<ul>
<li><p>로깅 레벨은 7단계가 있다:</p>
<ul>
<li>TRACE(레벨1): DEBUG 레벨보다 더 상세한 정보</li>
<li>DEBUG(레벨2): 응용 프로그램을 디버깅하는 데 필요한 세부 정보</li>
<li>INFRO(레벨3): 응용 프로그램의 순조로운 진행 정보</li>
<li>WARN(레벨4): 잠재적으로 유해한 상황 정보</li>
<li>ERROR(레벨5): 응용 프로그램이 수행할 수 있는 정도의 오류 정보</li>
<li>FATAL(레벨6): 응용 프로그램이 중단될 만한 심각한 오류 정보</li>
<li>OFF(레벨7): 로깅 기능 해제</li>
</ul>
</li>
<li><p>서버 실행하고 쿼리 확인:
<img src="https://velog.velcdn.com/images/ttt-1-2/post/66b39964-26bf-43a1-9d54-d1d177d2cafd/image.png" alt=""></p>
</li>
<li><p>H2 Console 접속:
<img src="https://velog.velcdn.com/images/ttt-1-2/post/b22fd324-6043-4d04-9b82-112dca16b050/image.png" alt=""></p>
</li>
</ul>
<h2 id="92-sql-쿼리-로그-확인하기">9.2 SQL 쿼리 로그 확인하기</h2>
<blockquote>
<p><strong>INSERT 문</strong></p>
</blockquote>
<p>데이터를 생상(Create)할 때 어떤 SQL 쿼리가 동작하는지 확인해 보겠습니다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/5800f202-dd52-4c07-a4eb-8cdcfd320227/image.png" alt=""></p>
<blockquote>
<p><strong>SELECT 문</strong></p>
</blockquote>
<p>데이터를 조회(Read)할 때 어떤 SQL 쿼리가 동작하는지 확인해 보겠습니다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/e521d4c8-50a0-4c04-9e50-93d1ff63c7d1/image.png" alt=""></p>
<blockquote>
<p><strong>UPDATE 문</strong></p>
</blockquote>
<p>데이터를 수정(UPDATE)할 때 어떤 SQL 쿼리가 동작하는지 확인해 보겠습니다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/be1c79f0-f4d4-4b3e-8ff1-51008256cc67/image.png" alt=""></p>
<blockquote>
<p><strong>DELETE 문</strong></p>
</blockquote>
<p>데이터를 삭제(DELETE)할 때 어떤 SQL 쿼리가 동작하는지 확인해 보겠습니다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/554470bf-ead6-4697-aeb4-46b967dfa313/image.png" alt=""></p>
<h2 id="93-기본-sql-쿼리-작성하기">9.3 기본 SQL 쿼리 작성하기</h2>
<ul>
<li>CREATE TABLE 문의 형식은 다음과 같습니다:</li>
</ul>
<pre><code class="language-bash">CREATE TABLE 테이블명 (
    속성명1 자료형,
    속성명2 자료형
    속성명3 자료형,
    PRIMARY KEY (기본키)
);</code></pre>
<ul>
<li>ex: coffee 테이블을 만들어 보겠습니다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/1ab44956-7a5c-490f-8580-e84f915504dd/image.png" alt=""></li>
</ul>
<blockquote>
<p><strong>coffee 데이터 생성하기</strong></p>
</blockquote>
<pre><code class="language-sql">//데이터 생성하기
****
INSERT
INTO    
        coffee
        (id, name, price) 
VALUES
        (1, &#39;아메리카노&#39;, 4100)</code></pre>
<p><img src="https://velog.velcdn.com/images/ttt-1-2/post/7a8fec85-aa50-4fd4-9209-951a4e95a62c/image.png" alt=""></p>
<blockquote>
<p><strong>coffee 데이터 조회하기</strong></p>
</blockquote>
<pre><code class="language-sql">// 데이터 조회하기

SELECT
    id, name, price
FROM
    coffee
WHERE
    id = 3;</code></pre>
<blockquote>
<p><strong>coffee 데이터 수정하기</strong></p>
</blockquote>
<pre><code class="language-sql">UPDATE
    coffee
SET
    price = 9900
WHERE
    id = 4;</code></pre>
<blockquote>
<p><strong>coffee 데이터 삭제하기</strong></p>
</blockquote>
<pre><code class="language-sql">DELETE
FROM
    coffee
WHERE
    id = 4;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Part 2 게시판 CRUD 만들기 (3-5장)]]></title>
            <link>https://velog.io/@ttt-1-2/Part-2-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-5%EC%9E%A5</link>
            <guid>https://velog.io/@ttt-1-2/Part-2-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-5%EC%9E%A5</guid>
            <pubDate>Fri, 19 Sep 2025 16:46:27 GMT</pubDate>
            <description><![CDATA[<h1 id="3장---게시판-만들고-새-글-작성하기-create">3장 - 게시판 만들고 새 글 작성하기: Create</h1>
<h2 id="31">3.1</h2>
<ul>
<li>폼 데이터(form data)는 HTML <code>&lt;form&gt;</code> 태그로 브라우저에서 서버로 전송되는 사용자 입력값이다</li>
<li>서버 컨트롤러는 이 값을 DTO(Data Transfer Object)로 받아 처리한 뒤 최종적으로 DB에 저장한다</li>
</ul>
<h2 id="32-💡-실습-폼-데어터를-dto로-받기">3.2 <strong>💡 실습: 폼 데어터를 DTO로 받기</strong></h2>
<ul>
<li><strong><em>Step 1: 입력 폼 만들기</em></strong>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/748f7070-1e7a-4e4e-a88e-9e8978145555/image.png" alt=""></li>
<li><strong><em>Step 2: 컨트롤러 만들기</em></strong></li>
</ul>
<pre><code class="language-java">// AritcleController.java
package com.example.firstproject.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ArticleController {
    @GetMapping(&quot;/articles/new&quot;)
    public String newArticleForm() {
        return &quot;articles/new&quot;;
    }
}</code></pre>
<ul>
<li><strong><em>Step 3:  품 데이터 전송하기</em></strong><ul>
<li>action 속성: 어디에 데이터 보낼지</li>
<li>method 속성: 어떻게 데이터 보낼지</li>
</ul>
</li>
</ul>
<pre><code class="language-java">//templates/articles/new.mustache
&lt;form class=&quot;container&quot; action=&quot;/articles/create&quot; method=&quot;post&quot;&gt;</code></pre>
<ul>
<li><strong><em>Step 4:  품 데이터 받기</em></strong></li>
</ul>
<p>@PostMapping 만들기 </p>
<pre><code class="language-java">    @PostMapping(&quot;articles/create&quot;)
    public String createArticle() {
        return &quot;&quot;;
    }</code></pre>
<ul>
<li><p><strong><em>Step 5: DTO 만들기</em></strong></p>
<ul>
<li><p><code>com.example.firstproject</code> 에서 <code>dto</code> packege 생성</p>
</li>
<li><p><code>dto</code> 패키지에 <code>ArticleFrom</code> 클래스 생성</p>
<pre><code class="language-java">package com.example.firstproject.dto;

public class ArticleForm {

  private String title;
  private String content;

      //전송받은 제목과 내용을 필드에 저장하는 생성자 추가
  public ArticleForm(String title, String content) {
      this.title = title;
      this.content = content;
  }

      //데이터를 잘 받았는지 확인할 toString() 메소드 추가
  @Override
  public String toString() {
      return &quot;ArticleForm{&quot; +
              &quot;title=&#39;&quot; + title + &#39;\&#39;&#39; +
              &quot;, content=&#39;&quot; + content + &#39;\&#39;&#39; +
              &#39;}&#39;;
  }
}</code></pre>
</li>
</ul>
</li>
<li><p><strong><em>Step 6: 품 데이터를 DTO에 담기</em></strong></p>
</li>
</ul>
<pre><code class="language-java">//ArticleController.java
  @PostMapping(&quot;articles/create&quot;)
  public String createArticle(ArticleForm form) {
      System.out.println(form.toString());
      return &quot;&quot;;
  }</code></pre>
<ul>
<li><strong><em>Step 7: 입력 폼과 DTO 필드 연결하기</em></strong></li>
</ul>
<hr>
<h2 id="33-dto를-데이터베이스에-저장하기">3.3 <strong>DTO를 데이터베이스에 저장하기</strong></h2>
<p><strong>데이터베이스와 JPA</strong></p>
<ul>
<li>JPA: JAVA ↔ DB 명령을 내리는 도구. JPA의 핵심 도구:<ul>
<li>entity: 자바 객체를 DB가 이해할 수 있게 만든 것</li>
<li>repository: 엔티티가 DB속 테이블에 저장 및 관리하게 만든 것</li>
</ul>
</li>
</ul>
<p><strong>DTO를 엔티티로 변환하기 &amp;&amp; 리파지터리로 엔티티를 DB에 저장하기</strong></p>
<pre><code class="language-bash">[브라우저 폼 제출]
        │  POST /articles/create
        ▼
[Spring MVC]
  - 요청 파라미터 바인딩 → ArticleForm (DTO) 생성
        ▼
[Controller]
  - form.toEntity() 호출 → Article (Entity)로 변환
        ▼
[Repository (JPA)]
  - articleRepository.save(entity) → DB 저장
        ▼
[응답]
  - redirect 또는 뷰 렌더링</code></pre>
<pre><code class="language-bash">src/main/java/com/example/firstproject
├─ controller/
│  └─ ArticleController.java
├─ dto/
│  └─ ArticleForm.java        // DTO: 요청 데이터 운반
├─ entity/
│  └─ Article.java            // Entity: JPA 관리 객체
└─ repository/
   └─ ArticleRepository.java  // CRUD 인터페이스</code></pre>
<p><strong>DB 데이터 조회하기</strong></p>
<ul>
<li>H2 DB 접속하기</li>
</ul>
<pre><code class="language-markdown">**Step 1:**

&gt;application.properties
spring.application.name=firstproject
spring.h2.console.enabled=true

**Step 2: `run` tab에서 jdbc 검색**
Database available at &#39;jdbc:h2:mem:......&#39;

**Step 3: `localhost:8080/h2-console` 접속**
JDBC URL:.... </code></pre>
<ul>
<li>데이터 조회하기
<img src="https://velog.velcdn.com/images/ttt-1-2/post/0a257a50-837e-4a66-a824-3df95209b77a/image.png" alt=""></li>
</ul>
<h1 id="4장---롬복과-리팩터링">4장 - 롬복과 리팩터링</h1>
<p><em>롬복(lombok): 코드 간단하게 만들어 주는 라이브러리</em></p>
<ul>
<li>코드 반복 최소화</li>
<li>로깅 기능 지원</li>
</ul>
<hr>
<p><strong>롬복을 활용해 리팩터링하기</strong></p>
<ul>
<li>롬복 설치하기</li>
</ul>
<pre><code class="language-java">dependencies {
    compileOnly &#39;org.projectlombok:lombok&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;
//생략
}</code></pre>
<ul>
<li><p>DTO &amp; Entity 리팩터링하기</p>
<ul>
<li><code>@AllArgsConstructor</code> : 모든 필드를 받는 생성자를 자동 생성</li>
<li><code>@ToString</code> : 객체를 보기 좋게 문자열로 출력</li>
</ul>
</li>
<li><p>컨트롤러에 로그 남기기</p>
<ul>
<li><p><code>import lombok.extern.slf4j.Slf4j;</code> + 클래스에 <code>@Slf4j</code></p>
<p>→ <code>System.out.println</code> 대신 레벨별 로그 관리 가능(<code>info/debug/warn/error</code>), 운영환경에서 필터링 쉬움</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ttt-1-2/post/91c1e0b6-da8b-4343-bfce-a4421dc63597/image.png" alt=""></p>
<h1 id="5장---게시글-읽기-read">5장 - 게시글 읽기: Read</h1>
<p><strong>데이터 조회 과정</strong>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/79401688-ae4d-425d-88d3-1d28d9703da6/image.png" alt=""></p>
<hr>
<p><strong>단일 데이터 조회하기</strong></p>
<pre><code class="language-java">[Client/Browser]
      │ (1) GET /articles/{id}
      ▼
[Controller]
      │ (2) 요청 파라미터(id) 해석 → 조회 지시
      ▼
[Repository]
      │ (3) DB에 조회 질의
      ▼
[DB]
      │ (4) 결과를 Entity로 반환
      ▼
[Model + View(템플릿)]
      │ (5) Model에 담아 템플릿 렌더링
      ▼
[Client/Browser]
      (6) 최종 HTML 응답 표시
</code></pre>
<hr>
<p><strong>데이터 목록 조회하기</strong></p>
<p><code>GET /articles</code> 요청이 오면 DB에서 모든 <code>Article</code>을 조회 → 모델에 <code>articleList</code>로 담음 → <code>articles/index</code> 템플릿으로 목록 렌더링</p>
<pre><code class="language-java">@GetMapping(&quot;/articles&quot;)
    public String index(Model model) {
        // 1. 모든 데이터 가져오기
        ArrayList&lt;Article&gt; articleEntityList = articleRepository.findAll();
        // 2. 모델에 데이터 등록하기
        model.addAttribute(&quot;articleList&quot;, articleEntityList);
        // 3. 뷰 페이지 설정하기
        return &quot;articles/index&quot;;

    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Part 1 스프링 부트 개요 (1-2장)]]></title>
            <link>https://velog.io/@ttt-1-2/Part-1-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EA%B0%9C%EC%9A%94-1-2%EC%9E%A5</link>
            <guid>https://velog.io/@ttt-1-2/Part-1-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EA%B0%9C%EC%9A%94-1-2%EC%9E%A5</guid>
            <pubDate>Thu, 11 Sep 2025 08:21:25 GMT</pubDate>
            <description><![CDATA[<p>Checkpoint | 1장을 시작하기 전에 준비할 것:</p>
<ol>
<li>JDK: Java 17</li>
<li>JDE: IntelliJ IDEA Community Edition</li>
<li>스프링 부트: 3.1.0 버전</li>
</ol>
<p>실습환경 구축 글 👉 <a href="https://velog.io/@ttt-1-2/%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%B9%98">https://velog.io/@ttt-1-2/실습-환경-설치</a></p>
<hr>
<h1 id="1장---스프링-부트-시작하기">1장 - 스프링 부트 시작하기</h1>
<p>스프링 부트(Spring Boot)는 스프링을 개선하여 자바 애플리케이션 개발을 단순화한 프레임워크다</p>
<h2 id="💡-11-실습-헬로-월드-출력">💡 1.1 <strong>실습: <em>헬로 월드!</em> 출력</strong></h2>
<ul>
<li><em>Step 1: Run ‘FirstprojectAp…main()’</em>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/c2f44c46-9531-4992-93c4-a64127fe4c2d/image.png" alt=""></li>
<li><em>Step 2: 크롬 브라우저에  <a href="http://localhost:8080/"><code>localhost:8080</code></a> 접속한다</em></li>
<li><em>Step 3: 출력</em></li>
</ul>
<p>src &gt; main &gt; resources &gt; static 디렉터리에서 hello.html 파일 생성</p>
<pre><code class="language-java">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Title&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;헬로 월드!&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<ul>
<li><p><em>Step 4: Rerun firstproject</em></p>
<p><code>localhost:8080/hello.html</code>로 접속하면 ‘헬로 월드!’가 화면에 표시된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/8fe21aad-6b15-4be8-b339-d288b53f06fc/image.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="12-웹-서비스의-동작-원리-이해하기">1.2 웹 서비스의 동작 원리 이해하기</h2>
<p>클라이언트 ↔ 서버:</p>
<ul>
<li>클라이언트(Client): 서버에 요청을 보내는 쪽</li>
<li>서버(Server): 클라이언트의 요청을 받아 처리하고 응답하는 쪽</li>
</ul>
<p>만약 서버가 중지되면 웹사이트에 접속할 수 없고 다시 실행(Rerun)하면 정상적으로 접근할 수 있다. 실행 로그의 메시지를 살펴보면:</p>
<pre><code class="language-java">Tomcat started on port(s): 8080 (http) with context path &#39;&#39;
//--&gt; 톰캣 서버가 8080번 포트에서 구동 중임을 의미한다</code></pre>
<blockquote>
<p><a href="http://localhost:8080/hello.html"><code>localhost:8080/hello.html</code></a> 의 의미</p>
</blockquote>
<ul>
<li>localhost: 현재 사용 중인 내 컴퓨터</li>
<li>8080: 톰캣이 실행되고 있는 포트 번호</li>
<li>hello.html: 클라이언트가 요청하면 서버는 <code>/static</code>, <code>/public</code>, <code>/resources</code>, <code>/META-INF/resources</code> 경로에서 해당 파일을 순서대로 탐색한다. 파일이 존재하면 반환하고 없으면 404 에러를 응답한다.</li>
</ul>
<hr>
<h1 id="2장---mvc-패턴-이해와-실습">2장 - MVC 패턴 이해와 실습</h1>
<h2 id="21-뷰-템플릿과-mvc-패턴">2.1 뷰 템플릿과 MVC 패턴</h2>
<ul>
<li>뷰 템플릿: 웹 애플리케이션에서 데이터를 화면에 출력할 때 사용하는 틀이다.</li>
<li>MVC 패턴:<ul>
<li>Model (모델): 데이터와 비즈니스 로직을 담당한다</li>
<li>View (뷰): 사용자에게 보여지는 화면을 담당한다. 즉 데이터를 받아서 웹 페이지나 화면에 출력한다</li>
<li>Controller (컨트롤러): 클라이언트의 요청을 받아 어떤 Model을 사용할지 결정하고 그 결과를 View에 전달한다</li>
</ul>
</li>
</ul>
<pre><code class="language-java"> //MVC 패턴

 [ Client ]
     |
     v
[ Controller ] -----&gt; [ Model ] -----&gt; [ Database / Data ]
     |                       |
     v                       |
   [ View ] &lt;-----------------
     |
     v
 [ Client Screen ]
</code></pre>
<h2 id="💡22-실습-mvc-패턴-활용해-뷰-템플릿-페이지-만들기">💡2.2 실습: MVC 패턴 활용해 뷰 템플릿 페이지 만들기</h2>
<pre><code class="language-java">[ View: greetings.mustache ]
    ↓
    ( {{username}} )

[ Controller: FirstController.java ]
    ↓
    @GetMapping(&quot;/hi&quot;)
    public String niceToMeetYou(Model model) {
        model.addAttribute(&quot;username&quot;, &quot;홍팍&quot;);
        return &quot;greetings&quot;;
    }

[ Model: 데이터 ]
    ↓
    model.addAttribute(&quot;username&quot;, &quot;홍팍&quot;);

[ Client 화면 출력 ]
    ↓
    &lt;h1&gt;Trang님, 반갑습니다!&lt;/h1&gt;</code></pre>
<h2 id="23-mvc의-역할과-실행-흐름-이해하기">2.3 MVC의 역할과 실행 흐름 이해하기</h2>
<blockquote>
<p><code>/hi</code> 페이지의 실행 흐름</p>
</blockquote>
<pre><code class="language-java">@Controller // 1. 컨트롤러 선언
public class FirstController {
    @GetMapping(&quot;/hi&quot;) // 2. URL 요청

    public String niceToMeetYou(Model model) { // 3. 메소드 수행 + 4. 모델 객체 가져오기
        model.addAttribute(&quot;username&quot;, &quot;hongpark&quot;); // 5. 모델 변수 등록
        return &quot;greetings&quot;; // 6. 뷰 템플렛 페이지 반환
    }
}    </code></pre>
<h2 id="24-뷰-템플릿-페이지에-레이아웃-적용하기">2.4 뷰 템플릿 페이지에 레이아웃 적용하기</h2>
<ul>
<li>레이아웃: 화면에 요소를 배치하는 일</li>
</ul>
<p><strong>💡 실습 <code>/hi</code> 페이지에 헤더-푸터 레이아웃 적용하기</strong></p>
<ul>
<li><p>Bootstrap은 HTML, CSS, JavaScript가 미리 준비되어 있는 프레임워크다. <a href="https://getbootstrap.com/">Bootstrap 공식 사이트</a>에 접속해서 필요한 코드와 컴포넌트를 가져올 것이다.
<img src="https://velog.velcdn.com/images/ttt-1-2/post/1ee78039-5529-4181-8026-130ff505e654/image.png" alt="">
이번 실습에서는 템플릿을 헤더와 푸터로 나누어 재사용할 수 있도록 구성해 코드의 가독성과 유지보수성을 높인다</p>
</li>
<li><p>폴더 구조:</p>
</li>
</ul>
<pre><code class="language-java">main
├── java
│   └── com.example.firstproject
│       ├── FirstprojectApplication.java
│       └── controller
│           └── FirstController.java
│
└── resources
    ├── static
    ├── templates
    │   ├── greetings.mustache
    │   ├── goodbye.mustache
    │   └── layouts
    │       ├── header.mustache
    │       └── footer.mustache
    │
    └── application.properties
</code></pre>
<ul>
<li>공통 헤더-푸터를 포함하고 본문에 <code>{{username}}</code>을 출력하는 템플릿:</li>
</ul>
<pre><code class="language-java">{{&gt;layouts/header}}

    &lt;!-- content --&gt;
    &lt;div class=&quot;bg-dark text-white p-5&quot;&gt;
        &lt;h1&gt;{{username}}님, 반갑습니다!&lt;/h1&gt;
    &lt;/div&gt;

{{&gt;layouts/footer}}</code></pre>
<hr>
<h1 id="마무리">마무리</h1>
<p>실습 코드: <a href="https://github.com/ttt-1-2/BE-SpringBoot.git">https://github.com/ttt-1-2/BE-SpringBoot.git</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실습환경 구축]]></title>
            <link>https://velog.io/@ttt-1-2/%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@ttt-1-2/%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Thu, 11 Sep 2025 01:20:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>소스 코드 레포지토리</strong></p>
</blockquote>
<ul>
<li><a href="https://github.com/gilbutITbook/080354">https://github.com/gilbutITbook/080354</a></li>
</ul>
<blockquote>
<p><strong>실습 환경 (Mac OS)</strong></p>
</blockquote>
<h2 id="1-jdk-java-17">1. JDK: Java 17</h2>
<ul>
<li>터미널에서 자바 버전 확인하기: <code>java --version</code>. 만약 출력 결과가 JDK 17로 설정되어 있다면 정상적으로 설치된 것이다
<img src="https://velog.velcdn.com/images/ttt-1-2/post/dcde7867-1b76-4597-904b-c5557685fbf2/image.png" alt=""></li>
<li>만약 <code>java --version</code> 실행 시 JDK 17이 설정되어 있지 않다면 Homebrew를 이용해 설치할 수 있다<pre><code class="language-bash">$ brew update
$ brew install openjdk@17</code></pre>
</li>
</ul>
<h2 id="2-jde-인텔리제이-cecommunity-edition">2. JDE: 인텔리제이 CE(Community Edition)</h2>
<ul>
<li>다운로드 페이지: <a href="https://www.jetbrains.com/idea/download/?section=mac">https://www.jetbrains.com/idea/download/?section=mac</a>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/68d470f5-b1cc-4982-a4b8-c1c3d7cf5afc/image.png" alt=""></li>
</ul>
<h2 id="3-스프링-부트-310">3. 스프링 부트: 3.1.0</h2>
<ul>
<li><p>Spring Initializr 페이지: <a href="https://start.spring.io/">https://start.spring.io/</a>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/07eca22e-9d8e-4632-9b56-0bfeb200f870/image.png" alt=""></p>
</li>
<li><p>첫번째 프로젝트 설정:</p>
<ul>
<li>Project: Gradle - Groovy</li>
<li>Language: Java</li>
<li>Spring Boot: 3.5.5 (or 기본값)</li>
<li>Packaging: Jar</li>
<li>Java: 17</li>
<li>Dependencies → Add dependencies:<ul>
<li>Spring Web</li>
<li>H2 Database</li>
<li>Mustache</li>
<li>Spring Data JPA</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><em>이제 설치한 인텔리제이 CE에서 <code>firstproject</code>를 열고 실행하면 된다.</em>
<img src="https://velog.velcdn.com/images/ttt-1-2/post/f25e9761-2d8d-4167-9d91-67be24fde495/image.png" alt=""></p>
<ul>
<li><strong>Note:</strong> 책에서 스프링 부트 3.1.0 버전을 사용한다. 책을 따라 실습하고 싶으시면 <code>build.gradle</code> 파일을 열고 코드 수정하면 된다.<pre><code class="language-java">plugins {
  id &#39;java&#39;
  id &#39;org.springframework.boot&#39; version &#39;3.1.0&#39; //여기에서 버전 바꾸면 된다
  id &#39;io.spring.dependency-management&#39; version &#39;1.1.7&#39;
}</code></pre>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>