자세한 코드 내용은 아래 링크를 통해 확인 가능하고
Repository, Service, Controller 설명에서 적은 코드는 영역마다 어떤 역할을 하는지 간략하게
깨우쳐 주기 위해서 작성했습니다.
코드 내용
📢 DB가 없다는 가정에 domain 객체로 코드를 작성했습니다.
📢 강의에서는 간략한 부분만 다뤄서 자세한 부분은 세미나를 통해 디테일하게 조사하겠습니다.
Repository, Service, Controller
JDBC, JPA 를 배우지 않았다는 가정하에 작성하였습니다.
Domain(Class)
데이터베이스에 있는 칼럼 정보를 객체로 정의하여 사용함.
(@Entity, @Id, @Generate 는 자동으로 칼럼을 정보를 넣기 위해서 작성한 내용이고 JPA 설명했을 때 나온다.)
package hello.hellospring.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
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;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Repository(Interface + Class)
저장소 역할을 함. 즉, 데이터 단에 매칭되는 Entity를 통해 받아온 정보를 데이터베이스에 저장하고 조회하는 기능을 수행 인터페이스를 통해 형태를 만들고 클래스를 통해 구현체를 만든다
(JPA 설명할 때 작성한 코드 내용을 가져온것이다. Interface를 상속받아 구현 Class를 작성하는게 기본적인 원칙이다.)
package hello.hellospring.repository;
import hello.hellospring.domain.Person;
import java.util.List;
import java.util.Optional;
public interface PersonRepository {
Person save(Person person);
Optional<Person> findByPerson(Long id);
Optional<Person> findByName(String name);
Optional<Person> findByAge(Integer age);
List<Person> findAll();
}
package hello.hellospring.repository;
import hello.hellospring.domain.Person;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaPersonRepository implements PersonRepository {
private EntityManager em;
public JpaPersonRepository(EntityManager em) {
this.em = em;
}
@Override
public Person save(Person person) {
em.persist(person);
return person;
}
@Override
public Optional<Person> findByPerson(Long id) {
Person person = em.find(Person.class, id);
return Optional.ofNullable(person);
}
@Override
public Optional<Person> findByName(String name) {
String sql = "select p from Person p where p.name= :name";
List<Person> result = em.createQuery(sql,Person.class)
.setParameter("name",name).getResultList();
return result.stream().findAny();
}
@Override
public Optional<Person> findByAge(Integer age) {
return Optional.empty();
}
@Override
public List<Person> findAll() {
String sql = "select p from Person p";
return em.createQuery(sql, Person.class).getResultList();
}
}
Service(Interface + Class)
Repository 에서 꺼내온 데이터들을 가공하여 Controller 단에 보내는 역할 Controller 쪽에서 바로 꺼내온 정보를 CRUD로가공하면 데이터베이스의 테이블에 저장된 원본 정보가 손상될 우려가 크기 때문에 Service 단을 한번 더 거쳐서 Controller로 데이터를 넘김.
(JPA 설명할 때 작성한 코드 내용을 가져온것이다.)
package hello.hellospring.service;
import hello.hellospring.domain.Person;
import hello.hellospring.repository.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Transactional
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
/**
* 회원가입
* 주석은 시간 재는거
* @param person
* @return
*/
public Long join(Person person) {
// long start = System.currentTimeMillis();
// try{
validateDuplicatePerson(person);
personRepository.save(person);
return person.getId();
// }finally {
// long finish = System.currentTimeMillis();
// long timeMs = finish - start;
// System.out.println("join " + timeMs + "ms");
// }
}
/**
* 중복 회원 검증
* person객체가 이미 있는지 확인
* findByName으로 같은 Person 있는지 확인
* 반환된 Optional 객체 값이 없다면 false
* 있으면 이미 존제하는 회원 에러
* @param person
*/
private void validateDuplicatePerson(Person person) {
personRepository.findByName(person.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/**
* 전체 Person 조회, 특정 회원 조회는 생략
*/
}
Controller(Class)
클라이언트에서 요청하는 URL을 통해 실행되는 영역, 반환 값으로 통해서 데이터를 전달함
package hello.hellospring.controller;
import hello.hellospring.domain.Person;
import hello.hellospring.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller //빈생성
public class PersonController {
private final PersonService personService;
@Autowired //의존성 주입
public PersonController(PersonService personService) {
this.personService = personService;
}
@PostMapping( "/person/new")
@ResponseBody
public Person create(@RequestParam("name") String name,@RequestParam("age") Integer age) {
Person person = new Person();
person.setName(name);
person.setAge(age);
personService.join(person);
return person;
}
@GetMapping("/persons")
@ResponseBody
public List list() {
List<Person> persons = personService.findPersons();
return persons;
}
}
테스트 케이스 작성 이유, 방법
단위 테스트란, 영역마다 구현 클래스마다 테스트하는 것이다.
이유
프로그램을 실행 시키기 전에 단위별로 제대로 동작하는지 테스트 하기 위함이다.
작성하는 방법과 주의사항
- given(생성), when(실행문), then(비교) 으로 주석을 표시하면서 적으면 작성하기 쉽니다.
- @BeforeEach로 동일한 객체를 생성하고 @AfterEach로 테이블을 초기화 해준다.
- 각각의 메서드를 실행할 수 있지만 클래스 전체로 실행시 순서가 보장되지 않는다.
- cntrl + shift + T 단축키를 누르면 자동으로 테스트 케이스를 만들 수 있고 파일은 test/java/해당 구역 파일 에 저장된다.
코드내용
(JPA 설명할 때 작성한 코드 내용을 가져온것이다. 어노테이션은 따로 다룰 예정이다.)
package hello.hellospring.service;
import hello.hellospring.domain.Person;
import hello.hellospring.repository.PersonRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class PersonServiceIntegrationTest {
@Autowired PersonService personService;
@Autowired PersonRepository personRepository;
/**
* 회원가입
*/
@Test
void join() {
//given
Person person = new Person();
person.setName("JS");
person.setAge(20);
//when
Long saveId = personService.join(person);
//then
Person findPerson = personRepository.findByPerson(saveId).get();
assertEquals(person.getName(),findPerson.getName());
}
/**
* 중복회원
*/
@Test
public void 중복회원예외() {
Person person1 = new Person();
person1.setName("JS");
person1.setAge(20);
Person person2 = new Person();
person2.setName("JS");
person2.setAge(43);
personService.join(person1);
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> personService.join(person2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
✔️강의 참고
스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
'공부(Study) > 스프링(Spring)' 카테고리의 다른 글
스프링 DB 연결 및 JDBC Template, JPA (0) | 2022.10.13 |
---|---|
스프링 DI, 의존성 주입 (0) | 2022.10.13 |
스프링 컨텐츠 종류 및 동작 방식 (0) | 2022.10.13 |
스프링 프로젝트 생성 및 빌드 방법 (0) | 2022.10.13 |
Infrean 스프링 입문 강의 추천! 김영한님의 모든 것! (0) | 2022.10.05 |