.gradlew build
cd build/libs
java -jar hello-spring-0.0.1-SNAPSHORT.jar
정적컨텐츠
- resources/static/hello-static.html 등록 후 localhost 실행
MVC와 템플릿 엔진
@Controller
public class HelloController {
@GetMapping("hello-mvc")
public String helloMvc(@RequestParam("name") String name, Model model) {
model.addAttribute("name", name);
return "hello-template";
}
}
# View
# resources/static/hello-static.html
<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
</body>
</html>
-> localhost:8080/hello-mvc?name=spring 확인
API
src
main
ㄴ resources
ㄴ static
ㄴ index.html
ㄴ templates
ㄴ hello.html
ㄴ hello-template.html
ㄴ application.properties
ㄴ java
ㄴ hello.hellospring
ㄴ controller
ㄴ HelloController
ㄴ domain
ㄴ Member
ㄴ repository
ㄴ MemberRepository (i)
ㄴ MemoryMemberRepository
멤버 객체, 멤버 레포지토리, 멤버 레포지토리 구현 객체
# domain.Member.java
package hello.hellospring.domain;
public class Member {
private Long id;
private String name;
// Getter and Setter
}
# MemberRepository - 인터페이스
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
# 멤버레포지토리 메모리 구현체
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();
}
}
서비스 개발
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
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);
}
}
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
//given 이러한 상황에서
Member member = new Member();
member.setName("spring");
//when 이걸 실행하면
repository.save(member);
//then 이렇게 됨
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);
}
}
서비스 테스트
public class MemberService {
// private final MemberRepository memberRepository =
// new MemoryMemberRepository();
// 기존에는 서비스가 메모리 리포지토리를 직접 생성하게 함
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// 서비스 코드를 DI 가능하게 변경
.
...
# 각 테스트가 종료될 때마다 기능 실행(메모리 DB에 저장된 데이터 삭제
@AfterEach
public void afterEach() {
memberRepository.clearStore(); // clear 처리 해줌
}
# test 하는 것이 new 객체가 아닌 바로 그 객체를 사용하기 위해
# test 전에 만들어줌 (D.I)
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
# @Autowired
스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌
@Test 껍데기 자동으로 만들기
클래스 네임 - cmd+sh+t
null 오류 발생 위험이 있으면 Optional로 감싸기
DI(Dependency Injection) : 객체 의존관계를 외부에서 넣어주는 것
@Controller
public class MemberController {
private final MemberService memberService;
// 객체를 생성할 필요없이 하나를 공용으로 사용하면 됨
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
@Autowired로 memberService를 자동 주입
-> 스프링 빈으로 등록되어 있지 않아서 오류남
-> @Component 로 컴포너트 스캔하면 자동 등록 됨
(@Controller, @Service, @Repository)
# 생성자에 @Autowired 사용하면 객체 생성 시점에
스프링 컨테이너에서 해당 스프링 빈을 찾아 주입
# 스프링 빈을 등록할 때 기본으로 싱글톤으로 등록
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberService());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
controller(GetMapping) -> return creat.html -> 뷰 리졸버가 creat.html 선택 - thymeleaf 템플릿 엔진이 렌더링
H2 연결
# build.gradle
dependencies 에 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
# 스프링부트 DB연결 설정 추가
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
스프링부트 테스트
@SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행
@Transactinal : 테스트 시작 전 시작하고 테스트 완료 후 항상 롤백(DB에 데이터가 넘어가지 않으므로 다음 테스트에 영향 x)