[Spring] ์คํ๋ง ๊ธฐ์ด (1)
ํ๋ก์ ํธ ์์ฑ
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());
}
}
}
์ด๋ก ์ธํด ์ป์ ์ ์๋ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ํ์๊ฐ์
, ํ์ ์กฐํ๋ฑ ํต์ฌ ๊ด์ฌ์ฌํญ๊ณผ ์๊ฐ์ ์ธก์ ํ๋ ๊ณตํต ๊ด์ฌ ์ฌํญ์ ๋ถ๋ฆฌํ๋ค.
- ์๊ฐ์ ์ธก์ ํ๋ ๋ก์ง์ ๋ณ๋์ ๊ณตํต ๋ก์ง์ผ๋ก ๋ง๋ค์๋ค.
- ํต์ฌ ๊ด์ฌ ์ฌํญ์ ๊น๋ํ๊ฒ ์ ์งํ ์ ์๋ค.
- ๋ณ๊ฒฝ์ด ํ์ํ๋ฉด ์ด ๋ก์ง๋ง ๋ณ๊ฒฝํ๋ฉด ๋๋ค.
- ์ํ๋ ์ ์ฉ ๋์์ ์ ํํ ์ ์๋ค.