<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>s_em_tudy.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 22 May 2023 16:05:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. s_em_tudy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/s_em_tudy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring Boot_AOP 적용]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootAOP-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootAOP-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Mon, 22 May 2023 16:05:05 GMT</pubDate>
            <description><![CDATA[<h3 id="aop">AOP</h3>
<p>:Aspect Oriented Programming
/공통 관심 사항과 핵심 관심 사항을 분리</p>
<p>시간 측정 로직을 TimeTraceAop라는 곳에 모아놓고,
helloController든, memberService든, memberRepository든
내가 원하는 곳에 적용해줌.</p>
<h3 id="시간-측정-aop-등록">시간 측정 AOP 등록</h3>
<p>우선 hello - helloSpring에 aop라는 패키지를 하나 생성하였다.
그리고 그 밑에 TimeTraceAop라는 클래스를 생성하였다.</p>
<p>AOP는 @Aspect라는 애노테이션을 필요로 하기 때문에 적어주었고,</p>
<pre><code>public Object excute(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><p>위와 같은 시간 측정 코드를 적어주었다.</p>
<p>저번에 AOP 없이 했던 코드와 비슷하게 진행된다.</p>
<blockquote>
<p>return joinPoint.proceed(); 는 
Object result = joinPoint.proceed()
return result
를 Inline으로 Refactor 해준 것이다.</p>
</blockquote>
<p>joinPoint.proceed()는
다음 메소드로 진행하게 해준다.</p>
<p>이제 등록을 하기 위해서는 
TimeTraceAop에 @Component 라는 애노테이션을 적는 방법과,</p>
<p>더 좋은 방법은
SpringConfig의 springbean에 등록을 하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/53b38711-fc85-41b9-b691-9d2520a67c84/image.png" alt=""></p>
<pre><code>@Bean
    public TimeTraceAop timeTraceAop() {
        return new TimeTraceAop();
    }</code></pre><p>이렇게 스프링 빈에 직접 등록을 하는 방법이
확실하게 Aop가 등록돼서 쓰이는 구나 를 알 수 있기 때문에 더 좋은 방법이다.</p>
<p>여기서는 그냥 @Component를 사용하겠다.</p>
<p>그리고 이제 이 공통 관심 사항을 &#39;어디에&#39; 적용할 것인지를 targeting 해주기 위해</p>
<pre><code>@Around(&quot;execution(* hello.hellospring..*(..))&quot;)</code></pre><p>를 적어준다.
hello.hellospring의 모든 하위 패키지에 적용한다는 뜻이다.
여기에는 클래스명도 적을 수 있고 여러가지 다양한 것들을 적을 수가 있다.</p>
<p>예를 들어 service와 service 하위만 측정하고 싶으면,</p>
<blockquote>
</blockquote>
<p>@Around(&quot;execution(* hello.hellospring.service..*(..))&quot;)</p>
<p>이렇게 적어주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/33d327b2-2e5a-4ce4-9342-bac1e319ee08/image.png" alt="">이렇게 코드를 작성한 후,
실행을 시켜보았다.</p>
<p>HelloSpringApplication을 돌려서
서버를 띄우고,
회원 목록에 들어가본 후,
실행 결과를 보니</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/4e698c02-e606-4925-8a50-cb9ca1f40001/image.png" alt="">이렇게 MemberController, MemberService, JpaRepository들이 딱딱 나오고,</p>
<blockquote>
<p>END: execution(List org.springframework.data.jpa.repository.JpaRepository.findAll()) 97ms</p>
</blockquote>
<p>DB에서 조회하는 것 - 97ms</p>
<blockquote>
<p>END: execution(List hello.hellospring.service.MemberService.findMembers()) 105ms</p>
</blockquote>
<p>MemberService - 105ms</p>
<blockquote>
<p>END: execution(String hello.hellospring.controller.MemberController.list(Model)) 121ms</p>
</blockquote>
<p>MemberController - 121ms</p>
<p>이렇게 모두 나오는 것을 볼 수 있다.</p>
<p>이렇게 하면 어디에서 병목현상이 있는지 찾을 수가 있다.</p>
<p>호출이 될 때마다 joinPoint.로 원하는 것을 조작할 수 있다.</p>
<p>joinPoint가 다음으로 호출을 해주면서, intercept 가 걸리는 것이다.</p>
<p>이런 식으로 intercepting해서 풀어나갈 수 있는 이러한 기술이 바로 AOP이다.</p>
<p>이렇게 해서,
AOP를 사용하지 않고 시간을 측정했을 때 발생했던 문제들을
해결할 수 있는 것이다.</p>
<h3 id="해결">해결</h3>
<ul>
<li>회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.</li>
<li>시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.</li>
<li>핵심 관심 사항을 깔끔하게 유지할 수 있다.</li>
<li>변경이 필요하면 이 로직만 변경하면 된다.</li>
<li>원하는 적용 대상을 선택할 수 있다.</li>
</ul>
<h3 id="스프링의-aop-동작-방식-설명">스프링의 AOP 동작 방식 설명</h3>
<p>AOP를 적용하기 전의 의존관계는,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/315589b1-864e-4ef0-8609-e84f7669eb2a/image.pdf" alt=""></p>
<p>helloController가 memberService를 의존하고 있고,
의존 관계에 의해 호출을 했을 것이다.</p>
<p>그런데 AOP에서는</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/d534b81f-89f1-4473-ad3a-d99ed4e49e31/image.pdf" alt=""></p>
<p>적용할 위치를 지정을 하면, 
프록시라고 하는 가짜 memberService를 만들어 내고,</p>
<p>컨테이너에 스프링 빈을 등록할 때,
진짜 스프링 빈 말고 
가짜 스프링 빈 앞에 세워놓는다.
그리고 가짜 스프링 빈이 끝나면(joinPoint.proceed())
그때 진짜를 호출해 준다.</p>
<p>그래서 
helloController가 호출하는 것은 
진짜 memberService가 아닌
프록시라는 가짜 memberService인 것이다.</p>
<h3 id="실제-proxy가-주입되는지-콘솔에-출력해서-확인하기">실제 Proxy가 주입되는지 콘솔에 출력해서 확인하기</h3>
<p>controller - MemberController에</p>
<pre><code> @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
        System.out.println(&quot;memberService = &quot; + memberService.getClass());
    }</code></pre><p>이렇게 코드를 추가해 주었다.</p>
<p>결과는 가짜 memberService가 호출된 것을 확인해 볼 수 있었다.</p>
<p>&lt;AOP 적용 전 전체 그림&gt;
<img src="https://velog.velcdn.com/images/s_em_tudy/post/62088d34-136f-47b7-9eab-96fbd6b0c588/image.pdf" alt=""></p>
<p>&lt;AOP 적용 후 전체 그림&gt;
<img src="https://velog.velcdn.com/images/s_em_tudy/post/e6a2008a-6bd5-448e-87d3-804c69a512bd/image.pdf" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_AOP]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootAOP</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootAOP</guid>
            <pubDate>Mon, 22 May 2023 13:42:05 GMT</pubDate>
            <description><![CDATA[<h3 id="aop가-필요한-상황">AOP가 필요한 상황</h3>
<ul>
<li>모든 메소드의 호출 시간을 측정하고 싶다면?</li>
<li>공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)</li>
<li>회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?</li>
</ul>
<h3 id="aop-없이-회원-가입-시간-측정">AOP 없이 회원 가입 시간 측정</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/1423a4f7-02eb-44c8-bc51-2b97db176e4b/image.png" alt=""></p>
<pre><code> 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;);
        }

    }</code></pre><p>우선 repository - service - MemberService의 <strong>join 메소드</strong>에 </p>
<blockquote>
</blockquote>
<p>long start = System.currentTimeMillis();</p>
<p>로 시작하는 시간을 저장한다.</p>
<p>총 걸린 시간은 logic이 끝날 때의 시간을 재야 하는 것이고,
예외가 터져도 시간은 찍혀야 하기 때문에
try - finally 문을 사용하여 예외를 잡아준다.</p>
<blockquote>
<p>long finish = System.currentTimeMillis();</p>
</blockquote>
<p>를 finally문 안에 적어주고,</p>
<blockquote>
<p>long timeMs = finish - start;</p>
</blockquote>
<p>finish - start를 한 결과물을 timeMs에 저장한다.</p>
<p>그리고</p>
<blockquote>
<p>System.out.println(&quot;join = &quot; + timeMs + &quot;ms&quot;);</p>
</blockquote>
<p>출력문을 작성해준다.</p>
<p>이렇게 작성하고, 
회원가입을 돌려보면,
<img src="https://velog.velcdn.com/images/s_em_tudy/post/b6792319-7882-43ae-bcff-db2f2f5d47ea/image.png" alt="">이러한 결과가 나오는 것이다.</p>
<p>또 위와 똑같이 findMembers에도</p>
<pre><code>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;);
        }
      // return memberRepository.findAll();
    }</code></pre><p>이러한 코드를 작성해놓고,</p>
<p>돌려보면, 잘 돌아가는 것을 볼 수 있고
실행 결과를 확인하기 위해서
HelloSpringApplication을 실행시키고
localhost:8080의 회원 목록을 들어갔다가
다시 실행결과를 확인해보면
<img src="https://velog.velcdn.com/images/s_em_tudy/post/e3de0b51-fef9-40b6-822b-0bb2349feeb7/image.png" alt="">위와 같이 실행 시간이 확인된다.</p>
<h3 id="위와-같은-방식의-문제">위와 같은 방식의 문제</h3>
<ul>
<li>회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.</li>
<li>시간을 측정하는 로직은 공통 관심 사항이다.</li>
<li>시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.</li>
<li>시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.</li>
<li>시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다</li>
</ul>
<p><strong><em>=&gt; AOP 사용하여 해결</em></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_스프링 데이터 JPA]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</guid>
            <pubDate>Mon, 22 May 2023 06:50:13 GMT</pubDate>
            <description><![CDATA[<h3 id="스프링-데이터-jpa-사용-시-장점">스프링 데이터 JPA 사용 시 장점</h3>
<p>스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고,
개발해야할 코드도 확연히 줄어든다.</p>
<p>여기에 스프링 데이터 JPA를 사용하면 기존의 한계를 넘어 마치 마법처럼,
리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있다.</p>
<p>그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공한다.</p>
<p>지금까지 조금이라도 단순하고 반복이라고 생각했던 개발 코드들이 확연하게 줄어든다.</p>
<p>따라서 개발자는 핵심 비즈니스 로직을 개발하는 데 집중할 수 있다.</p>
<p>실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 이제 선택이 아니라 필수이다.</p>
<p>주의: 스프링 데이터 JPA는 편리하게 사용하도록 도와주는 기술이다. 
    따라서 JPA를 꼭 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 한다.</p>
<ul>
<li>앞의 JPA 설정을 그대로 사용한다.</li>
</ul>
<h3 id="직접-해보기">직접 해보기</h3>
<p> <img src="https://velog.velcdn.com/images/s_em_tudy/post/14a31bd2-ae90-42ea-81a9-09592da30771/image.png" alt="">repository에 SpringDataJpaMemberRepository라는 interface를 생성한다.
 그리고 JpaRepository를 상속받는다. 
 인터페이스가 인터페이스를 받을 때는 implements가 아니라 extends를 사용한다.</p>
<pre><code>package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

@Primary
public interface SpringDataJpaMemberRepository extends JpaRepository&lt;Member, Long&gt;, MemberRepository {
    @Override
    Optional&lt;Member&gt; findByName(String name);
}
</code></pre><blockquote>
<p>public interface SpringDataJpaMemberRepository extends JpaRepository&lt;Member, Long&gt;, MemberRepository {</p>
</blockquote>
<p> 첫 번째 매개변수에는 사용하는 entity 즉, Member를 넣고, 두 번째 매개변수에는 Id의 타입 즉, Long을 넣는다.</p>
<p> 그리고 interface는 다중상속이 가능하므로, MemberRepository도 함께 상속받는다.</p>
<blockquote>
<p>  @Override
    Optional<Member> findByName(String name);</p>
</blockquote>
<p> 그 다음 findByName을 넣어주면, 끝난다.</p>
<p>  지금 현재 SpringDataJpaMemberRepository라는 interface만 존재하는데, 
  JpaRepository를 받고 있으면</p>
<p>  스프링데이터 Jpa가 SpringDataJpaMemberRepository를 보고,
  자동으로 구현체를 만들어서 스프링 빈에 자동으로 등록을 해 준다.</p>
<p>  이제 작성한 코드를 사용하려면, SpringConfig에서
  <img src="https://velog.velcdn.com/images/s_em_tudy/post/9a1adf99-9201-424f-9994-184d3419f0a6/image.png" alt=""></p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/32933018-801d-4eb5-8910-e6ed323d01e1/image.png" alt=""></p>
<p> 전의 코드들을 모두 주석처리 하고,</p>
<pre><code>@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>  이 코드를 작성해 주었다.
  우선,</p>
<blockquote>
<p>  private final MemberRepository memberRepository;</p>
</blockquote>
<p>  를 정의하고,</p>
<blockquote>
<p>  public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }</p>
</blockquote>
<p>  injection 해준다.</p>
<p>  그러면, 스프링데이터 Jpa가 구현체를 만들어오는 것이 등록이 된다.</p>
<blockquote>
<p>  @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }</p>
</blockquote>
<p> 그리고 MemberService에 의존관계를 setting 해주어야 한다.</p>
<p>  <em>**public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }에는 @Autowired를 붙여줘도, 안 붙여줘도 된다. (생성자가 1개이기 때문에)</em></p>
<p>  이렇게 작성을 해주면,
  스프링 컨테이너에서 MemberRepository를 찾는다.</p>
<p>  현재 등록해놓은 것이 없지만,
  아까 만든 interface, SpringDataJpaMemberRepository 덕분에 
  스프링데이터Jpa가 interface에 대한 구현체를 직접 만들어낸다.</p>
<p>  그리고 스프링 빈에 등록을 해준다.</p>
<p>  그래서 우리는 springConfig에서 injection을 받아서 등록해 줄 수 있는 것이다.</p>
<p>  자 이렇게 SpringConfig에서의 구현까지 끝났으니,
 MemberServiceIntegrationTest 회원가입 코드를 돌려보겠다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/c8654d65-69f6-4675-87e5-7ef8399f490d/image.png" alt="">코드가 잘 돌아간다.</p>
<p>  *사실은 처음에 강의에서 나온 그대로 코드를 작성하여 돌려보았을 때, 계속해서 오류가 나며 회원가입 코드가 돌아가지 않았다.
  오류의 내용은 이랬었다.</p>
<pre><code> Parameter 0 of constructor in hello.hellospring.service.SpringConfig required a single bean, but 2 were found:
    - memoryMemberRepository: defined in file [/Users/isemi/Downloads/hello-spring/build/classes/java/main/hello/hellospring/repository/MemoryMemberRepository.class]
    - springDataJpaMemberRepository: defined in hello.hellospring.repository.SpringDataJpaMemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration</code></pre><p> 대체 무슨 말인지 모르겠어서 ChatGpt에게 물어봤더니, 
<em>** &quot;주입될 MemberRepository 빈이 두 개 이상인 경우, 스프링은 어떤 빈을 주입해야 하는지 결정할 수 없기 때문에 이 오류가 발생합니다.&quot;**</em>
  라는 답변이 왔다.</p>
<p>  <strong>memoryMemberRepository</strong>와, 
 ** springDataJpaMemberRepository **
  이 두 개의 빈이 정의되어 있어서, 
 SpringConfig 클래스의 생성자 매개변수로 주입될 
  MemberRepository 빈이 하나만 존재하도록 해야 한다는 것이다. </p>
<p>  즉, 두 개의 MemberRepository 빈 중 하나를 사용하도록 스프링에게 명시해야 한다고 하였다.</p>
<p>  그렇게 하기 위해선 
 <em>** @Primary 어노테이션**</em> 을 사용하여 우선적으로 주입될 빈을 지정하라는 것이다.</p>
<p>  난 강의에서 하라는 대로 한 것 밖에 없는데 대체 무슨 빈이 두 개나 주입되었다는 것인지 이해 불가;; 였지만</p>
<p>  @Primary 어노테이션을 해당하는 MemberRepository 구현 클래스에 추가하여,
  해당 빈이 주입되도록 스프링에게 알려주어야 하니까</p>
<p>  현재 구현하고자 하는 클래스인 SpringDataJpaMemberRepository에
  @Primary 어노테이션을 추가해 주었다.</p>
<p>  (거짓말 안 치고 3시간 넘게 걸려서 오류를 찾았다.; 어이없음)</p>
<p>  하여튼 이렇게 해서 정상적으로 코드가 돌아가게 할 수 있었다..</p>
<p>  사담 끝----------------</p>
<p>  어떻게 이 코드가 잘 실행이 되는지 정리를 해 보자면,</p>
<p>  스프링 데이터 JPA가 아까 만든 인터페이스, SpringDataJpaMemberRepository를 보고 객체를 자동 생성해서 스프링 빈으로 자동 등록해준다.</p>
<p>  이전에는 직접 구현했었던 save메소드와 같은 것들은 모두
  스프링 데이터에 구현이 되어 있다.</p>
<p>  SpringDataJpaMemberRepository에서 상속받았던 JpaRepository에 들어가 보면,</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/31a11ad4-8030-4c69-b9cf-f9eb1c327d9f/image.png" alt="">위와 같이 기본적인 CRUD와 단순 조회 등을 하는 메소드들이 모두 제공되고 있다. 
  우리가 머릿속으로 상상할 수 있는, 
  공통화할 수 있는 메소드들을 모두 공통화해서 제공을 해 주어서, 
  그냥 가져다가 사용하면 되는 것이다.</p>
<h3 id="스프링-데이터-jpa-제공-기능">스프링 데이터 JPA 제공 기능</h3>
<ul>
<li>인터페이스를 통한 기본적인 CRUD</li>
<li>findByName() , findByEmail() 처럼 메서드 이름 만으로 조회 기능 제공</li>
<li>페이징 기능 자동 제공</li>
</ul>
<p>_**참고: 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 
  복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. 
  Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고,
  동적 쿼리도 편리하게 작성할 수 있다. 
  이 조합으로 해결하기 어려운 쿼리는 
  JPA가 제공하는 네이티브 쿼리를 사용하거나, 
  앞서 학습한 스프링 JdbcTemplate를 사용하면 된다.
  _</p>
<h3 id="스프링-db-접근-기술-정리">스프링 DB 접근 기술 정리</h3>
<ul>
<li><p>H2 데이터베이스 설치</p>
<ul>
<li>순수 Jdbc</li>
<li>스프링 통합 테스트</li>
<li>스프링 JdbcTemplate</li>
<li>JPA</li>
<li>스프링 데이터 JPA</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_JPA]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootJPA</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootJPA</guid>
            <pubDate>Thu, 18 May 2023 14:58:32 GMT</pubDate>
            <description><![CDATA[<h3 id="jpa">JPA</h3>
<p>JPA는 기존의 반복 코드는 물론이고,
기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
→ 개발 생산성을 크게 높일 수 있다.</p>
<p>JPA를 사용하면, 
SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.</p>
<p>JPA를 사용하면 개발 생산성을 크게 높일 수 있다.</p>
<p>먼저,
JPA를 사용하려면 build.gradle에서 </p>
<blockquote>
<p>implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa’</p>
</blockquote>
<p>라는 것을 추가해 주어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5816ac52-cfc3-4c49-b56c-49f4acd7037d/image.png" alt=""></p>
<p>전에 만들어두었던
implementation &#39;org.springframework.boot:spring-boot-starter-data-jdbc’
는 없애고 추가해 주면 된다.</p>
<p>implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa’
가 JPA뿐만 아니라 jdbc까지 포함하기 때문이다.</p>
<p>그리고 gradle refresh를 해준다.(코끼리 모양 버튼)</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/c61ed006-dc59-45f8-a2d2-cad6604e0fe5/image.png" alt=""></p>
<p>resources - application.properties에 JPA와 관련된 설정을 추가해 주어야 한다.</p>
<blockquote>
<p>spring.jpa.show-sql=true</p>
</blockquote>
<p>→ JPA에서 날리는 sql을 모두 볼 수가 있다.</p>
<blockquote>
<p>spring.jpa.hibernate.ddl-auto=none</p>
</blockquote>
<p><em>*<em>→ JPA를 사용하면 회원 객체를 보고 table을 다 만들어준다. *</em></em>
그런데 이미 table이 만들어져 있고, 만들어진 table을 쓸 것이기 때문에 
auto = none (자동 생성 기능을 끔)으로 설정한다.
none말고 create를 하면 table을 모두 자동으로 만들어준다.</p>
<p>먼저 JPA를 쓰려면 entity를 mapping해야한다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/93f66e4c-4bfe-43c7-bd68-5284085b1af4/image.png" alt=""></p>
<p>우선 _<strong>jpa</strong>_와 </p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/123a7c6a-cc42-4214-9844-0c070e622913/image.png" alt=""></p>
<p>_<strong>hibernate</strong>_가 잘 설치되었는지 External Libraries에서 확인을 해준다.</p>
<p>JPA는 interface만 제공이 된다.</p>
<p>구현체로 hibernate, eclipse 등 여러 개가 있는데,
여기선 JPA interface의 hibernate만 거의 쓴다고 보면 된다.</p>
<p>JPA라는 것은 자바 진영의 표준 인터페이스이다.
구현은 여러 업체들이 한다.</p>
<p>JPA는 <em><strong>객체와 ORM</strong></em>(객체 object와, relational 데이터베이스 테이블을 mapping한다)이라는 기술이다. </p>
<p>mapping을 어떻게 하냐면, annotation으로 한다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/48ff2de1-372e-4953-a050-29f0d5f42724/image.png" alt=""></p>
<p>main - Member 클래스에 
<em><strong>@Entity</strong></em> 라는 애노테이션을 치면, 이제부터 이것은 JPA가 관리하는 entity라고 표현한다.</p>
<p>그리고 PK를 mapping해줘야 한다.</p>
<p>지금 PK는 db에서 값을 생성해주고 있다. </p>
<p> query에 id를 넣는 것이 아니라,
 db에 값을 넣으면 db가 id를 자동으로 생성해 주는 것을 identity라고 한다.</p>
<p>따라서 </p>
<blockquote>
<p>@Id @GeneratedValue(strategy = GenerationType.IDENTITY)</p>
</blockquote>
<p>라고 적어줘야 한다.</p>
<p>oracle같은 경우는 시퀀스를 쓰기도 하고,
내가 직접 넣어줄 수도 있고 등등 여러 가지 방법이 있는데,
이렇게 db가 알아서 생성해 주는 것은 identity라고 하는 것이다.</p>
<p>만약 db의 column 명이 username이면,</p>
<blockquote>
<p>@Column(name = &quot;username&quot;) </p>
</blockquote>
<p>이라고 해주면 된다.</p>
<p>하지만 현재는 name이기 때문에 그대로 두면 된다.</p>
<p>그러니까 이 애노테이션들을 가지고, db에 그대로 mapping이 되는 것이다.</p>
<p>이제 이 정보를 가지고 insert문, update문, select문 등등 모두 만들 수 있게 된다.</p>
<p>이제 repository를 하나 생성해보겠다.</p>
<p>main - repository에 <em><strong>JpaMemberRepository</strong></em> 를 생성하고,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/f453788b-a49f-41b3-b84c-3ce6eecaf2d7/image.png" alt=""></p>
<p>MemberRepository를 implements 한다.</p>
<p>JPA는 entitymanager라는 걸로 모든 것이 동작한다.</p>
<p>따라서 </p>
<blockquote>
<p>private final EntityManager em(entity manager 줄인말);</p>
</blockquote>
<p>을 해주고,
constructor을 추가해준다.</p>
<p>아까 build.gradle에서 data-jpa library를 받았던 것이 기억날 것이다.</p>
<p>이렇게 하면 스프링 부트가 자동으로 entitymanager를 생성해준다.</p>
<p>우리는 이렇게 만들어진 entitymanager을 주입받으면 된다.</p>
<p>그럼 이제 저장을 하려면</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/c87cdcab-50a1-4728-ba63-38d09e903f33/image.png" alt=""></p>
<blockquote>
<p>em.persist(member);</p>
</blockquote>
<p>를 해주고,</p>
<blockquote>
<p>return member;</p>
</blockquote>
<p>를 해주면
JPA가 insert query를 다 만들어서 db에 집어넣고,
member에 setId까지 모두 해준다.</p>
<p>조회를 하려면,</p>
<pre><code>@Overridepublic
Optional&lt;Member&gt; findById(Long id) { 
    Member member = em.find(Member.class, id); 
     return Optional.*ofNullable*(member);
}</code></pre><p>em.find를 하고 조회 할 type과 식별자 pk값을 넣어주면 조회가 되는 것이다.</p>
<p>return을 할 땐,
Optional로 반환하기 때문에 null값일 수도 있어서 </p>
<blockquote>
<p>return Optional.ofNullable(member);</p>
</blockquote>
<p>를 해준다.</p>
<p>findByName은
jpql이라는 객체지향 쿼리 sql을 써야한다.</p>
<p>차이가 어떻게 되냐면
findAll은</p>
<pre><code>
@Overridepublic
List&lt;Member&gt; findAll() {
    List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m&quot;, Member.class)        .getResultList();return result;
}</code></pre><p>을 해주면 되는데, </p>
<p>_<strong>command + option + n(control + t 하고 선택)</strong>_을 해주면</p>
<p>_<strong>Inline Variable</strong>_을 해준다.</p>
<p>→</p>
<pre><code>
@Overridepublic
List&lt;Member&gt; findAll() {    

    return em.createQuery(&quot;select m from Member m&quot;, Member.class)                    
    .getResultList();

}
</code></pre><p>이게 jpql이라는 쿼리 언어이다.</p>
<p>보통 table 대상으로 sql을 날리는데, 이건 객체를 대상으로 쿼리를 날린다.</p>
<p>그럼 sql로 번역을 해준다.</p>
<p>Member entity를 대상으로 쿼리를 날리는데
select할 때 보통 sql은 m.id, m.name등등 다 적어야 했었다.</p>
<p>그런데 얘는 _<strong>select m이 멤버 객체 자체를 조회</strong>_한다는 뜻이다.</p>
<p>이렇게 mapping도 해줄 필요 없이 간단하게 끝낼 수가 있다.</p>
<p>mapping도 다 해주기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/7b5d041e-90cf-4d3b-bd03-a86a8e176448/image.png" alt=""></p>
<p>이제 남은 것은 findByName이다.</p>
<pre><code>@Overridepublic
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><p>을 해주면</p>
<p>저장과 조회가 완성이 된다.</p>
<p><em><strong>저장, 조회, 업데이트, 삭제는 sql 짤 필요가 없다.</strong></em></p>
<p>모두 자동으로 된다.</p>
<p>그러나 findByName이나 findAll과 같이 pk 기반이 아닌 나머지 것들은
jpql이라는 것을 작성해 주어야 한다.</p>
<p>이 다음시간에 배우겠지만,
이 JPA 기술을 spring에 감싸서 제공하는 기술이 있는데,
_<strong>SpringDataJPA</strong>_이다.</p>
<p>이것을 사용하면 findByName이나 findAll도 jpql로 작성하지 않아도 된다.</p>
<blockquote>
<p>또한
JPA를 쓸 때 주의해야 할 점은 
항상 @Transactional 이 있어야 한다는 점이다.</p>
</blockquote>
<p>따라서 
MemberService 클래스에 <em><strong>@Transactional</strong></em> 을 해주었다.</p>
<p>데이터를 저장하거나 변경할 땐 항상 @Transactional이 있어야 한다.</p>
<p>JPA는 join이 들어올 때 모든 데이터 변경이 모두 transaction으로 실행이 되어야 한다.</p>
<p>이제 실행을 해 보아야 한다.</p>
<p>그러려면 SpringConfig 클래스에 가서 </p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5bed0d74-71ec-4747-9b18-1652f4556779/image.png" alt=""></p>
<p>_<strong>public MemberRepository memberRepository</strong>_에 </p>
<blockquote>
<p>return new JpaMemberRepository();</p>
</blockquote>
<p>를 추가하고,</p>
<p>기존에 있던 </p>
<pre><code>
private DataSource dataSource;

@Autowired

public SpringConfig(DataSource dataSource){    

    this.dataSource = dataSource;

}</code></pre><p>를 삭제하고,</p>
<pre><code>private EntityManager em;

@Autowired

public SpringConfig(EntityManager em){   

     this.em = em;

}</code></pre><p>를 해준다.</p>
<p>이제 모든 구현이 끝났으니,</p>
<p>진짜 실행을 해 볼 차례이다.</p>
<p>test - MemberServiceIntegrationTest에 가서</p>
<p>회원가입만 살짝 돌려보았다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/0dca2d2c-90b3-47aa-aa4b-312d39eeae1c/image.png" alt=""></p>
<p>잘 돌아가는 모습이 보인다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/ed13e0fa-4a67-4cd2-843d-10815c7cf525/image.png" alt=""></p>
<p>@Commit도 해줘서 db에 반영이 되게 해 보았다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/94c24607-0165-4b60-9cd4-ac851f4def03/image.png" alt=""></p>
<p>저장한 spring이 잘 뜨는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/f01a5f46-0b91-4ac9-8c8b-db44744a7520/image.png" alt=""></p>
<p>name을 spring100으로 하고,
재실행한 후
다시 db에 들어가 보았더니,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5c337cc5-7f24-4f4e-a100-03bbadebc9b9/image.png" alt=""></p>
<p>추가한 spring100이 뜨는 것을 볼 수 있다.</p>
<p>확인 후
@commit은 다시 지워주었다.</p>
<p>db에서도 delete from member로 데이터를 모두 지워주었다.</p>
<p>그리고 나서 
회원가입과 중복 회원 예외를 함께 실행해보았는데,
잘 돌아가는 모습을 볼 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_스프링 JdbcTemplate]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</guid>
            <pubDate>Thu, 18 May 2023 14:43:40 GMT</pubDate>
            <description><![CDATA[<h3 id="스프링-jdbctemplate">스프링 JdbcTemplate</h3>
<p>*설정은 순수 Jdbc와 동일하게 환경설정을 하면 된다.</p>
<p>스프링 JdbcTemplate은 MyBatis와 비슷한 라이브러리인데, 
JDBC API에서의 반복적인 코드를 제거해준다.
(sql은 직접 작성해주긴 해야 한다.)</p>
<h3 id="직접-해보기">직접 해보기</h3>
<p>우선 repository에 _*<em>JdbcTemplateMemberRepository *</em>_클래스를 생성한다.</p>
<p>MemberRepository를 implements 해준 후,
option + enter 로 <strong><em>implements method</em></strong>를 해주었다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/17d4312b-caac-4c58-91ec-18071934bda2/image.png" alt=""></p>
<p>그리고 JdbcTemplate를 정의해준 후,
_<strong>Constructor</strong>_를 생성해주었다.</p>
<pre><code>@Autowired

public JdbcTemplateMemberRepository(DataSource dataSource) {   

 jdbcTemplate = new JdbcTemplate(dataSource);

}
</code></pre><p>이렇게 datasource를 injection 해주고,
jdbcTemplate에 dataSource를 넣어주었다.</p>
<p>*참고) 생성자가 딱 하나만 있으면, @Autowired 생략이 가능하다.</p>
<p>조회하는 query를 먼저 해보면,</p>
<pre><code>
@Overridepublic Optional&lt;Member&gt; findById(Long id) {   
    return jdbcTemplate.query(&quot;select + from member where id = ?&quot;,    );
}
</code></pre><p>를 작성하고,
결과가 나오는 것을 rowMapper로 mapping을 해주어야 하는데, 
그것을 제일 하단에 작성하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/9f7a17de-67d5-49bb-acfb-1257482614a4/image.png" alt=""></p>
<pre><code>
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><p>코드는 이러하다.</p>
<p>위 코드는 </p>
<pre><code>private RowMapper&lt;Member&gt; memberRowMapper(){ 
    return new RowMapper&lt;Member&gt;() { 
    @Override
    public Member mapRow(ResultSet rs, int rowNum) throws SQLException {           Member member = new Member(); 
    member.setId(rs.getLong(&quot;id&quot;));   
    member.setName(rs.getString(&quot;name&quot;));
    return member;
    }
} </code></pre><p>를 <strong><em>option + enter</em></strong>로 람다함수로 바꿔준 코드이다.</p>
<p>그리고 다시 findById로 돌아가서,
코드를 마무리한다.</p>
<pre><code>@Overridepublic 
Optional&lt;Member&gt; findById(Long id) {
    List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where id = ?&quot;,         memberRowMapper());
    return result.stream().findAny();
}</code></pre><p>이 과정들은 
이전에 순수 jdbc 코드로 작성하였던 것보다 훨씬 간단하다.</p>
<p>jdbcTemplate 라이브러리로 이렇게 간단하게 줄일 수 있는 것이다.</p>
<p>이제 save를 작성해보겠다.</p>
<pre><code>@Overridepublic
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;
    }</code></pre><blockquote>
<p>SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);  </p>
</blockquote>
<p>여기서 _<strong>SimpleJdbcInsert</strong>_라는 기능이 있는데,</p>
<blockquote>
<p>jdbcInsert.withTableName(&quot;member&quot;).usingGeneratedKeyColumns(&quot;id&quot;); </p>
</blockquote>
<p>로 넣으면,
query를 짤 필요가 없게 된다.</p>
<p>→ Document를 보면 친절하게 다 나와있으니 보고 하면 된다.</p>
<p>*이렇게 하는구나 정도만 알면 됨.</p>
<p>findByName과 findAll도
findById와 비슷하게 각각</p>
<pre><code>
@Overridepublic
    Optional&lt;Member&gt; findByName(String name) {
    List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where name = ?&quot;,       memberRowMapper());
    return result.stream().findAny();
    }</code></pre><pre><code>@Overridepublic
    List&lt;Member&gt; findAll() { 
    return jdbcTemplate.query(&quot;select * from member&quot;, memberRowMapper());
    }</code></pre><p>이렇게 코드를 작성해 주었다.</p>
<p>*사용법은 jdbcTemplate menual 검색하면 많이 나오니까 참고 (김영한씨도 data 접근 기술 강의할 때 깊이있게 설명해줄 예정)</p>
<p>정리하자면,
findById는 jdbcTemplate에서 query를 날리고, 
그 결과를 memberRowMapper를 통해서 mappingg하고,
List로 받아서 optional로 바꿔서 반환해준다.</p>
<p>findByName도 똑같은 과정을 거치고
findAll은 어차피 select * from member를 list로 반환해주기 때문에 간단하다.</p>
<p>그 결과를 
RowMapper에서 Member 객체로 mapping을 한 후에 돌려주면 된다.</p>
<p>이렇게 JdbcTemplateMemberRepository를 모두 작성하였고,
SpringConfig에서 조립해준다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/d2c9b18e-dc7d-458b-8e1e-85755e5a5000/image.png" alt=""></p>
<pre><code>
@Beanpublic 

MemberRepository memberRepository() {    

//return new MemoryMemberRepository();    

//return new JdbcMemberRepository(dataSource);   

 return new JdbcTemplateMemberRepository(dataSource);
 }</code></pre><p>이렇게 새로운 JdbcTemplateMemberRepository를 추가해주었고,
이제 실행을 해 볼 차례이다.</p>
<p>우리는 spring 통합 test를 만들어 놓았기 때문에,
웹 어플리케이션을 띄워서 검증해 볼 필요가 없이
MemberServiceIntegrationTest를 돌려보면 된다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/ecee3f37-b08d-4e56-8ce3-05b6b840c3cd/image.png" alt=""></p>
<p>오류가 떴다.</p>
<p>살펴보니 parameter가 제대로 setting되지 않았다는 오류이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/3fce06bd-ca35-47ba-ba0c-1dff0201829e/image.png" alt=""></p>
<pre><code>
@Overridepublic 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();
    }
@Overridepublic
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();
 }</code></pre><p>findById와 findByName에 parameter id, name을 추가해주지 않아서 뜬 오류이므로 수정해주고, 
다시 실행해보았더니 제대로 실행된 모습을 볼 수 있었다.</p>
<p>(*단축키: control + r → 마지막에 실행한 것을 재실행)</p>
<p>====⇒ JdbcTemplate 버전의 db까지 연동한 test가 실제로 성공한 것</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_ 스프링 통합 테스트]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 18 May 2023 04:09:15 GMT</pubDate>
            <description><![CDATA[<h3 id="스프링-컨테이너와-db까지-연결한-통합-테스트">스프링 컨테이너와 DB까지 연결한 통합 테스트</h3>
<p>이전에 했던 test들은, 전혀 스프링과 관련이 없는 테스트들 이었다.</p>
<p>순수한 java 코드를 가지고 테스트 한 것이었는데,
데이터베이스 connection 정보도 스프링부트가 가지고 있으니
이젠 java 코드를 가지고 테스트 할 수 없다.</p>
<p>테스트를 스프링과 엮어서 해볼 것이다.</p>
<p>MemberServiceTest와 똑같은 버전인데, 
db까지 연결해서 실행하는 test를 만들어 보겠다.</p>
<p>test - service에 MemberServiceTest를 복사붙여넣기 한 클래스, _<strong>MemberServiceIntegrationTest</strong>_를 만들었다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/4aa0a616-9de1-4fbf-b8a1-0fb1cadce205/image.png" alt=""></p>
<p>그리고 <em><strong>@SpringBootTest, @Trasactional</strong></em> 을 추가하였다.</p>
<p>MemberServiceTest에서는 직접 @BeforeEach로 memberService와 memberRepository를 객체 생성해서 넣었었는데,</p>
<p>이제는 스프링 컨테이너한테 요청을 해야하기 때문에 
@BeforeEach 코드를 삭제하였다.</p>
<p>Test는 최대한 제일 편한 방법을 쓰기 때문에
<strong><em>MemberService memberService;</em></strong> 와 
<em><strong>MemoryMemberRepository memberRepository;</strong></em> 의 앞에
_<strong>@Autowired</strong>_를 넣어주면 편하다.</p>
<p>그리고 MemoryMemberRepository memberRepository;는</p>
<p>_<strong>MemberRepository memberRepository;</strong>_로 바꿔준다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/31a56266-1304-44b3-b0db-c4ca68a20d49/image.png" alt=""></p>
<p>@AfterEach는 메모리 db에 있는 data를 
다음 test의 영향 없게 지워주는 코드였으므로
필요 없으니 지워주면, 위와 같은 코드가 된다.</p>
<p>또 맨 밑에 있었던</p>
<pre><code>@Test
void findMembers() {
}
@Test
void findOne() {
}</code></pre><p>도 지워주었다.</p>
<p>이 상태로 회원가입 코드만 실행시켜 보았더니,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/14359a8e-c77d-4f49-b46e-ffbf63ad70aa/image.png" alt=""></p>
<p>‘이미 존재하는 회원입니다’ 라는 에러가 뜬다.</p>
<p><em><strong>이유가 무엇일까?</strong></em></p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/45a7cb18-7f26-4705-ad2e-a7be26294bae/image.png" alt=""></p>
<p><em>*<em>바로 이전에 실습했었던 db에 spring이라는 이름을 가진 data가 남아있었기 때문이다.
*</em></em>
돌려본 회원 가입 Test도 spring이라는 이름으로 test를 하기 때문에 에러가 나는 것이다.</p>
<p>따라서 db의 데이터를 완전히 지워주어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/159262d8-48a2-491e-94a0-27af30333c40/image.png" alt=""></p>
<p>이렇게 member를 지워주었고,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/f727fe8f-fd9f-4518-af72-e4488f6007cd/image.png" alt=""></p>
<p>다시 실행해보았더니
이렇게 모든 데이터가 삭제된 모습으로 나온다.</p>
<p>이 상태에서 다시 test를 돌려보겠다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5bcb44d2-34e5-40d7-8fe1-ab51880544e2/image.png" alt=""></p>
<p>드디어 회원가입 test가 돌아가는 것을 볼 수 있다.</p>
<p>또한 spring이 뜨는 것을 볼 수 있다.</p>
<p>이전 test에서는 spring이 뜨지 않았었는데 말이다.</p>
<p>다시 MemberServiceIntegrationTest 코드로 돌아가서,
@Transactional이 필요한 이유를 알아보기 위해 
_<strong>@Transactional</strong>_을 주석 처리 해 보았다.</p>
<p>그리고 실행시켜보았는데 제대로 돌아간다.</p>
<p>db도 돌려보았더니</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/9ca86258-4fa3-4d5e-83e4-af9d2ba27fd5/image.png" alt=""></p>
<p>이렇게 spring이 뜨는 것을 볼 수 있다.</p>
<p>_*ID 값은 이전 기록까지 모두 포함하여 더해지기 때문에 member를 삭제해도 계속해서 값이 올라간다.
_
그런데 
test는 반복할 수 있어야 한다고 했었다.</p>
<p>db 내용을 삭제하고 처음 한 번 돌려보았을 땐 실행도 잘 되고 
오류도 뜨지 않았었는데
다시 돌려보았더니,
<img src="https://velog.velcdn.com/images/s_em_tudy/post/6d122456-0233-4805-ba26-8eacf52c10af/image.png" alt=""></p>
<p>이미 존재하는 회원이라는 경고가 다시 뜬다.</p>
<p><strong><em>이미 spring이 db에 존재하고 있기 때문이다.</em></strong></p>
<p>그러면 어떻게 해야할까?</p>
<p>또 @AfterEach @BeforeEach 를 만들어서 지워야할까?</p>
<p>db는 기본적으로 transaction이라는 개념이 있다.</p>
<p>그래서 db에 data를 insertquery 한 다음에 commit을 해주어야 db에 반영이 된다.</p>
<p>그리고 test가 끝나면 rollback을 해주어야 되는데,</p>
<p><em><strong>&quot;@Transactional을 test case에 달면
test를 실행할 때, transaction을 먼저 하고
db에 data를 insertquery 해주고
test가 끝나면 rollback을 해준다.&quot;</strong></em></p>
<p>그래서 db의 데이터가 반영이 안되고 깔끔하게 모두 지워진다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/90750974-c4eb-4ca4-be32-52f600385759/image.png" alt=""></p>
<p>아까 주석 처리 해놨던 @Transactional을 다시 활성화 시키고,
db의 member를 일단 delete from member 시켜놓고
회원가입 test를 돌려 보았더니
이렇게 실행이 제대로 된다.</p>
<p>그리고 db를 확인해보았더니, </p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/2a7ab6bb-12da-42f2-b684-d3b5b1d4f672/image.png" alt=""></p>
<p>delete한 그대로 아무 data도 저장되어 있지 않은 것을 볼 수 있다.</p>
<p><strong><em>Transaction이 rollback을 해주어서 data가 db에 반영되지 않았기 때문이다.</em></strong></p>
<p>그래서 test case를 무한대로 돌려볼 수 있게 되는 것이다.</p>
<p>두 번 세 번 더 돌려봐도 db에는 아무 data가 저장되어 있지 않은 모습을 확인할 수 있다.</p>
<h3 id="정리">정리</h3>
<ul>
<li><p><strong>@SpringBootTest</strong></p>
<p>  : 스프링 컨테이너와 테스트를 함께 실행한다.</p>
<p>  → 진짜 스프링을 띄워서 테스트를 하는 것</p>
</li>
</ul>
<ul>
<li><p><strong>@Transactional</strong></p>
<p>  : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 transaction을 시작하고, 테스트 완료 후에 항상 롤백한다.(test 시작할 때마다 실행된다) 이렇게 하면 db에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.</p>
</li>
</ul>
<p>그러면 이제 MemberServiceTest처럼 순수하게 자바 코드로 작성하면서, 
최소한의 단위로 하는 단위 test는 필요가 없지 않느냐 는 의문이 들 수 있다.</p>
<p>그런데 MemberServiceTest와 MemberServiceIntegrationTest를 
둘 다 실행시켜보면, 결과가 뜨는 시간이 매우 차이가 난다는 것을 알 수 있다.</p>
<p><em><strong>MemberServiceTest는 실행되는 시간이 매우 짧다.</strong></em></p>
<p>스프링 컨테이너와 db까지 연동하는 
통합 test(MemberServiceIntegrationTest)가 아니기 때문이다.</p>
<p>사실은
<strong>순수한 단위 test</strong>가 더 좋은 test일 확률이 높다.</p>
<p>스프링 컨테이너 없이 test 할 수 있도록 훈련을 해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_H2 데이터베이스 설치]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootH2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98-kjkeuwyu</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootH2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98-kjkeuwyu</guid>
            <pubDate>Thu, 18 May 2023 03:52:52 GMT</pubDate>
            <description><![CDATA[<p>이전까지 했던 것은 사실 메모리에 저장을 했다가 서버가 내려가면 데이터가 모두 사라지기 떄문에 실무에서는 모두 데이터베이스에 데이터들을 저장하고 관리한다.</p>
<p>그러기 위해서 필요한 H2 데이터 베이스를 설치해 볼 것이다.</p>
<h3 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h3>
<p>보통 실무에서는 mysql이나 oracle 같은 DB를 많이 사용한다.
H2 데이터베이스는 교육용으로 매우 좋은,
용량도 가볍고 웹 화면도 제공해주는 데이터베이스이다.</p>
<p>다음 링크에 들어가서 1.4.200 버전을 설치하였다.
<a href="https://www.h2database.com/html/download-archive.html">https://www.h2database.com/html/download-archive.html</a></p>
<p>압축을 해제하고</p>
<p>terminal을 켜서</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/56a20a9b-1d9a-46f5-a28e-301043be6333/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/56a20a9b-1d9a-46f5-a28e-301043be6333/image.png"></p>
<p>download 폴더에 있는</p>
<p>h2파일에 들어가서</p>
<p>bin에 들어가서</p>
<blockquote>
<p>chmod 755 h2.sh</p>
</blockquote>
<p>로 권한을 허용시켜 주었다.</p>
<blockquote>
<p>./h2.sh</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5a2fea48-f206-402f-869c-3e5e4c0959ec/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/5a2fea48-f206-402f-869c-3e5e4c0959ec/image.png"></p>
<p>home directory에 test.mv.db가 존재하는지 확인까지 해 준 후,</p>
<p>실행시켜 주었더니 몇 초 후,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/71e3ee4e-6bf8-4e18-8f77-cb158308592f/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/71e3ee4e-6bf8-4e18-8f77-cb158308592f/image.png"></p>
<p>위와 같은 창이 떴다.</p>
<p>현재 JDBC URL은 <strong><em>jdbc:h2:~/test</em></strong> 로 되어 있는데,
이렇게 파일로 접근을 하게 되면 애플리케이션과 웹 콘솔리 동시에 접근이 안 될 수가 있기 때문에</p>
<p><strong><em>jdbc:h2:tcp://localhost/~/test</em></strong></p>
<p>로 socket을 통해 접근하도록 바꾸어 주었다.</p>
<p>이렇게 해야 여러 군데에서 접근을 할 수가 있게 된다.</p>
<p>바꾸어 준 후 연결을 누르면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/6e1421e2-510d-4941-acec-a7ca9d62f98e/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/6e1421e2-510d-4941-acec-a7ca9d62f98e/image.png"></p>
<p>창이 하나 뜨는데
여기에 member table을 만들어 주고,</p>
<p><strong>command + enter</strong></p>
<p>을 눌러 실행해준다.</p>
<h3 id="테이블-생성하기">테이블 생성하기</h3>
<pre><code>drop table if exists member CASCADE;
create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);
</code></pre><p>실행을 하면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/545b9ad1-29c2-41d8-a5a2-1d53127e53e8/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/545b9ad1-29c2-41d8-a5a2-1d53127e53e8/image.png"></p>
<p>이렇게 MEMBER가 만들어진 것을 볼 수 있다.</p>
<p>이후부터는 select * from member;로 조회를 할 수가 있게 되는데,</p>
<p>그냥 아무것도 없는 상태에서 MEMBER를 누르면 select * from member가 저절로 뜬다.</p>
<p>실행을 해보면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/8cf635b4-cbdf-4ea6-b6d1-283846c2471d/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/8cf635b4-cbdf-4ea6-b6d1-283846c2471d/image.png"></p>
<p>위와 같이 만들어져 있는 것을 볼 수가 있다.</p>
<p>sql문을 잠깐 살펴보면,</p>
<pre><code>create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);
</code></pre><p>member 클래스에서 만들어놨던 id가 있고, (type이 자바에서는 long이고 db에서는 bigint라고 한다)
그 다음에 나오는 generated by default as identity는 값을 세팅하지 않고 insert하면, db가 자동으로 id 값을 채워준다는 뜻이다.</p>
<p>name은 varchar(255)로 그냥 만들어 놓았고,</p>
<p>pk는 id로 잡아놓았다.</p>
<p>이제 insert를 해보겠다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring&#39;)</p>
</blockquote>
<p>을 실행하고,</p>
<p>select * from member
를 하면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/b5035ef4-81ae-4e60-bcfb-5e9daced5d58/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/b5035ef4-81ae-4e60-bcfb-5e9daced5d58/image.png"></p>
<p>이렇게 id 1번이 자동으로 들어가 있는 것을 볼 수 있다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring2&#39;)</p>
</blockquote>
<p>로 id를 하나 더 만들어 보면,</p>
<p>(다음은 똑같이 실행)</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/1d6552fc-b388-42ee-a6de-12895c91a170/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/1d6552fc-b388-42ee-a6de-12895c91a170/image.png"></p>
<p>이렇게 id 2번까지 들어가 있는 것을 볼 수 있다.</p>
<p>MemoryMemberRepository의 코드와 같은 원리이다.</p>
<h3 id="테이블-관리하기">테이블 관리하기</h3>
<p>테이블 관리를 위해 프로젝트 루트에 sql/ddl.sql 파일을 생성하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/504beab3-4d71-42ed-a15c-e6d70b320a6b/image.png" alt="https://velog.velcdn.com/images/s_em_tudy/post/504beab3-4d71-42ed-a15c-e6d70b320a6b/image.png"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_H2 데이터베이스 설치]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootH2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootH2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Mon, 15 May 2023 22:10:53 GMT</pubDate>
            <description><![CDATA[<p>이전까지 했던 것은 사실 메모리에 저장을 했다가 서버가 내려가면 데이터가 모두 사라지기 떄문에 실무에서는 모두 데이터베이스에 데이터들을 저장하고 관리한다.</p>
<p>그러기 위해서 필요한 H2 데이터 베이스를 설치해 볼 것이다.</p>
<h3 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h3>
<p>보통 실무에서는 mysql이나 oracle 같은 DB를 많이 사용한다.
H2 데이터베이스는 교육용으로 매우 좋은,
용량도 가볍고 웹 화면도 제공해주는 데이터베이스이다.</p>
<p>다음 링크에 들어가서 1.4.200 버전을 설치하였다.
<a href="https://www.h2database.com/html/download-archive.html">https://www.h2database.com/html/download-archive.html</a></p>
<p>압축을 해제하고</p>
<p>terminal을 켜서
<img src="https://velog.velcdn.com/images/s_em_tudy/post/56a20a9b-1d9a-46f5-a28e-301043be6333/image.png" alt="">download 폴더에 있는 </p>
<p>h2파일에 들어가서 </p>
<p>bin에 들어가서</p>
<blockquote>
<p>chmod 755 h2.sh</p>
</blockquote>
<p>로 권한을 허용시켜 주었다.</p>
<blockquote>
<p>./h2.sh </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5a2fea48-f206-402f-869c-3e5e4c0959ec/image.png" alt=""> home directory에 test.mv.db가 존재하는지 확인까지 해 준 후,</p>
<p>실행시켜 주었더니 몇 초 후,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/71e3ee4e-6bf8-4e18-8f77-cb158308592f/image.png" alt="">위와 같은 창이 떴다.</p>
<p>현재 JDBC URL은 <em><strong>jdbc:h2:~/test</strong></em> 로 되어 있는데, 
이렇게 파일로 접근을 하게 되면 애플리케이션과 웹 콘솔리 동시에 접근이 안 될 수가 있기 때문에</p>
<p><em><strong>jdbc:h2:tcp://localhost/~/test</strong></em></p>
<p>로 socket을 통해 접근하도록 바꾸어 주었다.</p>
<p>이렇게 해야 여러 군데에서 접근을 할 수가 있게 된다.</p>
<p>바꾸어 준 후 연결을 누르면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/6e1421e2-510d-4941-acec-a7ca9d62f98e/image.png" alt="">창이 하나 뜨는데 
여기에 member table을 만들어 주고, 
<strong>command + enter</strong>을 눌러 실행해준다.</p>
<h4 id="테이블-생성하기">테이블 생성하기</h4>
<pre><code>drop table if exists member CASCADE;
create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre><p>실행을 하면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/545b9ad1-29c2-41d8-a5a2-1d53127e53e8/image.png" alt=""> 이렇게 MEMBER가 만들어진 것을 볼 수 있다.</p>
<p>이후부터는 select * from member;로 조회를 할 수가 있게 되는데,</p>
<p>그냥 아무것도 없는 상태에서 MEMBER를 누르면 select * from member가 저절로 뜬다.</p>
<p>실행을 해보면,
<img src="https://velog.velcdn.com/images/s_em_tudy/post/8cf635b4-cbdf-4ea6-b6d1-283846c2471d/image.png" alt="">위와 같이 만들어져 있는 것을 볼 수가 있다.</p>
<p>sql문을 잠깐 살펴보면,</p>
<pre><code>create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre><p>member 클래스에서 만들어놨던 id가 있고, (type이 자바에서는 long이고 db에서는 bigint라고 한다)
그 다음에 나오는 generated by default as identity는 값을 세팅하지 않고 insert하면, db가 자동으로 id 값을 채워준다는 뜻이다.</p>
<p>name은 varchar(255)로 그냥 만들어 놓았고,</p>
<p>pk는 id로 잡아놓았다.</p>
<p>이제 insert를 해보겠다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring&#39;)</p>
</blockquote>
<p>을 실행하고,</p>
<p>select * from member
를 하면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/b5035ef4-81ae-4e60-bcfb-5e9daced5d58/image.png" alt="">이렇게 id 1번이 자동으로 들어가 있는 것을 볼 수 있다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring2&#39;)</p>
</blockquote>
<p>로 id를 하나 더 만들어 보면,</p>
<p>(다음은 똑같이 실행)</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/1d6552fc-b388-42ee-a6de-12895c91a170/image.png" alt="">이렇게 id 2번까지 들어가 있는 것을 볼 수 있다.</p>
<p>MemoryMemberRepository의 코드와 같은 원리이다.</p>
<h3 id="테이블-관리하기">테이블 관리하기</h3>
<p>테이블 관리를 위해 프로젝트 루트에 sql/ddl.sql 파일을 생성하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/504beab3-4d71-42ed-a15c-e6d70b320a6b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_H2 데이터 베이스 설치]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootH2-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootH2-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Mon, 15 May 2023 22:06:11 GMT</pubDate>
            <description><![CDATA[<p>이전까지 했던 것은 사실 메모리에 저장을 했다가 서버가 내려가면 데이터가 모두 사라지기 떄문에 실무에서는 모두 데이터베이스에 데이터들을 저장하고 관리한다.</p>
<p>그러기 위해서 필요한 H2 데이터 베이스를 설치해 볼 것이다.</p>
<h3 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h3>
<p>보통 실무에서는 mysql이나 oracle 같은 DB를 많이 사용한다.
H2 데이터베이스는 교육용으로 매우 좋은,
용량도 가볍고 웹 화면도 제공해주는 데이터베이스이다.</p>
<p>다음 링크에 들어가서 1.4.200 버전을 설치하였다.
<a href="https://www.h2database.com/html/download-archive.html">https://www.h2database.com/html/download-archive.html</a></p>
<p>압축을 해제하고</p>
<p>terminal을 켜서
<img src="https://velog.velcdn.com/images/s_em_tudy/post/56a20a9b-1d9a-46f5-a28e-301043be6333/image.png" alt="">download 폴더에 있는 </p>
<p>h2파일에 들어가서 </p>
<p>bin에 들어가서</p>
<blockquote>
<p>chmod 755 h2.sh</p>
</blockquote>
<p>로 권한을 허용시켜 주었다.</p>
<blockquote>
<p>./h2.sh </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5a2fea48-f206-402f-869c-3e5e4c0959ec/image.png" alt=""> home directory에 test.mv.db가 존재하는지 확인까지 해 준 후,</p>
<p>실행시켜 주었더니 몇 초 후,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/71e3ee4e-6bf8-4e18-8f77-cb158308592f/image.png" alt="">위와 같은 창이 떴다.</p>
<p>현재 JDBC URL은 <em><strong>jdbc:h2:~/test</strong></em> 로 되어 있는데, 
이렇게 파일로 접근을 하게 되면 애플리케이션과 웹 콘솔리 동시에 접근이 안 될 수가 있기 때문에</p>
<p><em><strong>jdbc:h2:tcp://localhost/~/test</strong></em></p>
<p>로 socket을 통해 접근하도록 바꾸어 주었다.</p>
<p>이렇게 해야 여러 군데에서 접근을 할 수가 있게 된다.</p>
<p>바꾸어 준 후 연결을 누르면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/6e1421e2-510d-4941-acec-a7ca9d62f98e/image.png" alt="">창이 하나 뜨는데 
여기에 member table을 만들어 주고, 
<strong>command + enter</strong>을 눌러 실행해준다.</p>
<h4 id="테이블-생성하기">테이블 생성하기</h4>
<pre><code>drop table if exists member CASCADE;
create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre><p>실행을 하면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/545b9ad1-29c2-41d8-a5a2-1d53127e53e8/image.png" alt=""> 이렇게 MEMBER가 만들어진 것을 볼 수 있다.</p>
<p>이후부터는 select * from member;로 조회를 할 수가 있게 되는데,</p>
<p>그냥 아무것도 없는 상태에서 MEMBER를 누르면 select * from member가 저절로 뜬다.</p>
<p>실행을 해보면,
<img src="https://velog.velcdn.com/images/s_em_tudy/post/8cf635b4-cbdf-4ea6-b6d1-283846c2471d/image.png" alt="">위와 같이 만들어져 있는 것을 볼 수가 있다.</p>
<p>sql문을 잠깐 살펴보면,</p>
<pre><code>create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre><p>member 클래스에서 만들어놨던 id가 있고, (type이 자바에서는 long이고 db에서는 bigint라고 한다)
그 다음에 나오는 generated by default as identity는 값을 세팅하지 않고 insert하면, db가 자동으로 id 값을 채워준다는 뜻이다.</p>
<p>name은 varchar(255)로 그냥 만들어 놓았고,</p>
<p>pk는 id로 잡아놓았다.</p>
<p>이제 insert를 해보겠다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring&#39;)</p>
</blockquote>
<p>을 실행하고,</p>
<p>select * from member
를 하면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/b5035ef4-81ae-4e60-bcfb-5e9daced5d58/image.png" alt="">이렇게 id 1번이 자동으로 들어가 있는 것을 볼 수 있다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring2&#39;)</p>
</blockquote>
<p>로 id를 하나 더 만들어 보면,</p>
<p>(다음은 똑같이 실행)</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/1d6552fc-b388-42ee-a6de-12895c91a170/image.png" alt="">이렇게 id 2번까지 들어가 있는 것을 볼 수 있다.</p>
<p>MemoryMemberRepository의 코드와 같은 원리이다.</p>
<h3 id="테이블-관리하기">테이블 관리하기</h3>
<p>테이블 관리를 위해 프로젝트 루트에 sql/ddl.sql 파일을 생성하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/504beab3-4d71-42ed-a15c-e6d70b320a6b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 웹 기능-조회]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%EC%A1%B0%ED%9A%8C</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%EC%A1%B0%ED%9A%8C</guid>
            <pubDate>Mon, 15 May 2023 16:04:13 GMT</pubDate>
            <description><![CDATA[<h3 id="회원-웹-기능---조회">회원 웹 기능 - 조회</h3>
<p>회원 목록을 눌렀을 때 동작하는 코드를 만든다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/bcbb89d3-2b4f-44c2-b0aa-742ad03dfcad/image.png" alt="">우선 웹에서 회원 목록을 누르면</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/f4ed2cbd-4afd-42f9-9926-5500b1bf3af9/image.png" alt="">members로 가게 코딩을 해놓았었다.</p>
<p>MemberController에 가서</p>
<pre><code>@GetMapping(&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>를 작성하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/294282ba-5b1d-4cf8-804e-8f3e13848041/image.png" alt=""></p>
<blockquote>
<p> List<Member> members = memberService.findMembers();</p>
</blockquote>
<p>  를 하면 member를 다 끄집어 올 수가 있게 된다.</p>
<blockquote>
<p>   model.addAttribute(&quot;members&quot;, members);</p>
</blockquote>
<p>  로 member의 list 자체를 model에 담아서, view(화면)에 넘긴다.</p>
<blockquote>
<p>  return &quot;members/memberList&quot;;</p>
</blockquote>
<p>  를 하였으므로 memberList를 생성해주겠다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/2b2ee31e-e375-4218-9f8e-708d2c0e9ca7/image.png" alt=""> resources - templates - members 에 가서 
  memberList.html을 생성하고 위 사진처럼 html 코드를 작성해 주었다.</p>
<p>  여기서 thymeleaf가 이제 본격적으로 동작을 한다.</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>  코드에서 </p>
<blockquote>
   <tr th:each="member : ${members}">
</blockquote>
<p>  ${members}의 뜻은 model 안의 값을 꺼내는 것이다.</p>
<p>  아까 memberList.html에서 </p>
<pre><code>@GetMapping(&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>  를 작성했던 것이 기억이 날 것이다.</p>
<blockquote>
<p>   model.addAttribute(&quot;members&quot;, members);</p>
</blockquote>
<p>  로 model 안에 members를 모두 담아놓았었다.</p>
<p>  다시 html코드로 돌아가서,</p>
<blockquote>
  <tr th:each="member : ${members}">
</blockquote>
<p>  로 members라는 key에 담아놓은 모든 회원 list를 loof를 돌면서
  반복해서 꺼내는 것이다.(th:each)</p>
<p>  따라서 loof를 돌며</p>
<pre><code> &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;</code></pre><p>  이 로직을 실행하게 된다.</p>
<p>${members}에서 첫번째 List를 꺼내서 member에 담고,
  내려가서 id와 name을 출력하는 것이다.</p>
<p>  ...반복</p>
<p>  여기서 참고로 id와 name은 Member 클래스에서 생성했던 private id와 name을
  getter와 setter에서 접근을 해서 출력을 해주게 되는 것이다.</p>
<p>  이제 코드를 실행해보면,
  <img src="https://velog.velcdn.com/images/s_em_tudy/post/7a7e6c0b-c0d4-4115-b29d-dade65c97de6/image.png" alt="">spring1과 spring2를 등록했을 때,
  회원 목록에 들어가보면 위 사진과 같은 화면이 뜨는 것을 볼 수 있다.</p>
<p> **memory에 이전 기록들이 다 저장이 되고 있지만,
  실행을 중단하고 재실행하면 memory가 다 초기화되고 다시 List가 아무것도 남아있지 않게 된다.</p>
<p>  이렇게 회원 조회까지 다 완성을 하게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 웹 기능-등록]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%EB%93%B1%EB%A1%9D</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%EB%93%B1%EB%A1%9D</guid>
            <pubDate>Mon, 15 May 2023 13:22:02 GMT</pubDate>
            <description><![CDATA[<h2 id="회원-웹-기능---등록">회원 웹 기능 - 등록</h2>
<p>이번에는 회원 가입과 목록 버튼을 클릭하면 나오는 페이지를 구성하는 코드를 작성해 보겠다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/4947a0ab-afa7-4f0c-9db5-b1449fdcd334/image.png" alt="">우선 기존에 만들어 놓았던 MemberController 클래스에 가서, </p>
<pre><code>@GetMapping(&quot;/members/new&quot;)
    public String createForm(){
        return &quot;members/createMemberForm&quot;;
    }</code></pre><p>GetMapping 코드를 작성하였다.
localhost:8080에서 회원 가입 링크를 눌렀을 때, 
<a href="http://localhost:8080/members/new">http://localhost:8080/members/new</a> 로 연결된다고 떴었기 때문에</p>
<p>/members/new로 연결을 하였고,
members/createMemberForm으로 이동을 한다고 코드를 구성하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/cb4d19c9-8823-4c6a-9c2b-754248f64b5a/image.png" alt="">
그리고 templates에 members라는 패키지를 하나 생성해서 createMemberForm 이라는 html파일을 생성해서</p>
<p>사진 속 html 코드를 작성하였다.</p>
<p>실행해보면, </p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/a759ff68-bd08-45f0-8e44-110396e630d0/image.png" alt="">회원 가입 링크를 눌렀을 때의 실행 화면이 제대로 나오는 것을 볼 수 있다.</p>
<p>html코드에 의해서 이름입력 부분에 만약 spring을 입력하고 등록하면,
spring이라는 name과 value가 서버에 등록이 된다.</p>
<p>이렇게 회원 등록 껍데기는 만들어 졌고, 
이제 회원 등록 controller을 만들어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/efd6a1cf-1767-4571-ae26-b8d6fec9fe99/image.png" alt="">controller에 MemberForm이라는 클래스를 하나 생성해주고, </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>
<p>위 코드에 getter setter로 설정한 name이 
createMemberForm의 name과 매칭이 되면서 값이 들어올 것이다.</p>
<p>그 다음 MemberController에 
<img src="https://velog.velcdn.com/images/s_em_tudy/post/699a7b91-cc92-4adb-88af-cb51b2b43266/image.png" alt=""></p>
<pre><code>@PostMapping(&quot;/members/new&quot;)
    public String create(MemberForm form){
        Member member = new Member();
        member.setName(form.getName());

        System.out.println(&quot;member = &quot; + member.getName());

        memberService.join(member);

        return &quot;redirect:/&quot;;
    }</code></pre><p>위 코드를 작성하였다.</p>
<p>member.setName(form.getName());을 해주면 member가 만들어지고,</p>
<p>memberService.join(member);로 member을 넘긴다.</p>
<p>그 다음 return &quot;redirect:/&quot;를 하여 home 화면으로 보낸다.</p>
<p>실행해보면, 정상 동작되는 것을 볼 수 있다.
아직 웹 서버에서는 등록 버튼을 눌러도 아무것도 뜨지 않는다.</p>
<p>이 로직의 원리는 이렇다.</p>
<p>웹 서버에서 회원가입으로 들어가면, members/new로 들어가지는 것을 볼 수 있다.</p>
<p>get방식으로 MemberController에서 @GetMapping(&quot;/members/new&quot;)에 의해</p>
<pre><code> public String createForm(){
        return &quot;members/createMemberForm&quot;;
    }</code></pre><p>가 mapping이 되는데, 위 코드는 아무 일을 하지 않고 members/createMemberForm 으로 이동을 한다.</p>
<p>그래서 createMemberForm에 작성 돼 있는 html 코드가 뿌려지는 것이다.</p>
<p>그런데 이제 코드가 뿌려질 때, form이라는 태그가 있는데 
form은 값을 입력할 수 있는 html 태그이다.
form안을 보면 action은 /members/new 라고 되어 있고, method는 post라고 되어 있다.</p>
<pre><code> &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot; placeholder=&quot;이름을
입력하세요&quot;&gt;</code></pre><p>중요한 건 이제 이 부분인데, 
input type = text를 했을 때 text를 입력하는 박스가 생긴 것이고, name은 서버로 넘어갈 때의 key가 된다.
또 placeholder는 아무것도 없을 때 뜨는 문구이다.</p>
<p>그래서 웹 서버에서 뜨는 text box에 spring이라고 적어주고 등록 버튼을 누르면,</p>
<p>/members/new로 post 방식으로 넘어오게 되고,</p>
<p>controller에 있는 @PostMapping으로 넘어오게 된다.</p>
<p>(기본적으로 url 창에 직접 치는 것은 GetMapping으로 넘어온다)</p>
<p>**Post방식은 데이터를 폼에 넣어서 전달할 때 주로 쓰고,
Get방식은 조회를 할 때 주로 쓴다.</p>
<p>그러면</p>
<pre><code>@PostMapping(&quot;/members/new&quot;)
    public String create(MemberForm form){
        Member member = new Member();
        member.setName(form.getName());

        System.out.println(&quot;member = &quot; + member.getName());

        memberService.join(member);

        return &quot;redirect:/&quot;;
    }</code></pre><p>create라는 메소드가 호출이 되어
MemberForm에 spring이라는 값이 전달된다.</p>
<p>그래서 MemberForm의 </p>
<pre><code>public class MemberForm {
    private String name;
    //members의 name이랑 매칭이 됨
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}</code></pre><p>코드로 인해 
setname에 spring이 저장되고,
getname으로 꺼내지게 된다.</p>
<p>그래서 다시 MemberController의 코드 중 </p>
<blockquote>
<p> member.setName(form.getName());</p>
</blockquote>
<p>getName으로 꺼낸 것이다.</p>
<p>눈으로 확인하기 위해 member.setName(form.getName()); 뒤에 다음 코드를 추가해 주었다.</p>
<blockquote>
<p>System.out.println(&quot;member = &quot; + member.getName());</p>
</blockquote>
<p>실행해서,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/7f77a967-44aa-4cd5-821a-ec13de4b75f5/image.png" alt="">spring을 등록했더니</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5c677225-65e6-4f1f-ab51-7d60a2f7baca/image.png" alt="">실행창에 name = spring 이라는 값이 뜨는 것을 볼 수가 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 웹 기능-홈 화면 추가]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%ED%99%88-%ED%99%94%EB%A9%B4-%EC%B6%94%EA%B0%80</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%9B%B9-%EA%B8%B0%EB%8A%A5-%ED%99%88-%ED%99%94%EB%A9%B4-%EC%B6%94%EA%B0%80</guid>
            <pubDate>Sun, 14 May 2023 21:21:49 GMT</pubDate>
            <description><![CDATA[<h2 id="회원-관리-예제를-웹-mvc로-개발하기">회원 관리 예제를 웹 MVC로 개발하기</h2>
<p>이전에 만들었던 MemberController을 통해서 
회원을 조회하고 등록하는 예제를 만들어보겠다.</p>
<h3 id="회원-웹-기능---홈-화면-추가하기">회원 웹 기능 - 홈 화면 추가하기</h3>
<p>아주 단순하게 회원을 등록하고 조회할 수 있는 버튼이 있는 사이트를 하나 만들 것이다.</p>
<p>우선,
main - java - hellospring - controller 밑에 HomeController라는 클래스를 하나 생성하였다.</p>
<p>거기에 </p>
<pre><code>package hello.hellospring.controller;

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

public class HomeController {

    @GetMapping(&quot;/&quot;)
    public String home(){
        return &quot;home&quot;;
    }
}
</code></pre><p>이렇게 코드를 작성하였고,</p>
<p>이제 화면을 구현하는 home.html을 resources - templates 밑에 생성하였다.</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>그리고 위 html 코드를 작성하였다.</p>
<p>이제 run을 해보면, 
제대로 돌아가는 것을 볼 수 있고 이를 확인 후
실제 웹 페이지에 가서
localhost:8080에 접속해보았다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/a34a699d-2662-480f-b39b-92979d189f30/image.png" alt="">이렇게 제대로 페이지가 뜨는 것을 볼 수 있다.</p>
<h3 id="우선순위">우선순위</h3>
<p>분명 저번에 static에 index.html을 만들어서
처음에 아무것도 없으면 welcome 페이지가 나오도록 코딩을 해 놓은 적이 있었다.</p>
<p>그런데 어떻게 회원가입 화면이 바로 떴느냐, 
바로 우선순위가 있어서이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/887a2d76-c429-49fc-972e-359243aa876f/image.png" alt="">정적 컨텐츠 설명할 때, 
먼저 웹 브라우저에서 요청이 오면
스프링 컨테이너에서 관련 컨트롤러를 우선적으로 찾고,
없으면 static 파일을 찾도록 되어 있다고 했었다.</p>
<p>그래서 
localhost:8080 요청이 왔을 때
먼저 controller에서 mapping된 것을 찾은 것이다(HomeController).
-&gt; index.html은 무시되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_자바 코드로 직접 스프링 빈 등록하기]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%A7%81%EC%A0%91-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%A7%81%EC%A0%91-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 14 May 2023 20:59:52 GMT</pubDate>
            <description><![CDATA[<h3 id="자바-코드로-직접-스프링-빈-등록하기">자바 코드로 직접 스프링 빈 등록하기</h3>
<ul>
<li>기존에 작성하였던 회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션을 제거하고 진행한다. </li>
<li>MemberController는 그대로 놔둔다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/43973fd1-00e1-4276-a473-f42e8f66dfbb/image.png" alt="">스프링 빈을 직접 등록하는 코드를 작성하기 위해 main - java - hellospring - service 밑에 SpringConfig 클래스를 하나 생성하였다.</p>
<pre><code>@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }</code></pre><p>우선 @Configuration를 해주고,
Spring Bean을 등록할 거라는 의미로 @Bean을 해준다.</p>
<p>위 코드를 작성하면 
스프링이 Contiguration을 읽고, 
스프링 빈에 등록하라는 뜻으로 이해한다. </p>
<p>그리고 memberService 로직을 호출해서 등록을 해준다.</p>
<p>근데 return new MemberService();부분에서 생성자에 뭘 넣어줘야 하는데,
뭘 넣어줘야 하는지 명령어 command + p 로 확인을 해 보면, MemberRepository라고 뜬다. 
따라서</p>
<pre><code>@Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }</code></pre><p>MemberRepository를 등록을 해주고, 
MemberRepository는 인터페이스이기 때문에 new 가 안되므로 
구현체인 MemoryMemberRepository를 return 해준다.</p>
<p>그 다음
MemberService에는 MemberRepository를 엮어주어야 하기 때문에</p>
<pre><code> public MemberService memberService(){
        return new MemberService(memberRepository(memberRepository));
    }</code></pre><p>MemberService 부분에 memberRepository를 넣어준다.</p>
<p>결론적으로 </p>
<pre><code>@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}</code></pre><p>memberService와 memberRepository를 
둘 다 스프링 빈에 등록을 해주고, 
스프링 빈에 등록되어 있는 memberRepository를 
memberService에 등록 해주면서 완성되는 것이다.</p>
<p>Controller는 스프링이 어차피 관리하는 것이기 때문에 그대로 두면,
Autowired가 그냥 알아서 해준다.</p>
<p>run을 시켜보면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/9e49350e-d894-4703-9566-59f35ec0deec/image.png" alt="">정상적으로 실행이 되는 것을 볼 수 있다.</p>
<p>이렇게 컴포넌트 스캔과 자동 의존관계 설정 방법과,
이번에 진행했던 자바 코드로 직접 스프링 빈 등록하는 방법 
두 가지를 다 진행해보았다.</p>
<hr>
<h4 id="di에는-필드-주입-setter-주입-생성자-주입-세-가지-방법이-있다">DI에는 필드 주입, setter 주입, 생성자 주입 세 가지 방법이 있다.</h4>
<p>먼저 <strong>생성자 주입 방법</strong>은,</p>
<pre><code>@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}</code></pre><p>작성했었던 Controller 코드와 같이, 생성자를 통해서 memberService가 MemberController에 주입이 되는 것이다.
**
필드 주입 방법**은,</p>
<pre><code>@Controller
public class MemberController {

    @Autowired private MemberService memberService;

   // @Autowired
   // public MemberController(MemberService memberService) {
   //    this.memberService = memberService;
    }
}</code></pre><p>아예 생성자를 빼고 필드에 Autowired를 해주는 것이다.</p>
<p>하지만 이 방법은 별로 좋지 않은 방법이다.</p>
<p>그 다음이 <strong>setter 주입 방법</strong>인데,</p>
<pre><code>@Controller
public class MemberController {

    private MemberService memberService;

    @Autowired
    public void setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }
}</code></pre><p>이런 식으로 command + n 단축키를 이용하여 setter을 생성하고, 
@Autowired를 해주면 된다.</p>
<p>이 방법의 단점은 
누군가가 memberController을 호출했을 때 setMemberService가 public으로 항상 노출이 되어 있어야 한다는 것이다.</p>
<p>의존 관계가 실행중에 동적으로 변하는 경우는 거의 없으므로, 
제일 권장하는 방법은
처음에 했던** 생성자 주입 방법**이다.</p>
<p>참고) 실무에서는 주로 정형화된 controller, service, repository 같은 코드는 컴포넌트 스캔을 사용한다. </p>
<p>정형화된 코드란 일반적으로 작성하는 controller, service, repository를 말한다.</p>
<p><em><strong>정형화되지 않거나 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다(중요).</strong></em></p>
<p>----&gt;
memberRepository를 설계할 때, 아직 데이터 저장소가 선정되지 않았다는 가상의 시나리오가 있었다.</p>
<p>때문에 일단 메모리를 만들고 나중에 교체하자고 했었다.</p>
<p>그래서 interface MemberRepository를 설계 하고,
Memory MemberRepository를 구현체로 만들었던 것이다.</p>
<p>그런데 MemoryMemberRepository를 이제 database에 실제로 연결하는 repository로 바꿀 것이다.</p>
<p>바꿔치기 할 때 기존의 MemberService나 나머지 코드에 하나도 손 대지 않고 바꿀 수 있는 방법이 있다.</p>
<p>이 방법이 무엇이냐면,</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/a949ee1a-3af5-4298-b7f0-52a80a18fc15/image.png" alt="">아까 작성하였던 SpringConfig의</p>
<pre><code>  @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }</code></pre><p>위 코드에서,</p>
<blockquote>
<p>return new DbMemberRepository();</p>
</blockquote>
<p>이 부분만 <strong>DbMemberRepository();</strong>로 바꿔주면 dataBase에 연결할 수 있게 되는 것이다.</p>
<p>다른 부분은 전혀 손댈 필요가 없다.</p>
<p>이것이 _<strong>직접 코드로 작성할 때의 장점</strong>_이다.
컴포넌트 스캔을 사용하면,
여러 코드를 바꿔야 하는 단점이 있다.</p>
<p>주의사항은
@Autowired를 통한 DI는 helloController, MemberService 등과 같이 스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_컴포넌트 스캔과 자동 의존관계 설정]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94%EA%B3%BC-%EC%9E%90%EB%8F%99-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94%EA%B3%BC-%EC%9E%90%EB%8F%99-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 14 May 2023 16:34:30 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-빈과-의존관계">스프링 빈과 의존관계</h2>
<h3 id="스프링-빈을-등록하고-의존관계-설정하기">스프링 빈을 등록하고, 의존관계 설정하기</h3>
<p>-
회원 컨트롤러가 회원서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 준비하자. </p>
<p>이제 화면을 구축해야 하는데, 그러려면 <strong>멤버 컨트롤러</strong>를 만들어야 한다. </p>
<p>멤버 컨트롤러는 멤버 서비스를 통해서 회원가입하고, 데이터를 조회할 수 있어야 한다.
-&gt; 서로 <strong>의존관계</strong>가 있다.</p>
<h3 id="회원-컨트롤러에-의존관계-추가">회원 컨트롤러에 의존관계 추가</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/c7a4355a-5793-4880-951f-65c547a78b73/image.png" alt="">우선 src - main - java - hellospring - controller에 MemberController라는 자바 클래스를 하나 생성하였다.</p>
<p>그리고 <strong>@Controller</strong>를 작성하여 위 사진과 같은 코드를 생성해 놓았다.</p>
<p>이 상태에서는 기능은 아무것도 없지만, 
스프링이 처음에 뜰 때 <strong>&#39;스프링 컨테이너&#39;</strong>라는 스프링 통이 생기는데
이 @Controller이 있으면 
그 스프링 통에 MemberController 객체를 생성해서 넣어두고 관리를 한다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/b3857998-d4ba-4e42-bbf5-9bed041f81ed/image.png" alt="">@Controller가 있으면
그림에서 보이는 이 스프링 컨테이너에 helloController가 들어가서 관리되고 있는 것처럼 관리를 해주고, 스프링과 관련된 컨트롤러의 기능들이 동작을 하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/6119b9cf-4a52-41a8-8181-ae153dd6f734/image.png" alt=""></p>
<blockquote>
<p>private final MemberService memberService = new MemberService();</p>
</blockquote>
<p>memberService를 가져다 써야하므로 <strong>new</strong>로 이렇게 생성을 해줄 수 있다.</p>
<p>그런데 이렇게 new로 생성을 하게 되면, 
memberController말고 다른 여러 컨트롤러(ex: 주문 컨트롤러 등)들도 memberService를 가져다 쓸 수 있게 된다. </p>
<p>근데 memberService를 들어가보면, 그다지 별 기능이 없다. 
여러 개를 생성할 필요 없이 하나만 생성해놓고 공유해서 쓰면 된다. </p>
<p>따라서 new로 생성하는 방법보다는 더 나은 방법이 있다.</p>
<p><strong>스프링 컨테이너에 등록</strong>을 해놓고 쓰는 방법이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/3747654f-6c37-42a3-a13a-f32982a87fdd/image.png" alt="">연결을 해주는 방법은 <strong><em>option + enter</em></strong>로 생성자를 자동작성해주면 된다.</p>
<p>그리고 <strong>@Autowired</strong>를 해주는데, 
이는 memberService와 
스프링 컨테이너에 있는 memberService를 가져다가 연결을 해준다. </p>
<p>이 상태에서 run을 하려고 보면, 에러가 나면서 돌아가지 않는다.</p>
<p>MemberService 클래스를 보자. </p>
<p>이 클래스는 그냥 순수한 자바 클래스이다. </p>
<p>spring이 이 클래스가 무엇인지 알 수 있는 방법이 없다.</p>
<p>따라서** @Service **를 해주어야 하는 것이다. 
<img src="https://velog.velcdn.com/images/s_em_tudy/post/f422a275-f90b-4b8f-8444-5332f0b32335/image.png" alt="">이렇게 @Service를 해주고 나서 생각해보자. 
@Service를 해주면, 스프링이 어? 서비스네? 하면서 스프링 컨테이너에 MemberService를 등록해준다. </p>
<p>MemberService 클래스엔 @Service, 
memberController 클래스엔 @Controller,
이런 정형화된 패턴으로 
MemoryMemberRepository 클래스에도 @Repository를 해준다.</p>
<p>controller을 통해서 외부 요청을 받고, service에서 비즈니스 로직을 만들고, repository에서 데이터를 저장하는 패턴이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5be11d5a-87e2-4990-95ea-85cae69cf7dd/image.png" alt=""></p>
<p>이제, <strong>controller와 service를 연결</strong>시켜줘야 한다.</p>
<p>아까 해주었던 MemberController의 @Autowired가 이 기능을 해 주는 것이다.</p>
<p>MemberController가 생성 될 때, 
스프링 빈에 등록되어 있는 MemberService 객체를 가져다가 넣어준다.</p>
<p>이 과정들이 바로 <em><strong>Dependency Injection(DI) : 의존성 주입</strong></em> 인 것이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/d5006b92-9b85-412a-b95d-232d25ef46c0/image.png" alt="">MemberService 클래스에 가서 보면, 
MemberService는 MemberRepository를 필요로 하기 때문에 </p>
<p>MemberService에서도 @Autowired를 해주면,
아까와 똑같이 스프링에서 알아채고 컨테이너에 있는 MemberRepository를 넣어준다.</p>
<p>MemoryMemberRepository가 구현체로 있기 때문에 MemoryMemberRepository를 Service에 주입을 해준다.</p>
<p>이렇게 모두 의존 관계를 주입해 준 후,
main method를 run 해보면 제대로 동작되는 것을 볼 수 있다.<img src="https://velog.velcdn.com/images/s_em_tudy/post/b28044e6-88d7-45eb-bf93-f5ddbd043e96/image.png" alt=""><em>*<em>--&gt; Spring이 컨테이너를 만들 때 문제 없이 잘 되었다는 것이다.
*</em></em></p>
<p>하지만 지금은 회원 컨트롤러와 관련된 어떤 기능도 존재하진 않는 상태이다.</p>
<h3 id="정리">정리</h3>
<p>스프링 빈을 등록하는 데에는 두 가지 방법이 있다.
*<em>1. 컴포넌트 스캔과 자동 의존관계 설정
2. 자바 코드로 직접 스프링 빈 등록하기
*</em>
위에서 하였던 방식은 1번째 방식이다.</p>
<p>왜 컴포넌트 스캔 방식이라고 하냐면, 
원래는 MemberService에 @Service가 아니라 <strong>@Component</strong>를 해줘야 한다. </p>
<p>근데 왜 @Service를 해주느냐, </p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/ecbe5508-6d00-4fc7-9481-5811253ab4ea/image.png" alt="">_<strong>command + b</strong>_를 통해 service 안으로 들어와 보면 
Component가 이미 등록이 돼 있는 것을 볼 수 있다. </p>
<p>Controller, Repository도 마찬가지로 Component가 등록이 되어 있다.</p>
<p>그래서 결론적으론 Component annotation이 되어 있으면 
컨테이너가 등록을 해주고, 
AutoWired가 이들을 연결해 주는 것이다.</p>
<p><em>**
==&gt; 이것이 바로 컴포넌트 스캔과 자동 의존관계 설정이다.
**</em></p>
<p>사실 웬만한 것들은 모두 @Component를 이용해 스프링 빈으로 등록을 해놔야 한다고 생각하면 된다. 
자세한 어떤 이점이 있는지는 더 뒷부분에서 진행하겠다.</p>
<p>그럼 질문이 하나 생길 수 있다.</p>
<p>아무데나 @Component를 해도 되는 것일까?</p>
<p>결론부터 말하면 안 된다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/d202153c-3e7b-48d9-b27c-10ebe27a9be6/image.png" alt="">HelloSpringApplication를 run 시키는 것이므로 
<em><strong>hello.hellospring과 그 하위 패키지들만 @Component를 허용한다.</strong></em></p>
<p>**참고) 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다. 싱글톤이라는 것은 유일하게 하나만 등록해서 공유한다는 뜻이다. 따라서 같은 스프링 빈이면 모두 같은 인스턴스이다. 설정으로 싱글톤이 아니게 설정할 수는 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 서비스 테스트]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 12 May 2023 19:17:19 GMT</pubDate>
            <description><![CDATA[<h3 id="test-case-작성하기">Test Case 작성하기</h3>
<p>이전에 테스트할 때는 직접 package를 만들고 클래스를 만들고 하였었는데, 
훨씬 편하게 생성하는 단축키가 존재한다.</p>
<p><em>**단축키: command + shift + t</em></p>
<p>이 단축키를 사용하여 MemberServiceTest class를 자동 생성 하였다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/134e1ce3-38a6-4ab0-9ab1-04684933fa05/image.png" alt="">test - java - hellospring - service 밑에 MemberServiceTest 클래스가 자동 생성 되었다. 게다가 틀까지 모두 짜준다(ㄱㅇㄷ).</p>
<p>_**참고) Test는 @Test void 회원가입() { } 처럼 한글로 이름을 작성해도 된다.
_</p>
<p>그리고 이제 내용을 채워넣을 차례이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/c7d6aa2c-6661-4751-9963-5802e67fb494/image.png" alt="">먼저 MemberService memberService = new MemberService();를 만들었다.</p>
<p>회원가입을 하려면 먼저 member부터 만들어야 하는데,
이 때 추천하는 문법이 있는데</p>
<p><em><strong>given - when - then 문법이다.</strong></em></p>
<p>테스트 코드는 나누자면 
무언가가 주어졌는데, 이를 실행했을 때, 무언가가 나와야 한다. 
이 세 가지 단계로 구성된다.</p>
<p>테스트를 이 세 가지 단계로 나누어서 작성하면 테스트 코드가 길 때도 
given을 보고 &#39;아 이 데이터를 기반으로 하는구나&#39;
when을 보고 &#39;아 이걸 검증하는 거구나&#39;
then을 보고 &#39;여기가 검증부구나&#39;를 알 수가 있다.
이 주석들을 깔고 코드를 짜면 훨씬 편하다.</p>
<p>일단 //given부분에 </p>
<pre><code>Member member = new Member();
member.setName(&quot;hello&quot;);</code></pre><p>를 작성하고, </p>
<p>//when부분에 (검증할 내용 작성)</p>
<pre><code> Long saveId = memberService.join(member);</code></pre><p>를 작성하였다.</p>
<p>memberService의 join을 검증하는 것이기 때문에
memberService.join(member); 를 작성하고,
    return은 저장한 Id가 나와야하므로 
    Long saveId = memberService.join(member);
    로 코드를 수정한 것이다.</p>
<p>  ** //then부분에는 (검증)**</p>
<pre><code> Member findMember = memberService.findOne(saveId).get();

 assertThat(member.getName()).isEqualTo(findMember.getName());</code></pre><p>를 작성하였다.</p>
<p>우선 내가 저장한 것이 repository에 있는 게 맞는지를 찾아보고 싶기 때문에 repository를 꺼내야 한다.</p>
<blockquote>
<p>memberService.findOne(saveId); </p>
</blockquote>
<p>saveId를 memberId로 넘기는 것이다.</p>
<p>return값이 Optional이기 때문에 </p>
<blockquote>
<p>Optional<Member> one = memberService.findOne(saveId); </p>
</blockquote>
<p>  로 코드를 수정해주고,</p>
<p>단순화하기 위해 바로 get()으로 받도록 코드를 수정해준다.</p>
<p>그래서 최종적으로 </p>
<blockquote>
<p>Member findMember = memberService.findOne(saveId).get(); </p>
</blockquote>
<p>  이 된 것이다.</p>
<p>  그리고 나서 member의 이름이 findMember의 이름과 같은지를 검증하기 위해서  </p>
<blockquote>
<p>  Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());</p>
</blockquote>
<p> 위 코드를 작성하였는데,</p>
<p>단축키: option + enter을 눌러 <strong>static import</strong>로 대체해주었다.</p>
<p>그래서 최종적으로 </p>
<blockquote>
<p>assertThat(member.getName()).isEqualTo(findMember.getName()); </p>
</blockquote>
<p>  이 된 것이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/b41db0bb-9806-4b08-8dce-87307df38bd0/image.png" alt="">실행했을 때 모두 잘 돌아가는 것이 확인되었다.</p>
<h3 id="예외-플로우-확인하기">예외 플로우 확인하기</h3>
<p><strong>Test는 정상 플로우도 중요한데, 예외 플로우가 훨씬 더 중요하다.</strong></p>
<p>  join의 핵심은 저장이 되는 것도 중요하지만, 중복 회원 검증 logic을 잘 타서 예외가 잘 터뜨려지는가 를 확인하는 것도 중요하다.</p>
<p>이를 확인하기 위해서 중복 회원 예외가 실제로 발생하는 test code도 작성해 보았다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/cb3e08b9-2a8d-47f5-93f9-bcf345d009cb/image.png" alt=""></p>
<pre><code>@Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);

        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);
        //when
        memberService.join(member1);
        try {
            memberService.join(member2);
            fail();
        }catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
        }
        //then
    }</code></pre><p>이 코드도 역시 given - when - then문법을 이용해서 작성하였다.
  given 부분: </p>
<pre><code>  Member member1 = new Member();
        member1.setName(&quot;spring&quot;);

        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);
</code></pre><p>  member1에 spring이라는 이름을 저장하였고, member2에도 spring이라는 이름을 저장하였다.</p>
<p>  when 부분:</p>
<pre><code> memberService.join(member1); //여기까진 문제 없음
        try {
            memberService.join(member2);
              //두 번째 join부터 validateDuplicateMember에서 걸려서 
              예외가 터져야 하기 때문에 예외를 잡기 위해 try-catch문 안에 작성하였다.
            fail(); 
              //memberService.join(member2)줄에서 예외가 터지지 않고 
              그 다음줄로 내려가면 실패인 것이기 때문에 fail()이라고 적었다.
        }catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
              //예외 메시지가 MemberService 클래스의
              validateDuplicateMember에 작성한 에러 메시지와 동일한지 
              확인하는 코드를 작성하였다.
        }</code></pre><p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/426ee100-88ed-4d21-9ebb-3d296ca12227/image.png" alt="">성공! 잘 돌아간다.</p>
<h3 id="또-다른-방법">또 다른 방법</h3>
<p>  이 경우에서 try catch문을 사용하는 것보다 더 좋은 문법이 존재한다.</p>
<p>  assertThrows라는 문법이다.</p>
<p>  try catch문을 모두 주석처리 하고, asserThrows를 사용한 다음 코드를 작성하였다.</p>
<blockquote>
<p>   assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));</p>
</blockquote>
<p> memberService.join(member2)를 실행할 때, IllegalStateException.class 예외가 터져야 한다는 뜻이다.</p>
<p>  그럼 메시지는 어떻게 검증할까?</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/c011965c-c38d-49a5-a2cb-fdda2e8e363f/image.png" alt="">아까 작성했던 
  assertThrows(IllegalStateException.class, ()
  -&gt;memberService.join(member2)); 코드에 단축키 command + option + v를 사용하여</p>
<p>  IllegalStateException e = assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));로 코드를 수정하였다.</p>
<p>  그 후 
assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;); 라는 메시지 검증 코드를 작성하였다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/69892e82-6ffc-4f33-b71f-bc57e7c64a2d/image.png" alt="">회원가입의 setName을 spring으로 바꾸어봤더니, 제대로 돌아가지 않았다. 
  그 이유는 코드를 돌릴 때마다 join에서 database에 memory가 계속 쌓여서, name의 값이 spring인 
값이 계속 누적이 되었기 때문이다. 
  spring은 이미 가입이 되어있는 것이다.</p>
<p>  그래서 여기서도 clear을 시켜줘야 한다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/924ae3d8-af76-431b-b30a-64d7d33a22f4/image.png" alt="">기존에는 MemberService memberService = new MemberService(); 밖에 안 되어 있었기 때문에 </p>
<blockquote>
<p>  MemoryMemberRepository memberRepository = new MemoryMemberRepository();</p>
</blockquote>
<p>  를 추가하고, MemoryMemberRepositoryTest에 작성했던 </p>
<blockquote>
<p>  @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }</p>
</blockquote>
<p>AfterEach를 가져와서 새로 작성하였다.</p>
<p>  --&gt; 실행될 때마다 DB의 값을 clear해준다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/dc023df3-d7bb-4da1-aed9-9aa8d2b2c4a6/image.png" alt=""> 아까는 hello를 spring으로 바꿨을 때 제대로 동작하지 않았었는데, clear을 해준 지금은 제대로 동작하는 것을 볼 수 있다.</p>
<p>  <em>**참고) 단축키: control + r -&gt; 이전에 실행했던 것을 재실행 해줌</em></p>
<p>  그런데 여기서 문제가 하나 있다.</p>
<p>  현재 MemberService 클래스에 있는 
  private final MemberRepository memberRepository = new MemoryMemberRepository();
  코드의 MemoryMemberRepository와,</p>
<p>  MemberServiceTest 클래스에 있는
  MemoryMemberRepository memberRepository = new MemoryMemberRepository();
  코드의 MemoryMemberRepository가</p>
<p>  서로 다른 객체라는 것이다.</p>
<p>  물론 현재는 MemoryMemberRepository 클래스의 
  private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;(); 코드를
  static으로 선언을 해놓았기 때문에 문제가 발생하지 않았지만,
  static이 아니었다면 분명 문제가 발생했을 것이다.</p>
<p>  이 뿐만이 아니라 그냥 원래 MemberService 클래스와 MemberServiceTest는 같은 Repository를 테스트 하는 것이 맞는 것이다.</p>
<p>  그래서 해결 방법은, 둘이 같은 instance를 쓰게 만들어야 한다.</p>
<p> 우선 MemberService 클래스에서 </p>
<blockquote>
<p>  private final MemberRepository memberRepository = new MemoryMemberRepository();</p>
</blockquote>
<p>  를</p>
<blockquote>
<p>  private final MemberRepository memberRepository;</p>
</blockquote>
<p>  로 바꾸고,</p>
<p>  constructor를 사용하여 memberRepository를 직접 new에서 생성하는 것이 아니라, 외부에서 생성하도록 바꿔준다.</p>
<blockquote>
<p>  public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }</p>
</blockquote>
<p>  그리고 MemberServiceTest 클래스에 가서,</p>
<pre><code>MemberService memberService = new MemberService();
  MemoryMemberRepository memberRepository = new MemoryMemberRepository();</code></pre><p>  였던 코드에서,
  MemberService를 생성할 때 MemoryMemberRepository를 *<em>직접 *</em>넣어주는 코드로 수정해준다.</p>
<p>  @BeforeEach로, 동작하기 전에 넣어주는 코드를 작성한다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/dc1070f8-7ce1-4ca6-b1a3-eed642bb0577/image.png" alt=""></p>
<pre><code>class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }</code></pre><p>  beforeEach이므로, <strong>각 테스트를 실행하기 전</strong>에 </p>
<blockquote>
<p>   memberRepository = new MemoryMemberRepository();</p>
</blockquote>
<p>  MemoryMemberRepository를 만들고, 그것을 memberRepository에 넣는다.</p>
<blockquote>
<p>   memberService = new MemberService(memberRepository);</p>
</blockquote>
<p>  그리고 MemberSevice를 만들어서 memberRepository를 넣으면, 같은 repository가 되는 것이다.</p>
<p>  memberService입장에서는 직접 new하지 않고 외부에서 넣어주는 것인데, 이런 것을 
  <strong>Dependency Injection이라고 한다.(DI)</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 서비스 개발]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Thu, 11 May 2023 19:02:07 GMT</pubDate>
            <description><![CDATA[<h3 id="회원-서비스-클래스-만들기">회원 서비스 클래스 만들기</h3>
<p><strong>회원 서비스</strong>: 회원 리포지토리와 도메인을 활용하여 실제 비즈니스 로직을 작성하는 것.</p>
<h3 id="직접-해보기">직접 해보기</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/da45dc43-de50-4b6a-91b9-a3ac60f4cf77/image.png" alt="">우선
<strong>main - java - hellospring</strong>에 <strong>service</strong>라는 패키지를 하나 생성하였다.
그리고 service 패키지 밑에 <strong>MemberService</strong>라는 클래스를 하나 생성하였다.</p>
<pre><code>package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

import java.util.Optional;

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    public Long join(Member member){
        //같은 이름이 있는 중복 회원은 X
        Optional&lt;Member&gt; result = memberRepository.findByName(member.getName());
        result.ifPresent(m -&gt; {
            throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
        });

        memberRepository.save(member);
        return member.getId();
    }
}
</code></pre><p>MemberService 클래스에 작성한 코드이다.</p>
<p>우선 private final MemberRepository memberRepository = new MemoryMemberRepository();를 한 후
회원 가입 코드를 작성하였다.</p>
<pre><code>public Long join(Member member){

        memberRepository.save(member);
        return member.getId();
    }</code></pre><p>  회원가입은 그냥 단순하게 memberRepository에 save만 호출해주면 된다.
  그리고 임의로 Id만 반환하게 해 놓았다.</p>
<p>  그런데 비즈니스 로직 중에 같은 이름을 가진 회원이 있는 회원은 안 된다는 룰이 있었다.</p>
<p>  따라서 같은 이름이 있는 중복 회원은 안 된다는 코드를 추가 작성해야 한다.</p>
<p>memberRepsoitory.findByName으로 같은 이름을 검색한다.</p>
<blockquote>
<p>memberRepsoitory.findByName(member.getName());</p>
</blockquote>
<p><strong><em>*단축키 command + option + v</em></strong>
-&gt; Optional<Member> result = memberRepository.findByName(member.getName());
  자동반환</p>
<p>  이렇게 Optional로 반환이 되었고,</p>
<pre><code>  result.ifPresent(m -&gt; {
            throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
        });</code></pre><p> 만약 member가 존재한다면, 이미 존재하는 회원입니다. 라는 에러를 출력하는 코드를 작성하였다.</p>
<p>ifPresent는 member가 null이 아니라 이미 어떠한 값이 있으면, 동작한다.
  Optional이기 때문에 가능한 일이다.</p>
<p> 기존에는 member가 null이 아니면, 이라고 했을 것이다..</p>
<p>  Optional로 한 번 감싸면, Optional 안에 member 객체가 있는 것이기 때문에 Optional에 존재하는 여러 메소드를 사용할 수가 있다.</p>
<p> ** =&gt; 과거에는 if null이 아니면 이라고 코딩을 하였지만, 지금은 Null일 가능성이 있으면 Optional로 감싸서 코딩을 해주고, 감싼 덕분에 ifPresent와 같은 메소드를 사용 가능하다.**</p>
<p> 꺼내고 싶으면 그냥 
  result.get();으로 꺼내주면 된다.</p>
<p>  그럼 자동으로 Member member1 = result.get();으로 변환된다.</p>
<p>  하지만 get()으로 직접 꺼내는 것은 별로 권장하진 않는 방법이다.</p>
<pre><code>Optional&lt;Member&gt; result = memberRepository.findByName(member.getName());
        result.ifPresent(m -&gt; {
            throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
        });</code></pre><p>  이렇게 logic이 쭉 나열 돼 있는 코드보다는, 메소드로 뽑아주는 코드가 더 좋다.</p>
<p>  따라서 아래 사진처럼 코드를 수정해 주었다.
  <img src="https://velog.velcdn.com/images/s_em_tudy/post/080a5991-9668-4eeb-ba22-736e80068046/image.png" alt="">Optional코드를 드래그해서, <em><strong>단축키 control + t</strong>_를 사용하여 Extract Method를 선택해 주었다. _<strong>command + option + m</strong></em> 을 사용하면 바로 Extract Method가 가능하다.</p>
<pre><code>public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    public Long join(Member member){

        //같은 이름이 있는 중복 회원은 X
        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;);
                 });
    }
}</code></pre><p>  수정된 코드를 포함한 MemberService 코드이다.</p>
<p>  validateDuplicateMember는 임의로 정한 메소드 이름이다.</p>
<p>  **<em>전체 회원 조회 기능 코드를 이 다음에 작성하는데, 자꾸 뭘 추가하려고만 하면 validateDuplicateMember가 사라지길래 맨 처음에 command + option + m을 치면 나오는 extracted(member);의 extracted를 내가 직접 validateDuplicateMember로 수정하지 않고, extracted옆에 뜨는 환경설정 표시를 눌러서 more option에 들어간 후, refactor로 수정해줬더니 더 이상 그런 오류가 생기지 않았다</em>.</p>
<p>  다음은 <strong>전체 회원 조회 기능</strong>을 만들었다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/0eb9d719-fc0d-45db-9a97-97156b83ce65/image.png" alt="">전체 회원 조회 기능은 매우 간단하다.</p>
<pre><code>public List&lt;Member&gt; findMembers(){
       return memberRepository.findAll();
    }</code></pre><p>반환 타입이 List<Member>였기 때문에 return 만 해주면 간단하게 끝이 난다.</p>
<pre><code>public Optional&lt;Member&gt; findOne(Long memberId)  {
        return memberRepository.findById(memberId);
    }</code></pre><p>  이 코드는 findById를 했을 때 memberId를 반환해주는 코드이다.</p>
<h3 id="검증해보기">검증해보기</h3>
<p>  여러 가지 방법이 있는데 제일 좋은 방법은 이전에 배웠던 테스트 케이스를 작성하는 방법이다.
  그 방법은 다음 시간에...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 리포지토리 테스트 케이스 작성]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%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/@s_em_tudy/Spring-Boot%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>Mon, 08 May 2023 11:21:46 GMT</pubDate>
            <description><![CDATA[<h3 id="회원-리포지토리-테스트-케이스-작성">회원 리포지토리 테스트 케이스 작성</h3>
<p>회원 리포지토리 클래스가 원하는 대로 동작하는가를 검증하는 방법</p>
<p>개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다. </p>
<p>이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고, 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다. </p>
<p>자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.</p>
<h3 id="직접-실행해보기">직접 실행해보기</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/fc0f323f-9095-4f67-9bd2-32113f766c48/image.png" alt="">테스트 케이스를 작성해보기 위해서 test - java - hello - hellospring 밑에 repository라는 패키지를 생성하였다.</p>
<pre><code>package hello.hellospring.repository;

import org.junit.jupiter.api.Test;

public class MemoryMemberRespositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save(){


    }</code></pre><p>save기능이 잘 동작하는지 실행시켜보기 위해 만든 테스트 코드이다.</p>
<p><strong>@Test</strong>를 입력하고, import org.junit.jupiter.api.Test;와 같이 <strong>Test를 import</strong> 해주었다.
<img src="https://velog.velcdn.com/images/s_em_tudy/post/b9482913-b673-4ff8-8a58-320b15a7e187/image.png" alt="">그 후 run save()해보았더니 위 사진과 같이 메소드가 실행되었다는 녹색 표시가 뜬다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5c37c176-1703-45f6-95a4-c4f35ba17d0a/image.png" alt="">다음은 저장이 정말 제대로 잘 되는지 테스트 코드를 작성해서 돌려보았다.</p>
<pre><code>@Test
    public void save(){
        Member member = new Member();
        member.setName(&quot;spring&quot;);
        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        System.out.println(&quot;result = &quot; + (result == member));
    }</code></pre><p>-&gt; 우선 Member 객체 member을 생성하였다.</p>
<p>member의 이름을 spring이라고 설정하였다.</p>
<p><em>*Tip) command + shift + enter를 누르면 spring 입력 후 바로 다음 줄로 엔터가 가능하다.</em></p>
<p>그 후 repository에 member를 save 하였다.</p>
<p>그리고 repository.findById(member.getId())으로 검증을 하는데,
타입이 *<em>Optional *</em>이었다. </p>
<p>-&gt; Optional에서 값을 꺼낼 때는 
<strong>repository.findById(member.getId()).get();</strong>를 사용한다.
**get()으로 값을 바로 꺼내는 것이 좋은 방법은 아니지만, 우선 테스트 코드이므로 감안하자.</p>
<p>값을 바로 꺼냈으므르 코드를</p>
<blockquote>
<p>Member result = repository.findById(member.getId()).get(); </p>
</blockquote>
<p>로 수정하였다.</p>
<p>새로 저장한 member name과 DB의 name이 똑같으면 참인 것이므로 
일단 출력문으로 작성을 해보았다.</p>
<blockquote>
<p>System.out.println(&quot;result = &quot; + (result == member));</p>
</blockquote>
<p>실행시켜보면 *<em>result = true *</em>라는 글자가 뜬다.</p>
<p>위처럼 출력해서 확인해보는 방법도 있으나, 계속 글자로 출력시켜볼 수는 없으니
<strong>&#39;assert&#39;</strong> 라는 기능을 쓴다.</p>
<blockquote>
<p>Assertions.assertEquals(member, result);</p>
</blockquote>
<p>member와 result가 같은지 판별해주는 기능을 가진다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/6b7482f7-f0dc-4711-bc70-f29098f770f5/image.png" alt="">코드를 실행해보면 
System.out.println 문으로 작성한 것처럼 result = true라는 글자가 뜨진 않지만, 
왼쪽 아래 실행 결과 창에 Test Results가 초록색 체크표시가 되어 있는 것을 볼 수 있다. </p>
<p>코드가 옳다는 뜻이다.</p>
<p>그렇다면 반대로 옳지 않은 결과를 내게 할 수 있도록 result를 적은 자리에 <strong>null</strong>을 넣어보자.<img src="https://velog.velcdn.com/images/s_em_tudy/post/9adb5d20-0a4a-4401-87c7-7ac5f773979d/image.png" alt=""><strong>Expected :hello.hellospring.domain.Member@38089a5a
Actual :null</strong><br>과 같은 경고가 뜬다.</p>
<p>기대했던 것과 다르게 실제 값으로 null이 들어왔다는 뜻의 경고문이다.</p>
<blockquote>
<p> Assertions.assertThat(member).isEqualTo(result);</p>
</blockquote>
<p>또 하나의 방법은 위 코드를 쓰는 방법인데, </p>
<blockquote>
<p>import static org.assertj.core.api.AssertionsForClassTypes.assertThat; </p>
</blockquote>
<p>를 해주면 다음부터 Assertions를 쓰지 않아도 assertThat를 사용 가능하다.</p>
<p>**강의에서는 아래 코드를 쓰라고 나와 있었지만, 왜인지 모르게 나는 위 코드를 써야만 제대로 동작했다.</p>
<p>(import static org.junit.jupiter.api.Assertions.*;)</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/bf35a1b2-794e-45d7-a977-84915cbd33d1/image.png" alt="">다음 테스트해 볼 것은 <strong>findByName</strong>이었다.</p>
<pre><code> @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName(&quot;spring1&quot;);
        repository.save(member1);

        Member member2 = new Member();
        member2.setName(&quot;spring2&quot;);
        repository.save(member2);

        Member result = repository.findByName(&quot;spring1&quot;).get();

        assertThat(result).isEqualTo(member1);

    }</code></pre><p>save를 테스트 해 봤던 것과 동일하게
Member 객체를 생성하고, member1의 이름을 spring1이라고 지정한 후,
저장소에 저장하였다.</p>
<p>그 후 result 객체에 spring1을 get()을 써서 꺼내주어 저장하고,</p>
<p>assertThat으로 동일한지 비교를 하였다.</p>
<p><em><em>원래는 *</em>Optional<Member> result = repository.findByName(&quot;spring1&quot;);** 이지만, 
  get()을 쓰면 Optional에서 꺼내서 result에 저장이 가능하다.</em></p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/57ca23f7-1f85-4c40-9878-dc971277dbc2/image.png" alt="">&quot;spring1&quot;을 &quot;spring2&quot;로 바꾸면 assertThat(result).isEqualTo(member1)을 그대로 놔뒀을 때, 다른 객체라는 경고가 뜰 것이다.</p>
<p>  따라서 member1도 함께 member2라고 바꿔주어야 한다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/07facd5a-9470-493b-8818-7d0a4c67868e/image.png" alt="">초록창을 보니 마음이 아주 편안~하다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/2294c787-1368-45a9-a25e-0ea39f2d1c28/image.png" alt=""><strong>TestCase의 장점</strong>은, class레벨에서 run을 하면 모든 Test가 돌아간다는 것이다.</p>
<p>  위 사진처럼 class MemoryMemberRepositoryTest에서 run을 실행하니 save와 findByName의 결과가 함께 뜨는 것을 확인할 수 있다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/41e34cee-c1f8-4d62-8e03-a1ec4875329f/image.png" alt="">이번엔 *<em>findAll *</em>Testcase 코드를 작성하였다.</p>
<pre><code>@Test
    public void findAll() {
        Member member1 = new Member();
        member1.setName(&quot;spring1&quot;);
        repository.save(member1);

        Member member2 = new Member();
        member2.setName(&quot;spring2&quot;);
        repository.save(member2);

        List&lt;Member&gt; result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }</code></pre><blockquote>
<p>assertThat(result.size()).isEqualTo(2);</p>
</blockquote>
<p>  위 코드는 isEqualTo 결과의 기댓값이 2개라는 뜻이다.</p>
<p>  실제 결과도 2개가 맞으므로 제대로 실행되는 걸 확인해 볼 수 있다.</p>
<p>isEqualTo(3)로 바꾸면</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/5b471de9-3468-4120-8bdf-034bcc02069e/image.png" alt="">사용자가 기대한 값은 3이었지만 실제론 2개라는 경고가 뜬다.</p>
<p>이제 모든 testcase를 작성했으니 <strong>전체 run</strong>을 실행해보자.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/7ea2e147-376d-4e1e-aadf-17a931253b40/image.png" alt="">어라? 그런데 갑자기 실행창에 경고가 떴다.</p>
<p>  <strong><em>이유가 무엇일까?</em></strong></p>
<blockquote>
<p>  &quot;test들은 서로 순서에 관한 의존관계 없이 설계 되어야 한다&quot;</p>
</blockquote>
<p>그러나 나의 코드를 살펴보면, 
  findAll에서 setName해주었던 것과 findByName에서 setName해주었던 것이 겹친다.</p>
<p> 그래서 먼저 돌아간 findAll만 실행이 정상적으로 되고, findByname에서 오류가 떴던 것이다.</p>
<p>  그렇다면 이 오류를 어떻게 수정할까?</p>
<p>  test가 하나 끝나고 나면, data를 깔끔하게 clear 해주어야 한다.</p>
<p>  관련 코드는 아래 코드이다.</p>
<pre><code> @AfterEach
    public void afterEach(){

    }
</code></pre><p>**  @AfterEach는, 메소드가 끝날때마다 callback을 해주는 메소드이다.**</p>
<p>  save가 끝나고 호출되고, findByname이 끝나고 호출되고, findAll이 끝나고 호출된다.</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/494955d6-9dc0-4b0d-9775-c4b44127a09d/image.png" alt="">위 코드를 test - java - hellospring - repository 아래의 <strong>MemoryMemberRepositoryTest</strong>에 추가하고 나서,</p>
<p>  <img src="https://velog.velcdn.com/images/s_em_tudy/post/c7d6cdec-4421-453a-a62f-b4abc0f7ba3b/image.png" alt="">main - java - hellospring - repository 아래의 <strong>MemoryMemberReposity</strong>에 </p>
<pre><code> public void clearStore(){
        store.clear();
    }</code></pre><p>-&gt; store을 clear해주는 위 코드를 작성한다.</p>
<p>  그리고 다시 <strong>MemoryMemberRepositoryTest</strong>에 돌아가서,</p>
<pre><code> @AfterEach
    public void afterEach(){
        repository.clearStore();
    }
</code></pre><p>  AfterEach 내부 코드를 채워넣는다.</p>
<p> .
 .
 .
 .
 .
  여태까지의 이 과정(작품을 만들고, 틀을 짜 보는 것)과 반대로,
  별 모양 작품을 만들고 싶은데, 미리 검증 하기 위한 별모양 틀을 만들어 놓고 작품이 완성되면 그 틀에 맞는지 맞춰보는 것.</p>
<p> 이것을 <strong>test 주도 개발, TTD</strong>라고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_회원 도메인과 리포지토리 만들기]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EB%8F%84%EB%A9%94%EC%9D%B8%EA%B3%BC-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%ED%9A%8C%EC%9B%90-%EB%8F%84%EB%A9%94%EC%9D%B8%EA%B3%BC-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 07 May 2023 20:51:21 GMT</pubDate>
            <description><![CDATA[<h3 id="회원-도메인과-리포지토리-만들기">회원 도메인과 리포지토리 만들기</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/effcafe3-a6b6-4a1c-b042-adccebe6382e/image.png" alt="">hello.hellospring 아래에 domain이라는 패키지를 생성하고, Member라는 자바 클래스를 하나 생성하였다. 
요구사항에서 id식별자와 이름은 있어야 한다는 내용이 있었기 때문에, 코드를 위 사진처럼 짜 두었다.
id는 임의의 값(시스템이 저장하는 id)이고, name은 이름이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/a442855e-6000-4f83-b288-f1ce9cc6b261/image.png" alt="">getter와 setter도 설정을 해 주었다.</p>
<p>hello.hellospring 밑에 repository라는 패키지를 하나 더 생성하고, MemberRepository라는 인터페이스를 하나 생성하여서 아래와 같은 코드를 입력하였다.</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();
}
회원 리포지토리 메모리 구현체
package hello.hellospring.reposi</code></pre><p>Member save(Member member);는 회원을 저장하면, 저장된 회원이 반환되는 코드이다.
Optional<Member> findById(Long id);는 findById로 방금 만든 id로 회원을 찾는 코드이다.
**Optional: findById나 findByName으로 가져올 때 null일 경우 Optional로 감싸서 반환한다.
Member save로 회원을 저장하였기 때문에 findById와 findByName으로 회원을 찾아올 수 있다.
List<Member> findAll();는 지금까지 저장된 모든 회원 list를 반환해주는 코드이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/1127c666-c4c7-413e-abd9-3de984d43322/image.png" alt="">이제 구현체를 만들기 위해 repository밑에 MemoryMemberRepository라는 자바 클래스를 하나 더 만들었다. 그리고 위 사진처럼 MemberRepository를 implements 해준 후,
  option + enter를 눌러서 implement method를 해준다.</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();
 }
   @Override
  public List&lt;Member&gt; findAll() {
        return new ArrayList&lt;&gt;(store.values());
    }
 public void clearStore() {
 store.clear();
 }
}</code></pre><p>위 코드를 그대로 작성하였다.</p>
<blockquote>
<p>private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;();</p>
</blockquote>
<p>위 코드는 저장을 하는 코드이다. key는 Long, 값은 Member로 해준 뒤, Map을 import 해주었다.</p>
<blockquote>
<p> private static long sequence = 0L;</p>
</blockquote>
<p>  sequence는 0, 1, 2 와 같은 키값을 생성해준다.</p>
<blockquote>
<p>  public Member save(Member member) {
 member.setId(++sequence);
 store.put(member.getId(), member);
 return member;
 }</p>
</blockquote>
<p>  member을 save하는 코드인데, 일단 sequence값을 하나 올려준다.
  (이미 이름은 save하기 전에 넘어 온 상태)
  store에 id 값을 넣어준다. 
  (name은 고객이 세팅을 하고, id는 시스템이 정해준다)
  return member 해준다.</p>
<blockquote>
<p>  public Optional<Member> findById(Long id) {
 return Optional.ofNullable(store.get(id));
 }</p>
</blockquote>
<p>  findById는 return store.get(id);를 해주면 되지만, 결과가 null일 때를 대비하여
  Optional로 감싸준다.</p>
<blockquote>
<p>  public Optional<Member> findByName(String name) {
 return store.values().stream()
 .filter(member -&gt; member.getName().equals(name))
 .findAny();
 }</p>
</blockquote>
<p>  람다식을 사용.
  member.getName()이 parameter로 넘어온 name이랑 같은지 확인한다.
  같은 경우에는 filtering을 하고, 찾으면 반환한다. 끝까지 돌렸는데 없으면 null을 반환한다.</p>
<blockquote>
<p>   @Override
  public List<Member> findAll() {
        return new ArrayList&lt;&gt;(store.values());
    }</p>
</blockquote>
<p>  store.values()는 member들이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_비즈니스 요구사항 정리]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-Boot%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</link>
            <guid>https://velog.io/@s_em_tudy/Spring-Boot%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</guid>
            <pubDate>Sun, 07 May 2023 19:46:06 GMT</pubDate>
            <description><![CDATA[<h4 id="회원-관리-예제---백엔드-개발">&lt;회원 관리 예제 - 백엔드 개발&gt;</h4>
<h4 id="-비즈니스-요구사항-정리">-비즈니스 요구사항 정리</h4>
<h4 id="-회원-도메인과-리포지토리-만들기">-회원 도메인과 리포지토리 만들기</h4>
<h4 id="-회원-리포지토리-테스트-케이스-작성">-회원 리포지토리 테스트 케이스 작성</h4>
<h4 id="-회원-서비스-개발">-회원 서비스 개발</h4>
<h4 id="-회원-서비스-테스트">-회원 서비스 테스트</h4>
<h3 id="비즈니스-요구사항-정리">비즈니스 요구사항 정리</h3>
<p>-데이터: 회원ID, 이름
-기능: 회원 등록, 조회
-아직 데이터 저장소가 선정되지 않음(가상의 시나리오) /아직 DB가 선정되지 않았다는 전제 하에</p>
<h3 id="일반적인-웹-애플리케이션-계층-구조">일반적인 웹 애플리케이션 계층 구조</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/8d41ac4e-8e98-4e96-89d7-c1ea6d0f9956/image.png" alt=""></p>
<p>컨트롤러, 서비스, 리포지토리, 도메인객체, DB로 구성이 된다.
-컨트롤러: 웹 MVC의 컨트롤러 역할
-서비스: 핵심 비즈니스 로직 구현 ex)회원은 중복 가입이 안 됨
 비즈니스 도메인 객체를 가지고 핵심 비즈니스 로직을 구현한 계층.
-리포지토리: 데이터베이스에 접근. 도메인 객체를 DB에 저장하고 관리
-도메인: 비즈니스 도메인 객체 ex)회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨</p>
<h3 id="클래스-의존관계">클래스 의존관계</h3>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/ce2cf556-9dbd-4928-b521-4f2121606342/image.png" alt=""></p>
<p>회원 비즈니스 로직에는 MemberService가 있음
MemberRepository(회원저장소)는 interface로 설계를 할 것임
why?_ 아직 데이터 저장소가 선정되지 않았다는 가정이기 때문에</p>
<p>구현체는 Memory구현체로 만들 것임 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot_API]]></title>
            <link>https://velog.io/@s_em_tudy/Spring-BootAPI</link>
            <guid>https://velog.io/@s_em_tudy/Spring-BootAPI</guid>
            <pubDate>Sun, 07 May 2023 19:33:05 GMT</pubDate>
            <description><![CDATA[<h3 id="api">API</h3>
<p>스프링 웹 개발에서 이야기하는 API방식에 대한 이야기.</p>
<p>정적 컨텐츠 방식을 제외하면, 이전에 봤던 MVC방식에서 view를 찾아서 템플릿 엔진을 통해 화면을 렌더링하여 html을 웹 브라우저에 넘겨주는 방식이 있고, 오늘 얘기할 API 방식이 있다.</p>
<h4 id="직접-코딩해보기">직접 코딩해보기</h4>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/5c5dddd8-963d-478c-975a-177e15e1ea2e/image.png" alt=""></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><p>위 사진처럼 HelloController에 위 코드를 추가해 주었다.
여기서 @ResponseBody를 꼭 추가해 주어야 한다. 
ResponseBody의 의미는
http의 header부분과 body부분이 있는데, <strong><em>body부분에 &quot;hello &quot; + name이라는 data를 직접 넣어주겠다는 뜻이다.</em></strong></p>
<p>String name에 spring이라고 값을 넣으면, return문은 hello spring이라고 바뀔 것이다.
문자가 내가 요청한 클라이언트에 그대로 내려간다는 뜻이다.
이전 템플릿 엔진과의 차이는 view가 없이 문자가 그대로!!! 내려간다는 점이 차이점이다.
<img src="https://velog.velcdn.com/images/s_em_tudy/post/13ba14d3-1bd4-4f65-a017-67cac3878f70/image.png" alt="">코드를 실행시켜 웹 브라우저에 localhost:8080/hello-string?name=spring!!!이라고 치고 접속을 해 보았더니 문자가 그대로 화면에 출력되었다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/8a8870ab-8f42-454d-aa64-045df3b0ad20/image.png" alt="">페이지 소스보기를 해봤더니 다른 html 태그가 일절 없이 hello spring!!!만 떠 있는 것을 볼 수 있다.</p>
<p>이전의 템플릿 엔진은 view라는 템플릿이 있는 상황에서 조작하는 방식이었다면, 이 API 방식은 문자를 그대로 내려주는 방식인 것이다.</p>
<h4 id="사실-위에-한-것은-별-의미가-없다-문자가-아니라-데이터를-내려달라고-했을-경우-때문에-api-방식을-사용하는-것이다">사실 위에 한 것은 별 의미가 없다. 문자가 아니라 데이터를 내려달라고 했을 경우 때문에 API 방식을 사용하는 것이다.</h4>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/2407ec3d-78cd-421c-a6d5-582960ea83f0/image.png" alt="">위 사진처럼 HelloController에 아래 코드를 추가해 보았다.</p>
<pre><code>@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><p>static class Hello라는 class를 만들어서 getter와 setter를 추가하고,
hello라는 객체를 만들어서 setter에 name을 주고, 값이 아닌 객체를 반환시켜 본 코드이다.</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/f18c499c-f371-4891-8f48-fe3c52ff0692/image.png" alt="">그 후 주소창에 /hello-api?name=spring!!!을 쳐서 접속했더니, 이전과는 다른 화면이 나타났다.</p>
<p>이는 json이라는 방식이다.
json은 key value로 이루어진 구조이다. {&quot;name&quot;:&quot;spring!!!&quot;}</p>
<p>과거에는 xml방식도 많이 쓰였다.
그런데 xml방식은 무겁고, 태그를 여러 번 써야한다는 단점이 있지만, 
json방식은 간단하기 때문에 최근 프로젝트를 하게 되면 모두 json방식을 쓴다.</p>
<p>**Getter Setter 단축키 : ctrl + enter</p>
<p><img src="https://velog.velcdn.com/images/s_em_tudy/post/b90ea32b-8d85-4577-aa8b-abad060f3e52/image.png" alt="">@ResponseBody 사용 원리를 그림으로 설명해보면 이렇다.</p>
<ol>
<li>웹 브라우저에서 localhost:8080/hello-api를 입력한다.</li>
<li>tomcat 내장 서버에서 hello-api가 왔다고 스프링에 던져준다.</li>
<li>스프링이 helloController에서 hello-api를 찾는다.</li>
<li>템플릿에서는 view resolver에게 던졌지만, @ResponseBody를 발견하면 스프링은 http 응답에 데이터를 그대로 넣어주는 동작을 한다.
but! 문자가 아니라 객체가 반환이 되어야 할 때, 스프링은 어떻게 해줄까?
객체가 오면 디폴트로 jason 방식의 데이터를 만들어서, http 응답에 반환을 한다.
어떻게??
ResponseBody가 나오면, HttpMessageConverter가 동작을 한다.(템플릿 방식에서는 viewResolver가 동작)
만약 도착한 값이 문자면, StringConverter가 동작을 하고, 객체면 JsonConverter가 동작을 하게 된다.
이 JsonConverter가 객체를 Json스타일로 바꾼다.
Json은 바꾼 객체를 요청한 웹 브라우저에 반환을 해준다.</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>