Search

[springboot] μŠ€ν”„λ§ μž…λ¬Έ - 3. νšŒμ› 관리 λ°±μ—”λ“œ

νƒ€μž…
μŠ€ν„°λ””
νƒœκ·Έ
springboot
μƒνƒœ
Published
생성일
2022/07/22 09:42
μ΅œμ’… νŽΈμ§‘ μΌμ‹œ
2023/02/27 03:14
μƒμœ„ ν•­λͺ©
1 more property

λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­ 정리

일반적인 μ›Ή μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ 계측 ꡬ쑰
β€’
컨트둀러 : MVC 의 컨트둀러
β€’
μ„œλΉ„μŠ€ : 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 κ΅¬ν˜„
β€’
리포지토리 : 도메인 객체λ₯Ό DB 에 μ €μž₯/관리 , DB에 μ ‘κ·Όν•˜λŠ” μ—­ν• 
β€’
도메인 : DB에 μ €μž₯λ˜λŠ” 객체 관리
νŠΉμ • DB에 μ’…μ†λ˜μ§€ μ•Šκ²Œλ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μž‘μ„±ν•˜μ—¬μ„œ 좔후에 DB에 λ§žλŠ” 클래슀 λ³€κ²½ν•  수 μžˆλ„λ‘ 섀계할 κ²ƒμž„.

νšŒμ› 도메인과 리포지토리 κ΅¬ν˜„

도메인
domain/Member.java
λ©€λ²„λŠ” id, name 이고, 각각에 λŒ€ν•œ getter/setter λ₯Ό μ •μ˜ν•΄μ€€λ‹€.
package hello.hellospring.domain; public class Member { private Long id; // PK μ—­ν•  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; } }
Java
볡사
리포지토리

리포지토리 μΈν„°νŽ˜μ΄μŠ€

repository/MemberRepository
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<Member> findById(Long id); Optional<Member> findByName(String name); List<Member> findAll(); }
Java
볡사
μš°μ„  DB와 κ΄€λ ¨λœ λ©”μ„œλ“œμ˜ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν–ˆλ‹€.
findById 와 findByName μ—μ„œ NULL 이 리턴됐을 λ•Œμ˜ μ˜ˆμ™Έλ₯Ό μ‰½κ²Œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„œ Optional 래퍼클래슀λ₯Ό μ‚¬μš©ν•œλ‹€.
TIP
option + Enter 둜 μΈν„°νŽ˜μ΄μŠ€ λ©”μ„œλ“œλ₯Ό μžλ™μœΌλ‘œ 뢈러올 수 μžˆλ‹€.

리포지토리 κ΅¬ν˜„μ²΄

repository/MemoryMemberRepository
package hello.hellospring.repository; import hello.hellospring.domain.Member; import java.util.*; public class MemoryMemberRepository implements MemberRepository{ private static Map<Long, Member> store = new HashMap<>(); private static long sequence = 0L; // Long μ—λŠ” null 이 λ“€μ–΄μ˜¬ 수 있고, // long μ—λŠ” null 이 λ“€μ–΄μ˜¬ 수 μ—†μ–΄μ„œ long νƒ€μž…μœΌλ‘œ μ„ μ–Έμ–Έ @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()); // 전체 value(=Member)듀을 리턴 } }
Java
볡사

νšŒμ› 리포지토리 ν…ŒμŠ€νŠΈ μ½”λ“œ

MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest { MemoryMemberRepository repository = new MemoryMemberRepository(); @Test public void save(){ Member member = new Member(); member.setName("spring"); repository.save(member); // 방금 repository에 save ν•œ member 의 id둜 member get // λ§ˆμ§€λ§‰μ— get() 은 Optioanl κ°μ²΄μ—μ„œ μ‹€μ œ 객체λ₯Ό μ–»κΈ° μœ„ν•΄ μ‚¬μš© Member result = repository.findById(member.getId()).get(); // test // member 와 result κ°€ 같은지 검사 assertThat(member).isEqualTo(result); } @Test public void findByName(){ // given Member member1 = new Member(); member1.setName("spring1"); repository.save(member1); // μ •κ΅ν•œ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ 멀버 ν•˜λ‚˜ 더 μΆ”κ°€ Member member2 = new Member(); member2.setName("spring2"); repository.save(member2); // when Member result = repository.findByName("spring1").get(); // then assertThat(result).isEqualTo(member1); } @Test public void findAll(){ Member member1 = new Member(); member1.setName("spring1"); repository.save(member1); Member member2 = new Member(); member2.setName("spring1"); repository.save(member2); List<Member> result = repository.findAll(); assertThat(result.size()).isEqualTo(2); } }
Java
볡사
ν…ŒμŠ€νŠΈλ₯Ό 돌렀보게 되면 findByName ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμ—μ„œ μ—λŸ¬κ°€ λ‚œλ‹€.
ν…ŒμŠ€νŠΈ μ½”λ“œ λ©”μ„œλ“œλŠ” μž„μ˜λŒ€λ‘œ μ‹€ν–‰λ˜λŠ”λ°, λ”°λΌμ„œ μ„œλ‘œ 의쑴적이면 μ•ˆ λœλ‹€.
이 경우 findAll() λ©”μ„œλ“œμ—μ„œ repository 에 이름이 spring1 인 member1 을 save ν–ˆλŠ”λ°,
뒀이어 μ‹€ν–‰λ˜λŠ” findByName() λ©”μ„œλ“œμ—μ„œ 이름이 spring1 인 객체λ₯Ό μ΄μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈν•˜κ³  μžˆλ‹€.
μ„œλ‘œ λ‹€λ₯Έ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ μ‚¬μš©ν•˜λŠ” λ°μ΄ν„°μ—μ„œ 좩돌이 λ‚œ 것이닀.
이런 경우λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄μ„œ ν•˜λ‚˜μ˜ ν…ŒμŠ€νŠΈλ©”μ„œλ“œκ°€ λλ‚˜λ©΄ μ‚¬μš©ν•œ 데이터λ₯Ό μ΄ˆκΈ°ν™” ν•΄μ€˜μ•Ό ν•œλ‹€.
λ ˆν¬μ§€ν† λ¦¬
repository/MemoryMemberRepository.java
public class MemoryMemberRepository implements MemberRepository{ // μƒλž΅.. // store μ΄ˆκΈ°ν™” (ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œ μ‚¬μš©) public void clearStore() { store.clear(); } }
Java
볡사
리포지토리에 store λ₯Ό μ΄ˆκΈ°ν™”ν•˜λŠ” ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€.
그리고 ν…ŒμŠ€νŠΈμ½”λ“œμ—μ„œ ν•˜λ‚˜μ˜ ν…ŒμŠ€νŠΈλ©”μ„œλ“œκ°€ λλ‚ λ•Œλ§ˆλ‹€ 이 ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜κ²Œλ” ν•œλ‹€.
λ ˆν¬μ§€ν† λ¦¬ ν…ŒμŠ€νŠΈ
MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest { MemoryMemberRepository repository = new MemoryMemberRepository(); // ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ“€μ΄ μ“°λŠ” 데이터듀을 λ…λ¦½μ μœΌλ‘œ κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄ store μ΄ˆκΈ°ν™” @AfterEach public void afterEach() { repository.clearStore(); } @Test // μƒλž΅.. }
Java
볡사
@AfterEach μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ 각 ν…ŒμŠ€νŠΈλ©”μ„œλ“œκ°€ λλ‚ λ•Œλ§ˆλ‹€ μ•Œμ•„μ„œ μ‹€ν–‰λœλ‹€.
ν…ŒμŠ€νŠΈ μ½”λ“œ μ‹€ν–‰ μ‹œ μ •μƒμ μœΌλ‘œ ν†΅κ³Όλœλ‹€.
TIP
junit 이 μ•„λ‹Œ assertj μ—μ„œ μ œκ³΅ν•˜λŠ” Assertions λ₯Ό μ‚¬μš©ν•œλ‹€.
(assertj μ—μ„œ μ§€μ›ν•˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ’€ 더 편리)
Assertions μ—μ„œ option+enter λ₯Ό μž…λ ₯ν•˜μ—¬ static import λ₯Ό μ‹œν‚€λ©΄
import static org.assertj.core.api.Assertions.*; static import 문이 μΆ”κ°€λ˜μ–΄μ„œ μ•žμ—λ‹€κ°€ Assertions λ₯Ό μ•ˆ 써주고 assertThat 만 μ‚¬μš©ν•  수 있게 λœλ‹€.
cf) ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό λ¨Όμ € λ§Œλ“€κ³  κ°œλ°œμ„ ν•˜λŠ” 것을 TDD(ν…ŒμŠ€νŠΈμ£Όλ„κ°œλ°œ) 라고 ν•œλ‹€.

νšŒμ› μ„œλΉ„μŠ€ 개발

νšŒμ›κ°€μž…

같은 이름이 μžˆλŠ” νšŒμ›μ΄ 있으면 μ˜ˆμ™Έμ²˜λ¦¬λ₯Ό ν•˜κ²Œλ” ν•  것이닀.
service/MemberService
public class MemberService { private final MemberRepository memberRepository = new MemoryMemberRepository(); // νšŒμ›κ°€μž… public Long join(Member member) { // 같은 이름이 μžˆλŠ” 쀑볡 νšŒμ› X memberRepository.findByName(member.getName()) .ifPresent(m -> { throw new IllegalStateException("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); }); /* μœ„λž‘ 같은 둜직 Optional<Member> result = memberRepository.findByName(member.getName()); // ifPresent : ν•΄λ‹Ή λ³€μˆ˜(result) κ°€ null 이 μ•„λ‹ˆλΌλ©΄(==이미 μ‘΄μž¬ν•œλ‹€λ©΄) result.ifPresent(m -> { throw new IllegalStateException("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); }); */ memberRepository.save(member); return member.getId(); } }
Java
볡사
Β memberRepository.findByName() 은 Optional κ°μ²΄μ΄λ―€λ‘œ Optional 객체의 λ‚΄μž₯ λ©”μ„œλ“œμΈ ifPresent() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ null 인지 μ•„λ‹Œμ§€ 체크λ₯Ό ν•  수 μžˆλ‹€.
TIP λ©”μ„œλ“œ μΆ”μΆœν•˜κΈ°
ctrl + T (λ¦¬νŒ©ν† λ§ 단좕킀) β†’ 7. λ©”μ„œλ“œ μΆ”μΆœ
μ–΄λ–€ μ½”λ“œκΉŒμ§€ λ©”μ„œλ“œλ‘œ μΆ”μΆœν•  것인지 선택
validateDuplicateMember() λΌλŠ” ν•¨μˆ˜λͺ…μœΌλ‘œ μΆ”μΆœμ„ ν•œ λͺ¨μŠ΅
전체 μ½”λ“œ
public class MemberService { private final MemberRepository memberRepository = new MemoryMemberRepository(); // νšŒμ›κ°€μž… public Long join(Member member) { // 같은 이름이 μžˆλŠ” 쀑볡 νšŒμ› 검증 ν›„ 쀑볡 있으면 μ˜ˆμ™Έμ²˜λ¦¬ validateDuplicateMember(member); /* μœ„λž‘ 같은 둜직 Optional<Member> result = memberRepository.findByName(member.getName()); // ifPresent : ν•΄λ‹Ή λ³€μˆ˜(result) κ°€ null 이 μ•„λ‹ˆλΌλ©΄(==이미 μ‘΄μž¬ν•œλ‹€λ©΄) result.ifPresent(m -> { throw new IllegalStateException("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); }); */ memberRepository.save(member); return member.getId(); } private 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); } }
Java
볡사

νšŒμ› μ„œλΉ„μŠ€ ν…ŒμŠ€νŠΈ

MemberServiceTest.java
class MemberServiceTest { MemberService memberService; MemoryMemberRepository memberRepository; // μ˜μ‘΄μ„± μ£Όμž… (DI) @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("hello"); // when (κ²€μ¦ν•˜λ €κ³  ν•˜λŠ” 상황) Long saveId = memberService.join(member); // then (검증) Member findMember = memberService.findOne(saveId).get(); assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test public void 쀑볡_νšŒμ›κ°€μž…_μ˜ˆμ™Έ() { // given Member member1 = new Member(); member1.setName("spring"); // member1 κ³Ό μ€‘λ³΅λœ μ΄λ¦„μœΌλ‘œ μ„€μ • Member member2 = new Member(); member2.setName("spring"); // when memberService.join(member1); // then IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); assertThat(e.getMessage()).isEqualTo("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); /* 같은 둜직 try{ memberService.join(member2); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage()).isEqualTo("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); } */ } }
Java
볡사
Β  DI κ°œλ…
ν…ŒμŠ€νŠΈμ˜ μ •ν™•μ„±κ³Ό 독립성을 μœ„ν•΄μ„œ 맀 ν…ŒμŠ€νŠΈλ©”μ„œλ“œκ°€ 싀행될 λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€μ— λŒ€ν•΄μ„œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•œλ‹€.
이λ₯Ό μœ„ν•΄μ„œ ν…ŒμŠ€νŠΈμ½”λ“œμ—μ„œ μ„œλΉ„μŠ€μ½”λ“œμ— μ˜μ‘΄μ„±μ„ μ£Όμž…(DI) ν•΄μ•Όν•œλ‹€.
@BeforeEach λ₯Ό 톡해 λ©”μ„œλ“œ μ‹€ν–‰ μ „λ§ˆλ‹€ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , κ·Έ μΈμŠ€ν„΄μŠ€μ— λŒ€ν•΄μ„œ ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό 돌리게 λœλ‹€.
Β νšŒμ›κ°€μž… μ„œλΉ„μŠ€λ‘œμ§ 같은 경우, μ œλŒ€λ‘œ λ™μž‘ν•˜λŠ” μΌ€μ΄μŠ€ 와 μ˜ˆμ™Έ μΌ€μ΄μŠ€(μ€‘λ³΅νšŒμ›) λ‘˜ 닀에 λŒ€ν•΄μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Ό ν•œλ‹€.
μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ 같은 κ²½μš°λŠ” μ˜ˆμ™Έκ°€ λ°œμƒν•˜λŠ” 상황에 λŒ€ν•΄μ„œ μ‹€μ œ μ˜ˆμ™Έκ°€ λΏœμ–΄μ Έ λ‚˜μ˜€λŠ”μ§€ μ²΄ν¬ν•΄λ³΄λŠ” λ°©μ‹μœΌλ‘œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€.
Β IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
: 두 번째 인자인 λžŒλ‹€ν•¨μˆ˜ μ‹€ν–‰ μ‹œ, 첫 번째 인자(IllegalStateException.class) 값인 StateException 이 λ°œμƒν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” μ½”λ“œμ΄λ‹€.
MemberService.java
public class MemberService { // ν…ŒμŠ€νŠΈμ½”λ“œμ—μ„œ μ˜μ‘΄μ„± μ£Όμž…(DI) λ°›κΈ° μœ„ν•¨ private final MemberRepository memberRepository; // μƒμ„±μž public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } // μ„œλΉ„μŠ€λ‘œμ§ μƒλž΅.. }
Java
볡사
Β memberRepository κ°€ λ³€ν•˜μ§€ μ•ŠλŠ” 것을 보μž₯ν•˜κΈ° μœ„ν•΄μ„œ private final 둜 μƒμˆ˜μ²˜λ¦¬λ₯Ό ν•˜κ³ , μƒμ„±μžμ—μ„œ memberRepository 멀버에 νŒŒλΌλ―Έν„°λ‘œ λ“€μ–΄μ˜€λŠ” memberRepository λ³€μˆ˜λ₯Ό ν• λ‹Ήν•œλ‹€.
TIP ν…ŒμŠ€νŠΈ μ½”λ“œ μžλ™ 생성
νŠΉμ • λ‘œμ§μ— ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό λ§Œλ“€κ³  싢을 λ•Œ μžλ™μœΌλ‘œ ν…ŒμŠ€νŠΈ μ½”λ“œ 양식을 λ§Œλ“€ 수 μžˆλ‹€.
ν…ŒμŠ€νŠΈ 생성 단좕어 : cmd + shift + T
μ•„λž˜μ²˜λŸΌ 빈 껍데기둜 ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ λ§Œλ“€μ–΄μ§„λ‹€.