๋ฐ์ดํฐ
: ํ์ID, ์ด๋ฆ
๊ธฐ๋ฅ
: ํ์ ๋ฑ๋ก, ์กฐํ
โ ์์ง ๋ฐ์ดํฐ ์ ์ฅ์๊ฐ ์ ์ ๋์ง ์์(๊ฐ์์ ์๋๋ฆฌ์ค) -> ์ด๋ค DB๋ฅผ ์ฌ์ฉํ ์ง ์ ํ์ง ์์์
์ปจํธ๋กค๋ฌ
: ์น MVC์ ์ปจํธ๋กค๋ฌ ์ญํ
์๋น์ค
: ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง ๊ตฌํ ex. ํ์์ ์ค๋ณต ๊ฐ์
์ ํ ์ ์๋ค
๋ฆฌํฌ์งํ ๋ฆฌ
: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผ, ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ DB์ ์ ์ฅํ๊ณ ๊ด๋ฆฌ
๋๋ฉ์ธ
: ํ์, ์ฃผ๋ฌธ, ์ฟ ํฐ ๋ฑ๋ฑ ์ฃผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ๊ด๋ฆฌ๋๋ ๋น์ฆ๋์ค ๋๋ฉ์ธ ๊ฐ์ฒด
public class Member {
private Long id; //์์์ ๊ฐ, ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํด ์์คํ
์ด ์ ์ฅํ๋ 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;
}
}
public interface MemberRepository {
Member save(Member member); //ํ์์ด ์ ์ฅ์์ ์ ์ฅ
Optional<Member> findById(Long id); //id๋ก ํ์ ์ฐพ๊ธฐ
Optional<Member> findByName(String name); //name์ผ๋ก ํ์ ์ฐพ๊ธฐ
List<Member> findAll(); //์ง๊ธ๊น์ง ์ ์ฅ๋ ๋ชจ๋ ํ์ list ๋ฐํ
}
๐ Optional์ด๋?
โช Optional์ Java8๋ถํฐ ๋์ ๋ Null์ด ์ฌ ์ ์๋ ๊ฐ์ ๊ฐ์ธ๋ Wrapper ํด๋์ค๋ก, ์ฐธ์กฐํ๋๋ผ๋ NPE๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๋์์ค๋ค.
โช NPE๋ NullPointerException์ผ๋ก null ๊ฐ์ฒด ์ฐธ์กฐ์์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ค๊ณ ํ ๋ ๋ฐ์ํ๋ ์์ธ์ด๋ค.
โช ๋น์ด์๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ ์ ์๋ ์ํฉ์์ Optional ๊ฐ์ฒด๋ก ๋ฐํ๊ฐ์ ๋ํํด์ค๋ค๋ฉด ๋ฐํ๋๋ ๊ฐ์ด Null์ผ์ง๋ผ๋ Optional ๊ฐ์ฒด๋ก ๋ํ๋์ด ๋ฐํ๋๊ธฐ ๋๋ฌธ์ Null๋ก๋ถํฐ ์์ ํด์ง๋ค. Optional์ ํด๋ผ์ด์ธํธ ์ฝ๋์๊ฒ ๋ช ์์ ์ผ๋ก ๋น ๊ฐ์ผ ์๋ ์๋ค๋ ๊ฒ์ ์๋ ค์ฃผ๊ณ ๋น ๊ฐ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ ํ๋ค.
โช ์๋ฐ์์ ์ ๊ณตํ๋ ๊ฐ์ฒด๋ฅผ Optional ๊ฐ์ฒด๋ก ๊ฐ์ธ๊ธฐ ์ํด์๋ Optional์์ ์ ๊ณตํ๋of
์ofNullable
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.of
๋ ์ธ์๋ก์ Null ๊ฐ์ ๋ฐ์ง ์๋๋ค๋ ๊ฒ์ด๊ณofNullable
์ Null ๊ฐ์ ํ์ฉํ๋ค๋ ๊ฒ์ด๋ค.
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>(); //ํ์์ ์ ์ฅ
private static long sequence = 0L; //0, 1, 2 ๋ฑ ํค ๊ฐ ์์ฑ
@Override
public Member save(Member member) {
member.setId(++sequence); //member id๋ ์์คํ
์ด ์์ฑ, name์ ํ์๊ฐ์
์ ์
๋ ฅ ๋ฐ์ผ๋ฏ๋ก ์ฌ๊ธฐ์ ์ง์ ํ์ง ์์
store.put(member.getId(), member); //member id์ member๋ฅผ ํ ์์ผ๋ก Map์ผ๋ก ์ ์ธํ store์ ์ ์ฅ
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id)); //Optional.ofNullable์ ํตํด ๋ฐํ ๊ฐ์ด null์ด๋๋ผ๋ ํด๋ผ์ด์ธํธ์์ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋๋ก ํจ
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name)) //ํ๋ผ๋ฏธํฐ๋ก ๋์ด์จ name๊ณผ member์ name์ด ๊ฐ์์ง ํ์ธ
.findAny(); //findAny๋ ๊ฐ์ฅ ๋จผ์ ํ์๋๋ ์์๋ฅผ ๋ฆฌํดํ์ฌ ํ๋๋ผ๋ ๋ฐํํจ. ์์ ๊ฒฝ์ฐ null์ด Optional๋ก ๊ฐ์ธ์ ธ ๋ฐํ
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values()); //store member๋ค์ ์ ๋ถ ๋ฐํ
}
public void clearStore() {
store.clear();
}
}
โ ํ์ฌ ์์ ์์๋ ๋์์ฑ ๋ฌธ์ ๊ฐ ๊ณ ๋ ค๋์ด ์์ง ์์ง๋ง ์ค๋ฌด์์๋ ConcurrentHashMap, AtomicLong ์ฌ์ฉ์ ๊ณ ๋ คํ๋ค.
โ ํ ์คํธ ์ผ์ด์ค๋ ์ฝ๊ฒ ๋งํด์ ์์์ ๋ง๋ ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ ํด๋์ค๊ฐ ์ ์์ ์ผ๋ก ์๋ํ๋์ง๋ฅผ ๊ฒ์ฆํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
โช ๊ฐ๋ฐํ ๊ธฐ๋ฅ์ ์คํํด์ ํ ์คํธ ํ ๋ ์๋ฐ์ main ๋ฉ์๋๋ฅผ ํตํด์ ์คํํ๊ฑฐ๋, ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ปจํธ๋กค๋ฌ๋ฅผ ํตํด์ ํด๋น ๊ธฐ๋ฅ์ ์คํํ๋ค.
โช ์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ค๋นํ๊ณ ์คํํ๋๋ฐ ์ค๋ ๊ฑธ๋ฆฌ๊ณ , ๋ฐ๋ณต ์คํํ๊ธฐ ์ด๋ ต๊ณ ์ฌ๋ฌ ํ ์คํธ๋ฅผ ํ๋ฒ์ ์คํํ๊ธฐ ์ด๋ ต๋ค๋ ๋จ์ ์ด ์๋ค.
โช ์๋ฐ๋JUnit
์ด๋ผ๋ ํ๋ ์์ํฌ๋ก ํ ์คํธ๋ฅผ ์คํํด์ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
@Test //import org.junit.jupiter.api.Test
public void save() {
Member member = new Member(); //์๋ก์ด member ์์ฑ
member.setName("spring"); //๋ง๋ member์ name์ spring์ผ๋ก ์ค์
repository.save(member); //๋ฆฌํฌ์งํ ๋ฆฌ์ member ์ ์ฅ
//๋ง๋ member๊ฐ ์ ๋๋ก ์ ์ฅ๋์๋์ง ๊ฒ์ฆ
Member result = repository.findById(member.getId()).get(); //๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ๋ ๊ฐ์ result๋ก ์ ์ฅ
assertThat(member).isEqualTo(result); //์์ฑํ member์ result๊ฐ ๊ฐ์์ง ํ์ธ import static org.assertj.core.api.Assertions.*
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring1").get(); //spring1 ์ด๋ฆ์ ๊ฐ์ง member๋ฅผ result์ ์ ์ฅ
//Member result = repository.findByName("spring2").get(); //์ด๋ ๊ฒ ์์ฑํ๋ฉด ํ
์คํธ ์คํจ
assertThat(result).isEqualTo(member1); //๊ฒ์ฆ
}
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2); //์ ์ฅ๋ member์ ์ธ์์๋ก ์ ์ฒด ๋ฉค๋ฒ๋ฅผ ์ ๋๋ก ๋ฐํํ๋์ง ๊ฒ์ฆ
}
}
๐ Save
@Test
public void save() {
Member member = new Member(); //์๋ก์ด member ์์ฑ
member.setName("spring"); //๋ง๋ member์ name์ spring์ผ๋ก ์ค์
repository.save(member); //๋ฆฌํฌ์งํ ๋ฆฌ์ member ์ ์ฅ
//๋ง๋ member๊ฐ ์ ๋๋ก ์ ์ฅ๋์๋์ง ๊ฒ์ฆ
Member result = repository.findById(member.getId()).get(); //๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ๋ ๊ฐ์ result๋ก ์ ์ฅ
assertThat(member).isEqualTo(result); //์์ฑํ member์ result๊ฐ ๊ฐ์์ง ํ์ธ
}
๐ findByName
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring1").get(); //spring1 ์ด๋ฆ์ ๊ฐ์ง member๋ฅผ result์ ์ ์ฅ
//Member result = repository.findByName("spring2").get(); //์ด๋ ๊ฒ ์์ฑํ๋ฉด ํ
์คํธ ์คํจ
assertThat(result).isEqualTo(member1); //๊ฒ์ฆ
}
๐ findAll
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2); //์ ์ฅ๋ member์ ์ธ์์๋ก ์ ์ฒด ๋ฉค๋ฒ๋ฅผ ์ ๋๋ก ๋ฐํํ๋์ง ๊ฒ์ฆ
}
์ด๋ ๊ฒ 3๊ฐ์ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์ class์์ ํ
์คํธ๋ฅผ ์คํํ๊ฒ ๋๋ฉด findByName()์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๊ทธ ์ด์ ๋ findAll()์ด ๋จผ์ ์คํ๋์๊ธฐ ๋๋ฌธ์ ์ด๋ฏธ member1๊ณผ member2์ ๊ฐ์ด ์ ์ฅ๋์๊ธฐ ๋๋ฌธ์ด๋ค.
โ ๋ชจ๋ ํ
์คํธ๋ ์์์ ์๊ด์์ด ๋ฉ์๋ ๋ณ๋ก ๋
๋ฆฝ์ ์ผ๋ก ์คํ๋์ด์ผ ํ๋ค.
๐ @AfterEach
@AfterEach
public void afterEach() {
repository.clearStore();
}
ํ๋ฒ์ ์ฌ๋ฌ ํ
์คํธ๋ฅผ ์คํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ DB์ ์ง์ ํ
์คํธ์ ๊ฒฐ๊ณผ๊ฐ ๋จ์ ์ ์๋ค. ์ด๋ ๊ฒ ๋๋ฉด ๋ค์ ์ด์ ํ
์คํธ ๋๋ฌธ์ ๋ค์ ํ
์คํธ๊ฐ ์คํจํ ๊ฐ๋ฅ์ฑ์ด ์๋ค. @AfterEach
๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ํ
์คํธ๊ฐ ์ข
๋ฃ๋ ๋๋ง๋ค ์ด ๊ธฐ๋ฅ์ ์คํํ๋ค. ์ฌ๊ธฐ์๋ ๋ฉ๋ชจ๋ฆฌ DB์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ค.
๐ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)
ํ์ฌ๋ MemoryMemberRepository ๊ฐ๋ฐ ์๋ฃ ํ ํ ์คํธ ํด๋์ค๋ฅผ ์์ฑํ์ง๋ง ๋ฐ๋๋ก ํ ์คํธ ํด๋์ค๋ฅผ ๋จผ์ ์์ฑํ๊ณ ๊ฐ๋ฐ ํด๋์ค๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ ์ด๋ฅผ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)๋ผ๊ณ ํ๋ค.
์๋น์ค ํด๋์ค๋ ๋น์ฆ๋์ค์ ๊ฐ๊น์ด ์ด๋ฆ์ ์ฌ์ฉํด์ผ ํ๋ค. ์๋น์ค๋ ๋น์ฆ๋์ค์ ์์กด์ ์ผ๋ก ์ค๊ณํ๊ณ , ๋ฆฌํฌ์งํ ๋ฆฌ๋ ๊ธฐ๊ณ์ ์ผ๋ก ๊ฐ๋ฐ์์ค๋ฝ๊ฒ ์ด๋ฆ์ ๋ถ์ธ๋ค.
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
//ํ์ ๊ฐ์
public Long join(Member member) {
validateDuplicateMember(member); //์ค๋ณต ํ์ ๊ฒ์ฆ : ๊ฐ์ ์ด๋ฆ์ ๊ฐ์ง ์ค๋ณต ํ์์ ์์ด์ผ ํจ
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName()) //findByName ๋ฉ์๋๋ Optional๋ก ๋ฐํ๋๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ifPresent๋ฅผ ์ฌ์ฉํ ์ ์์
.ifPresent(m -> { //ifPresent๋ ๊ฐ์ ๊ฐ์ง๊ณ ์์ผ๋ฉด ์คํํ๊ณ , ๊ฐ์ด ์์ผ๋ฉด ๋์ด๊ฐ
throw new IllegalStateException("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
});
}
//์ ์ฒด ํ์ ์กฐํ
public List<Member> findMembers() {
return memberRepository.findAll();
}
//ํ์ ํ ๋ช
์กฐํ
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
ํ ์คํธ ์ผ์ด์ค ๋ฉ์๋๋ช ์ ํ๊ธ๋ก ์์ฑํ๋ ๊ฒฝ์ฐ๋ ์ข ์ข ์๋ค. ์ง๊ด์ ์ด๊ณ , ๋น๋๋ ๋ Test ์ฝ๋๋ ์ค์ ์ฝ๋์ ํฌํจ๋์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
ํ ์คํธ๋ ๋๋ถ๋ถ ๋ฌด์ธ๊ฐ๊ฐ ์ฃผ์ด์ก๋๋ฐ(given), ์ด๊ฒ์ ์คํํ์ ๋(when), ๊ฒฐ๊ณผ๊ฐ ์ด๊ฒ ๋์์ผํด(then) ๋ผ๋ ๊ตฌ์กฐ๋ก ๋๋์ด์ง๊ธฐ ๋๋ฌธ์ ์ฃผ์์ผ๋ก given, when, then์ ์ ์ด๋๊ณ ํด๋นํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์ข๋ค.
class MemberServiceTest {
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("hello");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
assertEquals(member.getName(), findMember.getName());
}
@Test
public void ์ค๋ณต_ํ์_์์ธ() {
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); //์์ธ๊ฐ ๋ฐ์ํด์ผ ํจ
assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค."); //์๋ฌ ๋ฉ์์ง๋ก ์ํ ๊ฒ์ฆ
}
}
โ Window ํ
์คํธ ํด๋์ค ์๋ ์์ฑ ๋จ์ถํค : Ctrl + Shift + T
โ Window ์ด์ ์คํํ ์ฝ๋ ๋ค์ ์คํ ๋จ์ถํค : Shift + F10
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
}
public class MemberServiceTest {
MemberService memberService = new MemberService;
MemberRepository memberRepository = new MemoryMemberRepository();
}
โช ์์ ์ฝ๋์์ ๋ฌธ์ ๋ MemberService์์ ์์ฑํ memberRepository์ ํ
์คํธ ์ผ์ด์ค์์ ๋ง๋ memberRepository๊ฐ ์๋ก ๋ค๋ฅธ ์ธ์คํด์ค๋ผ๋ ๊ฒ์ด๋ค.
โช ์ง๊ธ ์ํฉ์์ ๋ฌธ์ ๊ฐ ๋์ง ์๋๋ค๊ณ ํด๋ memberRepository์ store ๊ฐ์ฒด๊ฐ static ํ๋๊ฐ ์๋๊ฒ ๋๋ฉด DB๊ฐ ๋ค๋ฅธ DB๊ฐ ๋๋ฉด์ ๋ฌธ์ ๊ฐ ์๊ธธ ๊ฒ์ด๋ค. ๊ทธ๋ฟ๋ง ์๋๋ผ ๊ฐ์ ๋ฆฌํฌ์งํ ๋ฆฌ๋ก ํ
์คํธํ๋ ๊ฒ์ด ๋ง๋๋ฐ ๋ค๋ฅธ ๋ฆฌํฌ์งํ ๋ฆฌ๋ก ํ
์คํธ๋๊ณ ์๋ค. ๊ทธ๋์ ๊ฐ์ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๋๋ก ๋ฐ๊ฟ์ผ ํ๋ค.
โช MemberRepository๋ฅผ MemberService์์ ์ง์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ ์์ฑ์ ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ์ธ๋ถ๋ก๋ถํฐ ์ฃผ์
๋ฐ๋๋ก ๋ฐ๊ฟ์ค๋ค. ์ด๋ฐ ๊ฒ์ ์์กด์ฑ ์ฃผ์
(DI)์ด๋ผ๊ณ ํ๋ค.
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
public class MemberServiceTest {
MemberService memberService;
MemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
}
@BeforeEach
: ๊ฐ ํ
์คํธ ์คํ ์ ์ ํธ์ถ๋๋ค. ํ
์คํธ๊ฐ ์๋ก ์ํฅ์ด ์๋๋ก ํญ์ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ์์กด๊ด๊ณ๋ ์๋ก ๋งบ์ด์ค๋ค.
์์กด์ฑ ์ฃผ์
(DI)
: ํด๋์ค ๊ฐ ์์กด๊ด๊ณ๋ฅผ ๊ด๋ฆฌํ๊ณ ์๋ Bean ์ค์์ ํ์ํ ๊ฒ์ ์ปจํ
์ด๋๊ฐ ์๋์ผ๋ก ์ฃผ์
ํด ์ฃผ๋ ๊ฒ์ ๋งํ๋ค. ์ฆ, ์ด๋ค ๊ฐ์ฒด๊ฐ ์ฌ์ฉํ๋ ์์กด ๊ฐ์ฒด๋ฅผ ์ง์ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๊ฒ ์๋๋ผ, ์ฃผ์
๋ฐ์์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋งํ๋ค.
์คํ๋ง ์ ๋ฌธ - ์ฝ๋๋ก ๋ฐฐ์ฐ๋ ์คํ๋ง ๋ถํธ, ์น MVC, DB ์ ๊ทผ ๊ธฐ์