domain : Member 클래스에 요구사항에 필요한 개인이 아닌 시스템이 저장해둔 임의의 값을 넣어둠
MemberRepository 인터페이스를 만들어줌 (요구 사항이 아직 데이터 베이스가 결정되지 않았기에 만들어줌)
인터페이스 이니깐 어느 기능을 넣을지만 해둠
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
Optional : null을 처리하기 위해 Optional로 감싼다음 반환한다.
MemoryMemberRepository : 인터페이스의 기능을 구현한다.
MemberRepository를 상속 받는다
기능 구현에 필요한 객체를 만든다
private static Map<Long, Member> store = new HashMap();
private static long sequence = 0l;
기능 정상 동작 확인 : test/java 아래에 테스트 파일 생성
@Test
public void save() {
Member member = new Member();
member.setName("sprintg");
repository.save(member);
Member result = repository.findById(member.getId()).get();
assertThat(member).isEqualTo(result);
}
asertThat(<변수1>).isEqualTo(<변수2>);
변수 1값이 2랑 같은가
서비스 개발
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("이미 존재하는 회원입니다.");
});
}
ctrl +alt _ shift + T : 리펙토링 관련(Extract Method를 눌러 한가지 관련 기능을 빼낸다.)
회원 정보 서비스 테스트
서비스의 클래스 이름을 클릭한 다음 alt + shift + T를 누른 다음 Create New Test를 누르고 JUnit5를 누르면 자동으로 테스트 껍데기를 생성해준다.
서비스 객체를 생성한 다음 테스트 할 기능을 만든다.
//given : 주어진 상황
//when : 무언가를 실행
//then : 나와야 하는 결과
@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");
Member member2 = new Member();
member2.setName("spring");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
DI : 테스트 케이스와 같은 걸로 바꾸기
서비스 클래스가 외부에서 리포지토리를 주입받게 바꾸기
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
테스트 케이스가 실행되기 전에 리포지토리를 생성하고 서비스에 넣어주게 설정.
실행 후에 리포지토리를 clear 해줌.
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
컨트롤러와 의존관계 설정하기
컨트롤러 안에 맴버서비스를 추가 후 @Autowired를 이용하여 컨트롤러와 서비를 연결한다.
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
서비스와, 리포지토리의 클래스에 @Service와 @Repository를 추가하여 스프링 빈에 추가한다.
@Autowired를 이용하여 서비스와 리포지토리를 연결한다
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
7번의 방법을 이용하지 않은 직접 빈에 등록하기
스프링 빈에 등록하라는 표시인 @Configuration을 클래스에 추가한다.
@Bean을 붙인 메소드를 만들어 의존관계를 정의한다.
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
컨트롤러는 @Autowired를 이용하여 리포지토리를 연결해야 한다.
이 방법을 사용하는 경우는 상황에 따라 구현 클래스를 변경해야 할 때 사용해야 한다.
웹 기능, 홈 화면 추가하기
컨트롤러를 하나 생성하여 주소로 /값을 받을때 home을 return하게 만든다
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "Home";
}
}
템플릿에 홈 화면을 구현한다
<!DOCTYPE html>
<html xmlns:th="<http://www.thymeleaf.org>">
<body>
<div class="container">
<div>
<h1>Hello Spring</h1>
<p>회원 기능</p>
<p>
<a href="/members/new">회원 가입</a>
<a href="/members">회원 목록</a>
</p>
</div>
</div>
</body>
</html>
새로운 주소를 받아주는 컨트롤러를 생성한다.
새로운 화면으로 보내주는 @GetMapping을 만든다.
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping("/members/new")
public String createForm() {
return "members/createMemberForm"; // 파일의 경로
}
}
화면을 구현한다.
<!DOCTYPE HTML>
<html xmlns:th="<http://www.thymeleaf.org>">
<body>
<div class = "container">
<form action = "/members/new" method="post">
<div class = "form-group">
<label for = "name">이름</label>
<input type = "text" id = "name" name = "name" placeholder = "이름을 입력하세요">
</div>
<button type = "submit">등록</button>
</form>
</div>
</body>
</html>
<form action = "/members/new" method="post"> 이는 Post 형식으로 요청을 보내는거니 @PostMapping으로 만든다.
@PostMapping("/members/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
새로운 주소를 받아주는 기능을 모두 구현한다
첫 화면 @GetMapping을 만든다
@GetMapping("/members")
public String list(org.springframework.ui.Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
화면을 구현한다.
<!DOCTYPE html>
<html xmlns:th="<http://www.thymeleaf.org>">
<body>
<div class = "container">
<div>
<table>
<thead>
<tr>
<th>#</th>
<th>이름</th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${members}">
<td th:text = "${member.id}"></td>
<td th:text="${member.name}"></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
데이터 베이스에 연동한다.