[스프링 입문] 1

smj_716·2025년 1월 6일

스프링 완전 정복

목록 보기
1/16

1. 스프링 웹 개발 기초

1) 정적 콘텐츠

클라이언트의 요청에 대해 서버에 미리 저장된 파일(HTML, CSS, JS 등)을 그대로 응답으로 제공하는 방식

이미지 설명
  1. 서버(Tomcat)이 클라이언트로부터 온 localhost:8080/hello-static.html 수신
  2. 만약 @GetMapping("/hello.html")이 있는 Controller가 있다면 → 이 메서드가 실행
  3. 없으면 /resources/static 디렉터리에서 정적 파일을 찾아 클라이언트에게 반환

💡 모든 클라이언트 요청에 대해 동일한 결과를 반환한다.


2) MVC(Model-View-Controller) 패턴

Model: 데이터와 비즈니스 로직을 담당
View: 클라이언트에게 표시되는 사용자 인터페이스를 생성
Controller: 클라이언트 요청을 처리하고 Model과 View를 연결

이미지 설명

🖥️ Controller

 @Controller
 public class HelloController {
    @GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello-template";
    }
 }

🖥️ View

 <html xmlns:th="http://www.thymeleaf.org">
 <body>
 <p th:text="'hello ' + ${name}">hello! empty</p>
 </body>
 </html>
  1. 사용자가 /hello 요청 → Controller가 요청을 처리
  2. Controller는 필요한 데이터를 Model에 저장 → View로 전달
  3. View는 데이터를 바탕으로 HTML 페이지를 렌더링

3) API

서버와 클라이언트 간 데이터를 교환하기 위한 인터페이스
html파일 자체가 아니라 @RestController JSON 형식을 사용해 클라이언트에게 전달

이미지 설명

🖥️ @ResponseBody 문자 반환

  • HTTP의 BODY에 문자 내용을 직접 반환
  • @ResponseBody 를 사용하여 viewResolver 대신해 HttpMessageConverter가 동작함
@Controller
 public class HelloController {
    @GetMapping("hello-string")
    @ResponseBody
    public String helloString(@RequestParam("name") String name) {
         return "hello " + name;
    }
 }

🖥️ @ResponseBody 객체 반환

  • 객체를 반환하면 객체가 JSON으로 변환
 @Controller
 public class HelloController {
    @GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
          Hello hello = new Hello();
          hello.setName(name);
          return hello;
    }
    
    static class Hello {
        private String name;
        public String getName() {
            return name;
        }
        public String setName(String name) {
            this.name =name;
        }
    }
}  

📌 요약


2. 회원 관리 예제

1) 테스트 케이스

자바의 main 메서드나 웹 애플리케이션의 컨트롤러를 통해 해당 기능을 실행하는 대신, 자바는 JUnit이라는 프레임워크로 테스트를 실행하여 반복 실행 및 여러 테스트를 한번에 실행하기 쉽게함


2) 회원 리포지토리 테스트 케이스

🖥️ MemoryMemberRepository

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();
    }
}

🖥️ MemoryMemberRepositoryTest

class MemoryMemberRepositoryTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach  //항상 새로운 객체와 의존관계 생성
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    public void save(){
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        memberRepository.save(member);

        //then
        Member result = memberRepository.findById(member.getId()).get();
        assertThat(result).isEqualTo(member); // Assertions.assertEquals(member, result);
    }

    @Test
    public void findByName(){
        //given
        Member member1 = new Member();
        member1.setName("spring1");
        memberRepository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        memberRepository.save(member2);

         //when
        Member result = memberRepository.findByName("spring1").get();

        // then
        assertThat(result).isEqualTo(member1);
    }
}
  • given 상황에서 when 했을 때 결과가 then 이어야 함
  • 한번에 여러 테스트를 실행하면 메모리 DB에 결과가 남을 수 있기 때문에 @AfterEach를 사용하여 각 테스트가 종료될 때 마다 이 기능을 실행함
  • 테스트는 각각 독립적으로 실행되어야함 -> 순서에 의존관계 있는 것은 좋지 않음

3) 서비스 리포지토리 테스트 케이스

동일한 인스턴스를 가지고 테스트 해야함

👉 MemberService 에서 직접 MemberRepository를 생성
따라서 MemberService의 MemberRepository와 테스트에서 사용하는 memberRepository가 다른 인스턴스이므로 제대로된 테스트가 아님

  • MemberService
private final MemberRepository memberRepository = new MemoryMemberRepository();
  • MemberServiceTest
MemberService memberService = new MemberService();
MemoryMemberRepository memberRepository = new MemoryMemberRepository();

👉 외부에서 MemberService 의 MemberRepository를 주입 = DI
@BeforeEach를 사용하여 각 test를 진행하기 전에 사용할 인스턴스들을 생성

🖥️ MemberService

private final MemberRepository memberRepository;

private MemberService(MemberRepository memberRepository){
    this.memberRepository = memberRepository;
}

🖥️ MemberServiceTest

MemberService memberService;
MemoryMemberRepository memberRepository;

@BeforeEach
public void beforeEach() {
    memberRepository = new MemoryMemberRepository();
    memberService = new MemberService(memberRepository);
}

4) 통합 테스트

🖥️ MemberServiceIntergrationTest

@SpringBootTest
@Transactional
class MemberServiceIntergrationTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {

        //Given
        Member member = new Member();
        member.setName("spring");

        //When
        Long saveId = memberService.join(member);

        //Then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() throws Exception {
        //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("이미 존재하는 회원입니다.");
    }
}
  • @Transactional: 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백하여 test에 사용했던 데이터를 DB에서 지워 다음 테스트에 영향을 주지 않도록 함
  • @SpringBootTest : 스프링 컨텍스트 전체를 로드하여 테스트를 수행함 -> 실제 스프링 애플리케이션 환경과 동일한 조건에서 테스트 진행 가능
  • @Autowired : 스프링 컨테이너에서 필요한 빈을 자동으로 주입 받을 때 사용

0개의 댓글