Spring 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 정리 인프런 강의 정리 (1)

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 정리
현업에서는 system.out.println을 사용하지 않고 log를 많이 사용한다. 보아서 볼 수 있기 때문.
JUnit은 테스트를 하는 도구로써 Java진영에서 많이 사용한다.
테스트 라이브러리
- junit : 테스트 프레임워크
- mockito : 목 라이브러리
- assertj : 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
- spring-test : 스프링 통합 테스트 지원
터미널에서 Spring을 실행하는법 (Mac에서 하는 방법)
실행하려는 프로젝트에서 터미널을 열고
./gradlew build
cd build/libs
java -jar hello-spring-0.0.1-SNAPSHOT.jar
을 하고 실행되는지 확인 해 보면 된다. 종료할땐 ^C로 종료하면 된다.
서버를 배포 할 때는 jar파일만 복사에서 서버에 넣어주고 java -jar로 실행만 시켜주면 된다.
gradlew clean을 하면 build 폴더를 지울 수 있다.
IntelliJ에서 cmd + shift + enter를 누르면 자동완성을 시켜준다.
정적 컨텐츠 : HTML 파일을 그대로 반환하는 것
MVC와 템플릿 엔진
MVC : Model View Controller
Model과 Controller는 비즈니스 로직을 담당하고 View는 화면을 그리는데에 집중해야한다.
API 방식 : 문자열을 반환하는 것이 아니라 객체를 반환하는 것
@ResponseBody를 사용하면 뷰 리졸버를 사용하지 않고 문자열을 반환하게 된다. (HTTP의 Body에 문자열을 반환. HTML Body태그를 말하는 것이 아니다.)
@ResponseBody 어노테이션이 붙었을 경우 return 값에 따라 다음과 같은 컨버터가 동작한다.
- 단순 문자일 경우 : String Converter가 동작하여 문자열을 반환한다.
- 객체일 경우 : Json Converter가 동작하여 JSON 형식으로 반환한다. 객체일 경우 Default로 JSON 형식으로 반환한다. xml을 원하면 설정으로 가능하다.
사실 실부에서 ResponseBody의 설정을 손을 데는 일은 거의 없다.
IntelliJ에서 Ctrl + Enter를 누르면 인터페이스의 구현체 오버라이드를 간편하게 할 수 있다.
클래스 가져올 땐 alt + shift + enter를 누르면 클래스를 가져올 수 있다.
테스트 할 때는 관례상 테스트 할 클래스의 이름 뒤에 Test를 붙여준다. 테스트 할 때는 굳이 public을 붙이지 않아도 된다.
테스트할 때는 테스트 할 메소드 위에 @Test 어노테이션을 붙여준다.
import org.assertj.core.api.Assertions;
Assertion.assertThat(테스트 대상 1).isEqualTo(테스트 대상 2);
위 방식이 테스트에서 제일 많이 사용하는 방법 중 하나다.
option + enter를 하면 static import를 할 수 있다.
변수명을 여러개 변경 하려는 경우에는 변경하려는 변수명을 클릭하고 shift + f6
@AfterEach는 해당 어노테이션이 붙은 메소드를 @Test 를 실행하고 끝날 때 마다 실행되게 한다.
cmd + option + v : 대입식 바로 만들어줌
Optional<Member> result = memberRepository.findByName(member.getName());
result.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
Optional에서 지원하기에 가능한 기능이다.
memberRepository.findByName(member.getName()).ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
이런 식으로 간략하게 하는 편이 좋다.
위와같이 findByName()안에 로직이 나열되어 있는 경우에는 메소드로 따로 뺴주는 편이 좋다.
ctrl + T를 누르면 메소드로 따로 분리한다던지 하는 리펙토링을 하는데 편하다
Service 쪽에 있는 클래스들은 비즈니스 로직에 가까운 이름으로 작명하여야 한다.
cmd + shift + T를 하면 해당 클래스의 테스트 케이스를 쉽게 만들 수 있다.
테스트 메소드의 이름은 한글이나 일본어를 사용할 수 도 있다. 실제로 현업에서는 한글을 많이 사용한다. 빌드 될 때 Test는 포함되 지 않기 떄문에 상관없다.
테스트를 할때는 given when then 이 3가지를 주석으로 두고 생각하면서 만들자!
테스트는 동작만 확인할 게 아니라 예외처리가 잘 되는지도 확인해야 한다.
@Test
public void 중복_회원_예외() {
// given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
// when
memberService.join(member1);
try{
memberService.join(member2);
fail( );
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
테스트에 굳이 try catch를 사용하지 않는 방법도 존재한다.
@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("이미 존재하는 회원입니다.");
}
assertThrows를 사용하면 try catch를 사용하지 않아도 된다.
memberServiceTest() 전체코드
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
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.*;
import static org.junit.jupiter.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("spring");
// 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);
// try{
// memberService.join(member2);
// fail( );
// } catch (IllegalStateException e) {
// assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
위 코드에서 @BeforeEach 어노테이션은 실행되기 전에 돌아가는 것이다. 해당 어노테이션에 있는 메서들를 통해 memberService 클래스는 memberRepository 의존성 주입(DI)을 받게 된다.
스프링 빈은 SpringApplication이 있는 위치와 그 이하의 디렉토리에 있는 컴포넌트만 등록한다.
스프링 빈을 수동으로 등록할 수도 있다.
package hello.hellospring;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.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 MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
@Configuration 어노테이션을 붙이고 @Bean 어노테이션을 써주면 스프링 빈에 등록이 가능하다.
의존성 주입 (DI) 에는 3가지 방식이 존재한다.
- 생성자 주입
- 필드 주입
- 세터 주입
3가지 방식중에서는 생성자 주입을 권장한다.
스프링에서 의존성 주입 방식 중 필드 주입과 세터 주입을 권장하지 않는 주된 이유는 다음과 같다
1. 필드 주입(Field Injection) 문제점
public class MemberService {
@Autowired
private MemberRepository memberRepository; // ❌
}
- 불변성(Immutable) 보장 불가: 필드가 final로 선언될 수 없어 객체 생성 후 의존성 변경 가능 테스트 어려움: 순수 자바 테스트 시 Mock 객체 주입 불가능 (반드시 스프링 컨테이너 필요)
의존성 숨김: 생성자 없이 의존 관계 파악 어려움
2. 세터 주입(Setter Injection) 문제점
public class MemberService {
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) { // ❌
this.memberRepository = memberRepository;
}
}
- 변경 가능성 노출: 런타임 중 의존성 변경 가능 (객체 불변성 파괴)
순환 의존성 발생 가능: 스프링 컨테이너 초기화 시 순환 참조 문제 발생 가능
Null 안정성 부족: 주입 전에 메서드 호출 시 NPE 발생 위험
✅ 생성자 주입 권장 이유
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) { // ✅
this.memberRepository = memberRepository;
}
}
불변성 보장: final 키워드 사용 가능
- 순환 의존성 사전 감지: 애플리케이션 구동 시점에 오류 발생
- 테스트 용이성: new 연산자로 객체 생성 시 의존성 주입 간편
- 코드 품질 향상: 생성자 파라미터 개수로 클래스 복잡도 가시화
📊 공식 문서 권장 사항
스프링 팀은 2016년부터 공식적으로 생성자 주입을 권장하고 있습니다. (Spring Framework 4.3 이후 단일 생성자 경우 @Autowired 생략 가능)
⚠️ 예외 사항
- 옵셔널한 의존성(선택적 주입)이 필요한 경우에만 세터 주입 사용
- 부트스트랩 클래스나 @Configuration 클래스에서의 제한적 필드 주입 허용
cmd + e 누르면 가장 최근에 봤던 파일 목록들을 볼 수 있다