๐Ÿƒ๐Ÿป‍โ™‚๏ธ ๊ธฐ๋ณธํ›ˆ๋ จ/Spring

[Spring] ์Šคํ”„๋ง ๊ธฐ์ดˆ (1)

hades1 2024. 7. 22. 15:04

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

Spring ํ”„๋กœ์ ํŠธ๋Š” https://start.spring.io/ ์—์„œ ์ƒ์„ฑํ•œ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ ์œ„์™€ ๊ฐ™์ด ์„ค์ • ํ›„ Generateํ•˜๋ฉด ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

 

๊ตฌ์„ฑ ํ•ญ๋ชฉ๋“ค์„ ์‚ดํŽด๋ณด์ž. src์—์„œ main๊ณผ test๋ฅผ ๋ถ„๋ฆฌํ•ด๋†“์•˜๋Š”๋ฐ, ์ด๋Š” test๊ฐ€ ์š”์ฆ˜ ๊ฐœ๋ฐœ์—์„œ ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

main์—์„œ java๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ํŒŒ์ผ์€ resources์— ์žˆ๋‹ค. gradle์€ ๋ฒ„์ „ ์„ค์ • ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

HelloSpringApplication์€ Application์„ ์‹คํ–‰์‹œํ‚ค๋Š”๋ฐ ์ž์ฒด์ ์œผ๋กœ ์„œ๋ฒ„ ๊ธฐ๋Šฅ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ†ฐ์บฃ์„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ์–ด์„œ, ์„œ๋ฒ„ ์—ญํ• ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.

 

Setting - Build - Gradle์—์„œ IntelliJ IDEA๋กœ ๋ฐ”๊พธ๋ฉด, ์ž์ฒด์ ์œผ๋กœ ์‹คํ–‰ํ•˜์—ฌ ๋นจ๋ผ์ง„๋‹ค.

 

build.Gradle์—๋Š” ๊ฐ€์ ธ์˜ค๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋งŽ์ง€ ์•Š์ง€๋งŒ, External Libraries๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ฌด์ง€ ๋งŽ๋‹ค. ์ด๋Š” ์˜์กด์„ฑ ๋•Œ๋ฌธ์— ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊นŒ์ง€ ๊ฐ™์ด ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ์ด๋‹ค.

 

์˜์กด ๊ด€๊ณ„๋Š” ์˜ค๋ฅธ์ชฝ Gradle์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (*)์€ ์ค‘๋ณต๋˜์–ด์„œ ์ œ๊ฑฐ๋˜์—ˆ๋‹ค๋Š” ๋œป์ด๊ณ , ๋”๋ธ” ํด๋ฆญํ•˜๋ฉด ์„ค์น˜๋œ ๊ณณ์œผ๋กœ ์ด๋™ํ•œ๋‹ค.

 

View ํ™˜๊ฒฝ ์„ค์ •

View๋ž€ ์œ ์ €์—๊ฒŒ ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด์„ ์˜๋ฏธํ•œ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ IP/static/resources/index.html๋กœ ์ ‘์†๋œ๋‹ค.

 

Controller๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, IP/์ถ”๊ฐ€๊ฒฝ๋กœ์— ๋”ฐ๋ผ ํ…œํ”Œ๋ฆฟ์„ ๋‹ค๋ฅด๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

package hades.hello_spring.controller;

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

@Controller
public class HelloController {
    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!!");
        return "hello";
    }
}

์ปจํŠธ๋กค๋Ÿฌ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ , ํด๋ž˜์Šค ์œ„์— @Controller๋ฅผ ๋ถ™์—ฌ์•ผ ํ•œ๋‹ค. @GetMapping("hello")๋Š” IP/hello๋กœ GET Request๊ฐ€ ๋“ค์–ด์™”์„ ๋•Œ ์ฒ˜๋ฆฌ๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ๋ชจ๋ธ์ด๋ผ๋Š” ๊ฒƒ์— ํŠน์„ฑ ์ด๋ฆ„์ด data, ํŠน์„ฑ ๊ฐ’์ด hello์ธ ํŠน์„ฑ์„ ๋”ํ•ด return์— ํ‘œ๊ธฐ๋œ hello.html๋กœ ์ „๋‹ฌํ•œ๋‹ค.

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'์•ˆ๋…•ํ•˜์„ธ์š”. ' + ${data}" >์•ˆ๋…•ํ•˜์„ธ์š”. ์†๋‹˜</p>
</body>
</html>

ํ…œํ”Œ๋ฆฟ ์—”์ง„์ธ thymeleaf๋ฅผ th๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ${data}์—๋Š” ์ „๋‹ฌ๋œ ํŠน์„ฑ ๊ฐ’์ด ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

 

์ •๋ฆฌํ•˜๋ฉด, ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ IP/hello๋ฅผ ์š”์ฒญํ•˜๋ฉด, ํ†ฐ์บฃ ์„œ๋ฒ„์—์„œ Controller์— ์ „๋‹ฌํ•˜๊ณ , ๋ชจ๋ธ์„ templates/hello.html์— ์ „๋‹ฌํ•˜์—ฌ ์›น ๋ธŒ๋ผ์šฐ์ €์— ์ „๋‹ฌํ•œ๋‹ค.

 

๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•˜๊ธฐ

cd PROJECT_FOLDER
gradlew
gradle build
cd lib
java -jar JAR

 

์Šคํ”„๋ง ์›น ๊ฐœ๋ฐœ ๊ธฐ์ดˆ

์ •์  ์ปจํ…์ธ 

ํ•ญ์ƒ ๊ฐ™์€ View ํŒŒ์ผ์„ ์ œ๊ณตํ•œ๋‹ค. 

์ปจํŠธ๋กค๋Ÿฌ์— ๋จผ์ € ์žˆ๋Š”์ง€ ํ™•์ธ ํ›„, /resources/static์—์„œ ํŒŒ์ผ์„ ์ฐพ๋Š”๋‹ค.

 

MVC์™€ ํ…œํ”Œ๋ฆฟ ์—”์ง„

๋™์ ์ธ View ํŒŒ์ผ ์ œ๊ณต, MVC = Model, View, Controller

View๋Š” ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ๋ฐ, Controller๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์œ„ํ•ด, Model์€ ๋‘˜ ์‚ฌ์ด๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š”๋ฐ ์ง‘์ค‘์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋ถ„๋ฆฌํ•ด๋†“์•˜๋‹ค.

@GetMapping("hello-mvc")
public String helloMvc(@RequestParam("name") String name, Model model){
    model.addAttribute("name", name);
    return "hello-mvc";
}

name์œผ๋กœ ์ „๋‹ฌ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ model์— ์ „๋‹ฌํ•˜์—ฌ hello-mvc์— ๋ฐ˜์˜ํ•œ๋‹ค.

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'์•ˆ๋…•ํ•˜์„ธ์š”. ' + ${name} + '๋‹˜ ์˜ค๋Š˜๋„ ํŒŒ์ดํŒ…!'" >hello empty</p>
</body>
</html>

text ๋‚ด์—์„œ +์™€ ''๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ๋ถ„์„ ์ž˜ ํ•ด์•ผ ํ•œ๋‹ค.

 

API

๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๋„˜๊ธฐ๋Š” ๋ฐฉ์‹์„ ์˜๋ฏธํ•œ๋‹ค.

@GetMapping("hello-string")
@ResponseBody
public String helloString(@RequestParam String name) {
   return "hello " + name;
}

@ResponseBody๋Š” HTTP์˜ Body์— return ๊ฐ’์„ ์ „๋‹ฌํ•˜๊ฒ ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

 

@GetMapping("hello-api")
@ResponseBody
public Hello helloApi(@RequestParam("name") 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;
	}
}

๊ฐ์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๋กœ HTTP Body์— ๋‹ด์•„ Response๋กœ ์ œ๊ณตํ•œ๋‹ค.

์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ํ†ฐ์บฃ ์„œ๋ฒ„๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์žˆ์œผ๋ฏ€๋กœ ResponseBody๊ฐ€ ์žˆ์œผ๋ฉด HttpMessageConverter๊ฐ€ ๋™์ž‘ํ•˜๋Š”๋ฐ, return value๊ฐ€ ๊ฐ์ฒด๋ผ๋ฉด JsonConverter๋ฅผ, String์ด๋ผ๋ฉด StringConverter๊ฐ€ ์ž‘๋™๋˜์–ด ์ „๋‹ฌ๋œ๋‹ค.

 

ํšŒ์› ๊ด€๋ฆฌ ์˜ˆ์ œ - ๋ฐฑ์—”๋“œ

๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ฆฌ

๋ฐ์ดํ„ฐ: ํšŒ์›ID, ์ด๋ฆ„
๊ธฐ๋Šฅ: ํšŒ์› ๋“ฑ๋ก, ์กฐํšŒ
์•„์ง ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๊ฐ€ ์„ ์ •๋˜์ง€ ์•Š์Œ(๊ฐ€์ƒ์˜ ์‹œ๋‚˜๋ฆฌ์˜ค)

 

์ปจํŠธ๋กค๋Ÿฌ: ์›น MVC์˜ ์ปจํŠธ๋กค๋Ÿฌ ์—ญํ• 
์„œ๋น„์Šค: ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ตฌํ˜„
๋ฆฌํฌ์ง€ํ† ๋ฆฌ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผ, ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ DB์— ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌ
๋„๋ฉ”์ธ: ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ ๊ฐ์ฒด, ์˜ˆ) ํšŒ์›, ์ฃผ๋ฌธ, ์ฟ ํฐ ๋“ฑ๋“ฑ ์ฃผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌ๋จ

 

interface๋ž€ ์ผ์ข…์˜ ๊ทœ์ œ์ด๋‹ค. interface๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋Š” interface๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฒƒ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. MemoryMemberRepository๋Š” DB ์ ‘๊ทผ๊ณผ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ , MemberService๋Š” ๊ทธ ๊ธฐ๋Šฅ๋“ค์„ ์ด์šฉํ•˜์—ฌ ์ถ”๊ฐ€ ๋กœ์ง์„ ์ž‘์„ฑํ•œ๋‹ค.

 

package hades.hello_spring.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;
    }
}

๋„๋ฉ”์ธ์€ ํ™œ์šฉํ•˜๋Š” ๋Œ€์ƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ ๋“ฏํ•˜๋‹ค.

 

package hades.hello_spring.repository;

import hades.hello_spring.domain.Member;

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

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}

interface์ธ MemberRepository์—๋Š” ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๊ธฐ๋Šฅ ํ•จ์ˆ˜๋ฅผ ๋ช…์‹œํ•œ๋‹ค.

 

package hades.hello_spring.repository;

import hades.hello_spring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
        //return store.values().stream().toList();
    }
}

Memory์— ์ €์žฅํ•  ์˜ˆ์ •์ด๋ฏ€๋กœ, Map์„ ์ƒ์„ฑํ•œ๋‹ค. (HashMap ๋ณด๋‹ค๋Š” ๋™์‹œ์„ฑ์„ ์œ„ํ•ด CurrentMap์„ ์‚ฌ์šฉํ•˜๋Š” ๋ถ„์œ„๊ธฐ์ด๋‹ค.)

 

Optional์€ null์ด ์™€๋„ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค. ofNullable์€ null์ผ ์ˆ˜๋„ ์žˆ๊ณ , ์•„๋‹ ์ˆ˜๋„ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

 

stream์€ ์ง‘ํ•ฉ์ฒด์˜ ์›์†Œ์˜ ํ๋ฆ„์ด๋‹ค.

 

List๋Š” interface์ด๊ณ  ArrayList๊ฐ€ ๊ทธ๊ฒƒ์„ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค์ด๋‹ค.

 

package hades.hello_spring.repository;

import hades.hello_spring.domain.Member;
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;

public class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

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

    @Test
    public void save(){
        Member member = new Member();
        member.setName("๊ฐ•ํ˜ธ๋™");
        repository.save(member);
        Member result = repository.findById(member.getId()).get();
        // Assertions.assertEquals(result,  member);
        Assertions.assertThat(member).isEqualTo(result);
    }

    @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName("์ด์Šน๊ธฐ");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("์€์ง€์›");
        repository.save(member2);

        Member result = repository.findByName("์€์ง€์›").get(); // ๋ฐ˜ํ™˜๊ฐ’์ด Optional์ธ ๊ฒฝ์šฐ, get์„ ์ด์šฉํ•ด Optional์„ ๊น” ์ˆ˜ ์žˆ์Œ

        Assertions.assertThat(result).isEqualTo(member2);
    }

    @Test
    public void findAll(){
        Member member1 = new Member();
        member1.setName("์ด์Šน๊ธฐ");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("์€์ง€์›");
        repository.save(member2);

        List<Member> result = repository.findAll();
        Assertions.assertThat(result.size()).isEqualTo(2);
    }
}

Test ํŒจํ‚ค์ง€์— repository๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค๊ณ , ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

 

@Test๋ฅผ ๋ถ™์ธ๋‹ค๋ฉด, ํ•œ ํด๋ž˜์Šค๋‚˜ ๋ฉ”์†Œ๋“œ ๋‹จ์œ„๋กœ Test๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ค‘์š”ํ•œ ๊ฒƒ์€ ํด๋ž˜์Šค๋ฅผ Test ํ•  ๋•Œ, ๋ฉ”์†Œ๋“œ์˜ ์ˆœ์„œ๊ฐ€ ๋žœ๋ค์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, findAll์ด ์‹คํ–‰๋˜๊ณ , findByName์ด ์‹คํ–‰๋˜๋ฉด, ์ด๋ฆ„์€ ๊ฐ™์ง€๋งŒ ์ฐธ์กฐ๊ฐ€ ๋‹ค๋ฅด๋ฏ€๋กœ, fail์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๋”ฐ๋ผ์„œ, ๋งค๋ฒˆ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰์ด ๋๋‚  ๋•Œ๋งˆ๋‹ค store๋ฅผ ๋น„์šฐ๊ธฐ ์œ„ํ•ด @AfterEach๋ฅผ ๋ถ™์ธ ๋ฉ”์†Œ๋“œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

 

Test๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ Test-driven development = Tdd ๋ผ๊ณ  ํ•œ๋‹ค.

 

package hades.hello_spring.service;

import hades.hello_spring.domain.Member;
import hades.hello_spring.repository.MemberRepository;
import hades.hello_spring.repository.MemoryMemberRepository;

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

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();

    public long join(Member member){
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    public void validateDuplicateMember(Member member){
        memberRepository.findByName(member.getName()).ifPresent(m->{
            throw new IllegalStateException("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.");
        });
    }

    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(long memberId){
        return memberRepository.findById(memberId);
    }
}

Repository๊ฐ€ DB์™€ ๊ด€๋ จ๋œ ์ž‘์—…๋งŒ ํ•œ๋‹ค๋ฉด, Service๋Š” ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ๋‹ค.

 

validateDuplicateMember์—์„œ member.getName()์˜ ๊ฒฐ๊ณผ๋Š” Optional ๊ฐ์ฒด์ด๊ณ , ifPresent๋Š” Optional ์•ˆ์— ์žˆ๋Š” ๊ฒƒ์ด Member์ธ์ง€ null์ธ์ง€ ํ™•์ธํ•˜๊ณ , null์ด ์•„๋‹ˆ๋ผ๋ฉด lambda ์‹์„ ์‹คํ–‰ํ•œ๋‹ค.

 

์–ด๋–ค ํด๋ž˜์Šค์—์„œ Ctrl + Shift + T๋ฅผ ๋ˆ„๋ฅด๋ฉด, Test๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

package hades.hello_spring.service;

import hades.hello_spring.domain.Member;
import hades.hello_spring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memberRepository;
    // MemoryMemberRepository repository = new MemoryMemberRepository();

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

    @Test
    void ํšŒ์›๊ฐ€์ž…() {
        // given ์กฐ๊ฑด ํ•˜์—์„œ
        Member member = new Member();
        member.setName("์ด์ˆ˜๊ทผ");

        // when ~๊ฐ€ ์‹คํ–‰๋  ๋•Œ
        long saveId = memberService.join(member);

        // then ~๊ฐ€ ๋˜์–ด์•ผ ํ•จ
        Member findMember = memberService.findOne(saveId).get();
        org.assertj.core.api.Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    void ์ค‘๋ณต_ํšŒ์›_์˜ˆ์™ธ() {
        // given
        Member member1 = new Member();
        member1.setName("์ด์ˆ˜๊ทผ");

        Member member2 = new Member();
        member2.setName("์ด์ˆ˜๊ทผ");

        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        org.assertj.core.api.Assertions.assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.");

        // then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

MemoryMemberRepository์—์„œ store๊ฐ€ static์œผ๋กœ ์„ ์–ธ๋˜์–ด ์žˆ์–ด์„œ ์ด ๊ฒฝ์šฐ์—๋Š” ๊ดœ์ฐฎ์ง€๋งŒ, static์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” new MemoryMemberRepository()์„ ํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค๋ฅธ store๋ฅผ ๊ฐ€๋ฅดํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ,  MemoryMemberRepository๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๊ทธ MemoryMemberRepository๋ฅผ MemoryService์— ์ „๋‹ฌํ•˜์—ฌ ๊ฐ™์€ store๋ฅผ ๊ฐ€์ง€๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

 

์Šคํ”„๋ง ๋นˆ๊ณผ ์˜์กด๊ด€๊ณ„

๋นˆ(Bean)์€ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์†Œํ”„ํŠธ์›จ์–ด ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. 

์ฆ‰, ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์ž๋ฐ” ๊ฐ์ฒด๋ฅผ ๋œปํ•˜๋ฉฐ, ํ•˜๋‚˜ ์ด์ƒ์˜ ๋นˆ(Bean)์„ ๊ด€๋ฆฌํ•œ๋‹ค.

 

๋นˆ์€ ์ธ์Šคํ„ด์Šคํ™”๋œ ๊ฐ์ฒด๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์— ๋“ฑ๋ก๋œ ๊ฐ์ฒด๋ฅผ ์Šคํ”„๋ง ๋นˆ์ด๋ผ๊ณ  ํ•œ๋‹ค. ์Šคํ”„๋ง ๋นˆ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์œ ์ผํ•˜๊ฒŒ ํ•˜๋‚˜๋งŒ ๋“ฑ๋กํ•ด์„œ ๊ณต์œ ํ•œ๋‹ค. memberService์™€ orderService๊ฐ€ memberRepository๋ฅผ ์ฃผ์ž…๋ฐ›๋Š”๋‹ค๋ฉด ๊ทธ๊ฒƒ์€ ๊ฐ™์€ ๊ฒƒ์ด๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค.

 

์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•

1. ์ปดํฌ๋„ŒํŠธ ์Šค์บ”์„ ํ•˜์—ฌ ์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•˜๊ณ , ์ž๋™ ์˜์กด ๊ด€๊ณ„๋กœ ์ด์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ ํด๋ž˜์Šค์—  @Controller, @Service, @Repository๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ์ฃผ์ž…๋ฐ›์œผ๋ ค๋ฉด @Autowired๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค. 

์ปดํฌ๋„ŒํŠธ ์Šค์บ”์˜ ๊ธฐ๋ณธ์ ์ธ ๋ฒ”์œ„๋Š” Application์ด ์žˆ๋Š” ์ƒ์œ„ ํŒจํ‚ค์ง€๋ผ๋Š” ๊ฒƒ์— ์œ ์˜ํ•ด์•ผ ํ•œ๋‹ค.

 

2. ์ง์ ‘ ๋“ฑ๋ก

package hades.hello_spring;

import hades.hello_spring.repository.MemberRepository;
import hades.hello_spring.repository.MemoryMemberRepository;
import hades.hello_spring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

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

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

 

ํšŒ์› ๊ด€๋ฆฌ - ์›น MVC

์ปจํŠธ๋กค๋Ÿฌ, ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ์ด๋ผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•˜๋‹ค.

 

์Šคํ”„๋ง DB ์ ‘๊ทผ ๊ธฐ์ˆ 

h2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ https://www.h2database.com ์—์„œ ์„ค์น˜ํ•œ๋‹ค.

 

/h2/bin/์—์„œ h2.bat์„ ์‹คํ–‰ํ•˜๋ฉด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŽ˜์ด์ง€๋กœ ์ ‘์†๋œ๋‹ค. ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด IP ๋ถ€๋ถ„์„ localhost๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค. ๋˜ํ•œ, cmd ์ฐฝ์€ ๋„์ง€ ๋ง์•„์•ผ ํ•œ๋‹ค.

 

JPA๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด build.grandle์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

 

package hades.hello_spring;

import hades.hello_spring.repository.JpaMemberRepository;
import hades.hello_spring.repository.MemberRepository;
import hades.hello_spring.repository.MemoryMemberRepository;
import hades.hello_spring.service.MemberService;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.swing.*;

@Configuration
public class SpringConfig {

    private final EntityManager em;

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

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

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

}

SpringConfig์—์„œ EntityManager๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. ์ž๋™์œผ๋กœ ์‚ฝ์ž…๋œ๋‹ค. memberRepository๋ฅผ JpaMemberRepository๋กœ ๋ฐ”๊พธ๊ณ , EntityManager๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

 

package hades.hello_spring.repository;

import hades.hello_spring.domain.Member;
import jakarta.persistence.EntityManager;

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


public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m form Member as m", Member.class).getResultList();
    }
}

 

package hades.hello_spring.repository;

import hades.hello_spring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
    @Override
    Optional<Member> findByName(String name);
}

JpaRepository๋ฅผ ์ƒ์†๋ฐ›์œผ๋ฉด, ์ž๋™์œผ๋กœ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๊ตฌํ˜„๋œ๋‹ค. ๋˜ํ•œ ์•Œ์•„์„œ JPQL ์ฟผ๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

 

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA ์ œ๊ณต ๊ธฐ๋Šฅ

  • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•œ ๊ธฐ๋ณธ์ ์ธ CRUD
  • findByName() , findByEmail() ์ฒ˜๋Ÿผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ๋งŒ์œผ๋กœ ์กฐํšŒ ๊ธฐ๋Šฅ ์ œ๊ณต
  • ํŽ˜์ด์ง• ๊ธฐ๋Šฅ ์ž๋™ ์ œ๊ณต

 

AOP

์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

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("join " + timeMs + "ms");
	}
}

 

๋ฌธ์ œ๋Š”...

ํšŒ์›๊ฐ€์ž…, ํšŒ์› ์กฐํšŒ์— ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์€ ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋‹ค.
์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์€ ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ์ด๋‹ค.
์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง๊ณผ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค์˜ ๋กœ์ง์ด ์„ž์—ฌ์„œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ต๋‹ค.
์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์„ ๋ณ„๋„์˜ ๊ณตํ†ต ๋กœ์ง์œผ๋กœ ๋งŒ๋“ค๊ธฐ ๋งค์šฐ ์–ด๋ ต๋‹ค.
์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์„ ๋ณ€๊ฒฝํ•  ๋•Œ ๋ชจ๋“  ๋กœ์ง์„ ์ฐพ์•„๊ฐ€๋ฉด์„œ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.

 

AOP๋Š” ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ๊ณผ ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ์„ ๋ถ„๋ฆฌํ•˜๊ณ , ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ์˜ ์ ์šฉ ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•˜์—ฌ ์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค.

 

package hades.hello_spring.aop;

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

@Aspect
public class TimeTraceAop {

    @Around("execution(* hades.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("START:" + joinPoint.toString());
        try{
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END:" + joinPoint.toString());
        }

    }
}

 

์ด๋กœ ์ธํ•ด ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

- ํšŒ์›๊ฐ€์ž…, ํšŒ์› ์กฐํšŒ๋“ฑ ํ•ต์‹ฌ ๊ด€์‹ฌ์‚ฌํ•ญ๊ณผ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.
- ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์„ ๋ณ„๋„์˜ ๊ณตํ†ต ๋กœ์ง์œผ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.
- ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ์„ ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
- ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฉด ์ด ๋กœ์ง๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋œ๋‹ค.
- ์›ํ•˜๋Š” ์ ์šฉ ๋Œ€์ƒ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.