

컨트롤러 : 웹 MVC의 컨트롤러 역할
서비스 : 핵심 비즈니스 로직 구현
리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
도메인 : 비즈니스 도메인 객체 (ex. 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨)

src/main/java/hello/hellospring에 domain 패키지(폴더)를 만들고, 그 안에 Member.java 파일을 만든다.

.java 파일을 만들면 자동으로

이런게 뜬다. 용도에 따라 선택하면 되는데, 지금은 클래스를 만들거니까 class로 선택하고 사용할 필드들을 쓴다.
// src/main/java/hello/hellospring/Member.java
package hello.hellospring.domain;
public class Member {
private Long id;
private String name;
}
클래스 안에 커서를 두고 Ctrl+.을 누르면 또 자동으로 주르륵

Getter와 Setter를 만든다.
그럼 이런 모양의 클래스가 만들어진다.
// src/main/java/hello/hellospring/Member.java
package 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;
}
}

src/main/java/hello/hellospring에 repository 패키지(폴더)를 만들고, 그 안에 MemberRepository.java 파일을 만든다.

이번에는 interface로 선택해서 아래 내용을 채운다.
// src/main/java/hello/hellospring/MemberRepository.java
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
🔸 틈새 Java
Interface: 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스
class : 함수의 내용까지 선언VSinterface : 함수의 이름만 선언Optional:null이 올 수 있는 값을 감싸는 Wrapper 클래스.null을 반환해도 에러가 발생하지 않도록 해준다.
repository 패키지 안에 MemoryMemberRepository.java 파일을 만든다.

MemberRepository를 상속한 MemoryMemberRepository 클래스를 만들고, Ctrl+.을 눌러서 인터페이스 안에 써놨던 메소드들을 선언해놓는다.



그리고 아래와 같이 각 메소드 안의 내용을 채운다.
// src/main/java/hello/hellospring/MemoryMemberRepository.java
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());
}
}
🔸 틈새 Java
HashMap: 키와 값으로 구성된 Entry 객체를 저장하는 구조를 가지고 있는 자료구조로, 해시 함수를 통해 '키'와 '값'이 저장되는 위치를 결정한다.put(key, value)로 값을 추가한다.ofNullable(): 명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환하고, 명시된 값이 null이면 비어있는 Optional 객체를 반환한다.->: 자바 람다표현식의 syntax 일부.(argument, ...) -> {expression}
src/test/java/hello/hellospring에 repository 패키지를 추가하고, MemoryMemberRepositoryTest.java 파일을 만든다.

// src/test/java/hello/hellospring/MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest {
@Test
public void test() {
}
}
이런식으로 메소드를 쓰고 위에 @Test를 붙여주면

이렇게 왼쪽에 삼각형이 생기는데, 얘를 누르면 해당 메소드가 실행된다. 실행했을 때 제대로 되면 초록색으로 체크 표시가 뜨고, 에러가 나면 빨갛게 된다.
이 문법을 따라서 위에서 작성한 메소드들을 테스트하는 코드를 다음과 같이 적는다.
// src/test/java/hello/hellospring/MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
// optional에서 값 꺼낼 때는 get()
System.out.println("result = " + (result == member));
}
@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();
Assertions.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();
Assertions.assertThat(result.size()).isEqualTo(2);
}
}
🔸 틈새 Java
인터페이스명변수명=new 클래스명();
하나하나 실행하면 잘 되는데, class 옆에 있는 거를 눌러서 한번에 실행시키면 오류가 난다.

이거는 테스트코드가 돌아가는 순서가 랜덤이라 값이 한번 쓰이고 비워지지 않아서 오류가 나는건데, 이를 방지 하기 위해서 아래 함수 2개를 추가해준다.
// src/test/java/hello/hellospring/MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest {
...
@AfterEach
public void afterEach() {
repository.clearStore();
}
...
}
// src/main/java/hello/hellospring/MemoryMemberRepository.java
public class MemoryMemberRepository implements MemberRepository{
...
public void clearStore(){
store.clear();
}
...
}
이러고 다시 돌리면 에러가 나지 않고 모두 제대로 실행된다.

src/main/java/hello/hellospring에 service 패키지를 만들고 MemberService.java 파일을 만든다.

아래처럼 내용을 채운다.
// src/main/java/hello/hellospring/MemberService.java
public class MemberService {
private final MemberRepository memberRepository;
// 생성자 => 이후 발생한 오류 때문에 만들어줌
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/* 회원가입 */
public Long join(Member member) {
validateDuplicateMember(member); // 중복 회원 검증
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);
}
}
중복 회원 검증하는 부분만 따로 떼어보면,
일단 이렇게 쓴 다음에

메소드로 뺄 부분을 드래그 하고 Ctrl+.을 누르면 이렇게 뜬다.

Extract to method를 누르고 메소드 명을 입력하면


이렇게 메소드로 빠진다.
테스트할 클래스 위에 커서를 대고 마우스 오른쪽을 클릭하고 Go to Test를 누른다.

딴딴딴 누르면



알아서 test 밑에 패키지랑 코드 뼈대를 만들어준다. 굿

대충 이런식으로 테스트해보고
// src/test/java/hello/hellospring/service/MemberServiceTest.java
public class MemberServiceTest {
MemberService memberService = new MemberService();
@Test
void 회원가입() {
// given
Member member = new Member();
member.setName("spring");
// when
Long saveId = memberService.join(member);
// then
Member findMember = memberService.findOne(saveId).get();
Assertions.assertThat(member.getName()).isEqualTo(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));
}
}
한번에 실행했을 때 각각 테스트 코드 실행 후 값이 비워지지 않아서 생기는 오류 발생 방지를 위해 아래 코드를 추가해준다.
// src/test/java/hello/hellospring/service/MemberServiceTest.java
public class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
}
@BeforeEach : 테스트 메소드 실행 이전에 수행@AfterEach : 테스트 메소드 실행 이후에 수행
테스트 메소드 실행 전에 메소드마다 새로운 객체(레포지토리와 서비스)를 만들어주고, 실행 후에 레포지토리를 비운다.