| 3. 회원 관리 예제 - 백엔드 개발
🌱 비즈니스 요구사항 정리
- 데이터 : id, name
- 기능 : 회원 등록, 조회
일반적인 웹 어플리케이션의 계층구조
- 컨트롤러 : 웹 MVC 의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 리포지토리 : 데이터베이스에 접근하고 도메인 객체를 DB 에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체
- ex. 회원,주문,쿠폰 등등 데베에 저장후 관리
클래스 의존관계
🌱 회원 도메인과 리포지토리 만들기 + 테스트
회원 객체 (Member)
package com.example.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; } }
회원 리포지토리 인터페이스 (MemberMemoryRepository)
package com.example.hellospring.repository; import com.example.hellospring.domain.Member; import java.util.*; 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()); } public void clearStore() { store.clear(); } }
회원 리포지토리 테스트케이스 (MemberMemoryRepositoryTest)
package com.example.hellospring.repository; import static org.assertj.core.api.Assertions.*; import com.example.hellospring.domain.Member; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import java.util.List; public class MemoryMemberRepositoryTest { MemoryMemberRepository repository = new MemoryMemberRepository(); @AfterEach public void afterEach() { repository.clearStore(); } @Test public void save() { Member member = new Member(); member.setName("spring"); repository.save(member); Member result = repository.findById(member.getId()).get(); assertThat(member).isEqualTo(result); } @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(); 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); } }
Assertions.assertThat() 오류
→ 임포트를 잘하자
static import가 자동완성이 되지 않습니다. - 인프런 | 질문 & 답변
테스트 성공으로 뜨는데 빨간색 오류
→ 테스트 코드 여러개중 일부만 수행햇을 때 생기는 거라 옆에 초록색만 생기면 ㄱㅊ
질문하나만 드려도될까요.. - 인프런 | 질문 & 답변
단축키 모음
optional?
@AfterEach
: 각 테스트 코드가 실행된 후에 자동으로 수행
@AfterEach
public void afterEach() {
repository.clearStore();
// 데이터 삭제
}
🌱 회원 서비스 개발 + 테스트
회원 서비스 개발 (MemberService)
package com.example.hellospring.service; import com.example.hellospring.domain.Member; import com.example.hellospring.repository.MemberRepository; import com.example.hellospring.repository.MemoryMemberRepository; import java.util.List; import java.util.Optional; public class MemberService { // private final MemberRepository memberRepository = new MemoryMemberRepository(); 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) { // Optional<Member> result = memberRepository.findByName(member.getName()); // result.ifPresent(m -> { // throw new IllegalStateException("이미 존재하는 회원입니다"); // }); 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); } }
회원 서비스 테스트 (MemberServiceTest)
package com.example.hellospring.service; import com.example.hellospring.domain.Member; import com.example.hellospring.repository.MemoryMemberRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; 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(); assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test public void 중복_회원_예외() { Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); memberService.join(member1); try { memberService.join(member2); fail("예외가 발생해야 합니다."); } catch (IllegalStateException e) { assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다"); } } @Test void findMembers() { } @Test void findOne() { } }
@BeforeEach
: 각 테스트 코드가 실행되기 전에 자동으로 수행
@BeforeEach
public void beforeEach() {
repository.clearStore();
}
4. 스프링 빈과 의존관계
🌱 컴포넌트 스캔과 자동 의존관계 설정
컨트롤러가 서비스와 리포지토리를 사용할 수 있게 의존관계를 준비하는 과정
회원 컨트롤러에 의존관계 추가
package com.example.hellospring.controller;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MemberController {
private final MemberService meberService;
@Autowired
public MemberController(MemberService meberService) {
this.meberService = meberService;
}
}
@Component
: 스프링 빈으로 자동 등록@Autowired
: 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌→ 의존성 주입 (DI : Dependency Injection)
회원 서비스 스프링 빈 등록
@Service public class MemberService { private final MemberRepository memberRepository; @Autowired public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; }
회원 리포지토리 스프링 빈 등록
@Repository public class MemoryMemberRepository implements MemberRepository {}
기본으로 싱글톤으로 등록 (하나만 등록해서 공유)
→ 같은 스프링 빈 = 같은 인스턴스
참고: helloController 는 스프링이 제공하는 컨트롤러 → 스프링 빈 자동 등록
스프링 빈 등록방법 2가지
- 컴포넌트 스캔과 자동 의존관계 설정 (위)
- 자바코드로 직접 스프링 빈 등록 (아래)
컴포넌트 스캔원리
다음 annotation 이 있으면 스프링 빈으로 자동 등록
@Component
@Service
@Repository
🌱 자바 코드로 직접 스프링 빈 등록하기
package com.example.hellospring;
import com.example.hellospring.repository.MemoryMemberRepository;
import com.example.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
5. 회원 관리 예제 - 웹 MVC 개발
홈 화면 추가
홈 컨트롤러
package com.example.hellospring.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
회원 관리용 홈 html
<!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> <!-- /container --> </body> </html>
등록
회원 등록 폼 컨트롤러
@Controller
public class MemberController {
private final MemberService meberService;
@Autowired
public MemberController(MemberService meberService) {
this.memberService = meberService;
}
@GetMapping("/members/new")
public String createForm() {
return "members/createMemberForm";
}
@PostMapping("/members/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
System.out.println("System : " + member.getName());
memberService.join(member);
return "redirect:/";
}
}
회원 등록 폼 화면 html
<!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> <!-- /container --> </body> </html>
회원 등록 컨트롤러
웹 등록 화면에서 데이터를 전달 받을 폼 객체
package com.example.hellospring.controller;
public class MemberForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
조회
회원 컨트롤러에서 조회 기능
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
회원 리스트 html
<!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>
'3-2기 스터디 > Spring 입문' 카테고리의 다른 글
[6주차] 스프링 핵심 원리 이해1 - 예제 만들기 (0) | 2022.05.20 |
---|---|
[5주차] 객체 지향 설계와 스프링 (0) | 2022.05.11 |
[4주차] AOP (0) | 2022.05.04 |
[3주차] 스프링 DB 접근 기술 (0) | 2022.05.04 |
[1주차] 프로젝트 환경 설정, 스프링 웹 개발 기초 (0) | 2022.04.06 |
댓글