<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>o_5x3o.log</title>
        <link>https://velog.io/</link>
        <description>주니어 백엔드 개발자</description>
        <lastBuildDate>Tue, 02 May 2023 06:46:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>o_5x3o.log</title>
            <url>https://velog.velcdn.com/images/o_5x3o/profile/cd80fdb9-db1a-47ba-b153-230cc8618ecc/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. o_5x3o.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/o_5x3o" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[윈도우에서 포트 죽이기]]></title>
            <link>https://velog.io/@o_5x3o/%EC%9C%88%EB%8F%84%EC%9A%B0%EC%97%90%EC%84%9C-%ED%8F%AC%ED%8A%B8-%EC%A3%BD%EC%9D%B4%EA%B8%B0</link>
            <guid>https://velog.io/@o_5x3o/%EC%9C%88%EB%8F%84%EC%9A%B0%EC%97%90%EC%84%9C-%ED%8F%AC%ED%8A%B8-%EC%A3%BD%EC%9D%B4%EA%B8%B0</guid>
            <pubDate>Tue, 02 May 2023 06:46:47 GMT</pubDate>
            <description><![CDATA[<pre><code>2022-10-28 16:38:06.954 ERROR 16532 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that&#39;s listening on port 8080 or configure this application to listen on another port.</code></pre><p>어플리케이션을 실행시키려는데 이런 에러가 났습니다.</p>
<p>8080포트가 이미 사용중이어서 다른 포트번호로 설정하여 사용하라는 에러인데요.</p>
<p>properties나 yml 설정파일에서 서버 포트번호를 바꾸는 방법도 있지만 윈도우 cmd에서 해당 포트번호로 실행중인 작업을 멈추는 방법을 알려드리려고 합니다.</p>
<p><br><br></p>
<h2 id="1--cmd에서-해당-포트번호-사용상태-확인하기">1.  cmd에서 해당 포트번호 사용상태 확인하기</h2>
<pre><code>netstat -ano | findstr 8080</code></pre><p>cmd에 치면 현재 사용하고 있는 8080 포트를 사용하는 네트워크 통계 정보를 출력합니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/f09031ac-1ab9-4fc0-932e-da2d71da23db/image.png" alt=""></p>
<br>




<h2 id="2-서버-죽이기">2. 서버 죽이기</h2>
<pre><code>taskkill /F /pid [process_id]</code></pre><p>위 명령어를 통해 출력된 목록의 process_id를 입력하여 강제 종료시킵니다.</p>
<p> <img src="https://velog.velcdn.com/images/o_5x3o/post/0b34adf6-402a-462e-93aa-08fd9fbf5e61/image.png" alt=""></p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] Maven -> Gradle로 변환하기 (Window)]]></title>
            <link>https://velog.io/@o_5x3o/Spring-Boot-Maven-Gradle%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0-Window</link>
            <guid>https://velog.io/@o_5x3o/Spring-Boot-Maven-Gradle%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0-Window</guid>
            <pubDate>Tue, 02 May 2023 06:42:25 GMT</pubDate>
            <description><![CDATA[<p>기존에 JPA를 사용하여 만들었던 maven 프로젝트를 gradle로 바꾸기로 했습니다.</p>
<p>lombok, querydsl 관련 에러가 나서 같이 다뤄보려고 합니다 !!!</p>
<br>

<h3 id="1--httpsgradleorgreleases">1.  <a href="https://gradle.org/releases/">https://gradle.org/releases/</a></h3>
<p>Gradle 공식홈페이지에서 원하는 버전을 다운받습니다.</p>
<p>저는 <strong>binary-only</strong> 로 다운받았습니다.</p>
<p><br><br></p>
<h3 id="2-환경변수-설정">2. 환경변수 설정</h3>
<p>JDK와 동일하게 Gradle도 환경변수 설정을 해야합니다.</p>
<p>[시스템 속성] -&gt; [환경 변수]</p>
<ol>
<li>시스템 변수<ul>
<li>변수 이름 : GRADLE_HOME<ul>
<li>변수 값 : GRADLE 압축해제 한 경로 예) C:\gradle-6.8.3</li>
</ul>
</li>
</ul>
</li>
<li>사용자 변수<ul>
<li>변수 이름 : Path</li>
<li>변수 값 : %GRADLE_HOME%\bin</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/5c26cb04-361f-4bd3-9d8d-761455338491/image.png" alt=""></p>
<p><br><br></p>
<h3 id="3-cmd에-gradle--v-쳐서-gradle-설정-확인하기">3. cmd에 gradle -v 쳐서 Gradle 설정 확인하기</h3>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/35aade2a-befc-4a0f-bfbf-7110228bf215/image.png" alt=""></p>
<p>위와 같이 나온다면 정상적으로 Gradle이 설정완료가 된 겁니다. </p>
<p><br><br></p>
<h3 id="4-pomxml이-위치해있는-원하는-프로젝트로-경로-이동-후-gradle-init---type-pom-쳐서-gradle-init-해주기">4. pom.xml이 위치해있는 원하는 프로젝트로 경로 이동 후, gradle init --type pom 쳐서 Gradle init 해주기</h3>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/2d159f31-0f52-4875-bdf4-4c42a7567d47/image.png" alt=""></p>
<p>gradle init --type pom 을 치면</p>
<blockquote>
<p>Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2]</p>
</blockquote>
<p>빌드에 사용할 DSL을 선택하라는 질문이 나옵니다.</p>
<p>기본으로 groovy로 선택되어서 groovy로 진행했습니다. (Enter 누르기)</p>
<br>

<blockquote>
<p>Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) </p>
</blockquote>
<p>새로운 API 를 사용해서 build 를 생성하겠냐는 질문에 기본 값(no)을 선택해 줍니다.</p>
<p><br><br></p>
<h3 id="5-spring-boot-buildgradle에서-querydsl-lombok-다시-설정하기">5. Spring boot build.gradle에서 Querydsl, lombok 다시 설정하기</h3>
<p>인텔리제이에서 프로젝트를 열었을 때 gradle 파일이 들어갔습니다. (저는 pom.xml을 지웠습니다.)</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/7d1998b3-55a1-44b8-8c35-67c012a5fe8c/image.png" alt=""></p>
<p>하지만 gradle 파일이 들어갔다고 해서 바로 실행되지 않았습니다.</p>
<br>

<ul>
<li>Querydsl 설정하기</li>
</ul>
<p>plugins</p>
<pre><code>/*
 * This file was generated by the Gradle &#39;init&#39; task.
 */

plugins {
    id &#39;java&#39;
    id &#39;maven-publish&#39;
    // querydsl plugins 추가
    id &quot;com.ewerk.gradle.plugins.querydsl&quot; version &quot;1.0.10&quot;
}</code></pre><p> dependencies</p>
<pre><code>dependencies {
    implementation &#39;com.querydsl:querydsl-jpa:5.0.0&#39;
    implementation &#39;com.querydsl:querydsl-apt:5.0.0&#39;
}</code></pre><p>추가 설정</p>
<pre><code>test {
    useJUnitPlatform()
}

/*
 * queryDSL 설정 추가
 */
// querydsl에서 사용할 경로 설정
def querydslDir = &quot;$buildDir/generated/querydsl&quot;
// JPA 사용 여부와 사용할 경로를 설정
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
// build 시 사용할 sourceSet 추가
sourceSets {
    main.java.srcDir querydslDir
}
// querydsl 컴파일시 사용할 옵션 설정
compileQuerydsl{
    options.annotationProcessorPath = configurations.querydsl
}
// querydsl 이 compileClassPath 를 상속하도록 설정
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    querydsl.extendsFrom compileClasspath
}</code></pre><p>다 설정한 후 마지막으로 Gradle을 ReImport 해줍니다.</p>
<p>ReImport 해준 후 compileQuerydsl을 더블클릭해주면 Qdomain이 생깁니다.</p>
<p><img src="blob:https://velog.io/0ec35bff-ef8c-4893-95fe-1cb769763f5c" alt="업로드중.."></p>
<p><br><br></p>
<ul>
<li>lombok 설정하기</li>
</ul>
<p>저는 기존에 lombok dependency가 있었음에도 불구하고 lombok이 인식이 안되어 에러가 났습니다.</p>
<pre><code>dependencies {
    annotationProcessor &#39;org.projectlombok:lombok:1.18.22&#39;
}</code></pre><p>dependencies에 annotationProcessor을 넣으니 해결되었습니다.</p>
<p><br><br></p>
<p>번외로, repository에서 @Query의 jpql에 쓰이는 변수들을 @Param으로 바인딩 해줬습니다.</p>
<p><br><br></p>
<hr>
<br>



<blockquote>
<p><strong>Reference</strong>
<a href="https://www.inflearn.com/questions/367372">https://www.inflearn.com/questions/367372</a>
<a href="https://data-make.tistory.com/728">https://data-make.tistory.com/728</a>
<a href="https://shanepark.tistory.com/360">https://shanepark.tistory.com/360</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Framework란? 개념 정리]]></title>
            <link>https://velog.io/@o_5x3o/Spring-Framework%EB%9E%80-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@o_5x3o/Spring-Framework%EB%9E%80-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 02 May 2023 06:32:24 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-framework란">Spring Framework란?</h2>
<p>*<em>자바 엔터프라이즈 개발을 편하게 해주는 오픈 소스 경량급 애플리케이션 프레임워크 *</em>라고 소개되어 있습니다.</p>
<p>여기서 단어별로 끊으면서 Spring Framework가 정확히 어떤 프레임워크인지 설명하겠습니다.</p>
<Br>

<p><strong>1. 엔터프라이즈 개발</strong></p>
<p>뜻을 풀이하면 <strong>기업을 대상으로 하는 개발</strong>이라는 말입니다.</p>
<p>즉, 대규모 데이터 처리와 트랜잭션이 동시에 여러 사용자로부터 행해지는 매우 큰 규모의 환경을 엔터프라이즈 환경이라고 합니다.</p>
<br>


<p><strong>2.  오픈소스 경량급</strong></p>
<p>여기서 경량급은 툴의 도움 없이는 다루기 힘든 난해한 설정파일과 불편한 서버 배치 등으로 인한 부담을 없애고, <strong>쉽게 해당 기능들을 사용할 수 있게 되었음</strong>을 의미합니다.</p>
<p>오픈 소스는 소스가 공개되어 있어서, 언제든 수정이 가능하고 이슈를 보내 해결할 수 있는 소스를 말합니다.</p>
<br> 

<p><strong>3. 애플리케이션 프레임워크</strong></p>
<p>애플리케이션 프레임워크는 특정 계층이나, 기술, 업무 분야에 국한되지 않고 <strong>애플리케이션의 전 영역을 포괄하는 범용적인 프레임워크</strong>를 말합니다. </p>
<p>즉, 애플리케이션 프레임워크는 애플리케이션 개발의 전 과정을 빠르고 편리하며 효율적으로 진행하는데 일차적인 목표를 두는 프레임워크입니다.</p>
<p><br><br></p>
<hr>
  <br>









<h2 id="spring-framework의-특징">Spring Framework의 특징</h2>
<br>

<p><strong>1.  경량 컨테이너</strong></p>
<p>Spirng Framework는 <strong>경량 컨테이너로 자바 객체를 담고 직접 관리합니다</strong>. 
객체의 생성 및 소멸 그리고 라이프 사이클을관리하며 언제든 Spring 컨테이너로 부터 필요한 객체를 가져와 사용할 수 있습니다. </p>
<p>이는 Spirng이 IOC 기반의 Framework임을 의미합니다.</p>
<br>


<p><strong>2.  POJO(Plain Old Java Object) 기반의 구성</strong></p>
<p><strong>평범한 자바 객체</strong>를 뜻합니다.  </p>
<p>POJO는 gettet/setter를 가진 단순 자바 오브젝트로 정의를 하고 있습니다. 이러한 단순 오브젝트는 의존성이 없고 코드가심플하여 추후 테스트 및 유지보수가 편리한 유연성의 장점을 가집니다. 이러한 장점들로 인해 객체지향적인 다양한 설계와 구현이 가능합니다.</p>
<br>


<p><strong>3. DI(Dependency Injection)</strong></p>
<p>앞에서 말했다시피 스프링은 IOC(Inversion of Control) 기반의 Framework입니다.  </p>
<p>IOC란 스프링 그 자체가 구조를 설계할 수 있어서 개발자가 직접 부품을 조립하는 형태의 개발이 가능합니다.</p>
<p><strong>이렇게 조립된 코드의 최종 호출은 개발자가 결정하는 것이 아니라 스프링 프레임워크 내부에서 이루어지는 것을, 제어의 역행(IOC)라고 합니다.</strong></p>
<p>의존성 주입(DI)은 제어의 역행이 일어나는 것을 전제로 하여 스프링 객체가 서로 의존하는 관계가 되게끔 의존성을 주입하는 것입니다.<br>IOC에서 DI란 각 클래스 사이에 필요로 하는 의존관계를 빈 설정 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것입니다.</p>
<br>


<p><strong>4.  AOP(Aspect Oriented Programming) 지원</strong></p>
<p>AOP란 말 그대로 풀이하자면 관점 지향 프로그래밍이란 뜻입니다.</p>
<p>대부분의 시스템에서 비즈니스 로직은 아니지만 보안, 로그, 트랜잭션과 같이 반드시 처리가 필요한 부분을 횡단 관심사라고 합니다.</p>
<p>AOP는 비즈니스 로직과 횡단 관심사를 분리시켜 비즈니스 로직에 영향을 끼치지 않게 횡단 관심사를 끼워 넣는 개발 형태이며 이로 인해 많은 장점들이 생깁니다.</p>
<ol>
<li><p>무분별하게 중복되는 코드를 한 곳에 모아 중복 코드 제거 가능</p>
</li>
<li><p>공통기능을 한 곳에 보관함으로써 공통 기능 하나의 수정으로 모든 핵심기능들의 공통기능을 수정 가능</p>
</li>
<li><p>그로 인해 효율적인 유지보수가 가능하며 재활용성이 극대화</p>
</li>
</ol>
<br>


<p><strong>5. 독립적인 개발 환경</strong></p>
<p>스프링은 가장 단순한 서버환경인 톰캣(Tomcat)이나 제티(Jetty)에서 완벽하게 동작합니다.</p>
<p>단순한 개발툴과 기본적인 개발환경으로도 엔터프라이즈 개발에서 필요로 하는 주요한 기능을 갖춘 애플리케이션을 개발하기에 충분합니다.</p>
<p><br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring AOP 적용]]></title>
            <link>https://velog.io/@o_5x3o/Spring-AOP-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@o_5x3o/Spring-AOP-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Tue, 02 May 2023 06:21:20 GMT</pubDate>
            <description><![CDATA[<h2 id="aop">AOP</h2>
<h4 id="aop-aspect-oriented-programming">AOP: Aspect Oriented Programming</h4>
<br>
<br>

<h3 id="aop가-필요한-상황">AOP가 필요한 상황</h3>
<ul>
<li>모든 메소드의 호출 시간을 측정하고 싶을 때</li>
<li>공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)</li>
<li>회원 가입 시간, 회원 조회 시간을 측정하고 싶을 때</li>
</ul>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/19b24f55-3606-4e75-8711-e26b67c94a02/image.png" alt=""></p>
<p><br><br></p>
<p>[ MemberService 회원 조회 시간 측정 추가 ]</p>
<pre><code>package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

// @Service
@Transactional
public class MemberService {
    private final MemberRepository memberRepository;

    // @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     * 회원가입
     */
    public Long join(Member member) {

        long start = System.currentTimeMillis();

        try {
            validateDuplicateMember(member); //중복 회원 검증
            memberRepository.save(member);
            return member.getId();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;join &quot; + timeMs + &quot;ms&quot;);
        }
    }
    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName()).ifPresent(m -&gt; {
                    throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
                });
    }

    /**
     * 전체 회원 조회
     */
    public List&lt;Member&gt; findMembers() {
        long start = System.currentTimeMillis();

        try {
            return memberRepository.findAll();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;findMembers &quot; + timeMs + &quot;ms&quot;);
        }
    }

    public Optional&lt;Member&gt; findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}
</code></pre><p><img src="https://velog.velcdn.com/images/o_5x3o/post/c6e97472-02e4-49d3-a6b3-aa4489ccfccf/image.png" alt=""></p>
<p>다음과 같이 조회 시간이 측정되는 것을 확인할 수 있습니다.</p>
<br>

<p><strong>문제</strong></p>
<ul>
<li>회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아닙니다.</li>
<li>시간을 측정하는 로직은 공통 관심 사항입니다.</li>
<li>시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵습니다.</li>
<li>시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵습니다.</li>
<li>시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 합니다.</li>
</ul>
<p><br><br></p>
<hr>
<br>


<h2 id="aop-적용">AOP 적용</h2>
<h4 id="공통-관심-사항cross-cutting-concern-vs-핵심-관심-사항core-concern-분리">공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리</h4>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/4fef690d-5cf3-4c27-a89d-15515c8b96fc/image.png" alt=""></p>
<br>

<p>[ 시간 측정 AOP 등록 ]</p>
<pre><code>package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import org.springframework.stereotype.Component;@Component

@Aspect
public class TimeTraceAop {
    @Around(&quot;execution(* hello.hellospring..*(..))&quot;)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        System.out.println(&quot;START: &quot; + joinPoint.toString());

        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;END: &quot; + joinPoint.toString()+ &quot; &quot; + timeMs + &quot;ms&quot;);
        }
    }
}</code></pre><br>


<p><strong>해결</strong></p>
<ul>
<li>회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리합니다.</li>
<li>시간을 측정하는 로직을 별도의 공통 로직으로 만들었습니다.</li>
<li>핵심 관심 사항을 깔끔하게 유지할 수 있습니다.</li>
<li>변경이 필요하면 이 로직만 변경하면 됩니다.</li>
<li>원하는 적용 대상을 선택할 수 있습니다.</li>
</ul>
<br>
<br>

<hr>
<br>


<h2 id="스프링의-aop-동작-방식-설명">스프링의 AOP 동작 방식 설명</h2>
<p>[ AOP 적용 전 의존관계 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/bc302250-853d-4e55-8986-2586af5836e5/image.png" alt=""></p>
<br>


<p>[ AOP 적용 후 의존관계 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/86e19ad4-977b-4d79-9046-081a25d9d879/image.png" alt=""></p>
<br>


<p>[ AOP 적용 전 전체 그림 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/f4b0af37-777b-4fbe-b988-e961881a4bb1/image.png" alt=""></p>
<br>



<p>[ AOP 적용 후 전체 그림 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/666bdb97-5bd6-466c-a619-2938061a3baf/image.png" alt=""></p>
<br>


<br>

<hr>
<br>


<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 스프링 데이터 JPA]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</link>
            <guid>https://velog.io/@o_5x3o/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</guid>
            <pubDate>Tue, 02 May 2023 06:15:49 GMT</pubDate>
            <description><![CDATA[<h2 id="jpa--java-persistence-api-">JPA ( Java Persistence API )</h2>
<ul>
<li>기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행함</li>
<li>SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있음</li>
<li>개발 생산성을 크게 높일 수 있음</li>
</ul>
<p><br><br></p>
<p>[ build.gradle 파일에 JPA, h2 데이터베이스 관련 라이브러리 추가 ]</p>
<pre><code>dependencies {
implementation &#39;org.springframework.boot:spring-boot-starter-thymeleaf&#39;
implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
//implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39;
implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
runtimeOnly &#39;com.h2database:h2&#39;
testImplementation(&#39;org.springframework.boot:spring-boot-starter-test&#39;) {
exclude group: &#39;org.junit.vintage&#39;, module: &#39;junit-vintage-engine&#39;
}
}</code></pre><p>추가한 후 코끼리 아이콘을 눌러 라이브러리 다운받는 것을 꼭 잊지마세요!</p>
<p>*<em>spring-boot-starter-data-jpa *</em>는 내부에 jdbc 관련 라이브러리를 포함하므로 jdbc는 제거해도 됩니다.</p>
<p><br><br></p>
<p>[ 스프링 부트에 JPA 설정 추가 ]</p>
<pre><code>spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none</code></pre><p><strong>show-sql</strong> : JPA가 생성하는 SQL을 출력함
<strong>ddl-auto</strong> : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none 를 사용하면 해당 기능을 끔
create 메소드를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성함</p>
<p><br><br></p>
<p>[ JPA 엔티티 매핑 ]</p>
<pre><code>package hello.hellospring.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }}</code></pre><br>


<p>[ JPA 회원 리포지토리 ]</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
 private final EntityManager em;
 public JpaMemberRepository(EntityManager em) {
 this.em = em;
 }
 public Member save(Member member) {
 em.persist(member);
 return member;
 }
 public Optional&lt;Member&gt; findById(Long id) {
 Member member = em.find(Member.class, id);
 return Optional.ofNullable(member);
 }
 public List&lt;Member&gt; findAll() {
 return em.createQuery(&quot;select m from Member m&quot;, Member.class)
 .getResultList();
 }
 public Optional&lt;Member&gt; findByName(String name) { List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m where 
m.name = :name&quot;, Member.class)
 .setParameter(&quot;name&quot;, name)
 .getResultList();
 return result.stream().findAny();
 }
}</code></pre><br>



<p>[ 서비스 계층에 @Transactional 추가 ]</p>
<pre><code>import org.springframework.transaction.annotation.Transactional
@Transactional
public class MemberService {}</code></pre><p>스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 @Transactional을 커밋합니다. 
만약 런타임 예외가 발생하면 롤백합니다.
<strong>JPA를 통한 모든 데이터 변경은 @Transactional 안에서 실행해야 합니다.</strong></p>
<p><br><br></p>
<p>[ JPA를 사용하도록 스프링 설정 변경 ]</p>
<pre><code>package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {
    private final DataSource dataSource;
    private final EntityManager em;

    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}</code></pre><p>이후 통합테스트를 진행하면 성공적으로 잘 돌아가는 것을 볼 수 있습니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/3aaecdc7-9652-4d24-baae-7569c05ff7e0/image.png" alt="">
<img src="https://velog.velcdn.com/images/o_5x3o/post/cab2d3a2-6d65-4e10-a8fc-0c8182ebd900/image.png" alt=""></p>
<p><br><br></p>
<hr>
<br>



<h2 id="스프링-데이터-jpa">스프링 데이터 JPA</h2>
<br>


<p>스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어듭니다. 
여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이
인터페이스 만으로 개발을 완료할 수 있습니다. </p>
<p>그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공합니다.
스프링 부트와 JPA라는 기반 위에, 스프링 데이터 JPA라는 환상적인 프레임워크를 더하면 개발이 정말 즐거워집니다. </p>
<p>지금까지 조금이라도 단순하고 반복이라 생각했던 개발 코드들이 확연하게 줄어듭니다. </p>
<p>따라서 개발자는 핵심 비즈니스 로직을 개발하는데, 집중할 수 있습니다.
실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 이제 선택이 아니라 필수입니다.</p>
<p><strong>스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술입니다.</strong>
<strong>따라서 JPA를 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 합니다.</strong></p>
<p><br><br></p>
<p>[ 스프링 데이터 JPA 회원 리포지토리 ]</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaMemberRepository extends JpaRepository&lt;Member,Long&gt;, MemberRepository {
 Optional&lt;Member&gt; findByName(String name);
}</code></pre><br>



<p>[ 스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정 변경 ]</p>
<pre><code>package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
 private final MemberRepository memberRepository; public SpringConfig(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 @Bean
 public MemberService memberService() {
 return new MemberService(memberRepository);
 }
}</code></pre><p>스프링 데이터 JPA가 SpringDataJpaMemberRepository 를 스프링 빈으로 자동 등록해줍니다.</p>
<p><br><br></p>
<p>[ 스프링 데이터 JPA 제공 클래스 이미지 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/d8ecf2b8-b824-4506-b83b-259ef51b7eda/image.png" alt=""></p>
<p><br><br></p>
<p>[ 스프링 데이터 JPA ]
<img src="https://velog.velcdn.com/images/o_5x3o/post/9caeec57-37de-4b2c-8150-32b1987936ee/image.png" alt=""></p>
<p><strong>스프링 데이터 JPA 제공 기능</strong></p>
<ul>
<li>인터페이스를 통한 기본적인 CRUD</li>
<li>findByName() , findByEmail() 처럼 메서드 이름 만으로 조회 기능 제공</li>
<li>페이징 기능 자동 제공</li>
</ul>
<p><br><br></p>
<p>실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 됩니다. 
Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고, 동적 쿼리도 편리하게 작성할 수 있습니다. 
이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 앞서 학습한 스프링 JdbcTemplate를 사용하면 됩니다.</p>
<p><br><br></p>
<hr>
<br>

<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 통합 테스트 | JDBCTemplate]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-JDBCTemplate</link>
            <guid>https://velog.io/@o_5x3o/Spring-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-JDBCTemplate</guid>
            <pubDate>Tue, 02 May 2023 06:08:09 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-통합-테스트">스프링 통합 테스트</h2>
<p>스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행해봅시다.</p>
<br>

<p>[ 회원 서비스 스프링 통합 테스트 ]</p>
<pre><code>package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
 @Autowired MemberService memberService;
 @Autowired MemberRepository memberRepository;
 @Test
 public void 회원가입() throws Exception { //Given
 Member member = new Member();
 member.setName(&quot;hello&quot;);
 //When
 Long saveId = memberService.join(member);
 //Then
 Member findMember = memberRepository.findById(saveId).get();
 assertEquals(member.getName(), findMember.getName());
 }
 @Test
 public void 중복_회원_예외() throws Exception {
 //Given
 Member member1 = new Member();
 member1.setName(&quot;spring&quot;);
 Member member2 = new Member();
 member2.setName(&quot;spring&quot;);
 //When
 memberService.join(member1);
 IllegalStateException e = assertThrows(IllegalStateException.class,
 () -&gt; memberService.join(member2));//예외가 발생해야 한다.
 assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
 }
}</code></pre><p><strong>@SpringBootTest</strong> : 스프링 컨테이너와 테스트를 함께 실행합니다.</p>
<p><strong>@Transactional</strong> : 테스트 케이스에 이 애노테이션이 있으면 테스트 시작 전에 트랜잭션을 시작하고, 
테스트 완료 후에 항상 롤백합니다.
이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않습니다.</p>
<p><br><br></p>
<p>테스트를 바로 실행하면 이미 spring이라는 데이터가 존재하므로 다음과 같은 메세지가 출력됩니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/92d5507f-abf3-4c3f-bf0c-3c5a0eef8f62/image.png" alt=""></p>
<br> 

<p>H2 콘솔에서 delete from member 로 데이터를 지워주고 실행해봅시다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/o_5x3o/post/16f3b957-c15b-4fa0-8634-b54092cd2893/image.png" alt=""></p>
<p>이전에 테스트를 많이 해서 다음과 같은 결과로 나왔습니다.</p>
<p>삭제 유무 관계없이 ID 번호가 올라간다고 하니 DB마다 결과가 다를 수 있습니다.</p>
<p><br><br></p>
<hr>
<br>





<h2 id="스프링-jdbctemplate">스프링 JdbcTemplate</h2>
<br>

<p>순수 Jdbc와 동일한 환경설정을 하면 됩니다.
스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해주지만 SQL은 직접 작성해야 합니다.</p>
<br>


<p>[ 스프링 JdbcTemplate 회원 리포지토리 ]</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
 private final JdbcTemplate jdbcTemplate;
 public JdbcTemplateMemberRepository(DataSource dataSource) {
 jdbcTemplate = new JdbcTemplate(dataSource);
 }
 @Override
 public Member save(Member member) {
 SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
 jdbcInsert.withTableName(&quot;member&quot;).usingGeneratedKeyColumns(&quot;id&quot;); Map&lt;String, Object&gt; parameters = new HashMap&lt;&gt;();
 parameters.put(&quot;name&quot;, member.getName());
 Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
 member.setId(key.longValue());
 return member;
 }
 @Override
 public Optional&lt;Member&gt; findById(Long id) {
 List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where id 
= ?&quot;, memberRowMapper(), id);
 return result.stream().findAny();
 }
 @Override
 public List&lt;Member&gt; findAll() {
 return jdbcTemplate.query(&quot;select * from member&quot;, memberRowMapper());
 }
 @Override
 public Optional&lt;Member&gt; findByName(String name) {
 List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where 
name = ?&quot;, memberRowMapper(), name);
 return result.stream().findAny();
 }
 private RowMapper&lt;Member&gt; memberRowMapper() {
 return (rs, rowNum) -&gt; {
 Member member = new Member();
 member.setId(rs.getLong(&quot;id&quot;));
 member.setName(rs.getString(&quot;name&quot;));
 return member;
 };
 }
}</code></pre><br>




<p>[ JdbcTemplate을 사용하도록 스프링 설정 변경 ]</p>
<pre><code>package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
 private final DataSource dataSource;
 public SpringConfig(DataSource dataSource) {
 this.dataSource = dataSource;
 }
 @Bean
 public MemberService memberService() {
 return new MemberService(memberRepository());
 }
 @Bean
 public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
 return new JdbcTemplateMemberRepository(dataSource);
 }
}</code></pre><br>

<p>성공적으로 테스트를 마쳤습니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/a00cc6c9-2f1b-426a-a1f0-d5664eace567/image.png" alt=""></p>
<p><br><br></p>
<hr>
<br>

<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring H2 데이터베이스 설치 | 순수 JDBC]]></title>
            <link>https://velog.io/@o_5x3o/Spring-H2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98-%EC%88%9C%EC%88%98-JDBC</link>
            <guid>https://velog.io/@o_5x3o/Spring-H2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98-%EC%88%9C%EC%88%98-JDBC</guid>
            <pubDate>Tue, 02 May 2023 05:59:16 GMT</pubDate>
            <description><![CDATA[<p>이번 시간에는 가볍고 심플한 H2 데이터베이스를 설치하여 서버와 DB를 연결하겠습니다.</p>
<br>

<h2 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h2>
<p><strong>개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공</strong></p>
<br>



<p>h2 데이터베이스는 꼭 다음 링크에 들어가서 <strong>1.4.200</strong> 버전을 설치해주세요.
최근에 나온 2.0.206 버전을 설치하면 일부 기능이 정상 동작하지 않습니다.</p>
<blockquote>
<p><a href="https://www.h2database.com/html/download-archive.html">https://www.h2database.com/html/download-archive.html</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/80bcb283-5d50-4594-821f-c50f9878ce33/image.png" alt=""></p>
<p>저는 윈도우 사용자이기 때문에 Windows Installer를 다운로드 했습니다.</p>
<br>


<p>다운로드 후 설치까지 진행하면 H2 콘솔로 이동할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/8b2b3a17-9e29-45fe-b1d5-b3c875c6ebd0/image.png" alt=""></p>
<p>데이터베이스 최초 파일을 생성하기 위해 그대로 연결을 한 번 시켜줍니다.
에러가 나신 분들은 IP를 localhost로 변경하시면 생성이 됩니다.</p>
<p>하지만 저는 생성이 되지 않아서 다음 링크를 보고 참고하여 직접 test.mv.db 파일을 생성하여 진행했습니다.</p>
<blockquote>
<p><a href="https://kmhan.tistory.com/581">https://kmhan.tistory.com/581</a></p>
</blockquote>
<p>한 번 최초 실행을 했으면 그 후로부터는 jdbc:h2:tcp://localhost/~/test 로 연결해줍니다.</p>
<p><br><br></p>
<p>성공적으로 연결되면 이런 화면이 나타납니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/ff9f2f26-5a57-49c2-bccd-b2746bb2c568/image.png" alt=""></p>
<p><br><br></p>
<hr>
<br>




<h2 id="h2-데이터베이스에-접근하여-테이블-생성하기">H2 데이터베이스에 접근하여 테이블 생성하기</h2>
<pre><code>create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre><p><img src="https://velog.velcdn.com/images/o_5x3o/post/01f4f76f-1c51-4ba8-af50-16319faf2f8c/image.png" alt=""></p>
<p><strong>id bigint generated by default as identity</strong> : id 값이 null로 들어왔을 때 자동으로 채워줍니다.</p>
<br>


<p>sql문을 작성한 후 &#39;실행&#39;을 누르면 테이블이 생성됩니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/136cece8-27f2-46dc-8f9a-dfb6a7050e37/image.png" alt=""></p>
<br>



<p>이제 데이터를 삽입해 봅시다.</p>
<pre><code>insert into member(name) values(&#39;spring&#39;);</code></pre><br>

<p>삽입 후 다시 조회해보면 잘 삽입한 것을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/d49fd12e-240d-4f57-97fd-9e618f00d965/image.png" alt=""></p>
<p><br><br></p>
<hr>
<br>






<h2 id="순수-jdbc">순수 JDBC</h2>
<p>이제 회원 가입 웹과 연결을 해보겠습니다.</p>
<br>

<h4 id="1-환경-설정">1. 환경 설정</h4>
<p>[ build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가 ]</p>
<pre><code>implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39;
runtimeOnly &#39;com.h2database:h2&#39;</code></pre><p>각각 JDBC 드라이버와 데이터베이스 클라이언트입니다.</p>
<br>

<p>[ 스프링 부트 데이터베이스 연결 설정 추가 ( resources/application.properties ) ]</p>
<pre><code>spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa</code></pre><p>추가한 후, 다시 gradle 파일로 가서 
<img src="https://velog.velcdn.com/images/o_5x3o/post/d8be7ad8-6b9f-4654-a475-cdb71d623005/image.png" alt=""></p>
<p>코끼리 아이콘을 눌러줍니다. </p>
<p>😺 :  스프링부트 2.4부터는 <strong>spring.datasource.username=sa</strong> 를 꼭 추가해주어야 합니다.</p>
<p>인텔리J 커뮤니티(무료) 버전의 경우 application.properties 파일의 왼쪽이 다음 그림과 같이 회색으로 나옵니다.</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/3c4eba78-44fb-4e49-9926-713089408857/image.png" alt=""></p>
<p>실제로 동작하는 데 문제 없습니다.</p>
<br>



<h4 id="2-jdbc-리포지토리-구현">2. Jdbc 리포지토리 구현</h4>
<br>

<p>[ Jdbc 회원 리포지토리 ]</p>
<pre><code>package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Member save(Member member) {
        String sql = &quot;insert into member(name) values(?)&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException(&quot;id 조회 실패&quot;);
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        String sql = &quot;select * from member where id = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public List&lt;Member&gt; findAll() {
        String sql = &quot;select * from member&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery(); List&lt;Member&gt; members = new ArrayList&lt;&gt;();
            while(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        String sql = &quot;select * from member where name = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            } return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
    {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}</code></pre><p><strong>DataSource</strong>
데이터베이스 커넥션을 획득할 때 사용하는 객체입니다.
스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둡니다. 
그래서 DI를 받을 수 있습니다.</p>
<p><br><br></p>
<p>[ SpringConfig 수정 ]</p>
<pre><code>package hello.hellospring;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    @Autowired
    DataSource dataSource;

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new JdbcMemberRepository(dataSource);
       // return new MemoryMemberRepository();
    }

}</code></pre><p><br><br></p>
<p>[ 구현 클래스 이미지 ]
<img src="https://velog.velcdn.com/images/o_5x3o/post/57a7c624-0956-4635-8292-6473396a7a0f/image.png" alt=""></p>
<br>


<p>[ 스프링 설정 이미지 ]
<img src="https://velog.velcdn.com/images/o_5x3o/post/29e0e4ae-fd65-4e39-9077-232d536bf851/image.png" alt=""></p>
<p><strong>개방-폐쇄 원칙(OCP, Open-Closed Principle)</strong> : 확장에는 열려있고, 수정, 변경에는 닫혀있다.
스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있습니다.</p>
<p><br><br></p>
<p>이제 회원을 등록하고 DB에 결과가 잘 입력되는지 확인하겠습니다.</p>
<p>😺 : 꼭 H2 콘솔 열어둔 상태로 실행하셔야 합니다!</p>
<p><br><br></p>
<p>[ 결과 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/776d5239-dec4-4aa0-95ee-ae0fd5dae757/image.png" alt=""></p>
<p>아까 넣었던 spring이 잘 출력되어 있습니다.</p>
<p>데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장됩니다.</p>
<p><br><br></p>
<hr>
<br>


<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 회원 웹 기능, 웹 MVC 개발]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%EC%9B%B9-MVC-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@o_5x3o/Spring-%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%EC%9B%B9-MVC-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Tue, 02 May 2023 05:47:27 GMT</pubDate>
            <description><![CDATA[<p>오늘은 회원 관리 예제를 웹 MVC로 개발해보겠습니다.</p>
<br>

<h2 id="1-회원-웹-기능---홈-화면">1. 회원 웹 기능 - 홈 화면</h2>
<p>[ 홈 컨트롤러 추가 ]</p>
<pre><code>package hello.hellospring.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
 @GetMapping(&quot;/&quot;)
 public String home() {
 return &quot;home&quot;;
 }
}</code></pre><p>[ 회원 관리용 홈 ]</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;
    &lt;div&gt;
        &lt;h1&gt;Hello Spring&lt;/h1&gt;
        &lt;p&gt;회원 기능&lt;/p&gt;
        &lt;p&gt;
            &lt;a href=&quot;/members/new&quot;&gt;회원 가입&lt;/a&gt;
            &lt;a href=&quot;/members&quot;&gt;회원 목록&lt;/a&gt;
        &lt;/p&gt;
    &lt;/div&gt;&lt;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>[ 결과 ]
<img src="https://velog.velcdn.com/images/o_5x3o/post/9085a688-b8a6-4a54-8b5a-c524961102a0/image.png" alt=""></p>
<p>기존에 index.html이 있어도 컨트롤러 안에 &quot;/&quot;가 맵핑되었기 때문에 home.html을 호출합니다.</p>
<p>😺 : 컨트롤러가 정적 파일보다 우선순위가 높음.</p>
<p><br><br> </p>
<hr>
<br>


<h2 id="2-회원-웹-기능---등록">2. 회원 웹 기능 - 등록</h2>
<br>

<ul>
<li>회원 등록 폼 개발</li>
</ul>
<p>[ 회원 등록 폼 컨트롤러 ]</p>
<pre><code>@Controller
public class MemberController {
 private final MemberService memberService;
 @Autowired
 public MemberController(MemberService memberService) {
 this.memberService = memberService;
 }
 @GetMapping(value = &quot;/members/new&quot;)
 public String createForm() {
 return &quot;members/createMemberForm&quot;;
 }
}</code></pre><p>[ 회원 등록 폼 HTML ( resources/templates/members/createMemberForm ) ]</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;
    &lt;form action=&quot;/members/new&quot; method=&quot;post&quot;&gt;
        &lt;div class=&quot;form-group&quot;&gt;
            &lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;
            &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot; placeholder=&quot;이름을 입력하세요&quot;&gt;
        &lt;/div&gt;
        &lt;button type=&quot;submit&quot;&gt;등록&lt;/button&gt;
    &lt;/form&gt;
&lt;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>form 태그로 이름을 입력 하면 /members/new에 POST 방식으로 넘어옵니다.</p>
<p><br><br></p>
<ul>
<li>회원 등록 컨트롤러</li>
</ul>
<p>[ 웹 등록 화면에서 데이터를 전달 받을 폼 객체 ]</p>
<pre><code>package hello.hellospring.controller;
public class MemberForm {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}</code></pre><p>[ 회원 컨트롤러에서 회원을 실제 등록하는 기능 ]</p>
<pre><code>@PostMapping(value = &quot;/members/new&quot;)
public String create(MemberForm form) {
 Member member = new Member();
 member.setName(form.getName());
 memberService.join(member);
 return &quot;redirect:/&quot;;
}</code></pre><p><br><br></p>
<p>이름을 입력하여 members/new에 POST 방식으로 넘어오면 @GetMapping이 아닌 @PostMapping으로 이동합니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/950d99ec-0ce3-4a10-a19f-88bf69881793/image.png" alt=""></p>
<p>그 후 MemberForm을 create 메소드로 호출합니다.</p>
<p> <br><br></p>
<p>Memberform의 name에 입력받은 이름이 setname메소드를 통해 들어옵니다.</p>
<p> <img src="https://velog.velcdn.com/images/o_5x3o/post/eb287fed-05ca-471c-a65f-332023ecf6bb/image.png" alt=""></p>
<p>그 다음 @PostMapping에서 getName으로 이름을 꺼내 join으로 가입 완료 처리를 합니다.</p>
<p>😺 : <strong>@PostMapping</strong>은 데이터를 폼에 넣어서 전달할 때 쓰이고 <strong>@GetMapping</strong>은 주로 조회할 때 쓰입니다.</p>
<p><br><br> </p>
<hr>
<br>


<h2 id="3-회원-웹-기능---조회">3. 회원 웹 기능 - 조회</h2>
<br>


<p>[ 회원 컨트롤러에서 조회 기능 ]</p>
<pre><code>@GetMapping(value = &quot;/members&quot;)
public String list(Model model) {
 List&lt;Member&gt; members = memberService.findMembers();
 model.addAttribute(&quot;members&quot;, members);
 return &quot;members/memberList&quot;;
}</code></pre><p>[ 회원 리스트 HTML ]</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;
    &lt;div&gt;
        &lt;table&gt;
            &lt;thead&gt; &lt;tr&gt;
                &lt;th&gt;#&lt;/th&gt;
                &lt;th&gt;이름&lt;/th&gt;
            &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
            &lt;tr th:each=&quot;member : ${members}&quot;&gt;
                &lt;td th:text=&quot;${member.id}&quot;&gt;&lt;/td&gt;
                &lt;td th:text=&quot;${member.name}&quot;&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;/tbody&gt;
        &lt;/table&gt;
    &lt;/div&gt;
&lt;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>이 html에서 thymeleaf 엔진이 본격적으로 사용됩니다.</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/b92cc90c-ca8b-4e85-86e7-1297d9655121/image.png" alt=""></p>
<p>템플릿 언어로 모델 안에 있는 ${members} 값을 읽어들입니다.</p>
<p>컨트롤러에서 addAttribute로 조회 리스트인 members를 담아놨기 때문에 <strong>th:each</strong>로 루프를 돌면서 실행합니다.</p>
<p>😺 : <strong>th:each는 thymeleaf 문법으로 for이나 while와 유사한 반복 처리를 할 때 쓰임</strong></p>
<p><br><br></p>
<p>그 후 member.id, member.name에 접근하여 출력해줍니다.</p>
<p>[ 결과 ]</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/e080cc3c-aea9-47ac-8270-f97a2377ce2c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/98039991-18d6-47fe-8ffe-a225ecdba5d5/image.png" alt=""></p>
<p><br><br></p>
<hr>
<br>

<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 컴포넌트 스캔 및 자동 의존관계 설정 | 스프링 빈 등록]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94-%EB%B0%8F-%EC%9E%90%EB%8F%99-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%84%A4%EC%A0%95-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EB%93%B1%EB%A1%9D</link>
            <guid>https://velog.io/@o_5x3o/Spring-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94-%EB%B0%8F-%EC%9E%90%EB%8F%99-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%84%A4%EC%A0%95-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EB%93%B1%EB%A1%9D</guid>
            <pubDate>Tue, 02 May 2023 05:40:10 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-빈과-의존관계">스프링 빈과 의존관계</h2>
<br>

<h3 id="컴포넌트-스캔과-자동-의존관계-설정">컴포넌트 스캔과 자동 의존관계 설정</h3>
<p>회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 준비해야 합니다. ( = 회원 컨트롤러가 회원 서비스를 의존한다)</p>
<p><br><br></p>
<h4 id="회원-컨트롤러에-의존관계-추가">회원 컨트롤러에 의존관계 추가</h4>
<pre><code>package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MemberController {

    private final MemberService memberService; //스프링 컨테이너에 등록

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}</code></pre><h4 id="autowired">@Autowired</h4>
<p>스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줍니다. = DI (의존성 주입)
이전 테스트에서는 개발자가 직접 주입했고, 여기서는 @Autowired에 의해 스프링이 주입해줍니다.</p>
<br>
...

<br>



<p><strong>오류 발생</strong></p>
<blockquote>
<p>Consider defining a bean of type &#39;hello.hellospring.service.MemberService&#39; in your configuration.</p>
</blockquote>
<p><strong>이유</strong></p>
<p>memberService가 스프링 빈으로 등록되어 있지 않음
<img src="https://velog.velcdn.com/images/o_5x3o/post/849e1dbc-643b-4ec9-9317-fa8303e5a788/image.png" alt=""></p>
<p>helloController는 스프링이 제공하는 컨트롤러여서 스프링 빈으로 자동 등록됩니다.
(<strong>@Controller</strong> 가 있으면 자동 등록)</p>
<p>하지만 MemberService는 <strong>순수 자바 클래스</strong>이기 때문에 자동 등록 X</p>
<p><br><br></p>
<h4 id="스프링-빈-등록-방법">스프링 빈 등록 방법</h4>
<ol>
<li>컴포넌트 스캔과 자동 의존관계 설정</li>
<li>자바 코드로 직접 스프링 빈 등록</li>
</ol>
<br>

<h4 id="컴포넌트-스캔-원리">컴포넌트 스캔 원리</h4>
<ul>
<li>@Component 애노테이션이 있으면 스프링 빈으로 자동 등록됨</li>
<li>@Component 포함하는 다음 애노테이션도 자동 등록이 된다. (@Controller, @Service, @Repository)</li>
<li>@Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문</li>
</ul>
<p><br><br></p>
<hr>
<br>





<h3 id="1-컴포넌트-스캔과-자동-의존관계-설정">1. 컴포넌트 스캔과 자동 의존관계 설정</h3>
<p><br><br></p>
<p>회원 서비스 스프링 빈 등록
<img src="https://velog.velcdn.com/images/o_5x3o/post/0250d1f1-4216-45d3-a355-1e2f728ae137/image.png" alt=""></p>
<p>MemberService : @Service, @Autowired 추가</p>
<p>생성자에 @Autowired 를 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입합니다.</p>
<p>생성자가 1개만 있으면 <strong>@Autowired 는 생략 가능</strong>!</p>
<p><br><br></p>
<p>회원 리포지토리 스프링 빈 등록
<img src="https://velog.velcdn.com/images/o_5x3o/post/e0b83363-bb3d-484d-a42e-0f1dd3ac38ef/image.png" alt=""></p>
<p>MemoryMemberRepository : <strong>@Repository 추가</strong></p>
<p><br><br></p>
<p>스프링 빈 등록 이미지
<img src="https://velog.velcdn.com/images/o_5x3o/post/3b82a696-f6c0-421f-9b48-6dcd39b62f5a/image.png" alt=""></p>
<p>현재 memberService 와 memberRepository 가 스프링 컨테이너에 스프링 빈으로 등록된 상태입니다.</p>
<p>스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 특별한 경우가 아니면 기본적으로 싱글톤으로 등록합니다.
( = 유일하게 하나만 등록해서 공유)</p>
<p>따라서 같은 스프링 빈이면 모두 같은 인스턴스입니다.</p>
<p><br><br></p>
<hr>
<br>


<h3 id="2-자바-코드로-직접-스프링-빈-등록">2. 자바 코드로 직접 스프링 빈 등록</h3>
<br>


<p>위에서 추가했던 <strong>@Service, @Autowired, @Repository</strong> 제거 후 진행합니다.</p>
<pre><code>package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

}</code></pre><p>여기서는 향후 메모리 리포지토리를 다른 리포지토리로 변경할 예정이므로, 자바 코드로 스프링 빈을 설정하겠습니다.</p>
<p>XML로 설정하는 방식도 있지만 최근에는 잘 사용하지 않으므로 생략합니다.</p>
<p><br><br>
<strong>DI 방식</strong></p>
<ol>
<li>필드 주입</li>
<li>setter 주입</li>
<li>생성자 주입 &lt;= Best!!</li>
</ol>
<p>의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장합니다.</p>
<p>실무에서는 주로 정형화된 코드는 <strong>컴포넌트 스캔을 사용</strong>하지만, 정형화 되지 않거나 상황에 따라 구현 클래스를 변경해야 하면 <strong>설정을 통해 스프링 빈으로 등록</strong>합니다.</p>
<br>


<p>※ @Autowired 를 통한 DI는 helloConroller , memberService 등과 같이 스프링이 관리하는 객체에서만 동작합니다.
스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작 X</p>
<p><br><br></p>
<hr>
<br>

<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 회원 서비스 개발 및 테스트]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C-%EB%B0%8F-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@o_5x3o/Spring-%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C-%EB%B0%8F-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 02 May 2023 05:27:35 GMT</pubDate>
            <description><![CDATA[<p>회원 서비스 개발</p>
<pre><code>package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import java.util.List;import java.util.Optional;
public class MemberService {
 private final MemberRepository memberRepository = new
MemoryMemberRepository();
 /**
 * 회원가입
 */
 public Long join(Member member) {
 validateDuplicateMember(member); //중복 회원 검증
 memberRepository.save(member);
 return member.getId();
 }
 private void validateDuplicateMember(Member member) {
 memberRepository.findByName(member.getName())
 .ifPresent(m -&gt; {
 throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
 });
 }
 /**
 * 전체 회원 조회
 */
 public List&lt;Member&gt; findMembers() {
 return memberRepository.findAll();
 }
 public Optional&lt;Member&gt; findOne(Long memberId) {
 return memberRepository.findById(memberId);
 }
}</code></pre><p><br><br></p>
<p>회원 서비스 테스트</p>
<pre><code>package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest { 
 MemberService memberService;
 MemoryMemberRepository memberRepository;

 @BeforeEach
 public void beforeEach() {
 memberRepository = new MemoryMemberRepository();
 memberService = new MemberService(memberRepository);
 }

 @AfterEach
 public void afterEach() {
 memberRepository.clearStore();
 }

 @Test
 public void 회원가입() throws Exception {
 //Given
 Member member = new Member();
 member.setName(&quot;hello&quot;);

 //When
 Long saveId = memberService.join(member);

 //Then
 Member findMember = memberRepository.findById(saveId).get();
 assertEquals(member.getName(), findMember.getName());
 }

 @Test
 public void 중복_회원_예외() throws Exception {
 //Given
 Member member1 = new Member();
 member1.setName(&quot;spring&quot;);
 Member member2 = new Member();
 member2.setName(&quot;spring&quot;); 

 //When
 memberService.join(member1);
 IllegalStateException e = assertThrows(IllegalStateException.class,
 () -&gt; memberService.join(member2));//예외가 발생해야 한다.
 assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
 }
}</code></pre><p><br><br></p>
<h3 id="테스트-코드-작성할-시-고려할-점">테스트 코드 작성할 시 고려할 점</h3>
<p>Given : 주어진 상황 또는 검증할 것
When : 실행
Then : 결과</p>
<br>


<p>기존에는 회원 서비스가 메모리 회원 리포지토리를 직접 생성함</p>
<pre><code>public class MemberService {
 private final MemberRepository memberRepository = 
 new MemoryMemberRepository();
}</code></pre><p>회원 리포지토리의 코드가 회원 서비스 코드를 DI 가능하게 변경함</p>
<pre><code>public class MemberService {
 private final MemberRepository memberRepository;
 public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 ...
}</code></pre><ul>
<li>DI ( Dependency Injection, 의존성 주입 ) : 두 객체 간의 관계를 맺어주는 것</li>
</ul>
<p><br><br></p>
<h4 id="beforeeach">@BeforeEach</h4>
<p>각 테스트 실행 전에 호출되며 테스트가 서로 영향이 없도록 항상 <strong>새로운 객체를 생성하고, 의존관계도 새로 맺어줌.</strong></p>
<p> <br><br></p>
<hr>
 <br>


<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 비즈니스 요구사항 정리 | 회원 리포지토리 & 테스트 케이스 작성]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC-%ED%9A%8C%EC%9B%90-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@o_5x3o/Spring-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC-%ED%9A%8C%EC%9B%90-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Tue, 02 May 2023 05:23:45 GMT</pubDate>
            <description><![CDATA[<h2 id="비즈니스-요구사항-정리">비즈니스 요구사항 정리</h2>
<p>기본 사항들로만 구성된 단순한 구조로 개발할 예정입니다.</p>
<p><strong>데이터</strong> : 회원 ID, 이름
<strong>기능</strong> : 회원 등록, 조회
아직 데이터 저장소 선정 X</p>
<p><br><br></p>
<h3 id="일반-웹-애플리케이션-계층-구조">일반 웹 애플리케이션 계층 구조</h3>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/056a0bc6-88d7-47f3-b75c-3f4a1d2cd2c5/image.png" alt=""></p>
<ul>
<li>컨트롤러: 웹 MVC의 API 또는 컨트롤러 역할</li>
<li>서비스: 핵심 비즈니스 로직 구현 (ex. 회원 중복 가입 예외처리)</li>
<li>리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리</li>
<li>도메인: 비즈니스 도메인 객체 (ex. 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨)</li>
</ul>
<p><br><br></p>
<h3 id="클래스-의존관계">클래스 의존관계</h3>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/2dd50302-028a-4f32-a932-7740dcb3345c/image.png" alt=""></p>
<ul>
<li>아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 단순한 메모리 구현 클래스를 변경할 수 있도록 설계</li>
<li>데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정</li>
<li>을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용</li>
</ul>
<p>데이터 저장소가 선정이 안된 상황에서는, 나중에 구체적인 기술이 선정이 된 후 바꿔 끼우기 위해 <strong>인터페이스</strong>가 필요합니다.</p>
<p><br><br></p>
<hr>
<br>

<h2 id="회원-도메인과-리포지토리-만들기">회원 도메인과 리포지토리 만들기</h2>
<br>



<p>회원 객체</p>
<pre><code>package hello.hellospring.domain;
public class Member {

 private Long id;
 private String name;
 public Long getId() {
 return id;
 }
 public void setId(Long id) {
 this.id = id;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}</code></pre><p><br><br></p>
<p>회원 리포지토리 인터페이스</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
 Member save(Member member); Optional&lt;Member&gt; findById(Long id);
 Optional&lt;Member&gt; findByName(String name);
 List&lt;Member&gt; findAll();
}</code></pre><p><br><br></p>
<p>회원 리포지토리 메모리 구현체</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
/**
 * 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
 */
public class MemoryMemberRepository implements MemberRepository {
 private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;();
 private static long sequence = 0L;
 @Override
 public Member save(Member member) {
 member.setId(++sequence);
 store.put(member.getId(), member);
 return member;
 }
 @Override
 public Optional&lt;Member&gt; findById(Long id) {
 return Optional.ofNullable(store.get(id));
 }
 @Override
 public List&lt;Member&gt; findAll() {
 return new ArrayList&lt;&gt;(store.values()); }
 @Override
 public Optional&lt;Member&gt; findByName(String name) {
 return store.values().stream()
 .filter(member -&gt; member.getName().equals(name))
 .findAny();
 }
 public void clearStore() {
 store.clear();
 }
}</code></pre><p><br><br></p>
<hr>
<br>




<h2 id="회원-리포지토리-테스트-케이스-작성">회원 리포지토리 테스트 케이스 작성</h2>
<br>



<p>자바는 어떻게 테스트 할까
개발한 기능을 실행해서 테스트 할 때 <strong>자바의 main 메서드를 통해서 실행</strong>하거나, 
<strong>웹 애플리케이션의 컨트롤러</strong>를 통해서 해당 기능을 실행한다. </p>
<p>하지만 </p>
<ul>
<li>준비하고 실행하는데 오래 걸리고, 반복 실행하기 어려움</li>
<li>여러 테스트를 한번에 실행하기 어려움</li>
</ul>
<p>자바는 <strong>JUnit</strong>이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.</p>
<p><br><br></p>
<p>회원 리포지토리 메모리 구현체 테스트</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;class MemoryMemberRepositoryTest {
 MemoryMemberRepository repository = new MemoryMemberRepository();
 @AfterEach
 public void afterEach() {
 repository.clearStore();
 }
 @Test
 public void save() {
 //given
 Member member = new Member();
 member.setName(&quot;spring&quot;);
 //when
 repository.save(member);
 //then
 Member result = repository.findById(member.getId()).get();
 assertThat(result).isEqualTo(member);
 }
 @Test
 public void findByName() {
 //given
 Member member1 = new Member();
 member1.setName(&quot;spring1&quot;);
 repository.save(member1);
 Member member2 = new Member();
 member2.setName(&quot;spring2&quot;);
 repository.save(member2);
 //when
 Member result = repository.findByName(&quot;spring1&quot;).get();
 //then assertThat(result).isEqualTo(member1);
 }
 @Test
 public void findAll() {
 //given
 Member member1 = new Member();
 member1.setName(&quot;spring1&quot;);
 repository.save(member1);
 Member member2 = new Member();
 member2.setName(&quot;spring2&quot;);
 repository.save(member2);
 //when
 List&lt;Member&gt; result = repository.findAll();
 //then
 assertThat(result.size()).isEqualTo(2);
 }
}</code></pre><br>


<h4 id="aftereach">@AfterEach</h4>
<p>한 번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트의 결과가 남을 수 있어서
이전 테스트 때문에 다음 테스트가 실패할 가능성이 높습니다. </p>
<p>@AfterEach 를 사용하면 각 테스트가 종료될 때 마다 이 기능을 실행하여 메모리 DB에 저장된 데이터를 삭제합니다.</p>
<p><br><br></p>
<hr>
<br>


<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 정적 컨텐츠 기능과 동작 과정 | MVC | API]]></title>
            <link>https://velog.io/@o_5x3o/Spring-%EC%A0%95%EC%A0%81-%EC%BB%A8%ED%85%90%EC%B8%A0-%EA%B8%B0%EB%8A%A5%EA%B3%BC-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95-MVC-API</link>
            <guid>https://velog.io/@o_5x3o/Spring-%EC%A0%95%EC%A0%81-%EC%BB%A8%ED%85%90%EC%B8%A0-%EA%B8%B0%EB%8A%A5%EA%B3%BC-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95-MVC-API</guid>
            <pubDate>Tue, 02 May 2023 04:43:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/o_5x3o/post/4997dcb0-4870-4a39-b191-01dd5d002b88/image.png" alt=""></p>
<h2 id="정적-컨텐츠란-">정적 컨텐츠란 ?</h2>
<p><strong>서버를 거치지 않고 파일을 웹 브라우저에 그대로 내려주는 것</strong></p>
<p> <br><br><br></p>
<p>예시를 들어봅시다.</p>
<p>static 폴더에 hello-static.html 파일을 생성해 줍니다.</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;title&gt;static content&lt;/title&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
정적 컨텐츠 입니다.
&lt;/body&gt;
&lt;/html&gt;</code></pre><p><a href="http://localhost:8080/hello-static.html">http://localhost:8080/hello-static.html</a> 로 접속하면 다음과 같은 결과가 나옵니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/eaa458f5-7486-4132-990b-dc95dd073454/image.png" alt=""></p>
<p>static 폴더에 원하는 파일을 넣으면 정적 파일로 반환됩니다.</p>
<p><strong>하지만 이 파일에 그 어떤 프로그래밍도 할 수 없습니다.</strong></p>
<p><br><br></p>
<h4 id="정적-컨텐츠-동작-과정">정적 컨텐츠 동작 과정</h4>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/64658a02-a307-4b90-96bd-3ca358766c5a/image.png" alt=""></p>
<blockquote>
<p>Reference : <a href="https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-bootfeatures.html#boot-features-spring-mvc-static-content">https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-bootfeatures.html#boot-features-spring-mvc-static-content</a></p>
</blockquote>
<p><br><br></p>
<hr>
<br>

<h2 id="mvc와-템플릿-엔진">MVC와 템플릿 엔진</h2>
<p><strong>MVC : Model, View, Controller의 줄임말이다.</strong></p>
<p> <br><br></p>
<p>Controller</p>
<pre><code>@Controller
public class HelloController {
 @GetMapping(&quot;hello-mvc&quot;)
 public String helloMvc(@RequestParam(&quot;name&quot;) String name, Model model) {
 model.addAttribute(&quot;name&quot;, name);
 return &quot;hello-template&quot;;
 }
}</code></pre><p>resources/template/hello-template.html</p>
<pre><code>&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;hello &#39; + ${name}&quot;&gt;hello! empty&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;</code></pre><br>


<p>이렇게 코드를 작성한 후 실행해보면 다음과 같이 나옵니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/b6b35866-796a-4edc-a894-b353f7aee673/image.png" alt=""></p>
<p><strong>@RequestParam</strong>의 <strong>name</strong>을 통해 Model이 담겨지기 때문입니다.</p>
<p>thymeleaf 템플릿은 주소창에 <strong>파일 경로</strong>로 이동해도 그대로 결과가 나옵니다.</p>
<p><br><br></p>
<p><strong>MVC, 템플릿 엔진 동작 과정</strong>
<img src="https://velog.velcdn.com/images/o_5x3o/post/2a799a08-644c-491e-9cab-43afbdb63946/image.png" alt=""></p>
<p><strong>viewResolver</strong></p>
<ul>
<li>View를 찾아주고 템플릿을 연결시키는 해결자</li>
<li>변환한 html을 웹브라우저에 변환</li>
</ul>
<p> <br><br></p>
<hr>
 <br>


<h2 id="api">API</h2>
<br>


<p>@ResponseBody 문자 반환</p>
<pre><code>@GetMapping(&quot;hello-string&quot;)
 @ResponseBody
 public String helloString(@RequestParam(&quot;name&quot;) String name) {
 return &quot;hello &quot; + name;
 }</code></pre><ul>
<li><strong>@ResponseBody 를 사용하면 뷰 리졸버( viewResolver )를 사용 X</strong></li>
<li><strong>대신에 HTTP의 BODY에 문자 내용을 직접 반환(HTML BODY TAG 아님)</strong></li>
</ul>
<br>


<p>@ResponseBody 객체 반환</p>
<pre><code>@Controller
public class HelloController {
 @GetMapping(&quot;hello-api&quot;)
 @ResponseBody
 public Hello helloApi(@RequestParam(&quot;name&quot;) String name) {
 Hello hello = new Hello();
 hello.setName(name);
 return hello;
 }
 static class Hello {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 }
}</code></pre><ul>
<li><strong>@ResponseBody 를 사용하고, 객체를 반환하면 객체가 JSON으로 변환</strong></li>
</ul>
<p><br><br></p>
<h4 id="responsebody-사용-원리">@ResponseBody 사용 원리</h4>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/eab860ea-e32f-49be-92f4-a5e7955dd8b1/image.png" alt=""></p>
<ul>
<li>HTTP의 BODY에 문자 내용을 직접 반환</li>
<li>viewResolver 대신에 HttpMessageConverter 가 동작</li>
<li>기본 문자처리: StringHttpMessageConverter</li>
<li>기본 객체처리: MappingJackson2HttpMessageConverter</li>
<li>byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음</li>
</ul>
<p>객체로 넘겨주었다면 디폴트로 JSON 형식으로 만들어서 HTTP에 반환합니다.</p>
<p><br><br></p>
<hr>
<br>

<p> 학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring, View 환경 설정 | thymeleaf 템플릿 동작 과정]]></title>
            <link>https://velog.io/@o_5x3o/Spring-View-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95-thymeleaf-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@o_5x3o/Spring-View-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95-thymeleaf-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Tue, 02 May 2023 04:29:43 GMT</pubDate>
            <description><![CDATA[<p>인프런 강의를 통해 친구들과 Spring 스터디를 하기로 했습니다.</p>
<p>먼저 실습을 하기 위해서 아래와 같은 환경을 만들어야 합니다.</p>
<ol>
<li>JDK 11 설치</li>
<li>IDE: IntelliJ 또는 Eclipse 설치</li>
</ol>
<p>저는 IntelliJ Community로 진행하였습니다.</p>
<blockquote>
<p><a href="https://start.spring.io">https://start.spring.io</a></p>
</blockquote>
<p>위 사이트를 통해 프로젝트 생성 후 다운받아 IntelliJ에서 열었습니다.
<br><br><br></p>
<p>main 메소드를 실행시키면 정상적으로 작동됩니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/0e182825-8d14-4405-8bb6-19b4da9aa150/image.png" alt="">
<br>
Gradle을 통하지 않고 바로 결과를 확인하기 위해 setting에 들어가 설정해주었습니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/05b810f5-2ba2-4c84-b6d3-d591350b2040/image.png" alt=""></p>
<br>

<hr>
<br>

<h3 id="라이브러리">라이브러리</h3>
<p>Gradle은 의존관계가 있는 라이브러리를 함께 다운로드 합니다.</p>
<h4 id="spring-boot-라이브러리">Spring Boot 라이브러리</h4>
<p>spring-boot-starter-web
ㄴ spring-boot-starter-tomcat: 톰캣 (웹서버)
ㄴ spring-webmvc: 스프링 웹 MVC
spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(View)
spring-boot-starter(공통): 스프링 부트 + 스프링 코어 + 로깅
ㄴ spring-boot
   ㄴ spring-core
ㄴ spring-boot-starter-logging
   ㄴ logback, slf4j</p>
<h4 id="테스트-라이브러리">테스트 라이브러리</h4>
<p>spring-boot-starter-test
ㄴ junit: 테스트 프레임워크
ㄴ mockito: 목 라이브러리
ㄴ assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
ㄴ spring-test: 스프링 통합 테스트 지원</p>
<br>

<hr>
<br>

<h3 id="view-환경-설정">View 환경 설정</h3>
<h4 id="--spring-boot에서-제공하는-welcome-page-기능">- Spring Boot에서 제공하는 Welcome Page 기능</h4>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/3a5a5ce4-7518-4f0d-ac76-fc1bd72ddea4/image.png" alt=""></p>
<p>static/index.html 을 올려두면 Welcome page 기능을 제공합니다.</p>
<p>index.html에 hello가 출력되게끔 간단한 코드를 작성합니다.</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;title&gt;Hello&lt;/title&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
Hello
&lt;a href=&quot;/hello&quot;&gt;hello&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p><br><br></p>
<blockquote>
<p><a href="http://localhost:8080/">http://localhost:8080/</a></p>
</blockquote>
<p>서버를 멈추고 다시 실행해주면 에러 페이지에서 welcome page로 바뀝니다.
<img src="https://velog.velcdn.com/images/o_5x3o/post/a7e29151-13c0-4ab0-a233-7e510df099ff/image.png" alt=""></p>
<p><br><br></p>
<blockquote>
<p>Reference : <a href="https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-welcome-page">https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-welcome-page</a></p>
</blockquote>
<br>

<hr>
<br>

<h3 id="thymeleaf-템플릿-엔진">thymeleaf 템플릿 엔진</h3>
<br>

<p><img src="https://velog.velcdn.com/images/o_5x3o/post/0a787978-4bc8-4374-a2d2-252c00364df0/image.png" alt="">
controller 패키지를 만들어서 HelloController 클래스를, templates 폴더에 hello.html을 생성합니다.</p>
<br>







<p>&lt; HelloController 코드 &gt;</p>
<pre><code>@Controller
public class HelloController {
 @GetMapping(&quot;hello&quot;)
 public String hello(Model model) {
 model.addAttribute(&quot;data&quot;, &quot;hello!!&quot;);
 return &quot;hello&quot;;
 }
}</code></pre><p>&lt; hello 코드 &gt;</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Hello&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;안녕하세요. &#39; + ${data}&quot; &gt;안녕하세요. 손님&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>그런 다음 <a href="http://localhost:8080/hello">http://localhost:8080/hello</a> 로 접속하면 다음과 같이 결과가 나옵니다.</p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/8005f551-f9cc-484c-ab02-94048ff7ee15/image.png" alt=""></p>
<p><br><br></p>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/4ec8a325-8416-4170-b3c2-87f129b01a80/image.png" alt="">
<strong>attributeValue 값이 html의 ${data}에 치환되기 때문입니다.</strong></p>
<p><br><br></p>
<hr>
<br>

<h3 id="동작-환경-그림">동작 환경 그림</h3>
<p><img src="https://velog.velcdn.com/images/o_5x3o/post/49fa3122-194b-4a5c-bc74-98ac6f97bd64/image.png" alt=""></p>
<p>컨트롤러에서 리턴 값으로 문자를 반환하면 뷰 리졸버( viewResolver )가 화면을 찾아서 처리합니다.
ㄴ 스프링 부트 템플릿엔진 기본 viewName 매핑
ㄴ resources:templates/ +{ViewName}+ .html</p>
<p>◆ 참고: spring-boot-devtools 라이브러리를 추가하면, html 파일을 컴파일만 해주면 서버 재시작 없이 View 파일 변경이 가능합니다.</p>
<p>인텔리J 컴파일 방법: 메뉴 build Recompile
<br><br></p>
<blockquote>
</blockquote>
<p>Reference :
thymeleaf 공식 사이트: <a href="https://www.thymeleaf.org/">https://www.thymeleaf.org/</a>
스프링 공식 튜토리얼: <a href="https://spring.io/guides/gs/serving-web-content/">https://spring.io/guides/gs/serving-web-content/</a>
스프링부트 메뉴얼: <a href="https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-template-engines">https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-template-engines</a></p>
<p><br><br></p>
<hr>
<br>

<h3 id="빌드하고-실행하기">빌드하고 실행하기</h3>
<br>

<h4 id="콘솔-이동">콘솔 이동</h4>
<p> ./gradlew build
cd build/libs
java -jar hello-spring-0.0.1-SNAPSHOT.jar
실행 확인</p>
<br>


<h4 id="윈도우-유저-tip">윈도우 유저 Tip</h4>
<p>콘솔 ➡️ 이동 명령 프롬프트(cmd)로 이동
./gradlew ➡️ gradlew.bat 를 실행
명령 프롬프트에서 gradlew.bat 를 실행하려면 gradlew 하고 엔터
gradlew build
폴더 목록 확인 ls ➡️ dir</p>
<p><br><br></p>
<hr>
<br>



<p>학습중인 스프링 강의 : <a href="https://inf.run/pcut">https://inf.run/pcut</a>
스프링 실습 코드 저장소 : <a href="https://github.com/0pyaq0/Spring_Study.git">https://github.com/0pyaq0/Spring_Study.git</a></p>
<p><br><br></p>
<p>본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : <a href="https://develop-about-leejin.tistory.com/">https://develop-about-leejin.tistory.com/</a></p>
]]></description>
        </item>
    </channel>
</rss>