<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-easy.log</title>
        <link>https://velog.io/</link>
        <description>안녕하세요</description>
        <lastBuildDate>Sun, 01 May 2022 12:26:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-easy.log</title>
            <url>https://velog.velcdn.com/images/dev-easy/profile/66ea9684-bbd6-4c27-979b-deb03df6e29c/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-easy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-easy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[스프링 입문(4) 스프링 빈과 의존관계]]></title>
            <link>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B84-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B84-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</guid>
            <pubDate>Sun, 01 May 2022 12:26:46 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>이번 강의에서는 스프링 빈을 어떻게 등록하고, 의존관계가 어떻게 설정되는지 알아볼 것이다.</p>
<h3 id="컴포넌트-스캔과-자동-의존관계-설정">컴포넌트 스캔과 자동 의존관계 설정</h3>
<p>Spring 이 실행 될 때, Spring Container 라는 통이 하나 생긴다. 그 안에 <code>@Controller</code> 라는 어노테이션이 있으면, 해당 어노테이션을 가진 객체를 생성해서 Spring 에 넣어준다. 그리고 Spring 에서 관리해준다.
<a href="https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9E%85%EB%AC%B83-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C">스프링 입문(3) 회원 관리 예제-백엔드 개발</a> 에서 작성한 <code>MemberController</code> 를 기반으로 설명할 예정이다.</p>
<h4 id="회원-컨트롤러에-의존관계-추가">회원 컨트롤러에 의존관계 추가</h4>
<p>회원 컨트롤러(MemberController)가 회원 서비스와 회원 리포지토리를 사용할 수 있도록 의존 관계를 준비할 것이다.</p>
<ol>
<li><code>MemberService</code>에 <code>@Autowired</code> 붙여주기</li>
</ol>
<pre><code class="language-java">  package com.hello.hellospring.controller;

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

  @Controller
  public class MemberController {

      private final MemberService memberService;

      // @Autowired 는 Spring container에서 MemberService를 가져온다.
      @Autowired
      public MemberController(MemberService memberService) {
          this.memberService = memberService;
      }
  }</code></pre>
<ul>
<li>생성자에 <code>@Autowired</code> 가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. 이렇게 객체 의존관계를 외부에서 넣어주는 것을 <code>의존성 주입, DI(Dependency Injection)</code> 이라고 한다.</li>
<li><a href="https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9E%85%EB%AC%B83-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C">스프링 입문(3) 회원 관리 예제-백엔드 개발</a> 에서는 개발자가 직접 주입했고, 여기서는 <code>@Autowired</code> 에 의해 스프링이 주입해준다.<br>

</li>
</ul>
<ol start="2">
<li><p>회원 서비스 스프링 빈 등록</p>
<ul>
<li><code>MemberService</code> 클래스에 <code>@Service</code> 어노테이션 붙여주기</li>
<li><code>MemberController</code> 안의 <code>MemberService</code>에 <code>@Autowired</code> 를 붙여줬다고 해서, 의존성 관계가 바로 연결되지 않는다.</li>
<li><code>MemberService</code> 클래스에 <code>@Service</code> 어노테이션을 붙여줘야지만 스프링 빈으로 자동 등록되어 자동 의존관계가 설정되는 것이다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">    package com.hello.hellospring.service;

    import com.hello.hellospring.domain.Member;
    import com.hello.hellospring.repository.MemberRepository;
    import org.springframework.stereotype.Service;

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

    @Service
    public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }</code></pre>
<ol start="3">
<li>회원 리포지토리 스프링 빈 등록</li>
</ol>
<pre><code class="language-java">    @Repository
    public class MemoryMemberRepository implements MemberRepository {}</code></pre>
<br>

<p>이제 위 세가지 과정을 통해, <code>MemberService</code> 와 <code>MemberRepository</code> 가 스프링 컨테이너에 스프링 빈으로 등록되었다.</p>
<blockquote>
<p>참고 : 스프링은 스프링 컨테이너에 빈을 등록할 때, 기본적으로 싱글톤으로 등록한다. ( 유일하게 하나만 등록해서 그 객체를 공유 )
따라서 같은 스프링 빈이라면 모두 같은 인스턴스다. 설정을 통해 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하고 대부분 싱글톤을 사용한다.</p>
</blockquote>
<br>

<h3 id="스프링-빈-등록-방법">스프링 빈 등록 방법</h3>
<p>스프링 빈을 등록하는 데에는 2가지 방법이 있다.</p>
<ul>
<li>컴포넌트 스캔과 자동 의존관계 설정</li>
<li>자바 코드로 직접 스프링 빈 등록</li>
</ul>
<p><a href="#%ED%9A%8C%EC%9B%90-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC%EC%97%90-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%B6%94%EA%B0%80">회원 컨트롤러에 의존관계 추가</a> 에서는 컴포넌트 스캔을 통해 자동 의존관계를 등록한 것이다.</p>
<p>컴포넌트 스캔 원리</p>
<ol>
<li><code>@Component</code> 어노테이션이 있으면 스프링 빈으로 자동 등록된다.</li>
<li><code>@Controller</code>, <code>@Service</code>, <code>@Repository</code> 모두 <code>@Component</code>를 포함하고 있기 때문에, 해당 어노테이션을 붙여주면 스프링 빈으로 자동 등록되는 것이다.</li>
</ol>
<br>

<p><strong><em>실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.
그리고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하는 경우라면 설정을 통해서 직접 자바 코드로 스프링 빈을 등록한다.</em></strong></p>
<blockquote>
<p>해당 강의에서는, 향후 MemoryMemberRepository 를 다른 리포지토리로 변경할 예정이므로, 컴포넌트 스캔 방식 대신에 자바 코드로 스프링 빈을 설정하겠다. </p>
</blockquote>
<br>

<h3 id="자바-코드로-직접-스프링-빈-등록">자바 코드로 직접 스프링 빈 등록</h3>
<p>이번에는 직접 자바 코드를 통해서 스프링 빈을 등록해 볼 것이다.</p>
<ol>
<li>먼저 회원 서비스와 회원 리포지토리의 <code>@Service</code>, <code>@Repository</code> , <code>@Autowired</code> 어노테이션을 제거한다.</li>
<li><code>SpringConfig</code> 클래스를 만들고, 아래와 같이 코드를 작성한다.</li>
</ol>
<pre><code class="language-java">package com.hello.hellospring;

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


/*
    @Configuration 을 통해 spring이 뜰 때 @Configuraiton을 읽고 MemberService를 스프링빈에 등록해준다.
 */
@Configuration
public class SpringConfig {

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

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

}</code></pre>
<ul>
<li><code>@Configuration</code> 과 <code>@Bean</code> 어노테이션을 통해 스프링 빈으로 등록이 가능하다. </li>
<li><code>@Configuration</code> 을 통해 spring이 뜰 때 <code>@Configuraiton</code> 을 읽고 <code>MemberService</code> 를 스프링 빈으로 등록해준다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Map을 Key, Value로 정렬하기]]></title>
            <link>https://velog.io/@dev-easy/Java-Map%EC%9D%84-Key-Value%EB%A1%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev-easy/Java-Map%EC%9D%84-Key-Value%EB%A1%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 29 Apr 2022 15:35:12 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>Java에서 HashMap 정렬이 필요할 때, 그 방법에 대해 알아볼 것이다.</p>
<p>정렬 기준은 key, value 두가지로 나눌 수 있다.</p>
<h3 id="1-key-값을-기준으로-정렬하기">1. Key 값을 기준으로 정렬하기</h3>
<p>map 의 keySet을 이용하여 정렬한다.
오름차순 시에는 Collection.sort(), 내림차순 시에는 Collection.reverse() 메소드를 사용하여 정렬한다.</p>
<pre><code class="language-java">import java.util.*;

public class Sort {

    public static void main(String[] args) {

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

        map.put(&quot;A&quot;, 10);
        map.put(&quot;D&quot;, 30);
        map.put(&quot;C&quot;, 20);
        map.put(&quot;B&quot;, 40);


        List&lt;String&gt; keySet = new ArrayList&lt;&gt;(map.keySet());

        // 키 값으로 오름차순 정렬
        Collections.sort(keySet);

        for (String key : keySet) {
            System.out.print(&quot;Key : &quot; + key);
            System.out.println(&quot;, Val : &quot; + map.get(key));
        }

        /*  결과
            Key : A, Val : 10
            Key : B, Val : 40
            Key : C, Val : 20
            Key : D, Val : 30
         */



        // 키 값으로 내림차순 정렬
        Collections.reverse(keySet);

        for (String key : keySet) {
            System.out.print(&quot;Key : &quot; + key);
            System.out.println(&quot;, Val : &quot; + map.get(key));
        }

        /*  결과
            Key : D, Val : 30
            Key : C, Val : 20
            Key : B, Val : 40
            Key : A, Val : 10
        */
    }
}</code></pre>
<hr>
<h3 id="2value-값을-기준으로-정렬하기">2.Value 값을 기준으로 정렬하기</h3>
<p>Value 값을 기준으로 정렬할 때는 comparator를 사용하여 정렬한다.</p>
<p>comparator는 람다 표현식으로 간단하게 표현할 수도 있다.</p>
<pre><code class="language-java">import java.util.*;

public class Sort {

    public static void main(String[] args) {

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

        map.put(&quot;A&quot;, 10);
        map.put(&quot;D&quot;, 30);
        map.put(&quot;C&quot;, 20);
        map.put(&quot;B&quot;, 40);


        List&lt;String&gt; keySet = new ArrayList&lt;&gt;(map.keySet());

        // Value 값으로 오름차순 정렬
        keySet.sort(new Comparator&lt;String&gt;() {
            @Override
            public int compare(String o1, String o2) {
                return map.get(o1).compareTo(map.get(o2));
            }
        });

        for (String key : keySet) {
            System.out.print(&quot;Key : &quot; + key);
            System.out.println(&quot;, Val : &quot; + map.get(key));
        }

        /*
            결과
            Key : A, Val : 10
            Key : C, Val : 20
            Key : D, Val : 30
            Key : B, Val : 40
         */

        // Value 값으로 내림차순 정렬
        // 위 comparator 람다 표현식으로
        keySet.sort((o1, o2) -&gt; map.get(o2).compareTo(map.get(o1)));

        for (String key : keySet) {
            System.out.print(&quot;Key : &quot; + key);
            System.out.println(&quot;, Val : &quot; + map.get(key));
        }

        /* 결과
            Key : B, Val : 40
            Key : D, Val : 30
            Key : C, Val : 20
            Key : A, Val : 10
         */
    }
}</code></pre>
<p>더 많은 방법이 있으나 가장 간단하게 정렬할 수 있는 방법을 알아보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 입문(3) 회원 관리 예제-백엔드 개발]]></title>
            <link>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9E%85%EB%AC%B83-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9E%85%EB%AC%B83-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 29 Apr 2022 15:28:09 GMT</pubDate>
            <description><![CDATA[<h2 id="회원-관리-예제---백엔드-개발">회원 관리 예제 - 백엔드 개발</h2>
<p>이번 포스팅에서는 간단한 회원 관리에 대한 비즈니스 요구사항을 실제 코드로 구현해 볼 것이다. 구현 후 테스트 코드까지 작성한다.</p>
<h3 id="비즈니스-요구사항-정리">비즈니스 요구사항 정리</h3>
<p>현재 비즈니스 요구사항은 아래 세가지이다.</p>
<ul>
<li>데이터 : 회원 ID, 이름</li>
<li>기능 : 회원 등록, 조회</li>
<li>아직 DB 는 선정되지 않았다고 가정</li>
</ul>
<h4 id="웹-애플리케이션-계층-구조">웹 애플리케이션 계층 구조</h4>
<p>일반적인 웹 애플리케이션 계층 구조는 다음과 같다. 아래와 같은 구조로 코드를 작성할 예정이다.
<img src="https://velog.velcdn.com/images/dev-easy/post/e769da3b-31ef-4c4a-9f0f-b1664ae4ab41/image.png" alt=""></p>
<ul>
<li>컨트롤러 : 웹 MVC의 컨트롤러 역할이다. </li>
<li>서비스 : 핵심적인 비즈니스 로직을 구현한다.</li>
<li>리포지토리 : 실제 DB에 접근하고 도매인 객체를 DB에 저장하고 관리한다.</li>
<li>도메인 : 비즈니스 도메인 객체이다. Ex) 회원, 주문, 쿠폰 등등 (Model 과 비슷한 개념일까..?)</li>
</ul>
<h4 id="클래스-의존관계-설정">클래스 의존관계 설정</h4>
<p align="center"><img src="https://velog.velcdn.com/images/dev-easy/post/2b5a3c71-0197-4f48-82f6-7abcd4430bf8/image.png" height="60%" width="60%"></p>

<ul>
<li>아직 DB가 선정되지 않아서, 인터페이스를 통해 구현 클래스를 변경할 수 있도록 설계한다.</li>
<li>DB는 RDB, NoSQL 등 다양한 저장소를 고민하는 상황으로 가정한다.</li>
<li>개발 진행을 위해 초기 개발 단계에서는 구현체로서 가벼운 메모리 기반의 DB를 사용할 것이다.</li>
</ul>
<hr>
<h3 id="회원-도메인과-리포지토리-만들기">회원 도메인과 리포지토리 만들기</h3>
<ol>
<li>회원 객체<ul>
<li>비즈니스 요구사항인 <code>id</code>,  <code>name</code> 값을 갖는 <code>Member</code>  객체 생성</li>
</ul>
</li>
</ol>
<pre><code class="language-java">   package com.hello.hellospring.domain;

   public class Member {

       private Long id;
       private String name;

       public Long getId() {
           return id;
       }

       public void setId(Long id) {
           this.id = id;
       }

       public String getName() {
           return name;
       }

       public void setName(String name) {
           this.name = name;
       }
   }</code></pre>
<ol start="2">
<li><p>회원 리포지토리 인터페이스</p>
<pre><code class="language-java">package com.hello.hellospring.repository;

import com.hello.hellospring.domain.Member;

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

public interface MemberRepository {

    Member save(Member member);
    Optional&lt;Member&gt; findById(Long id);
    Optional&lt;Member&gt; findByName(String name);
    List&lt;Member&gt; findAll();

}</code></pre>
<ul>
<li>Optional : java 8의 기능 중 하나이며, 가져오는 값이 Null 일 때를 대비해 Optional 객체로 감싸서 return 한다.<ul>
<li>Null 값 return 을 막을 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li><p>회원 리포지토리 메모리 구현체</p>
</li>
</ol>
<pre><code class="language-java">   package com.hello.hellospring.repository;

   import com.hello.hellospring.domain.Member;
   import java.util.*;

   public class MemoryMemberRepository implements MemberRepository {

       // 실무에서는 동시성 문제가 있어서 아래처럼 공유되는 변수일 때는 HasnMap 이 아닌 ConcurrentHashMap 을 쓴다.
       private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;();
       // key 값 생성
       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 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());
       }

       // Test code에서 사용
       public void clearStore() {
           store.clear();
       }
   }</code></pre>
<ul>
<li>실무에서는 동시성 문제를 고려하여 <code>HashMap</code> 대신 <code>ConcurrentHashMap</code>, <code>AtomicLong</code> 사용을 고려한다.</li>
</ul>
<hr>
<h3 id="회원-리포지토리-테스트-케이스-작성">회원 리포지토리 테스트 케이스 작성</h3>
<p>개발한 기능을 테스트할 때는 자바의 main 메소드 혹은 컨트롤러를 통해서 실행할 수 있다. 이러한 방법은 몇가지 단점이 있다.</p>
<ul>
<li>준비하고 실행하는 데 오래 걸림</li>
<li>반복 실행 어려움</li>
<li>여러 테스트를 한번에 실행하기 어려움</li>
</ul>
<p>따라서 Java에서는 <code>JUnit</code> 이라는 프레임 워크를 사용하여 테스트를 진행한다.</p>
<p>Test 케이스를 먼저 만들고, 이후 비즈니스 로직이나 repository를 구현하는 경우도 있다. 이것이 Test-Driven-Development(TDD) 테스트 주도 개발이다!</p>
<h4 id="junit-으로-테스트-케이스-작성하기">Junit 으로 테스트 케이스 작성하기</h4>
<ol>
<li><code>src/test/java</code> 하위 폴더에 생성한다.</li>
<li><code>@Test</code> 어노테이션을 메소드 위에 붙여주면 끝!</li>
</ol>
<h4 id="test-case-작성-시-유의점">Test Case 작성 시 유의점</h4>
<ul>
<li><p>네이밍</p>
<ul>
<li>보통 Test Class 네이밍은 테스트 하고자 하는 클래스명 뒤에 Test 를 붙인다.</li>
<li>테스트 메소드명은 직관적으로 한글로 적기도 한다. Build 시에 test 코드는 포함되지 않는다.</li>
</ul>
</li>
<li><p>검증 방법</p>
<ul>
<li><code>try~catch</code> 를 이용하여 테스트할 수도 있으나, <code>junit</code> 혹은 <code>assertj</code> 를 통해 검증할 수 있다.<ul>
<li>Junit : <code>Assertions.assertEquals</code> 으로 검증</li>
<li>assertj : <code>Assertions.assertThat(비교대상).isEqualTo(결과값)</code>  로 검증</li>
</ul>
</li>
<li>이외에도 많은 메소드들이 존재함</li>
</ul>
</li>
<li><p>테스트의 독립성</p>
<ul>
<li>테스트는 각각 독립적으로 실행되어야 한다. 다시 말해서 모든 테스트는 순서 관계없이 모두 정상적으로 동작해야 한다.<ul>
<li>테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아니다.</li>
<li>한번에 여러 테스트를 실행했을 때 메모리 DB에 직전 테스트 결과가 남을 수 있기 때문에 <code>@AfterEach</code> 를 사용하여 데이터를 삭제해준다.<ul>
<li><code>@AfterEach</code> : 각 테스트가 종료될 때마다 이 기능을 실행한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Given - When - Then 패턴</p>
<ul>
<li>Given :  테스트에서 구체화하고자 하는 행동을 시작하기 전에 테스트 상태를 설명하는 부분</li>
<li>When : 구체화하고자 하는 그 행동</li>
<li>Then : 어떤 특정한 행동 때문에 발생할 것으로 예상되는 변화에 대한 설명</li>
</ul>
</li>
</ul>
<pre><code class="language-java">package com.hello.hellospring.repository;

import com.hello.hellospring.domain.Member;
//import org.junit.jupiter.api.Assertions;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.*;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repo = new MemoryMemberRepository();

    // 각 테스트 메소드들이 끝나면 실행되도록! 여기서는 repo를 초기화해주자
    @AfterEach
    public void afterEach(){
        repo.clearStore();
    }

    @Test
    public void save() {
        Member member = new Member();
        member.setName(&quot;spring&quot;);

        repo.save(member);

        Member result = repo.findById(member.getId()).get();

//        Assertions.assertEquals(member, result);                    &gt; junit
//        Assertions.assertThat(member).isEqualTo(result);  &gt; assertj

        // member가 result와 같니?
        assertThat(member).isEqualTo(result);

        /* 
           참고 : assertThat 은 static이기 때문에, static import 로 선언해주면 위처럼 Assertions를 안써도 됨
        */
    }

    @Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName(&quot;spring1&quot;);
        repo.save(member1);

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

        Member result = repo.findByName(&quot;spring1&quot;).get();
        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {
        Member member1 = new Member();
        member1.setName(&quot;spring1&quot;);
        repo.save(member1);

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

        List&lt;Member&gt; result = repo.findAll();
        assertThat(result.size()).isEqualTo(2);
    }
}
</code></pre>
<hr>
<h3 id="회원-서비스-개발">회원 서비스 개발</h3>
<p>회원 리포지토리 개발을 테스트 케이스를 통해 검증을 마쳤다면, 서비스단을 구현한다.</p>
<ul>
<li>join() - 회원가입</li>
<li>validateDuplicateMember() - 이름 중복 회원 존재 여부 판단</li>
<li>findMembers() - 전체 회원 조회</li>
<li>findOne() - 회원 한명 조회</li>
</ul>
<pre><code class="language-java">package com.hello.hellospring.service;

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

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

public class MemberService {

    // DI
    private final MMemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     *  회원 가입
     *  같은 이름의 중복 회원 안됨
     */
    public Long join(Member member) {

        validateDuplicateMember(member); // 중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }

        /**
         *  중복 회원 검증
         *    findByName(member.getName()) 의 Return type 은 Optional 이다.
         *  Optional 객체는 ifPresent 메소드를 통해 값 존재 여부 판단이 가능하다.
     *  이 코드에서는 이름이 중복되는 경우 Exception 반환한다!
     */
    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -&gt; {
                    throw new IllegalStateException(&quot;이미 존재하는 회원입니다&quot;);
                });
    }

    /**
     *  전체 회원 조회
     *  repository 같은 경우는 메소드명 작성 시 데이터 넣고빼는 느낌
     *  service 단 부터는 좀더 비즈니스적인 직관적 네이밍 하자!
     */
    public List&lt;Member&gt; findMembers() {
        return memberRepository.findAll();
    }

    public Optional&lt;Member&gt; findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }

}</code></pre>
<hr>
<h3 id="회원-서비스-테스트">회원 서비스 테스트</h3>
<p>회원 리포지토리의 코드가 회원 서비스 코드를 DI(Dependency Injection) 가능하도록 한다.</p>
<p>테스트 코드 작성 시 정상 Flow도 고려해야 하지만, 예외의 경우를 더욱 신경써줘야 한다.</p>
<pre><code class="language-java">package com.hello.hellospring.service;

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

import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

//    MemberService memberService = new MemberService();
//    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    /*
        지금 위의 MemberService 에서 선언된 MemoryMemberRepository 와
        테스트 클래스에 선언된 memberRepository 는 다른 인스턴스이다.
        만약 서비스단의 repository 가 static이 아니라면 에러가 날 수 있다.
        &gt;&gt; Memberservice 에서 memberrepository를 외부에서 넣어주도록 생성자를 만든다. (DI)
     */

    MemberService memberService;
    MemoryMemberRepository memberRepository;

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


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


    @Test
    void 회원가입() {

        // given
        Member member = new Member();
        member.setName(&quot;hello&quot;);

        // when : 뭘 검증할거냐 &gt; 회원가입
        Long saveId = memberService.join(member);

        // then : 회원가입 한 id 가 처음에 주어진 id 와 같야?
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    /*
        위 회원가입은 너무 단순함!
        테스트는 정상 flow도 중요하지만 예외 flow도 매우 중요하다.
     */

    @Test
    public void 중복_회원_예외() {

        // given &gt;&gt; 이름이 같은 회원 두명이 있다.
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);

        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);

        // when &gt;&gt; 이름 같은 회원 가입해보자
        memberService.join(member1);

        // try catch 로도 검증할 수 있지만, assertThrows를 이용해 보자
        /*
            try {
                memberService.join(member2);
                // 만약 catch에 안걸리고 아래 코드가 수행된다면
                fail();
            } catch (IllegalStateException e) {
                assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다&quot;);
            }
        */

        // memberService.join(member2) 실행 시 IllegalStateException이 발생하는가?
        assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));
        IllegalStateException e = assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다&quot;);

    }

}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 입문(2) 스프링 웹 개발 기초]]></title>
            <link>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9E%85%EB%AC%B82-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9E%85%EB%AC%B82-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 29 Apr 2022 15:14:01 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-웹-개발-기초">스프링 웹 개발 기초</h2>
<p>이번 강의에서는 웹 개발하는 방법을 크게 세가지로 나누고 정리해 보았다. 크게 정적 컨텐츠, MVC와 템플릿 엔진, API 방식이 있다.</p>
<h3 id="정적-컨텐츠">정적 컨텐츠</h3>
<ul>
<li>정적 컨텐츠는 서버에서 처리하는 것 없이, html 파일 그대로를 웹 브라우저에 전달한다.</li>
<li><a href="https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content">스프링 공식 문서</a> 에 나와 있듯이, <code>/resource/static</code> 폴더 안의 html을 spring 내에서 읽어서 바로 띄워줄 수 있다.
<img src="https://velog.velcdn.com/images/dev-easy/post/2370aa26-7cd0-4c93-ba0a-2fd78016c4bf/image.png" alt=""></li>
<li><code>http://localhost:8080/hello-static.html</code> 로 접속하면 컨트롤러를 타지 않고 바로 hello-static.html을 띄워준다.</li>
</ul>
<hr>
<h3 id="mvc-와-템플릿-엔진">MVC 와 템플릿 엔진</h3>
<ul>
<li><p>서버단에서 html을 동적으로 변경하여 웹 브라우저로 전달한다. 예를 들면 jsp</p>
</li>
<li><p>Model, View, Controller(MVC 패턴)</p>
<ul>
<li>View 는 화면을 그리는 것에만 모든 역량을 집중해야 한다.</li>
<li>Controller 는 비즈니스 로직에 집중해야 한다.</li>
</ul>
</li>
<li><p>요즘의 개발 패턴이나, 이미 레거시하다.
<img src="https://velog.velcdn.com/images/dev-easy/post/7d84b7d2-3f5c-4d7a-84cc-4dde2c0fb5e1/image.png" alt=""></p>
</li>
<li><p>controller 에서 ViewResolver로 던진다</p>
<pre><code class="language-java">@GetMapping(&quot;hello-mvc&quot;) 
public String helloMvc(@RequestParam(&quot;name&quot;) String name, Model model) {
  model.addAttribute(&quot;name&quot;, name); // hello-template.html에 name을 동적으로 넣어주고 웹 브라우저로 반환 
  return &quot;hello-template&quot;; 
}</code></pre>
</li>
</ul>
<hr>
<h3 id="api">API</h3>
<ul>
<li><p>안드로이드, IOS 등 개발 시 JSON 구조의 포맷으로 클라이언트에게 데이터를 전달한다.</p>
</li>
<li><p>Vue, React 등의 클라이언트 혹은 서버끼리 통신 시에도 사용된다.
<img src="https://velog.velcdn.com/images/dev-easy/post/6c5b1a1f-90d7-4aac-a1e5-0ef750af0197/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><code>@ResponseBody</code> 가 붙어있다면 ViewResolver에게 보내지 않고 그대로 데이터를 return 한다. ( return 내용을 http body부에 직접 넣어줌)</li>
</ul>
<pre><code class="language-java">  @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; 

      /*  getter setter 
          java bean 표준 방식 or property 접근 방식 이라고 부름 
      */ 
      public String getName() { 
          return name;
      } 
      public void setName(String name) { 
          this.name = name; 
      } 
  }</code></pre>
<ul>
<li>return 값이 객체인 경우 Default가 JSON 방식이기 때문에 JSON 데이터로 변환하여 http 응답으로 return 한다.<ul>
<li>단순 문자인 경우 StringConverter(StringHttpMessageConverter) 동작, 객체인 경우 JsonConverter(MappingJackson2HttpMessageConverter) 가 동작한다.<ul>
<li>객체를 JSON으로 변환해주는 라이브러리는 크게 두가지(Jackson, Gson &gt; spring 기본은 Jackson이다)</li>
<li>byte 처리 등, 기타 여러 HttpMessageConverter가 기본으로 등록되어 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h4 id="회고">회고</h4>
<p>이미 알고 있는 내용이지만 개념적으로 정리하는 시간이라서 좋았다. 빨리 완강하고 더 깊이있는 내용을 공부하고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 입문(1) 프로젝트 구성]]></title>
            <link>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%BD%94%EB%93%9C%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9B%B9-MVC-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A01-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@dev-easy/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%BD%94%EB%93%9C%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9B%B9-MVC-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A01-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Fri, 29 Apr 2022 14:17:16 GMT</pubDate>
            <description><![CDATA[<h2 id="강의를-시작하면서">강의를 시작하면서..</h2>
<p>스프링을 여러 번 학습했고, 현재 실무에서 사용하기도 하지만 기본 개념을 다시 다지자는 의미에서 기초 강의부터 따라가보자 한다.</p>
<p>개발자 관점에서 개념을 코드를 작성하면서 다뤄주고, 오래된 기술은 최대한 안쓰고.. 일단 첫 강의가 무료라서 우아한 형제들 김영한 강사님의 강의를 선택했다!
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/">스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술</a></p>
<h3 id="프로젝트-생성하기">프로젝트 생성하기</h3>
<p><a href="https://start.spring.io">스프링 부트 스타터 사이트</a> 에서 스프링 프로젝트를 생성한다.
위 사이트를 통해 손쉽게 스프링 프로젝트 생성이 가능하다.</p>
<ul>
<li>Project : Gradle</li>
<li>Language : Java</li>
<li>Spring Boot x.x.x<ul>
<li>뒤에 SNAPSHOT 등 아무것도 안붙은 버전 사용! ( 안정화된 버전임)</li>
</ul>
</li>
<li>Meta data<ul>
<li>Group : 보통 기업 도메인 명을 적음</li>
<li>Artifact : 실제 빌드될 때 나오는 결과물 이름</li>
</ul>
</li>
<li>Packaging : Jar</li>
<li>Java : 11</li>
<li>Dependencies<ul>
<li>Spring Web, Thymeleaf</li>
</ul>
</li>
</ul>
<h3 id="라이브러리-살펴보기">라이브러리 살펴보기</h3>
<ul>
<li><p>build.gradle 의 dependencies 의 라이브러리들을 가져온다.</p>
</li>
<li><p>기본적으로 <code>mavenCentral()</code> 이라는 원격 저장소에서 라이브러리를 가져온다.</p>
<pre><code class="language-java">  repositories {
     mavenCentral()
  }

  dependencies {
      implementation &#39;org.springframework.boot:spring-boot-starter-thymeleaf&#39;
      implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
      testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
  }</code></pre>
</li>
<li><p>Maven, Gradle 같은 빌드 툴들은 Spring 라이브러리의 의존 관계를 알아서 관리해준다.</p>
</li>
<li><p>예를 들어 spring-boot-starter-web 라이브러리를 가져오면, spring-boot-starter-tomacat 등 의존하고 있는 필요한 라이브러리들을 자동으로 가져온다.</p>
<ul>
<li>참고 : spring-boot-starter 자체에 tomcat 서버가 내장되어 있다! 예전처럼 톰캣 서버 로컬에 깔고 할필요 없음 ㅎㅎ</li>
</ul>
</li>
<li><p>로그 라이브러리</p>
<ul>
<li>slf4j : 인터페이스</li>
<li>logback : 구현체</li>
<li>위 두가지를 스프링에서는 표준 로그 라이브러리로 제공한다.</li>
</ul>
</li>
<li><p>Test 라이브러리</p>
<ul>
<li>Junit 쓰며 요즘은 5버전 쓴다.</li>
</ul>
</li>
<li><p>spring-boot-starter-thymeleaf : 타임리프 템플릿 엔진(View)</p>
</li>
<li><p>spring-boot-starter(공통) : 스프링 부트 + 스프링 코어 + 로깅</p>
</li>
</ul>
<h3 id="빌드">빌드</h3>
<ol>
<li>스프링 프로젝트 내부의 .gradlew 파일을 이용한다.</li>
<li>아래 커맨드를 사용하여 터미널에서 어플리케이션을 실행해볼 수 있다. (mac 기준)</li>
</ol>
<pre><code class="language-sql">&gt; ./gradlew build
&gt; cd build/libs
&gt; java -jar {프로젝트 네임}-0.0.1-SNAPSHOT.jar</code></pre>
<ol>
<li>참고 : <code>./gradlew clean build</code> 시 build 폴더가 깔끔하게 삭제되고 새로 빌드된다.</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>