컨트롤러 : 웹 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
: 테스트 메소드 실행 이후에 수행테스트 메소드 실행 전에 메소드마다 새로운 객체(레포지토리와 서비스)를 만들어주고, 실행 후에 레포지토리를 비운다.