├── .DS_Store ├── .gitignore ├── 01-Intro └── 01-스프링부트소개.md ├── 02-스프링부트시작 └── 01-스프링부트시작.md ├── 03-JPA ├── 01-JPA시작하기.md ├── 02-Lombok-1.md └── 03-Lombok-2.md ├── 04-JPA-Relation ├── 01-JPA-Relation-1.md ├── 02-JPA-Relation-2.md ├── 03-JPA-Relation-3.md ├── 04-JPA-Query-Method.md ├── 05-JPA-@Query.md ├── 06-JPA-data.sql사용하기.md └── images │ ├── query-method-1.png │ ├── query-method-2.png │ └── query-method-3.png ├── 05-Controller ├── 01-@GetMapping사용하기.md ├── 02-@PostMapping사용하기.md ├── 03-@PutMapping사용하기.md └── 04-@DeleteMapping사용하기.md ├── 06-Refactoring ├── 01-리팩토링-도메인코드-1.md └── 02-리팩토링-도메인코드-2.md ├── 07-Controller-Test ├── 01-Controller-Test-1.md ├── 02-Controller-Test-2.md └── 03-Controller-Test-3.md ├── 08-Repository-Test └── 01-Repository-Test.md ├── 09-Mock-Test ├── 01-Service-Test-1.md ├── 02-Service-Test-2.md ├── 03-Service-Test-3.md └── 04-Service-Test-4.md ├── 10-Exception ├── 01-Exception-Handling-1.md ├── 02-Exception-Handling-2.md └── 03-Exception-Handling-3.md ├── 11-Validator └── 01-Parameter-Validator.md ├── 12-Paging └── 01-List-Api-And-Paging.md ├── 13-Summary └── 01-SpringBoot-Project-학습정리.md └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeism/fastcampus-java/24b78167eaa7d379a12f31abdebffae6c5a2d56b/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /01-Intro/01-스프링부트소개.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | ## 스프링부트소개 4 | 5 | ### 강사소개 6 | 7 | - N/A 8 | 9 | ### 준비사항 10 | 11 | - SpringBoot 2.1.x 12 | - Java Jdk8 13 | - Spring 5.x 14 | - Intellij, Eclipse, VSCode 15 | - 강의환경 : MacOS 16 | 17 | ## 스프링부트란? 18 | 19 | ### 스프링부트의 탄생 20 | 21 | - SpringFramework의 설정이 너무 복합하고 어려웠기 때문에, 이를 해결하기 위해 만들어짐 22 | - SpringBoot가 나오면서 초기 생산성이 획기적으로 좋아짐 23 | 24 | ### 스프링과 스프링부트의 차이점 25 | 26 | - 스프링은 다양한 식재료이고, 스프링부트는 완성된 케이크라고 볼 수 있음 27 | - 식재료를 다양하게 섞어서 자신만의 요리를 만들고 싶은 사람도 있을 것이고, 거의 다 만들어져 있는 음식을 데우기만 해서 먹고 싶은 사람도 있을 것 28 | 29 | ### 스프링부트의 특징 30 | 31 | - Starter를 통한 어플리케이션의 간편하고 자동화된 빌드 및 설정을 제공함 32 | - Embed 서버를 제공함으로써 복잡한 배포 설정들을 간편하게 제공함 33 | - Production에서 사용할 수 있는 추가적인 기능을 제공함 (actuator) 34 | 35 | ### 웹설정 예제 36 | 37 | #### Xml Configuration 38 | 39 | ```xml 40 | 41 | dispatcher 42 | org.springframework.web.servlet.DispatcherServlet 43 | 44 | contextConfigLocation 45 | /WEB-INF/spring/dispatcher-config.xml 46 | 47 | 1 48 | 49 | ``` 50 | 51 | #### Xml + Java Configuration 52 | 53 | ```java 54 | public class MyWebAppInitializer implements MyWebAppInitializer { 55 | @Override 56 | public void onStartup(ServletContext container) { 57 | XmlWebApplicationContext context = new XmlWebApplicationContext(); 58 | context.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml"); 59 | 60 | ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(context)); 61 | 62 | dispatcher.setLoadOnStartup(1); 63 | dispatcher.addMapping("/api"); 64 | } 65 | } 66 | ``` 67 | 68 | #### Only Java Configuration 69 | 70 | ```java 71 | public class MyWebAppInitializer implements MyWebAppInitializer { 72 | @Override 73 | public void onStartup(ServletContext container) { 74 | AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); 75 | context.setConfigLocation("com.fastcampus.myapp.config"); 76 | 77 | container.addListener(new ContextLoadListener(context)); 78 | 79 | ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(context)); 80 | 81 | dispatcher.setLoadOnStartup(1); 82 | dispatcher.addMapping("/api"); 83 | } 84 | } 85 | ``` 86 | 87 | #### SpringBoot Configuration 88 | 89 | ``` 90 | spring.mvc.view.prefix=/WEB-INF/jsp/ 91 | spring.mvc.view.suffix=.jsp 92 | server.port=8081 93 | server.context-path=/api 94 | ``` 95 | 96 | ### Convention Over Configuration 97 | 98 | - 설정보다는 관습 (줄여서 CoC, Coding by convention) 99 | - 개발자가 설정해야 하는 것은 어플리케이션의 관례를 따르지 않는 점 뿐이다. 100 | 101 | ### 챕터요약 102 | 103 | - Spring과 SpringBoot는 다른 것이다 104 | - SpringBoot는 Java의 생산성 향상을 가져왔다 105 | - 개발자가 설정해야 하는 것은 관례를 따르지 않는 점 뿐이다 106 | -------------------------------------------------------------------------------- /02-스프링부트시작/01-스프링부트시작.md: -------------------------------------------------------------------------------- 1 | # 스프링부트 시작 2 | 3 | ## 스프링부트 시작하기 4 | 5 | ### 스프링부트 프로젝트를 생성하기 6 | 7 | - https://start.spring.io 에서 생성하기 8 | - IntelliJ > new Project > Spring Initializer 에서 생성하기 9 | 10 | ## HelloWorldController 생성하기 11 | 12 | ### Controller 생성 13 | 14 | ```java 15 | @RestController 16 | public class HelloWorldController { 17 | @GetMapping(value = "/api/helloWorld") 18 | public String helloWorld() { 19 | return "HelloWorld"; 20 | } 21 | } 22 | ``` 23 | 24 | ### Embed WAS 서버실행 25 | 26 | ```bash 27 | ./gradlew bootRun 28 | ``` 29 | 30 | ### 브라우저에서 테스트 31 | 32 | ``` 33 | http://localhost:8080/api/helloWorld 34 | ``` 35 | 36 | ### http 파일을 통한 테스트 37 | 38 | ``` 39 | GET http://localhost:8080/api/helloWorld 40 | ``` 41 | 42 | ## MockMvc 테스트 만들기 43 | 44 | ### gradle 의존성 추가 45 | 46 | ```groovy 47 | dependencies { 48 | implementation 'org.springframework.boot:spring-boot-starter-web' 49 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 50 | implementation 'com.h2database:h2' 51 | compileOnly 'org.projectlombok:lombok' 52 | annotationProcessor 'org.projectlombok:lombok' 53 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 54 | testImplementation 'org.junit.platform:junit-platform-launcher:1.5.0' 55 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.0' 56 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.5.0' 57 | testImplementation 'org.mockito:mockito-junit-jupiter' 58 | } 59 | ``` 60 | 61 | ### ControllerTest 생성 62 | 63 | ```java 64 | @SpringBootTest 65 | class HelloWorldControllerTest { 66 | @Test 67 | void helloWorld() { 68 | System.out.println("test"); 69 | } 70 | } 71 | ``` 72 | 73 | ### HelloWorldController 테스트 74 | 75 | ```java 76 | @SpringBootTest 77 | class HelloWorldControllerTest { 78 | @Autowired 79 | private HelloWorldController HelloWorldController; 80 | 81 | @Test 82 | void helloWorld() { 83 | assertThat(HelloWorldController.HelloWorldController()).isEqualTo("HelloWorld"); 84 | } 85 | } 86 | ``` 87 | 88 | ### MockMvc 테스트 89 | 90 | ```java 91 | @SpringBootTest 92 | class HelloWorldControllerTest { 93 | @Autowired 94 | private HelloWorldController HelloWorldController; 95 | 96 | private MockMvc mockMvc; 97 | 98 | @Test 99 | void helloWorld() { 100 | assertThat(HelloWorldController.HelloWorldController()).isEqualTo("HelloWorld"); 101 | } 102 | 103 | @Test 104 | void mockMvcTest() throws Exception { 105 | mockMvc = MockMvcBuilders.standaloneSetup(HelloWorldController).build(); 106 | 107 | mockMvc.perform( 108 | MockMvcRequestBuilders.get("/api/helloWorld")) 109 | .andDo(MockMvcResultHandlers.print()) 110 | .andExpect(status().isOk()) 111 | .andExpect(MockMvcResultMatchers.content().string("HelloWorld")); 112 | } 113 | } 114 | ``` -------------------------------------------------------------------------------- /03-JPA/01-JPA시작하기.md: -------------------------------------------------------------------------------- 1 | # JPA 시작하기 2 | 3 | 4 | ## 이론 5 | 6 | * JPA사용을 위한 의존성 추가하기 7 | * spring-boot-starter-data-jpa : JPA사용을 위한 스프링부트 스타터 8 | * h2 : 간단하게 사용할 수 있는 초경량 메모리 DB 9 | * JPA의 특징 10 | * DB의 종류에 상관없이 JPA문법에 따라서 로직을 생성할 수 있음 11 | * DB를 변경하더라도 JPA에서 해당 DB에 적합한 쿼리를 자동으로 변경생성해줌 12 | * 주요 Annotation의 역할 13 | * @Entity : 해당 클래스가 Domain Entity로 사용할 것이라는 표식 14 | * @Id : 해당 컬럼이 Domain Entity의 Pk임을 명시함 15 | * @GeneratedValue : 해당 컬럼의 값은 자동으로 생성되는 값임을 명시함 16 | * GenerationType : TABLE, SEQUENCE, IDENTITY, AUTO (default) 17 | 18 | ## 실전코드 19 | 20 | ```groovy 21 | dependencies { 22 | implementation 'org.springframework.boot:spring-boot-starter-web' 23 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 24 | implementation 'com.h2database:h2' 25 | compileOnly 'org.projectlombok:lombok' 26 | annotationProcessor 'org.projectlombok:lombok' 27 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 28 | testImplementation 'org.junit.platform:junit-platform-launcher:1.5.0' 29 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.0' 30 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.5.0' 31 | } 32 | ``` 33 | 34 | ```java 35 | @Entity 36 | public class Person { 37 | @Id 38 | @GeneratedValue 39 | private Long id; 40 | 41 | private String name; 42 | 43 | private int age; 44 | 45 | public Long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(Long id) { 50 | this.id = id; 51 | } 52 | 53 | public String getName() { 54 | return name; 55 | } 56 | 57 | public void setName(String name) { 58 | this.name = name; 59 | } 60 | 61 | public int getAge() { 62 | return age; 63 | } 64 | 65 | public void setAge(int age) { 66 | this.age = age; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "Person{" + 72 | "id=" + id + 73 | ", name='" + name + '\'' + 74 | ", age=" + age + 75 | '}'; 76 | } 77 | } 78 | ``` 79 | 80 | ```java 81 | public interface PersonRepository extends JpaRepository { 82 | } 83 | ``` 84 | 85 | ```java 86 | @SpringBootTest 87 | class PersonRepositoryTest { 88 | @Autowired 89 | private PersonRepository personRepository; 90 | 91 | @Test 92 | void crud() { 93 | Person person = new Person(); 94 | person.setName("martin"); 95 | person.setAge(10); 96 | 97 | personRepository.save(person); 98 | 99 | // System.out.println(personRepository.findAll()); 100 | 101 | List people = personRepository.findAll(); 102 | 103 | assertThat(people.size()).isEqualTo(1); 104 | assertThat(people.get(0).getName()).isEqualTo("martin"); 105 | assertThat(people.get(0).getAge()).isEqualTo(10); 106 | } 107 | } 108 | ``` 109 | 110 | ```yaml 111 | spring: 112 | jpa: 113 | show-sql: true 114 | ``` 115 | -------------------------------------------------------------------------------- /03-JPA/02-Lombok-1.md: -------------------------------------------------------------------------------- 1 | # Lombok #1 2 | 3 | ## 이론 4 | 5 | * Lombok의 필요성 6 | * 자바 코드에서 필요한 기본 메소드들을 자동으로 생성해줌 7 | * 특히, entity는 반복적인 생성자, Getter, Setter들을 많이 생성하게 되어 Lombok이 유용함 8 | * @Getter 9 | * getter는 class의 field variable의 값에 접근할 수 있는 메소드임 10 | * @Getter를 선언하면 기본 getter를 생성해줌 11 | * @Getter는 각 field variable에 선언할 수도 있고, class 상단에 선언할 수도 있음 12 | * @Setter 13 | * setter는 class의 field variable의 값을 저장할 수 있는 메소드임 14 | * @Setter를 선언하면 기본 setter을 생성ㅇ해줌 15 | * @Setter는 각 field variable에 선언할 수도 있고, class 상단에 선언할 수도 있음 16 | * @ToString 17 | * toString()은 기본적으로 해당 객체를 출력할 수 있도록 최상위 Object 객체에 선언되어 있음 18 | * toString()을 override하여 자신이 원하는 값을 출력할 수 있도록 커스터마이징 할 수 있음 19 | * Object 객체의 toString()은 해당 객체의 해쉬값을 출력하기 때문에, 일반적으로 toString()은 override 해야만 함 20 | * @ToString을 사용하면, 일반적으로 사용하는 방식의 toString()을 자동으로 생성해줌 21 | 22 | ## 실전코드 23 | 24 | ```java 25 | @Entity 26 | @Getter 27 | @Setter 28 | @ToString 29 | public class Person { 30 | @Id 31 | @GeneratedValue 32 | private Long id; 33 | 34 | private String name; 35 | 36 | private int age; 37 | 38 | private String hobby; 39 | 40 | private String bloodType; 41 | 42 | private String address; 43 | 44 | private LocalDate birthday; 45 | 46 | private String job; 47 | 48 | @ToString.Exclude 49 | private String phoneNumber; 50 | } 51 | ``` 52 | 53 | ```java 54 | @SpringBootTest 55 | class PersonRepositoryTest { 56 | @Autowired 57 | private PersonRepository personRepository; 58 | 59 | @Test 60 | void crud() { 61 | Person person = new Person(); 62 | person.setName("martin"); 63 | person.setAge(10); 64 | person.setBloodType("A"); 65 | 66 | personRepository.save(person); 67 | 68 | System.out.println(personRepository.findAll()); 69 | 70 | List people = personRepository.findAll(); 71 | 72 | assertThat(people.size()).isEqualTo(1); 73 | assertThat(people.get(0).getName()).isEqualTo("martin"); 74 | assertThat(people.get(0).getAge()).isEqualTo(10); 75 | assertThat(people.get(0).getBloodType()).isEqualTo("A"); 76 | } 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /03-JPA/03-Lombok-2.md: -------------------------------------------------------------------------------- 1 | # Lombok #2 2 | 3 | ## 이론 4 | 5 | * @NoArgsConstructor 6 | * 아무런 파라미터를 가지지 않은 생성자를 생성해줌 7 | * @AllArgsConstructor 8 | * 전체 field variable을 파라미터로 가지는 생성자를 생성해줌 9 | * @RequiredArgsConstructor 10 | * 필요한 field variable을 @NonNull로 선언하고, 해당 파라미터를 가지는 생성자를 생성해줌 11 | * @EqualsAndHashCode 12 | * HashCode : 해시코드가 동일하면, 같은 객체라는 것을 의미함 13 | * Equal : 해당 객체가 같은 값을 가지고 있다는 것을 의미함 14 | * @Data 15 | * @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode를 한꺼번에 선언해줌 16 | 17 | ## 실전코드 18 | 19 | ```java 20 | @Entity 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | @RequiredArgsConstructor 24 | @Data 25 | public class Person { 26 | @Id 27 | @GeneratedValue 28 | private Long id; 29 | 30 | @NonNull 31 | private String name; 32 | 33 | @NonNull 34 | private int age; 35 | 36 | private String hobby; 37 | 38 | @NonNull 39 | private String bloodType; 40 | 41 | private String address; 42 | 43 | private LocalDate birthday; 44 | 45 | private String job; 46 | 47 | @ToString.Exclude 48 | private String phoneNumber; 49 | } 50 | ``` 51 | 52 | ```java 53 | @SpringBootTest 54 | class PersonRepositoryTest { 55 | @Autowired 56 | private PersonRepository personRepository; 57 | 58 | @Test 59 | void crud() { 60 | Person person = new Person(); 61 | person.setName("martin"); 62 | person.setAge(10); 63 | person.setBloodType("A"); 64 | 65 | personRepository.save(person); 66 | 67 | System.out.println(personRepository.findAll()); 68 | 69 | List people = personRepository.findAll(); 70 | 71 | assertThat(people.size()).isEqualTo(1); 72 | assertThat(people.get(0).getName()).isEqualTo("martin"); 73 | assertThat(people.get(0).getAge()).isEqualTo(10); 74 | assertThat(people.get(0).getBloodType()).isEqualTo("A"); 75 | } 76 | 77 | @Test 78 | void hashCodeAndEquals() { 79 | Person person1 = new Person("martin", 10, "A"); 80 | Person person2 = new Person("martin", 10, "A"); 81 | 82 | System.out.println(person1.equals(person2)); 83 | System.out.println(person1.hashCode()); 84 | System.out.println(person2.hashCode()); 85 | 86 | Map map = new HashMap<>(); 87 | map.put(person1, person1.getAge()); 88 | 89 | System.out.println(map); 90 | System.out.println(map.get(person2)); 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /04-JPA-Relation/01-JPA-Relation-1.md: -------------------------------------------------------------------------------- 1 | # JPA Relation #1 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @Entity 7 | @Data 8 | @NoArgsConstructor 9 | @RequiredArgsConstructor 10 | public class Block { 11 | @Id 12 | @GeneratedValue 13 | private Long id; 14 | 15 | @NonNull 16 | private String name; 17 | 18 | private String reason; 19 | 20 | private LocalDate startDate; 21 | 22 | private LocalDate endDate; 23 | } 24 | ``` 25 | 26 | ```java 27 | @Entity 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | @RequiredArgsConstructor 31 | @Data 32 | public class Person { 33 | @Id 34 | @GeneratedValue 35 | private Long id; 36 | 37 | @NonNull 38 | private String name; 39 | 40 | @NonNull 41 | private int age; 42 | 43 | private String hobby; 44 | 45 | @NonNull 46 | private String bloodType; 47 | 48 | private String address; 49 | 50 | private LocalDate birthday; 51 | 52 | private String job; 53 | 54 | @ToString.Exclude 55 | private String phoneNumber; 56 | 57 | @OneToOne 58 | private Block block; 59 | } 60 | ``` 61 | 62 | ```java 63 | public interface BlockRepository extends JpaRepository { 64 | } 65 | ``` 66 | 67 | ```java 68 | @Service 69 | public class PersonService { 70 | @Autowired 71 | private PersonRepository personRepository; 72 | 73 | public List getPeopleExcludeBlocks() { 74 | List people = personRepository.findAll(); 75 | 76 | return people.stream().filter(person -> person.getBlock() == null).collect(Collectors.toList()); 77 | } 78 | } 79 | ``` 80 | 81 | ```java 82 | @SpringBootTest 83 | class PersonServiceTest { 84 | @Autowired 85 | private PersonService personService; 86 | @Autowired 87 | private PersonRepository personRepository; 88 | @Autowired 89 | private BlockRepository blockRepository; 90 | 91 | @Test 92 | void getPeopleExcludeBlocks() { 93 | givenPeople(); 94 | givenBlocks(); 95 | 96 | List result = personService.getPeopleExcludeBlocks(); 97 | 98 | // System.out.println(result); 99 | result.forEach(System.out::println); 100 | } 101 | 102 | private void givenPeople() { 103 | givenPerson("martin", 10, "A"); 104 | givenPerson("david", 9, "B"); 105 | givenBlockPerson("dennis",7,"O"); 106 | givenBlockPerson("martin", 11, "AB"); 107 | } 108 | 109 | private void givenBlocks() { 110 | givenBlock("martin"); 111 | } 112 | 113 | private void givenPerson(String name, int age, String bloodType) { 114 | personRepository.save(new Person(name, age, bloodType)); 115 | } 116 | 117 | private void givenBlockPerson(String name, int age, String bloodType) { 118 | Person blockPerson = new Person(name, age, bloodType); 119 | blockPerson.setBlock(givenBlock(name)); 120 | 121 | personRepository.save(blockPerson); 122 | } 123 | 124 | private Block givenBlock(String name) { 125 | return blockRepository.save(new Block(name)); 126 | } 127 | } 128 | ``` 129 | -------------------------------------------------------------------------------- /04-JPA-Relation/02-JPA-Relation-2.md: -------------------------------------------------------------------------------- 1 | # JPA Relation #2 2 | 3 | ## 이론 4 | 5 | * @OneToOne 6 | * Entity를 1:1 관계로 연결해주는 어노테이션 7 | * `optional=false`이면 `inner join`으로 쿼리가 생성됨 8 | * `optional=true`로 하면 `left outer join`으로 쿼리가 생성됨 9 | 10 | ## 실전코드 11 | 12 | ```java 13 | @SpringBootTest 14 | class BlockRepositoryTest { 15 | @Autowired 16 | private BlockRepository blockRepository; 17 | 18 | @Test 19 | void crud() { 20 | Block block = new Block(); 21 | block.setName("martin"); 22 | block.setReason("친하지않아서"); 23 | block.setStartDate(LocalDate.now()); 24 | block.setEndDate(LocalDate.now()); 25 | 26 | blockRepository.save(block); 27 | 28 | List blocks = blockRepository.findAll(); 29 | 30 | assertThat(blocks.size()).isEqualTo(1); 31 | assertThat(blocks.get(0).getName()).isEqualTo("martin"); 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /04-JPA-Relation/03-JPA-Relation-3.md: -------------------------------------------------------------------------------- 1 | # JPA Relation #3 2 | 3 | ## 이론 4 | 5 | * cascade 6 | * 관련된 entity의 영속성을 함께 관리할 수 있도록 해줌 7 | * CascadeType.PERSIST 8 | * insert할 경우 관련 entity도 함께 insert함 9 | * CascadeType.MERGE 10 | * update할 경우 관련 entity도 함께 update함 11 | * CascadeType.REMOVE 12 | * delete할 경우 관련 entity도 함께 delete함 13 | * CascadeType.ALL 14 | * 모든 케이스에 대해 영속성을 함께 관리함 15 | * orphanRemoval 16 | * 관련 entity의 relation이 사라질 때, entity를 함께 삭제해줌 17 | * fetch 18 | * FetchType.EAGER 19 | * 항상 relation이 있는 entity를 join하여 값을 함께 가져옴 20 | * FetchType.LAZY 21 | * 해당 객체가 필요한 시점에 id를 통해 새로 select해서 값을 가져옴 22 | 23 | ## 실전코드 24 | 25 | ```java 26 | @Entity 27 | @NoArgsConstructor 28 | @AllArgsConstructor 29 | @RequiredArgsConstructor 30 | @Data 31 | public class Person { 32 | @Id 33 | @GeneratedValue 34 | private Long id; 35 | 36 | @NonNull 37 | private String name; 38 | 39 | @NonNull 40 | private int age; 41 | 42 | private String hobby; 43 | 44 | @NonNull 45 | private String bloodType; 46 | 47 | private String address; 48 | 49 | private LocalDate birthday; 50 | 51 | private String job; 52 | 53 | @ToString.Exclude 54 | private String phoneNumber; 55 | 56 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 57 | @ToString.Exclude 58 | private Block block; 59 | } 60 | ``` 61 | 62 | ```java 63 | @Service 64 | @Slf4j 65 | public class PersonService { 66 | @Autowired 67 | private PersonRepository personRepository; 68 | 69 | public List getPeopleExcludeBlocks() { 70 | List people = personRepository.findAll(); 71 | 72 | return people.stream().filter(person -> person.getBlock() == null).collect(Collectors.toList()); 73 | } 74 | 75 | @Transactional(readOnly = true) 76 | public Person getPerson(Long id) { 77 | Person person = personRepository.findById(id).get(); 78 | 79 | log.info("person : {}", person); 80 | 81 | return person; 82 | } 83 | } 84 | ``` 85 | 86 | ```java 87 | @SpringBootTest 88 | class PersonServiceTest { 89 | @Autowired 90 | private PersonService personService; 91 | @Autowired 92 | private PersonRepository personRepository; 93 | @Autowired 94 | private BlockRepository blockRepository; 95 | 96 | @Test 97 | void getPeopleExcludeBlocks() { 98 | givenPeople(); 99 | 100 | List result = personService.getPeopleExcludeBlocks(); 101 | 102 | result.forEach(System.out::println); 103 | } 104 | 105 | @Test 106 | void cascadeTest() { 107 | givenPeople(); 108 | 109 | List result = personRepository.findAll(); 110 | result.forEach(System.out::println); 111 | 112 | Person person = result.get(3); 113 | person.getBlock().setStartDate(LocalDate.now()); 114 | person.getBlock().setEndDate(LocalDate.now()); 115 | 116 | personRepository.save(person); 117 | personRepository.findAll().forEach(System.out::println); 118 | 119 | // personRepository.delete(person); 120 | // personRepository.findAll().forEach(System.out::println); 121 | // blockRepository.findAll().forEach(System.out::println); 122 | 123 | person.setBlock(null); 124 | personRepository.save(person); 125 | personRepository.findAll().forEach(System.out::println); 126 | blockRepository.findAll().forEach(System.out::println); 127 | } 128 | 129 | @Test 130 | void getPerson() { 131 | givenPeople(); 132 | 133 | Person person = personService.getPerson(3L); 134 | 135 | System.out.println(person); 136 | } 137 | 138 | private void givenPeople() { 139 | givenPerson("martin", 10, "A"); 140 | givenPerson("david", 9, "B"); 141 | givenBlockPerson("dennis",7,"O"); 142 | givenBlockPerson("martin", 11, "AB"); 143 | } 144 | 145 | private void givenPerson(String name, int age, String bloodType) { 146 | personRepository.save(new Person(name, age, bloodType)); 147 | } 148 | 149 | private void givenBlockPerson(String name, int age, String bloodType) { 150 | Person blockPerson = new Person(name, age, bloodType); 151 | blockPerson.setBlock(new Block(name)); 152 | 153 | personRepository.save(blockPerson); 154 | } 155 | } 156 | ``` 157 | -------------------------------------------------------------------------------- /04-JPA-Relation/04-JPA-Query-Method.md: -------------------------------------------------------------------------------- 1 | # JPA QueryMethod 2 | 3 | ## 이론 4 | 5 | * 네이밍 컨벤션에 따라 인터페이스에 메소드를 선언하면, 해당 쿼리를 자동으로 생성해줌 6 | 7 | ![query-method-1](./images/query-method-1.png) 8 | ![query-method-2](./images/query-method-2.png) 9 | ![query-method-3](./images/query-method-3.png) 10 | 11 | ## 실전코드 12 | 13 | ```java 14 | public interface PersonRepository extends JpaRepository { 15 | List findByName(String name); 16 | 17 | List findByBlockIsNull(); 18 | 19 | List findByBloodType(String bloodType); 20 | 21 | List findByBirthdayBetween(LocalDate startDate, LocalDate endDate); 22 | } 23 | ``` 24 | 25 | ```java 26 | @Service 27 | @Slf4j 28 | public class PersonService { 29 | @Autowired 30 | private PersonRepository personRepository; 31 | 32 | public List getPeopleExcludeBlocks() { 33 | return personRepository.findByBlockIsNull(); 34 | } 35 | 36 | public List getPeopleByName(String name) { 37 | return personRepository.findByName(name); 38 | } 39 | 40 | @Transactional(readOnly = true) 41 | public Person getPerson(Long id) { 42 | Person person = personRepository.findById(id).get(); 43 | 44 | log.info("person : {}", person); 45 | 46 | return person; 47 | } 48 | } 49 | ``` 50 | 51 | ```java 52 | @SpringBootTest 53 | class PersonRepositoryTest { 54 | @Autowired 55 | private PersonRepository personRepository; 56 | 57 | @Test 58 | void crud() { 59 | Person person = new Person(); 60 | person.setName("martin"); 61 | person.setAge(10); 62 | person.setBloodType("A"); 63 | 64 | personRepository.save(person); 65 | 66 | System.out.println(personRepository.findAll()); 67 | 68 | List people = personRepository.findAll(); 69 | 70 | assertThat(people.size()).isEqualTo(1); 71 | assertThat(people.get(0).getName()).isEqualTo("martin"); 72 | assertThat(people.get(0).getAge()).isEqualTo(10); 73 | assertThat(people.get(0).getBloodType()).isEqualTo("A"); 74 | } 75 | 76 | @Test 77 | void hashCodeAndEquals() { 78 | Person person1 = new Person("martin", 10, "A"); 79 | Person person2 = new Person("martin", 10, "A"); 80 | 81 | System.out.println(person1.equals(person2)); 82 | System.out.println(person1.hashCode()); 83 | System.out.println(person2.hashCode()); 84 | 85 | Map map = new HashMap<>(); 86 | map.put(person1, person1.getAge()); 87 | 88 | System.out.println(map); 89 | System.out.println(map.get(person2)); 90 | } 91 | 92 | @Test 93 | void findByBloodType() { 94 | givenPerson("martin", 10, "A"); 95 | givenPerson("david", 9, "B"); 96 | givenPerson("dennis", 8, "O"); 97 | givenPerson("sophia", 7, "AB"); 98 | givenPerson("benny", 6, "A"); 99 | givenPerson("john", 5, "A"); 100 | 101 | List result = personRepository.findByBloodType("A"); 102 | 103 | result.forEach(System.out::println); 104 | } 105 | 106 | @Test 107 | void findByBirthdayBetween() { 108 | givenPerson("martin", 10, "A", LocalDate.of(1991, 8, 15)); 109 | givenPerson("david", 9, "B", LocalDate.of(1992, 7, 10)); 110 | givenPerson("dennis", 8, "O", LocalDate.of(1993, 1, 5)); 111 | givenPerson("sophia", 7, "AB", LocalDate.of(1994, 6, 30)); 112 | givenPerson("benny", 6, "A", LocalDate.of(1995, 8, 30)); 113 | 114 | List result = personRepository.findByBirthdayBetween(LocalDate.of(1991, 8, 1), LocalDate.of(1995, 8, 31)); 115 | 116 | result.forEach(System.out::println); 117 | } 118 | 119 | private void givenPerson(String name, int age, String bloodType) { 120 | givenPerson(name, age, bloodType, null); 121 | } 122 | 123 | private void givenPerson(String name, int age, String bloodType, LocalDate birthday) { 124 | Person person = new Person(name, age, bloodType); 125 | person.setBirthday(birthday); 126 | 127 | personRepository.save(person); 128 | } 129 | } 130 | ``` 131 | 132 | ```java 133 | @SpringBootTest 134 | class PersonServiceTest { 135 | @Autowired 136 | private PersonService personService; 137 | @Autowired 138 | private PersonRepository personRepository; 139 | @Autowired 140 | private BlockRepository blockRepository; 141 | 142 | @Test 143 | void getPeopleExcludeBlocks() { 144 | givenPeople(); 145 | 146 | List result = personService.getPeopleExcludeBlocks(); 147 | 148 | result.forEach(System.out::println); 149 | } 150 | 151 | @Test 152 | void getPeopleByName() { 153 | givenPeople(); 154 | 155 | List result = personService.getPeopleByName("martin"); 156 | 157 | result.forEach(System.out::println); 158 | } 159 | 160 | @Test 161 | void cascadeTest() { 162 | givenPeople(); 163 | 164 | List result = personRepository.findAll(); 165 | result.forEach(System.out::println); 166 | 167 | Person person = result.get(3); 168 | person.getBlock().setStartDate(LocalDate.now()); 169 | person.getBlock().setEndDate(LocalDate.now()); 170 | 171 | personRepository.save(person); 172 | personRepository.findAll().forEach(System.out::println); 173 | 174 | // personRepository.delete(person); 175 | // personRepository.findAll().forEach(System.out::println); 176 | // blockRepository.findAll().forEach(System.out::println); 177 | 178 | person.setBlock(null); 179 | personRepository.save(person); 180 | personRepository.findAll().forEach(System.out::println); 181 | blockRepository.findAll().forEach(System.out::println); 182 | } 183 | 184 | @Test 185 | void getPerson() { 186 | givenPeople(); 187 | 188 | Person person = personService.getPerson(3L); 189 | 190 | System.out.println(person); 191 | } 192 | 193 | private void givenPeople() { 194 | givenPerson("martin", 10, "A"); 195 | givenPerson("david", 9, "B"); 196 | givenBlockPerson("dennis",7,"O"); 197 | givenBlockPerson("martin", 11, "AB"); 198 | } 199 | 200 | private void givenPerson(String name, int age, String bloodType) { 201 | personRepository.save(new Person(name, age, bloodType)); 202 | } 203 | 204 | private void givenBlockPerson(String name, int age, String bloodType) { 205 | Person blockPerson = new Person(name, age, bloodType); 206 | blockPerson.setBlock(new Block(name)); 207 | 208 | personRepository.save(blockPerson); 209 | } 210 | } 211 | ``` 212 | -------------------------------------------------------------------------------- /04-JPA-Relation/05-JPA-@Query.md: -------------------------------------------------------------------------------- 1 | # JPA @Query 2 | 3 | ## 이론 4 | 5 | * @Embedded 6 | * 다른 객체를 Entity의 속성으로 가져옴 7 | * @Embeddable 8 | * 자기 객체를 다른 Entity의 속성으로 사용할 수 있음 9 | * @Query 10 | * Naming 컨벤션에 따라 생성되는 쿼리가 아니라, 커스터마이징된 쿼리를 직접 지정하여 생성함 11 | * ?1 (숫자)를 통해서 parameter를 순서대로 주입하여 사용할 수 있음 12 | * @Param("A")으로 parameter를 받게 되면, :A로 파라미터를 주입하여 사용할 수 있음 13 | * `nativeQuery=true`를 사용하게 되면, JPQL이 아니라 써준 그대로 네이티브 쿼리를 생성해줌 14 | * @Valid 15 | * 해당 객체의 유효성을 검토하겠다는 것을 의미함 16 | * @Min 17 | * 숫자관련 필드에서 최소값은 얼마인지 정의함 18 | * @Max 19 | * 숫자관련 필드에서 최대값은 얼마인지 정의함 20 | 21 | ## 실전코드 22 | 23 | ```java 24 | @Embeddable 25 | @Data 26 | @NoArgsConstructor 27 | public class Birthday { 28 | private int yearOfBirthday; 29 | 30 | @Min(1) 31 | @Max(12) 32 | private int monthOfBirthday; 33 | 34 | @Min(1) 35 | @Max(31) 36 | private int dayOfBirthday; 37 | 38 | public Birthday(LocalDate birthday) { 39 | this.yearOfBirthday = birthday.getYear(); 40 | this.monthOfBirthday = birthday.getMonthValue(); 41 | this.dayOfBirthday = birthday.getDayOfMonth(); 42 | } 43 | } 44 | ``` 45 | 46 | ```java 47 | @Entity 48 | @NoArgsConstructor 49 | @AllArgsConstructor 50 | @RequiredArgsConstructor 51 | @Data 52 | public class Person { 53 | @Id 54 | @GeneratedValue 55 | private Long id; 56 | 57 | @NonNull 58 | private String name; 59 | 60 | @NonNull 61 | private int age; 62 | 63 | private String hobby; 64 | 65 | @NonNull 66 | private String bloodType; 67 | 68 | private String address; 69 | 70 | @Valid 71 | @Embedded 72 | private Birthday birthday; 73 | 74 | private String job; 75 | 76 | @ToString.Exclude 77 | private String phoneNumber; 78 | 79 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 80 | @ToString.Exclude 81 | private Block block; 82 | } 83 | ``` 84 | 85 | ```java 86 | public interface PersonRepository extends JpaRepository { 87 | List findByName(String name); 88 | 89 | List findByBlockIsNull(); 90 | 91 | List findByBloodType(String bloodType); 92 | 93 | @Query(value = "select person from Person person where person.birthday.monthOfBirthday = :monthOfBirthday") 94 | List findByMonthOfBirthday(@Param("monthOfBirthday") int monthOfBirthday); 95 | } 96 | ``` 97 | 98 | ```java 99 | @SpringBootTest 100 | class PersonRepositoryTest { 101 | @Autowired 102 | private PersonRepository personRepository; 103 | 104 | @Test 105 | void crud() { 106 | Person person = new Person(); 107 | person.setName("martin"); 108 | person.setAge(10); 109 | person.setBloodType("A"); 110 | 111 | personRepository.save(person); 112 | 113 | System.out.println(personRepository.findAll()); 114 | 115 | List people = personRepository.findAll(); 116 | 117 | assertThat(people.size()).isEqualTo(1); 118 | assertThat(people.get(0).getName()).isEqualTo("martin"); 119 | assertThat(people.get(0).getAge()).isEqualTo(10); 120 | assertThat(people.get(0).getBloodType()).isEqualTo("A"); 121 | } 122 | 123 | @Test 124 | void hashCodeAndEquals() { 125 | Person person1 = new Person("martin", 10, "A"); 126 | Person person2 = new Person("martin", 10, "A"); 127 | 128 | System.out.println(person1.equals(person2)); 129 | System.out.println(person1.hashCode()); 130 | System.out.println(person2.hashCode()); 131 | 132 | Map map = new HashMap<>(); 133 | map.put(person1, person1.getAge()); 134 | 135 | System.out.println(map); 136 | System.out.println(map.get(person2)); 137 | } 138 | 139 | @Test 140 | void findByBloodType() { 141 | givenPerson("martin", 10, "A"); 142 | givenPerson("david", 9, "B"); 143 | givenPerson("dennis", 8, "O"); 144 | givenPerson("sophia", 7, "AB"); 145 | givenPerson("benny", 6, "A"); 146 | givenPerson("john", 5, "A"); 147 | 148 | List result = personRepository.findByBloodType("A"); 149 | 150 | result.forEach(System.out::println); 151 | } 152 | 153 | @Test 154 | void findByBirthdayBetween() { 155 | givenPerson("martin", 10, "A", LocalDate.of(1991, 8, 15)); 156 | givenPerson("david", 9, "B", LocalDate.of(1992, 7, 10)); 157 | givenPerson("dennis", 8, "O", LocalDate.of(1993, 1, 5)); 158 | givenPerson("sophia", 7, "AB", LocalDate.of(1994, 6, 30)); 159 | givenPerson("benny", 6, "A", LocalDate.of(1995, 8, 30)); 160 | 161 | List result = personRepository.findByMonthOfBirthday(8); 162 | 163 | result.forEach(System.out::println); 164 | } 165 | 166 | private void givenPerson(String name, int age, String bloodType) { 167 | givenPerson(name, age, bloodType, null); 168 | } 169 | 170 | private void givenPerson(String name, int age, String bloodType, LocalDate birthday) { 171 | Person person = new Person(name, age, bloodType); 172 | person.setBirthday(new Birthday(birthday)); 173 | 174 | personRepository.save(person); 175 | } 176 | } 177 | ``` 178 | -------------------------------------------------------------------------------- /04-JPA-Relation/06-JPA-data.sql사용하기.md: -------------------------------------------------------------------------------- 1 | # JPA data.sql 사용하기 2 | 3 | ## 이론 4 | 5 | * data.sql 6 | * spring context를 실행시킬 때, data.sql에 적혀있는 쿼리를 실행시킴 7 | * main/resources에 선언하게 되면, 서버를 시작할 때 쿼리가 실행됨 8 | * test/resources에 선언하게 되면, 테스트를 실행할 때 쿼리가 실행됨 9 | 10 | ## 실전코드 11 | 12 | ```java 13 | @Embeddable 14 | @Data 15 | @NoArgsConstructor 16 | public class Birthday { 17 | private Integer yearOfBirthday; 18 | 19 | @Min(1) 20 | @Max(12) 21 | private Integer monthOfBirthday; 22 | 23 | @Min(1) 24 | @Max(31) 25 | private Integer dayOfBirthday; 26 | 27 | public Birthday(LocalDate birthday) { 28 | this.yearOfBirthday = birthday.getYear(); 29 | this.monthOfBirthday = birthday.getMonthValue(); 30 | this.dayOfBirthday = birthday.getDayOfMonth(); 31 | } 32 | } 33 | ``` 34 | 35 | ```java 36 | @Entity 37 | @Data 38 | @NoArgsConstructor 39 | @RequiredArgsConstructor 40 | public class Block { 41 | @Id 42 | @GeneratedValue(strategy = GenerationType.IDENTITY) 43 | private Long id; 44 | 45 | @NonNull 46 | private String name; 47 | 48 | private String reason; 49 | 50 | private LocalDate startDate; 51 | 52 | private LocalDate endDate; 53 | } 54 | ``` 55 | 56 | ```java 57 | @Entity 58 | @NoArgsConstructor 59 | @AllArgsConstructor 60 | @RequiredArgsConstructor 61 | @Data 62 | public class Person { 63 | @Id 64 | @GeneratedValue(strategy = GenerationType.IDENTITY) 65 | private Long id; 66 | 67 | @NonNull 68 | private String name; 69 | 70 | @NonNull 71 | private int age; 72 | 73 | private String hobby; 74 | 75 | @NonNull 76 | private String bloodType; 77 | 78 | private String address; 79 | 80 | @Valid 81 | @Embedded 82 | private Birthday birthday; 83 | 84 | private String job; 85 | 86 | @ToString.Exclude 87 | private String phoneNumber; 88 | 89 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 90 | @ToString.Exclude 91 | private Block block; 92 | } 93 | ``` 94 | 95 | ```java 96 | @SpringBootTest 97 | class BlockRepositoryTest { 98 | @Autowired 99 | private BlockRepository blockRepository; 100 | 101 | @Test 102 | void crud() { 103 | Block block = new Block(); 104 | block.setName("martin"); 105 | block.setReason("친하지않아서"); 106 | block.setStartDate(LocalDate.now()); 107 | block.setEndDate(LocalDate.now()); 108 | 109 | blockRepository.save(block); 110 | 111 | List blocks = blockRepository.findAll(); 112 | 113 | assertThat(blocks.size()).isEqualTo(3); 114 | assertThat(blocks.get(0).getName()).isEqualTo("dennis"); 115 | assertThat(blocks.get(1).getName()).isEqualTo("sophia"); 116 | assertThat(blocks.get(2).getName()).isEqualTo("martin"); 117 | } 118 | } 119 | ``` 120 | 121 | ```java 122 | @Transactional 123 | @SpringBootTest 124 | class PersonRepositoryTest { 125 | @Autowired 126 | private PersonRepository personRepository; 127 | 128 | @Test 129 | void crud() { 130 | Person person = new Person(); 131 | person.setName("john"); 132 | person.setAge(10); 133 | person.setBloodType("A"); 134 | 135 | personRepository.save(person); 136 | 137 | List result = personRepository.findByName("john"); 138 | 139 | assertThat(result.size()).isEqualTo(1); 140 | assertThat(result.get(0).getName()).isEqualTo("john"); 141 | assertThat(result.get(0).getAge()).isEqualTo(10); 142 | assertThat(result.get(0).getBloodType()).isEqualTo("A"); 143 | } 144 | 145 | @Test 146 | void findByBloodType() { 147 | List result = personRepository.findByBloodType("A"); 148 | 149 | assertThat(result.size()).isEqualTo(2); 150 | assertThat(result.get(0).getName()).isEqualTo("martin"); 151 | assertThat(result.get(1).getName()).isEqualTo("benny"); 152 | } 153 | 154 | @Test 155 | void findByBirthdayBetween() { 156 | List result = personRepository.findByMonthOfBirthday(8); 157 | 158 | assertThat(result.size()).isEqualTo(2); 159 | assertThat(result.get(0).getName()).isEqualTo("martin"); 160 | assertThat(result.get(1).getName()).isEqualTo("sophia"); 161 | } 162 | } 163 | ``` 164 | 165 | ```java 166 | @SpringBootTest 167 | class PersonServiceTest { 168 | @Autowired 169 | private PersonService personService; 170 | @Autowired 171 | private PersonRepository personRepository; 172 | 173 | @Test 174 | void getPeopleExcludeBlocks() { 175 | List result = personService.getPeopleExcludeBlocks(); 176 | 177 | personRepository.findAll().forEach(System.out::println); 178 | 179 | assertThat(result.size()).isEqualTo(3); 180 | assertThat(result.get(0).getName()).isEqualTo("martin"); 181 | assertThat(result.get(1).getName()).isEqualTo("david"); 182 | assertThat(result.get(2).getName()).isEqualTo("benny"); 183 | } 184 | 185 | @Test 186 | void getPeopleByName() { 187 | List result = personService.getPeopleByName("martin"); 188 | 189 | assertThat(result.size()).isEqualTo(1); 190 | assertThat(result.get(0).getName()).isEqualTo("martin"); 191 | } 192 | 193 | @Test 194 | void getPerson() { 195 | Person person = personService.getPerson(3L); 196 | 197 | assertThat(person.getName()).isEqualTo("dennis"); 198 | } 199 | } 200 | ``` 201 | 202 | ```java 203 | import org.junit.jupiter.api.Test; 204 | import org.springframework.boot.test.context.SpringBootTest; 205 | 206 | @SpringBootTest 207 | public class MycontactApplicationTests { 208 | 209 | @Test 210 | public void contextLoads() { 211 | } 212 | 213 | } 214 | ``` 215 | 216 | ```sql 217 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (1, 'martin', 10, 'A', 1991, 8, 15); 218 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (2, 'david', 9, 'B', 1992, 7, 21); 219 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (3, 'dennis', 8, 'O', 1993, 10, 15); 220 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (4, 'sophia', 7, 'AB', 1994, 8, 31); 221 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (5, 'benny', 6, 'A', 1995, 12, 23); 222 | 223 | insert into block(`id`, `name`) values (1, 'dennis'); 224 | insert into block(`id`, `name`) values (2, 'sophia'); 225 | 226 | update person set block_id = 1 where id = 3; 227 | update person set block_id = 2 where id = 4; 228 | ``` 229 | -------------------------------------------------------------------------------- /04-JPA-Relation/images/query-method-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeism/fastcampus-java/24b78167eaa7d379a12f31abdebffae6c5a2d56b/04-JPA-Relation/images/query-method-1.png -------------------------------------------------------------------------------- /04-JPA-Relation/images/query-method-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeism/fastcampus-java/24b78167eaa7d379a12f31abdebffae6c5a2d56b/04-JPA-Relation/images/query-method-2.png -------------------------------------------------------------------------------- /04-JPA-Relation/images/query-method-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeism/fastcampus-java/24b78167eaa7d379a12f31abdebffae6c5a2d56b/04-JPA-Relation/images/query-method-3.png -------------------------------------------------------------------------------- /05-Controller/01-@GetMapping사용하기.md: -------------------------------------------------------------------------------- 1 | # @GetMapping 사용하기 2 | 3 | ## 이론 4 | 5 | * @GetMapping 6 | * Get 메소드의 Http 요청을 받을 수 있는 메소드임을 명시하는 어노테이션 7 | * @PathVariable 8 | * Rest의 Url의 값을 읽어서 메소드의 파라미터로 매핑시킬 수 있도록 도와줌 9 | * {id}로 표기하면, 해당 위치에 들어오는 문자열을 id 파라미터에 할당해줌 10 | 11 | ## 실전코드 12 | 13 | ```java 14 | @RequestMapping(value = "/api/person") 15 | @RestController 16 | public class PersonController { 17 | @Autowired 18 | private PersonService personService; 19 | 20 | @GetMapping 21 | @RequestMapping(value = "/{id}") 22 | public Person getPerson(@PathVariable Long id) { 23 | return personService.getPerson(id); 24 | } 25 | } 26 | ``` 27 | 28 | ```java 29 | @Service 30 | @Slf4j 31 | public class PersonService { 32 | @Autowired 33 | private PersonRepository personRepository; 34 | 35 | public List getPeopleExcludeBlocks() { 36 | return personRepository.findByBlockIsNull(); 37 | } 38 | 39 | public List getPeopleByName(String name) { 40 | return personRepository.findByName(name); 41 | } 42 | 43 | @Transactional(readOnly = true) 44 | public Person getPerson(Long id) { 45 | Person person = personRepository.findById(id).orElse(null); 46 | 47 | log.info("person : {}", person); 48 | 49 | return person; 50 | } 51 | } 52 | ``` 53 | 54 | ```java 55 | @SpringBootTest 56 | class PersonControllerTest { 57 | @Autowired 58 | private PersonController personController; 59 | 60 | private MockMvc mockMvc; 61 | 62 | @Test 63 | void getPerson() throws Exception { 64 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 65 | 66 | mockMvc.perform( 67 | MockMvcRequestBuilders.get("/api/person/1")) 68 | .andDo(print()) 69 | .andExpect(status().isOk()) 70 | .andExpect(jsonPath("$.name").value("martin")); 71 | } 72 | } 73 | ``` 74 | 75 | ```http request 76 | GET http://localhost:8080/api/person/1 77 | ``` 78 | -------------------------------------------------------------------------------- /05-Controller/02-@PostMapping사용하기.md: -------------------------------------------------------------------------------- 1 | # @PostMapping 사용하기 2 | 3 | ## 이론 4 | 5 | * @PostMapping 6 | * Post 메소드의 Http 요청을 받을 수 있는 메소드임을 명시하는 어노테이션 7 | * @RequestBody 8 | * Request Body에 있는 데이터를 읽어서 파라미터로 매핑할 수 있도록 도와줌 9 | * @ResponseStatus 10 | * http 응답에 대한 코드값을 지정한 값으로 변경할 수 있음 11 | 12 | ## 실전코드 13 | 14 | ```java 15 | @RequestMapping(value = "/api/person") 16 | @RestController 17 | @Slf4j 18 | public class PersonController { 19 | @Autowired 20 | private PersonService personService; 21 | @Autowired 22 | private PersonRepository personRepository; 23 | 24 | @GetMapping("/{id}") 25 | public Person getPerson(@PathVariable Long id) { 26 | return personService.getPerson(id); 27 | } 28 | 29 | @PostMapping 30 | @ResponseStatus(HttpStatus.CREATED) 31 | public void postPerson(@RequestBody Person person) { 32 | personService.put(person); 33 | 34 | log.info("person -> {} ", personRepository.findAll()); 35 | } 36 | } 37 | ``` 38 | 39 | ```java 40 | @Entity 41 | @NoArgsConstructor 42 | @AllArgsConstructor 43 | @RequiredArgsConstructor 44 | @Data 45 | public class Person { 46 | @Id 47 | @GeneratedValue(strategy = GenerationType.IDENTITY) 48 | private Long id; 49 | 50 | @NonNull 51 | @NotEmpty 52 | @Column(nullable = false) 53 | private String name; 54 | 55 | @NonNull 56 | @Min(1) 57 | private int age; 58 | 59 | private String hobby; 60 | 61 | @NotEmpty 62 | @NonNull 63 | @Column(nullable = false) 64 | private String bloodType; 65 | 66 | private String address; 67 | 68 | @Valid 69 | @Embedded 70 | private Birthday birthday; 71 | 72 | private String job; 73 | 74 | @ToString.Exclude 75 | private String phoneNumber; 76 | 77 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 78 | @ToString.Exclude 79 | private Block block; 80 | } 81 | ``` 82 | 83 | ```java 84 | @Service 85 | @Slf4j 86 | public class PersonService { 87 | @Autowired 88 | private PersonRepository personRepository; 89 | 90 | public List getPeopleExcludeBlocks() { 91 | return personRepository.findByBlockIsNull(); 92 | } 93 | 94 | public List getPeopleByName(String name) { 95 | return personRepository.findByName(name); 96 | } 97 | 98 | @Transactional(readOnly = true) 99 | public Person getPerson(Long id) { 100 | Person person = personRepository.findById(id).orElse(null); 101 | 102 | log.info("person : {}", person); 103 | 104 | return person; 105 | } 106 | 107 | @Transactional 108 | public void put(Person person) { 109 | personRepository.save(person); 110 | } 111 | } 112 | ``` 113 | 114 | ```java 115 | @SpringBootTest 116 | class PersonControllerTest { 117 | @Autowired 118 | private PersonController personController; 119 | 120 | private MockMvc mockMvc; 121 | 122 | @Test 123 | void getPerson() throws Exception { 124 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 125 | 126 | mockMvc.perform( 127 | MockMvcRequestBuilders.get("/api/person/1")) 128 | .andDo(print()) 129 | .andExpect(status().isOk()) 130 | .andExpect(jsonPath("$.name").value("martin")); 131 | } 132 | 133 | @Test 134 | void postPerson() throws Exception { 135 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 136 | 137 | mockMvc.perform( 138 | MockMvcRequestBuilders.post("/api/person") 139 | .contentType(MediaType.APPLICATION_JSON_UTF8) 140 | .content("{\n" 141 | + " \"name\": \"martin2\",\n" 142 | + " \"age\": 20,\n" 143 | + " \"bloodType\": \"A\"\n" 144 | + "}")) 145 | .andDo(print()) 146 | .andExpect(status().isCreated()); 147 | } 148 | } 149 | ``` 150 | -------------------------------------------------------------------------------- /05-Controller/03-@PutMapping사용하기.md: -------------------------------------------------------------------------------- 1 | # @PutMapping 사용하기 2 | 3 | ## 이론 4 | 5 | * @PutMapping 6 | * Put 메소드의 Http 요청을 받을 수 있는 메소드임을 명시하는 어노테이션 7 | * @PatchMapping 8 | * Patch 메소드의 Http 요청을 받을 수 있는 메소드임을 명시하는 어노테이션 9 | 10 | ## 실전코드 11 | 12 | ```java 13 | @RequestMapping(value = "/api/person") 14 | @RestController 15 | @Slf4j 16 | public class PersonController { 17 | @Autowired 18 | private PersonService personService; 19 | @Autowired 20 | private PersonRepository personRepository; 21 | 22 | @GetMapping("/{id}") 23 | public Person getPerson(@PathVariable Long id) { 24 | return personService.getPerson(id); 25 | } 26 | 27 | @PostMapping 28 | @ResponseStatus(HttpStatus.CREATED) 29 | public void postPerson(@RequestBody Person person) { 30 | personService.put(person); 31 | 32 | log.info("person -> {} ", personRepository.findAll()); 33 | } 34 | 35 | @PutMapping("/{id}") 36 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 37 | personService.modify(id, personDto); 38 | 39 | log.info("person -> {} ", personRepository.findAll()); 40 | } 41 | 42 | @PatchMapping("/{id}") 43 | public void modifyPerson(@PathVariable Long id, String name) { 44 | personService.modify(id, name); 45 | 46 | log.info("person -> {} ", personRepository.findAll()); 47 | } 48 | } 49 | ``` 50 | 51 | ```java 52 | @Data 53 | public class PersonDto { 54 | private String name; 55 | private int age; 56 | private String hobby; 57 | private String bloodType; 58 | private String address; 59 | private LocalDate birthday; 60 | private String job; 61 | private String phoneNumber; 62 | } 63 | ``` 64 | 65 | ```java 66 | @Entity 67 | @NoArgsConstructor 68 | @AllArgsConstructor 69 | @RequiredArgsConstructor 70 | @Data 71 | public class Person { 72 | @Id 73 | @GeneratedValue(strategy = GenerationType.IDENTITY) 74 | private Long id; 75 | 76 | @NonNull 77 | @NotEmpty 78 | @Column(nullable = false) 79 | private String name; 80 | 81 | @NonNull 82 | @Min(1) 83 | private int age; 84 | 85 | private String hobby; 86 | 87 | @NotEmpty 88 | @NonNull 89 | @Column(nullable = false) 90 | private String bloodType; 91 | 92 | private String address; 93 | 94 | @Valid 95 | @Embedded 96 | private Birthday birthday; 97 | 98 | private String job; 99 | 100 | @ToString.Exclude 101 | private String phoneNumber; 102 | 103 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 104 | @ToString.Exclude 105 | private Block block; 106 | 107 | public void set(PersonDto personDto) { 108 | if (personDto.getAge() != 0) { 109 | this.setAge(personDto.getAge()); 110 | } 111 | 112 | if (!StringUtils.isEmpty(personDto.getHobby())) { 113 | this.setHobby(personDto.getHobby()); 114 | } 115 | 116 | if (!StringUtils.isEmpty(personDto.getBloodType())) { 117 | this.setBloodType(personDto.getBloodType()); 118 | } 119 | 120 | if (!StringUtils.isEmpty(personDto.getAddress())) { 121 | this.setAddress(personDto.getAddress()); 122 | } 123 | 124 | if (!StringUtils.isEmpty(personDto.getJob())) { 125 | this.setJob(personDto.getJob()); 126 | } 127 | 128 | if (!StringUtils.isEmpty(personDto.getPhoneNumber())) { 129 | this.setPhoneNumber(personDto.getPhoneNumber()); 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | ```java 136 | @Service 137 | @Slf4j 138 | public class PersonService { 139 | @Autowired 140 | private PersonRepository personRepository; 141 | 142 | public List getPeopleExcludeBlocks() { 143 | return personRepository.findByBlockIsNull(); 144 | } 145 | 146 | public List getPeopleByName(String name) { 147 | return personRepository.findByName(name); 148 | } 149 | 150 | @Transactional(readOnly = true) 151 | public Person getPerson(Long id) { 152 | Person person = personRepository.findById(id).orElse(null); 153 | 154 | log.info("person : {}", person); 155 | 156 | return person; 157 | } 158 | 159 | @Transactional 160 | public void put(Person person) { 161 | personRepository.save(person); 162 | } 163 | 164 | @Transactional 165 | public void modify(Long id, PersonDto personDto) { 166 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 167 | 168 | if (!person.getName().equals(personDto.getName())) { 169 | throw new RuntimeException("이름이 다릅니다."); 170 | } 171 | 172 | person.set(personDto); 173 | 174 | personRepository.save(person); 175 | } 176 | 177 | @Transactional 178 | public void modify(Long id, String name) { 179 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 180 | 181 | person.setName(name); 182 | 183 | personRepository.save(person); 184 | } 185 | } 186 | ``` 187 | 188 | ```java 189 | @SpringBootTest 190 | class PersonControllerTest { 191 | @Autowired 192 | private PersonController personController; 193 | 194 | private MockMvc mockMvc; 195 | 196 | @Test 197 | void getPerson() throws Exception { 198 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 199 | 200 | mockMvc.perform( 201 | MockMvcRequestBuilders.get("/api/person/1")) 202 | .andDo(print()) 203 | .andExpect(status().isOk()) 204 | .andExpect(jsonPath("$.name").value("martin")); 205 | } 206 | 207 | @Test 208 | void postPerson() throws Exception { 209 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 210 | 211 | mockMvc.perform( 212 | MockMvcRequestBuilders.post("/api/person") 213 | .contentType(MediaType.APPLICATION_JSON_UTF8) 214 | .content("{\n" 215 | + " \"name\": \"martin2\",\n" 216 | + " \"age\": 20,\n" 217 | + " \"bloodType\": \"A\"\n" 218 | + "}")) 219 | .andDo(print()) 220 | .andExpect(status().isCreated()); 221 | } 222 | 223 | @Test 224 | void modifyPerson() throws Exception { 225 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 226 | 227 | mockMvc.perform( 228 | MockMvcRequestBuilders.put("/api/person/1") 229 | .contentType(MediaType.APPLICATION_JSON_UTF8) 230 | .content("{\n" 231 | + " \"name\": \"martin\",\n" 232 | + " \"age\": 20,\n" 233 | + " \"bloodType\": \"A\"\n" 234 | + "}")) 235 | .andDo(print()) 236 | .andExpect(status().isOk()); 237 | } 238 | 239 | @Test 240 | void modifyName() throws Exception { 241 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 242 | 243 | mockMvc.perform( 244 | MockMvcRequestBuilders.patch("/api/person/1") 245 | .param("name", "martin22")) 246 | .andDo(print()) 247 | .andExpect(status().isOk()); 248 | } 249 | } 250 | ``` 251 | 252 | ```sql 253 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`, `job`) values (1, 'martin', 10, 'A', 1991, 8, 15, 'programmer'); 254 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (2, 'david', 9, 'B', 1992, 7, 21); 255 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (3, 'dennis', 8, 'O', 1993, 10, 15); 256 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (4, 'sophia', 7, 'AB', 1994, 8, 31); 257 | insert into person(`id`, `name`, `age`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (5, 'benny', 6, 'A', 1995, 12, 23); 258 | 259 | insert into block(`id`, `name`) values (1, 'dennis'); 260 | insert into block(`id`, `name`) values (2, 'sophia'); 261 | 262 | update person set block_id = 1 where id = 3; 263 | update person set block_id = 2 where id = 4; 264 | ``` 265 | -------------------------------------------------------------------------------- /05-Controller/04-@DeleteMapping사용하기.md: -------------------------------------------------------------------------------- 1 | # @DeleteMapping 사용하기 2 | 3 | ## 이론 4 | 5 | * @DeleteMapping 6 | * Delete 메소드의 Http 요청을 받을 수 있는 메소드임을 명시하는 어노테이션 7 | 8 | ## 실전코드 9 | 10 | ```java 11 | @RequestMapping(value = "/api/person") 12 | @RestController 13 | @Slf4j 14 | public class PersonController { 15 | @Autowired 16 | private PersonService personService; 17 | @Autowired 18 | private PersonRepository personRepository; 19 | 20 | @GetMapping("/{id}") 21 | public Person getPerson(@PathVariable Long id) { 22 | return personService.getPerson(id); 23 | } 24 | 25 | @PostMapping 26 | @ResponseStatus(HttpStatus.CREATED) 27 | public void postPerson(@RequestBody Person person) { 28 | personService.put(person); 29 | 30 | log.info("person -> {} ", personRepository.findAll()); 31 | } 32 | 33 | @PutMapping("/{id}") 34 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 35 | personService.modify(id, personDto); 36 | 37 | log.info("person -> {} ", personRepository.findAll()); 38 | } 39 | 40 | @PatchMapping("/{id}") 41 | public void modifyPerson(@PathVariable Long id, String name) { 42 | personService.modify(id, name); 43 | 44 | log.info("person -> {} ", personRepository.findAll()); 45 | } 46 | 47 | @DeleteMapping("/{id}") 48 | public void deletePerson(@PathVariable Long id) { 49 | personService.delete(id); 50 | 51 | log.info("person -> {} ", personRepository.findAll()); 52 | } 53 | } 54 | ``` 55 | 56 | ```java 57 | @Entity 58 | @NoArgsConstructor 59 | @AllArgsConstructor 60 | @RequiredArgsConstructor 61 | @Data 62 | @Where(clause = "deleted = false") 63 | public class Person { 64 | @Id 65 | @GeneratedValue(strategy = GenerationType.IDENTITY) 66 | private Long id; 67 | 68 | @NonNull 69 | @NotEmpty 70 | @Column(nullable = false) 71 | private String name; 72 | 73 | @NonNull 74 | @Min(1) 75 | private int age; 76 | 77 | private String hobby; 78 | 79 | @NotEmpty 80 | @NonNull 81 | @Column(nullable = false) 82 | private String bloodType; 83 | 84 | private String address; 85 | 86 | @Valid 87 | @Embedded 88 | private Birthday birthday; 89 | 90 | private String job; 91 | 92 | @ToString.Exclude 93 | private String phoneNumber; 94 | 95 | @ColumnDefault("0") 96 | private boolean deleted; 97 | 98 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 99 | @ToString.Exclude 100 | private Block block; 101 | 102 | public void set(PersonDto personDto) { 103 | if (personDto.getAge() != 0) { 104 | this.setAge(personDto.getAge()); 105 | } 106 | 107 | if (!StringUtils.isEmpty(personDto.getHobby())) { 108 | this.setHobby(personDto.getHobby()); 109 | } 110 | 111 | if (!StringUtils.isEmpty(personDto.getBloodType())) { 112 | this.setBloodType(personDto.getBloodType()); 113 | } 114 | 115 | if (!StringUtils.isEmpty(personDto.getAddress())) { 116 | this.setAddress(personDto.getAddress()); 117 | } 118 | 119 | if (!StringUtils.isEmpty(personDto.getJob())) { 120 | this.setJob(personDto.getJob()); 121 | } 122 | 123 | if (!StringUtils.isEmpty(personDto.getPhoneNumber())) { 124 | this.setPhoneNumber(personDto.getPhoneNumber()); 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | ```java 131 | public interface PersonRepository extends JpaRepository { 132 | List findByName(String name); 133 | 134 | List findByBlockIsNull(); 135 | 136 | List findByBloodType(String bloodType); 137 | 138 | @Query(value = "select person from Person person where person.birthday.monthOfBirthday = :monthOfBirthday") 139 | List findByMonthOfBirthday(@Param("monthOfBirthday") int monthOfBirthday); 140 | 141 | @Query(value = "select * from Person person where person.deleted = true", nativeQuery = true) 142 | List findPeopleDeleted(); 143 | } 144 | ``` 145 | 146 | ```java 147 | @Service 148 | @Slf4j 149 | public class PersonService { 150 | @Autowired 151 | private PersonRepository personRepository; 152 | 153 | public List getPeopleExcludeBlocks() { 154 | return personRepository.findByBlockIsNull(); 155 | } 156 | 157 | public List getPeopleByName(String name) { 158 | return personRepository.findByName(name); 159 | } 160 | 161 | @Transactional(readOnly = true) 162 | public Person getPerson(Long id) { 163 | Person person = personRepository.findById(id).orElse(null); 164 | 165 | log.info("person : {}", person); 166 | 167 | return person; 168 | } 169 | 170 | @Transactional 171 | public void put(Person person) { 172 | personRepository.save(person); 173 | } 174 | 175 | @Transactional 176 | public void modify(Long id, PersonDto personDto) { 177 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 178 | 179 | if (!person.getName().equals(personDto.getName())) { 180 | throw new RuntimeException("이름이 다릅니다."); 181 | } 182 | 183 | person.set(personDto); 184 | 185 | personRepository.save(person); 186 | } 187 | 188 | @Transactional 189 | public void modify(Long id, String name) { 190 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 191 | 192 | person.setName(name); 193 | 194 | personRepository.save(person); 195 | } 196 | 197 | @Transactional 198 | public void delete(Long id) { 199 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 200 | 201 | person.setDeleted(true); 202 | 203 | personRepository.save(person); 204 | } 205 | } 206 | ``` 207 | 208 | ```java 209 | @Slf4j 210 | @SpringBootTest 211 | class PersonControllerTest { 212 | @Autowired 213 | private PersonController personController; 214 | @Autowired 215 | private PersonRepository personRepository; 216 | 217 | private MockMvc mockMvc; 218 | 219 | @BeforeEach 220 | void beforeEach() { 221 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 222 | } 223 | 224 | @Test 225 | void getPerson() throws Exception { 226 | mockMvc.perform( 227 | MockMvcRequestBuilders.get("/api/person/1")) 228 | .andDo(print()) 229 | .andExpect(status().isOk()) 230 | .andExpect(jsonPath("$.name").value("martin")); 231 | } 232 | 233 | @Test 234 | void postPerson() throws Exception { 235 | mockMvc.perform( 236 | MockMvcRequestBuilders.post("/api/person") 237 | .contentType(MediaType.APPLICATION_JSON_UTF8) 238 | .content("{\n" 239 | + " \"name\": \"martin2\",\n" 240 | + " \"age\": 20,\n" 241 | + " \"bloodType\": \"A\"\n" 242 | + "}")) 243 | .andDo(print()) 244 | .andExpect(status().isCreated()); 245 | } 246 | 247 | @Test 248 | void modifyPerson() throws Exception { 249 | mockMvc.perform( 250 | MockMvcRequestBuilders.put("/api/person/1") 251 | .contentType(MediaType.APPLICATION_JSON_UTF8) 252 | .content("{\n" 253 | + " \"name\": \"martin\",\n" 254 | + " \"age\": 20,\n" 255 | + " \"bloodType\": \"A\"\n" 256 | + "}")) 257 | .andDo(print()) 258 | .andExpect(status().isOk()); 259 | } 260 | 261 | @Test 262 | void modifyName() throws Exception { 263 | mockMvc.perform( 264 | MockMvcRequestBuilders.patch("/api/person/1") 265 | .param("name", "martin22")) 266 | .andDo(print()) 267 | .andExpect(status().isOk()); 268 | } 269 | 270 | @Test 271 | void deletePerson() throws Exception { 272 | mockMvc.perform( 273 | MockMvcRequestBuilders.delete("/api/person/1")) 274 | .andDo(print()) 275 | .andExpect(status().isOk()); 276 | 277 | log.info("people deleted : {}", personRepository.findPeopleDeleted()); 278 | } 279 | } 280 | ``` 281 | -------------------------------------------------------------------------------- /06-Refactoring/01-리팩토링-도메인코드-1.md: -------------------------------------------------------------------------------- 1 | # 리팩토링 도메인코드 #1 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @Embeddable 7 | @Data 8 | @NoArgsConstructor 9 | public class Birthday { 10 | private Integer yearOfBirthday; 11 | private Integer monthOfBirthday; 12 | private Integer dayOfBirthday; 13 | 14 | private Birthday(LocalDate birthday) { 15 | this.yearOfBirthday = birthday.getYear(); 16 | this.monthOfBirthday = birthday.getMonthValue(); 17 | this.dayOfBirthday = birthday.getDayOfMonth(); 18 | } 19 | 20 | public static Birthday of(LocalDate birthday) { 21 | return new Birthday(birthday); 22 | } 23 | } 24 | ``` 25 | 26 | ```java 27 | @Entity 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | @RequiredArgsConstructor 31 | @Data 32 | @Where(clause = "deleted = false") 33 | public class Person { 34 | @Id 35 | @GeneratedValue(strategy = GenerationType.IDENTITY) 36 | private Long id; 37 | 38 | @NonNull 39 | @NotEmpty 40 | @Column(nullable = false) 41 | private String name; 42 | 43 | private String hobby; 44 | 45 | @NotEmpty 46 | @NonNull 47 | @Column(nullable = false) 48 | private String bloodType; 49 | 50 | private String address; 51 | 52 | @Valid 53 | @Embedded 54 | private Birthday birthday; 55 | 56 | private String job; 57 | 58 | @ToString.Exclude 59 | private String phoneNumber; 60 | 61 | @ColumnDefault("0") 62 | private boolean deleted; 63 | 64 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 65 | @ToString.Exclude 66 | private Block block; 67 | 68 | public void set(PersonDto personDto) { 69 | if (!StringUtils.isEmpty(personDto.getHobby())) { 70 | this.setHobby(personDto.getHobby()); 71 | } 72 | 73 | if (!StringUtils.isEmpty(personDto.getBloodType())) { 74 | this.setBloodType(personDto.getBloodType()); 75 | } 76 | 77 | if (!StringUtils.isEmpty(personDto.getAddress())) { 78 | this.setAddress(personDto.getAddress()); 79 | } 80 | 81 | if (!StringUtils.isEmpty(personDto.getJob())) { 82 | this.setJob(personDto.getJob()); 83 | } 84 | 85 | if (!StringUtils.isEmpty(personDto.getPhoneNumber())) { 86 | this.setPhoneNumber(personDto.getPhoneNumber()); 87 | } 88 | } 89 | 90 | public Integer getAge() { 91 | if (this.birthday != null) { 92 | return LocalDate.now().getYear() - this.birthday.getYearOfBirthday() + 1; 93 | } else { 94 | return null; 95 | } 96 | } 97 | 98 | public boolean isBirthdayToday() { 99 | return LocalDate.now().equals(LocalDate.of(this.birthday.getYearOfBirthday(), this.birthday.getMonthOfBirthday(), this.birthday.getDayOfBirthday())); 100 | } 101 | } 102 | ``` 103 | 104 | ```java 105 | @Slf4j 106 | @SpringBootTest 107 | @Transactional 108 | class PersonControllerTest { 109 | @Autowired 110 | private PersonController personController; 111 | @Autowired 112 | private PersonRepository personRepository; 113 | 114 | private MockMvc mockMvc; 115 | 116 | @BeforeEach 117 | void beforeEach() { 118 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 119 | } 120 | 121 | @Test 122 | void getPerson() throws Exception { 123 | mockMvc.perform( 124 | MockMvcRequestBuilders.get("/api/person/1")) 125 | .andDo(print()) 126 | .andExpect(status().isOk()) 127 | .andExpect(jsonPath("$.name").value("martin")); 128 | } 129 | 130 | @Test 131 | void postPerson() throws Exception { 132 | mockMvc.perform( 133 | MockMvcRequestBuilders.post("/api/person") 134 | .contentType(MediaType.APPLICATION_JSON_UTF8) 135 | .content("{\n" 136 | + " \"name\": \"martin2\",\n" 137 | + " \"age\": 20,\n" 138 | + " \"bloodType\": \"A\"\n" 139 | + "}")) 140 | .andDo(print()) 141 | .andExpect(status().isCreated()); 142 | } 143 | 144 | @Test 145 | void modifyPerson() throws Exception { 146 | mockMvc.perform( 147 | MockMvcRequestBuilders.put("/api/person/1") 148 | .contentType(MediaType.APPLICATION_JSON_UTF8) 149 | .content("{\n" 150 | + " \"name\": \"martin\",\n" 151 | + " \"age\": 20,\n" 152 | + " \"bloodType\": \"A\"\n" 153 | + "}")) 154 | .andDo(print()) 155 | .andExpect(status().isOk()); 156 | } 157 | 158 | @Test 159 | void modifyName() throws Exception { 160 | mockMvc.perform( 161 | MockMvcRequestBuilders.patch("/api/person/1") 162 | .param("name", "martin22")) 163 | .andDo(print()) 164 | .andExpect(status().isOk()); 165 | } 166 | 167 | @Test 168 | void deletePerson() throws Exception { 169 | mockMvc.perform( 170 | MockMvcRequestBuilders.delete("/api/person/1")) 171 | .andDo(print()) 172 | .andExpect(status().isOk()); 173 | 174 | log.info("people deleted : {}", personRepository.findPeopleDeleted()); 175 | } 176 | } 177 | ``` 178 | 179 | ```java 180 | @Transactional 181 | @SpringBootTest 182 | class PersonRepositoryTest { 183 | @Autowired 184 | private PersonRepository personRepository; 185 | 186 | @Test 187 | void crud() { 188 | Person person = new Person(); 189 | person.setName("john"); 190 | person.setBloodType("A"); 191 | 192 | personRepository.save(person); 193 | 194 | List result = personRepository.findByName("john"); 195 | 196 | assertThat(result.size()).isEqualTo(1); 197 | assertThat(result.get(0).getName()).isEqualTo("john"); 198 | // assertThat(result.get(0).getAge()).isEqualTo(10); 199 | assertThat(result.get(0).getBloodType()).isEqualTo("A"); 200 | } 201 | 202 | @Test 203 | void findByBloodType() { 204 | List result = personRepository.findByBloodType("A"); 205 | 206 | assertThat(result.size()).isEqualTo(2); 207 | assertThat(result.get(0).getName()).isEqualTo("martin"); 208 | assertThat(result.get(1).getName()).isEqualTo("benny"); 209 | } 210 | 211 | @Test 212 | void findByBirthdayBetween() { 213 | List result = personRepository.findByMonthOfBirthday(8); 214 | 215 | assertThat(result.size()).isEqualTo(2); 216 | assertThat(result.get(0).getName()).isEqualTo("martin"); 217 | assertThat(result.get(1).getName()).isEqualTo("sophia"); 218 | } 219 | } 220 | ``` 221 | 222 | ```sql 223 | insert into person(`id`, `name`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`, `job`) values (1, 'martin', 'A', 1991, 8, 15, 'programmer'); 224 | insert into person(`id`, `name`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (2, 'david', 'B', 1992, 7, 21); 225 | insert into person(`id`, `name`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (3, 'dennis', 'O', 1993, 10, 15); 226 | insert into person(`id`, `name`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (4, 'sophia', 'AB', 1994, 8, 31); 227 | insert into person(`id`, `name`, `blood_type`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (5, 'benny', 'A', 1995, 12, 23); 228 | 229 | insert into block(`id`, `name`) values (1, 'dennis'); 230 | insert into block(`id`, `name`) values (2, 'sophia'); 231 | 232 | update person set block_id = 1 where id = 3; 233 | update person set block_id = 2 where id = 4; 234 | ``` 235 | -------------------------------------------------------------------------------- /06-Refactoring/02-리팩토링-도메인코드-2.md: -------------------------------------------------------------------------------- 1 | # 리팩토링 도메인코드 #2 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @Data 7 | public class PersonDto { 8 | private String name; 9 | private String hobby; 10 | private String address; 11 | private LocalDate birthday; 12 | private String job; 13 | private String phoneNumber; 14 | } 15 | ``` 16 | 17 | ```java 18 | @Entity 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | @RequiredArgsConstructor 22 | @Data 23 | @Where(clause = "deleted = false") 24 | public class Person { 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | private Long id; 28 | 29 | @NonNull 30 | @NotEmpty 31 | @Column(nullable = false) 32 | private String name; 33 | 34 | private String hobby; 35 | 36 | private String address; 37 | 38 | @Valid 39 | @Embedded 40 | private Birthday birthday; 41 | 42 | private String job; 43 | 44 | private String phoneNumber; 45 | 46 | @ColumnDefault("0") 47 | private boolean deleted; 48 | 49 | public void set(PersonDto personDto) { 50 | if (!StringUtils.isEmpty(personDto.getHobby())) { 51 | this.setHobby(personDto.getHobby()); 52 | } 53 | 54 | if (!StringUtils.isEmpty(personDto.getAddress())) { 55 | this.setAddress(personDto.getAddress()); 56 | } 57 | 58 | if (!StringUtils.isEmpty(personDto.getJob())) { 59 | this.setJob(personDto.getJob()); 60 | } 61 | 62 | if (!StringUtils.isEmpty(personDto.getPhoneNumber())) { 63 | this.setPhoneNumber(personDto.getPhoneNumber()); 64 | } 65 | 66 | if (personDto.getBirthday() != null) { 67 | this.setBirthday(Birthday.of(personDto.getBirthday())); 68 | } 69 | } 70 | 71 | public Integer getAge() { 72 | if (this.birthday != null) { 73 | return LocalDate.now().getYear() - this.birthday.getYearOfBirthday() + 1; 74 | } else { 75 | return null; 76 | } 77 | } 78 | 79 | public boolean isBirthdayToday() { 80 | return LocalDate.now().equals(LocalDate.of(this.birthday.getYearOfBirthday(), this.birthday.getMonthOfBirthday(), this.birthday.getDayOfBirthday())); 81 | } 82 | } 83 | ``` 84 | 85 | ```java 86 | public interface PersonRepository extends JpaRepository { 87 | List findByName(String name); 88 | 89 | @Query(value = "select person from Person person where person.birthday.monthOfBirthday = :monthOfBirthday") 90 | List findByMonthOfBirthday(@Param("monthOfBirthday") int monthOfBirthday); 91 | 92 | @Query(value = "select * from Person person where person.deleted = true", nativeQuery = true) 93 | List findPeopleDeleted(); 94 | } 95 | ``` 96 | 97 | ```java 98 | @Service 99 | @Slf4j 100 | public class PersonService { 101 | @Autowired 102 | private PersonRepository personRepository; 103 | 104 | public List getPeopleByName(String name) { 105 | return personRepository.findByName(name); 106 | } 107 | 108 | @Transactional(readOnly = true) 109 | public Person getPerson(Long id) { 110 | Person person = personRepository.findById(id).orElse(null); 111 | 112 | log.info("person : {}", person); 113 | 114 | return person; 115 | } 116 | 117 | @Transactional 118 | public void put(Person person) { 119 | personRepository.save(person); 120 | } 121 | 122 | @Transactional 123 | public void modify(Long id, PersonDto personDto) { 124 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 125 | 126 | if (!person.getName().equals(personDto.getName())) { 127 | throw new RuntimeException("이름이 다릅니다."); 128 | } 129 | 130 | person.set(personDto); 131 | 132 | personRepository.save(person); 133 | } 134 | 135 | @Transactional 136 | public void modify(Long id, String name) { 137 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 138 | 139 | person.setName(name); 140 | 141 | personRepository.save(person); 142 | } 143 | 144 | @Transactional 145 | public void delete(Long id) { 146 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 147 | 148 | person.setDeleted(true); 149 | 150 | personRepository.save(person); 151 | } 152 | } 153 | ``` 154 | 155 | ```java 156 | @Transactional 157 | @SpringBootTest 158 | class PersonRepositoryTest { 159 | @Autowired 160 | private PersonRepository personRepository; 161 | 162 | @Test 163 | void crud() { 164 | Person person = new Person(); 165 | person.setName("john"); 166 | 167 | personRepository.save(person); 168 | 169 | List result = personRepository.findByName("john"); 170 | 171 | assertThat(result.size()).isEqualTo(1); 172 | assertThat(result.get(0).getName()).isEqualTo("john"); 173 | // assertThat(result.get(0).getAge()).isEqualTo(10); 174 | } 175 | 176 | @Test 177 | void findByBirthdayBetween() { 178 | List result = personRepository.findByMonthOfBirthday(8); 179 | 180 | assertThat(result.size()).isEqualTo(2); 181 | assertThat(result.get(0).getName()).isEqualTo("martin"); 182 | assertThat(result.get(1).getName()).isEqualTo("sophia"); 183 | } 184 | } 185 | ``` 186 | 187 | ```java 188 | @SpringBootTest 189 | class PersonServiceTest { 190 | @Autowired 191 | private PersonService personService; 192 | @Autowired 193 | private PersonRepository personRepository; 194 | 195 | @Test 196 | void getPeopleByName() { 197 | List result = personService.getPeopleByName("martin"); 198 | 199 | assertThat(result.size()).isEqualTo(1); 200 | assertThat(result.get(0).getName()).isEqualTo("martin"); 201 | } 202 | 203 | @Test 204 | void getPerson() { 205 | Person person = personService.getPerson(3L); 206 | 207 | assertThat(person.getName()).isEqualTo("dennis"); 208 | } 209 | } 210 | ``` 211 | 212 | ```sql 213 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`, `job`) values (1, 'martin', 1991, 8, 15, 'programmer'); 214 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (2, 'david', 1992, 7, 21); 215 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (3, 'dennis', 1993, 10, 15); 216 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (4, 'sophia', 1994, 8, 31); 217 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (5, 'benny', 1995, 12, 23); 218 | ``` 219 | -------------------------------------------------------------------------------- /07-Controller-Test/01-Controller-Test-1.md: -------------------------------------------------------------------------------- 1 | # Controller Test #1 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @RequestMapping(value = "/api/person") 7 | @RestController 8 | @Slf4j 9 | public class PersonController { 10 | @Autowired 11 | private PersonService personService; 12 | @Autowired 13 | private PersonRepository personRepository; 14 | 15 | @GetMapping("/{id}") 16 | public Person getPerson(@PathVariable Long id) { 17 | return personService.getPerson(id); 18 | } 19 | 20 | @PostMapping 21 | @ResponseStatus(HttpStatus.CREATED) 22 | public void postPerson(@RequestBody Person person) { 23 | personService.put(person); 24 | } 25 | 26 | @PutMapping("/{id}") 27 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 28 | personService.modify(id, personDto); 29 | } 30 | 31 | @PatchMapping("/{id}") 32 | public void modifyPerson(@PathVariable Long id, String name) { 33 | personService.modify(id, name); 34 | } 35 | 36 | @DeleteMapping("/{id}") 37 | public void deletePerson(@PathVariable Long id) { 38 | personService.delete(id); 39 | } 40 | } 41 | ``` 42 | 43 | ```java 44 | @Data 45 | @NoArgsConstructor 46 | @AllArgsConstructor(staticName = "of") 47 | public class PersonDto { 48 | private String name; 49 | private String hobby; 50 | private String address; 51 | private LocalDate birthday; 52 | private String job; 53 | private String phoneNumber; 54 | } 55 | ``` 56 | 57 | ```java 58 | @Slf4j 59 | @SpringBootTest 60 | @Transactional 61 | class PersonControllerTest { 62 | @Autowired 63 | private PersonController personController; 64 | @Autowired 65 | private PersonRepository personRepository; 66 | @Autowired 67 | private ObjectMapper objectMapper; 68 | 69 | private MockMvc mockMvc; 70 | 71 | @BeforeEach 72 | void beforeEach() { 73 | mockMvc = MockMvcBuilders.standaloneSetup(personController).build(); 74 | } 75 | 76 | @Test 77 | void getPerson() throws Exception { 78 | mockMvc.perform( 79 | MockMvcRequestBuilders.get("/api/person/1")) 80 | .andDo(print()) 81 | .andExpect(status().isOk()) 82 | .andExpect(jsonPath("$.name").value("martin")); 83 | } 84 | 85 | @Test 86 | void postPerson() throws Exception { 87 | mockMvc.perform( 88 | MockMvcRequestBuilders.post("/api/person") 89 | .contentType(MediaType.APPLICATION_JSON_UTF8) 90 | .content("{\n" 91 | + " \"name\": \"martin2\",\n" 92 | + " \"age\": 20,\n" 93 | + " \"bloodType\": \"A\"\n" 94 | + "}")) 95 | .andDo(print()) 96 | .andExpect(status().isCreated()); 97 | } 98 | 99 | @Test 100 | void modifyPerson() throws Exception { 101 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 102 | 103 | mockMvc.perform( 104 | MockMvcRequestBuilders.put("/api/person/1") 105 | .contentType(MediaType.APPLICATION_JSON_UTF8) 106 | .content(toJsonString(dto))) 107 | .andDo(print()) 108 | .andExpect(status().isOk()); 109 | 110 | Person result = personRepository.findById(1L).get(); 111 | 112 | assertAll( 113 | () -> assertThat(result.getName()).isEqualTo("martin"), 114 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 115 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 116 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 117 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 118 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 119 | ); 120 | } 121 | 122 | @Test 123 | void modifyPersonIfNameIsDifferent() throws Exception { 124 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 125 | 126 | assertThrows(NestedServletException.class, () -> 127 | mockMvc.perform( 128 | MockMvcRequestBuilders.put("/api/person/1") 129 | .contentType(MediaType.APPLICATION_JSON_UTF8) 130 | .content(toJsonString(dto))) 131 | .andDo(print()) 132 | .andExpect(status().isOk())); 133 | } 134 | 135 | @Test 136 | void modifyName() throws Exception { 137 | mockMvc.perform( 138 | MockMvcRequestBuilders.patch("/api/person/1") 139 | .param("name", "martinModified")) 140 | .andDo(print()) 141 | .andExpect(status().isOk()); 142 | 143 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 144 | } 145 | 146 | @Test 147 | void deletePerson() throws Exception { 148 | mockMvc.perform( 149 | MockMvcRequestBuilders.delete("/api/person/1")) 150 | .andDo(print()) 151 | .andExpect(status().isOk()); 152 | 153 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 154 | } 155 | 156 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 157 | return objectMapper.writeValueAsString(personDto); 158 | } 159 | } 160 | ``` 161 | 162 | ```sql 163 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (1, 'martin', 1991, 8, 15); 164 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (2, 'david', 1992, 7, 21); 165 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (3, 'dennis', 1993, 10, 15); 166 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (4, 'sophia', 1994, 8, 31); 167 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (5, 'benny', 1995, 12, 23); 168 | ``` 169 | -------------------------------------------------------------------------------- /07-Controller-Test/02-Controller-Test-2.md: -------------------------------------------------------------------------------- 1 | # Controller Test #2 2 | 3 | ## 이론 4 | 5 | * Controller Test에서 사용하는 메소드 6 | * jsonPath 7 | * $ : 객체를 의미함 8 | * .name : 객체의 name attribute를 가져옴, getName()을 사용한다고 생각하면 됨 9 | * . 체이닝을 통해서 recursive하게 데이터 추출이 가능 10 | * value(A) 11 | * 값이 A와 동일한지 검증함 12 | * imEmpty() 13 | * 값이 빈 값인지 검증함 14 | * isNumber() 15 | * 값이 숫자값인지 검증함 16 | * isBoolean() 17 | * true/false 값인지 검증함 18 | * JsonSerializer 19 | * serialize() 20 | * @Configuration 21 | * Spring의 Configuration Bean임을 표시하는 어노테이션 22 | * MappingJackson2HttpMessageConverter 23 | * Spring의 Controller에서 반환하는 결과를 Json으로 만드는데, 해당 포맷을 커스터마이징할 수 있게 해줌 24 | * SimpleModule 25 | * 커스텀으로 만든 Serializer를 Converter에 등록하기 위해서 모듈화해줌 26 | 27 | ## 실전코드 28 | 29 | ```java 30 | @Configuration 31 | public class JsonConfig { 32 | @Bean 33 | public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { 34 | MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); 35 | converter.setObjectMapper(objectMapper); 36 | 37 | return converter; 38 | } 39 | 40 | @Bean 41 | public ObjectMapper objectMapper() { 42 | ObjectMapper objectMapper = new ObjectMapper(); 43 | objectMapper.registerModule(new BirthdayModule()); 44 | objectMapper.registerModule(new JavaTimeModule()); 45 | 46 | objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 47 | 48 | return objectMapper; 49 | } 50 | 51 | static class BirthdayModule extends SimpleModule { 52 | BirthdayModule() { 53 | super(); 54 | addSerializer(Birthday.class, new BirthdaySerializer()); 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | ```java 61 | public class BirthdaySerializer extends JsonSerializer { 62 | @Override 63 | public void serialize(Birthday value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 64 | if (value != null) { 65 | gen.writeObject(LocalDate.of(value.getYearOfBirthday(), value.getMonthOfBirthday(), value.getDayOfBirthday())); 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ```java 72 | @Slf4j 73 | @SpringBootTest 74 | @Transactional 75 | class PersonControllerTest { 76 | @Autowired 77 | private PersonController personController; 78 | @Autowired 79 | private PersonRepository personRepository; 80 | @Autowired 81 | private ObjectMapper objectMapper; 82 | @Autowired 83 | private MappingJackson2HttpMessageConverter messageConverter; 84 | 85 | private MockMvc mockMvc; 86 | 87 | @BeforeEach 88 | void beforeEach() { 89 | mockMvc = MockMvcBuilders.standaloneSetup(personController).setMessageConverters(messageConverter).build(); 90 | } 91 | 92 | @Test 93 | void getPerson() throws Exception { 94 | mockMvc.perform( 95 | MockMvcRequestBuilders.get("/api/person/1")) 96 | .andDo(print()) 97 | .andExpect(status().isOk()) 98 | .andExpect(jsonPath("$.name").value("martin")) 99 | .andExpect(jsonPath("hobby").isEmpty()) 100 | .andExpect(jsonPath("address").isEmpty()) 101 | .andExpect(jsonPath("$.birthday").value("1991-08-15")) 102 | .andExpect(jsonPath("$.job").isEmpty()) 103 | .andExpect(jsonPath("$.phoneNumber").isEmpty()) 104 | .andExpect(jsonPath("$.deleted").value(false)) 105 | .andExpect(jsonPath("$.age").isNumber()) 106 | .andExpect(jsonPath("$.birthdayToday").isBoolean()); 107 | } 108 | 109 | @Test 110 | void postPerson() throws Exception { 111 | mockMvc.perform( 112 | MockMvcRequestBuilders.post("/api/person") 113 | .contentType(MediaType.APPLICATION_JSON_UTF8) 114 | .content("{\n" 115 | + " \"name\": \"martin2\",\n" 116 | + " \"age\": 20,\n" 117 | + " \"bloodType\": \"A\"\n" 118 | + "}")) 119 | .andDo(print()) 120 | .andExpect(status().isCreated()); 121 | } 122 | 123 | @Test 124 | void modifyPerson() throws Exception { 125 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 126 | 127 | mockMvc.perform( 128 | MockMvcRequestBuilders.put("/api/person/1") 129 | .contentType(MediaType.APPLICATION_JSON_UTF8) 130 | .content(toJsonString(dto))) 131 | .andDo(print()) 132 | .andExpect(status().isOk()); 133 | 134 | Person result = personRepository.findById(1L).get(); 135 | 136 | assertAll( 137 | () -> assertThat(result.getName()).isEqualTo("martin"), 138 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 139 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 140 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 141 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 142 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 143 | ); 144 | } 145 | 146 | @Test 147 | void modifyPersonIfNameIsDifferent() throws Exception { 148 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 149 | 150 | assertThrows(NestedServletException.class, () -> 151 | mockMvc.perform( 152 | MockMvcRequestBuilders.put("/api/person/1") 153 | .contentType(MediaType.APPLICATION_JSON_UTF8) 154 | .content(toJsonString(dto))) 155 | .andDo(print()) 156 | .andExpect(status().isOk())); 157 | } 158 | 159 | @Test 160 | void modifyName() throws Exception { 161 | mockMvc.perform( 162 | MockMvcRequestBuilders.patch("/api/person/1") 163 | .param("name", "martinModified")) 164 | .andDo(print()) 165 | .andExpect(status().isOk()); 166 | 167 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 168 | } 169 | 170 | @Test 171 | void deletePerson() throws Exception { 172 | mockMvc.perform( 173 | MockMvcRequestBuilders.delete("/api/person/1")) 174 | .andDo(print()) 175 | .andExpect(status().isOk()); 176 | 177 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 178 | } 179 | 180 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 181 | return objectMapper.writeValueAsString(personDto); 182 | } 183 | } 184 | ``` 185 | -------------------------------------------------------------------------------- /07-Controller-Test/03-Controller-Test-3.md: -------------------------------------------------------------------------------- 1 | # Controller Test #3 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @RequestMapping(value = "/api/person") 7 | @RestController 8 | @Slf4j 9 | public class PersonController { 10 | @Autowired 11 | private PersonService personService; 12 | @Autowired 13 | private PersonRepository personRepository; 14 | 15 | @GetMapping("/{id}") 16 | public Person getPerson(@PathVariable Long id) { 17 | return personService.getPerson(id); 18 | } 19 | 20 | @PostMapping 21 | @ResponseStatus(HttpStatus.CREATED) 22 | public void postPerson(@RequestBody PersonDto personDto) { 23 | personService.put(personDto); 24 | } 25 | 26 | @PutMapping("/{id}") 27 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 28 | personService.modify(id, personDto); 29 | } 30 | 31 | @PatchMapping("/{id}") 32 | public void modifyPerson(@PathVariable Long id, String name) { 33 | personService.modify(id, name); 34 | } 35 | 36 | @DeleteMapping("/{id}") 37 | public void deletePerson(@PathVariable Long id) { 38 | personService.delete(id); 39 | } 40 | } 41 | ``` 42 | 43 | ```java 44 | @Service 45 | @Slf4j 46 | public class PersonService { 47 | @Autowired 48 | private PersonRepository personRepository; 49 | 50 | public List getPeopleByName(String name) { 51 | return personRepository.findByName(name); 52 | } 53 | 54 | @Transactional(readOnly = true) 55 | public Person getPerson(Long id) { 56 | Person person = personRepository.findById(id).orElse(null); 57 | 58 | log.info("person : {}", person); 59 | 60 | return person; 61 | } 62 | 63 | @Transactional 64 | public void put(PersonDto personDto) { 65 | Person person = new Person(); 66 | person.set(personDto); 67 | person.setName(personDto.getName()); 68 | 69 | personRepository.save(person); 70 | } 71 | 72 | @Transactional 73 | public void modify(Long id, PersonDto personDto) { 74 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 75 | 76 | if (!person.getName().equals(personDto.getName())) { 77 | throw new RuntimeException("이름이 다릅니다."); 78 | } 79 | 80 | person.set(personDto); 81 | 82 | personRepository.save(person); 83 | } 84 | 85 | @Transactional 86 | public void modify(Long id, String name) { 87 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 88 | 89 | person.setName(name); 90 | 91 | personRepository.save(person); 92 | } 93 | 94 | @Transactional 95 | public void delete(Long id) { 96 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 97 | 98 | person.setDeleted(true); 99 | 100 | personRepository.save(person); 101 | } 102 | } 103 | ``` 104 | 105 | ```java 106 | @Slf4j 107 | @SpringBootTest 108 | @Transactional 109 | class PersonControllerTest { 110 | @Autowired 111 | private PersonController personController; 112 | @Autowired 113 | private PersonRepository personRepository; 114 | @Autowired 115 | private ObjectMapper objectMapper; 116 | @Autowired 117 | private MappingJackson2HttpMessageConverter messageConverter; 118 | 119 | private MockMvc mockMvc; 120 | 121 | @BeforeEach 122 | void beforeEach() { 123 | mockMvc = MockMvcBuilders.standaloneSetup(personController).setMessageConverters(messageConverter).build(); 124 | } 125 | 126 | @Test 127 | void getPerson() throws Exception { 128 | mockMvc.perform( 129 | MockMvcRequestBuilders.get("/api/person/1")) 130 | .andDo(print()) 131 | .andExpect(status().isOk()) 132 | .andExpect(jsonPath("$.name").value("martin")) 133 | .andExpect(jsonPath("$.hobby").isEmpty()) 134 | .andExpect(jsonPath("$.address").isEmpty()) 135 | .andExpect(jsonPath("$.birthday").value("1991-08-15")) 136 | .andExpect(jsonPath("$.job").isEmpty()) 137 | .andExpect(jsonPath("$.phoneNumber").isEmpty()) 138 | .andExpect(jsonPath("$.deleted").value(false)) 139 | .andExpect(jsonPath("$.age").isNumber()) 140 | .andExpect(jsonPath("$.birthdayToday").isBoolean()); 141 | } 142 | 143 | @Test 144 | void postPerson() throws Exception { 145 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 146 | 147 | mockMvc.perform( 148 | MockMvcRequestBuilders.post("/api/person") 149 | .contentType(MediaType.APPLICATION_JSON_UTF8) 150 | .content(toJsonString(dto))) 151 | .andDo(print()) 152 | .andExpect(status().isCreated()); 153 | 154 | Person result = personRepository.findAll(Sort.by(Direction.DESC, "id")).get(0); 155 | 156 | assertAll( 157 | () -> assertThat(result.getName()).isEqualTo("martin"), 158 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 159 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 160 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 161 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 162 | () -> assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 163 | ); 164 | } 165 | 166 | @Test 167 | void modifyPerson() throws Exception { 168 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 169 | 170 | mockMvc.perform( 171 | MockMvcRequestBuilders.put("/api/person/1") 172 | .contentType(MediaType.APPLICATION_JSON_UTF8) 173 | .content(toJsonString(dto))) 174 | .andDo(print()) 175 | .andExpect(status().isOk()); 176 | 177 | Person result = personRepository.findById(1L).get(); 178 | 179 | assertAll( 180 | () -> assertThat(result.getName()).isEqualTo("martin"), 181 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 182 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 183 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 184 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 185 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 186 | ); 187 | } 188 | 189 | @Test 190 | void modifyPersonIfNameIsDifferent() throws Exception { 191 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 192 | 193 | assertThrows(NestedServletException.class, () -> 194 | mockMvc.perform( 195 | MockMvcRequestBuilders.put("/api/person/1") 196 | .contentType(MediaType.APPLICATION_JSON_UTF8) 197 | .content(toJsonString(dto))) 198 | .andDo(print()) 199 | .andExpect(status().isOk())); 200 | } 201 | 202 | @Test 203 | void modifyName() throws Exception { 204 | mockMvc.perform( 205 | MockMvcRequestBuilders.patch("/api/person/1") 206 | .param("name", "martinModified")) 207 | .andDo(print()) 208 | .andExpect(status().isOk()); 209 | 210 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 211 | } 212 | 213 | @Test 214 | void deletePerson() throws Exception { 215 | mockMvc.perform( 216 | MockMvcRequestBuilders.delete("/api/person/1")) 217 | .andDo(print()) 218 | .andExpect(status().isOk()); 219 | 220 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 221 | } 222 | 223 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 224 | return objectMapper.writeValueAsString(personDto); 225 | } 226 | } 227 | ``` 228 | -------------------------------------------------------------------------------- /08-Repository-Test/01-Repository-Test.md: -------------------------------------------------------------------------------- 1 | # Repository Test 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @Transactional 7 | @SpringBootTest 8 | class PersonRepositoryTest { 9 | @Autowired 10 | private PersonRepository personRepository; 11 | 12 | @Test 13 | void findByName() { 14 | List people = personRepository.findByName("tony"); 15 | assertThat(people.size()).isEqualTo(1); 16 | 17 | Person person = people.get(0); 18 | assertAll( 19 | () -> assertThat(person.getName()).isEqualTo("tony"), 20 | () -> assertThat(person.getHobby()).isEqualTo("reading"), 21 | () -> assertThat(person.getAddress()).isEqualTo("서울"), 22 | () -> assertThat(person.getBirthday()).isEqualTo(Birthday.of(LocalDate.of(1991, 7, 10))), 23 | () -> assertThat(person.getJob()).isEqualTo("officer"), 24 | () -> assertThat(person.getPhoneNumber()).isEqualTo("010-2222-5555"), 25 | () -> assertThat(person.isDeleted()).isEqualTo(false) 26 | ); 27 | } 28 | 29 | @Test 30 | void findByNameIfDeleted() { 31 | List people = personRepository.findByName("andrew"); 32 | 33 | assertThat(people.size()).isEqualTo(0); 34 | } 35 | 36 | @Test 37 | void findByMonthOfBirthday() { 38 | List people = personRepository.findByMonthOfBirthday(7); 39 | 40 | assertThat(people.size()).isEqualTo(2); 41 | assertAll( 42 | () -> assertThat(people.get(0).getName()).isEqualTo("david"), 43 | () -> assertThat(people.get(1).getName()).isEqualTo("tony") 44 | ); 45 | } 46 | 47 | @Test 48 | void findPeopleDeleted() { 49 | List people = personRepository.findPeopleDeleted(); 50 | 51 | assertThat(people.size()).isEqualTo(1); 52 | assertThat(people.get(0).getName()).isEqualTo("andrew"); 53 | } 54 | } 55 | ``` 56 | 57 | ```sql 58 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (1, 'martin', 1991, 8, 15); 59 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (2, 'david', 1992, 7, 21); 60 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (3, 'dennis', 1993, 10, 15); 61 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (4, 'sophia', 1994, 8, 31); 62 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`) values (5, 'benny', 1995, 12, 23); 63 | insert into person(`id`, `name`, `year_of_birthday`, `month_of_birthday`, `day_of_birthday`, `job`, `hobby`, `phone_number`, `address`) 64 | values (6, 'tony', 1991, 7, 10, 'officer', 'reading', '010-2222-5555', '서울'); 65 | insert into person(`id`, `name`, `deleted`) values (7, 'andrew', true); 66 | ``` 67 | -------------------------------------------------------------------------------- /09-Mock-Test/01-Service-Test-1.md: -------------------------------------------------------------------------------- 1 | # Service Test #1 2 | 3 | ## 이론 4 | 5 | * Mock 테스트의 장점 6 | * 테스트를 더 빠르게 실행할 수 있음 7 | * 테스트를 더 구체적이고 세밀하게 할 수 있음 8 | * @ExtendWith 9 | * 테스트를 진행할 컨테이너를 별도로 지정해줌 10 | * Junit4에서 @RunWith를 대체하는 어노테이션 11 | * MockitoExtension 12 | * Mockito를 사용할 수 있도록 처리해줌 13 | * @InjectMocks 14 | * @Mock으로 지정된 객체들을 생성해서, 테스트의 주체가 되는 클래스에 주입(Autowired)까지 해줌 15 | * @Mock 16 | * 실제 Bean이 아니라 가짜 객체(Mock)를 만들어서 실제 Bean을 대체함 17 | * when...thenReturn 18 | * Mock의 어떤 메소드와 파라미터가 매핑되는 경우, 결과값에 대해서 지정해줄 수 있음 19 | 20 | ## 실전코드 21 | 22 | ```groovy 23 | plugins { 24 | id 'org.springframework.boot' version '2.1.6.RELEASE' 25 | id 'java' 26 | } 27 | 28 | apply plugin: 'io.spring.dependency-management' 29 | 30 | group = 'com.fastcampus.javaallinone.project3' 31 | version = '0.0.1-SNAPSHOT' 32 | sourceCompatibility = '1.8' 33 | 34 | configurations { 35 | compileOnly { 36 | extendsFrom annotationProcessor 37 | } 38 | } 39 | 40 | repositories { 41 | mavenCentral() 42 | } 43 | 44 | dependencies { 45 | implementation 'org.springframework.boot:spring-boot-starter-web' 46 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 47 | implementation 'com.h2database:h2' 48 | compileOnly 'org.projectlombok:lombok' 49 | annotationProcessor 'org.projectlombok:lombok' 50 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 51 | testImplementation 'org.junit.platform:junit-platform-launcher:1.5.0' 52 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.0' 53 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.5.0' 54 | testImplementation 'org.mockito:mockito-junit-jupiter' 55 | } 56 | ``` 57 | 58 | ```java 59 | @ExtendWith(MockitoExtension.class) 60 | class PersonServiceTest { 61 | @InjectMocks 62 | private PersonService personService; 63 | @Mock 64 | private PersonRepository personRepository; 65 | 66 | @Test 67 | void getPeopleByName() { 68 | when(personRepository.findByName("martin")) 69 | .thenReturn(Lists.newArrayList(new Person("martin"))); 70 | 71 | List result = personService.getPeopleByName("martin"); 72 | 73 | assertThat(result.size()).isEqualTo(1); 74 | assertThat(result.get(0).getName()).isEqualTo("martin"); 75 | } 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /09-Mock-Test/02-Service-Test-2.md: -------------------------------------------------------------------------------- 1 | # Service Test #2 2 | 3 | ## 이론 4 | 5 | * verify 6 | * Mock의 액션에 대해서 별도로 검증할 수 있음 7 | * 주로 return값이 없는 void 타입의 메소드나, 반복문을 테스트해야 하는 경우에 사용함 8 | 9 | ## 실전코드 10 | 11 | ```java 12 | @Service 13 | @Slf4j 14 | public class PersonService { 15 | @Autowired 16 | private PersonRepository personRepository; 17 | 18 | public List getPeopleByName(String name) { 19 | return personRepository.findByName(name); 20 | } 21 | 22 | @Transactional(readOnly = true) 23 | public Person getPerson(Long id) { 24 | return personRepository.findById(id).orElse(null); 25 | } 26 | 27 | @Transactional 28 | public void put(PersonDto personDto) { 29 | Person person = new Person(); 30 | person.set(personDto); 31 | person.setName(personDto.getName()); 32 | 33 | personRepository.save(person); 34 | } 35 | 36 | @Transactional 37 | public void modify(Long id, PersonDto personDto) { 38 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 39 | 40 | if (!person.getName().equals(personDto.getName())) { 41 | throw new RuntimeException("이름이 다릅니다."); 42 | } 43 | 44 | person.set(personDto); 45 | 46 | personRepository.save(person); 47 | } 48 | 49 | @Transactional 50 | public void modify(Long id, String name) { 51 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 52 | 53 | person.setName(name); 54 | 55 | personRepository.save(person); 56 | } 57 | 58 | @Transactional 59 | public void delete(Long id) { 60 | Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("아이디가 존재하지 않습니다.")); 61 | 62 | person.setDeleted(true); 63 | 64 | personRepository.save(person); 65 | } 66 | } 67 | ``` 68 | 69 | ```java 70 | @ExtendWith(MockitoExtension.class) 71 | class PersonServiceTest { 72 | @InjectMocks 73 | private PersonService personService; 74 | @Mock 75 | private PersonRepository personRepository; 76 | 77 | @Test 78 | void getPeopleByName() { 79 | when(personRepository.findByName("martin")) 80 | .thenReturn(Lists.newArrayList(new Person("martin"))); 81 | 82 | List result = personService.getPeopleByName("martin"); 83 | 84 | assertThat(result.size()).isEqualTo(1); 85 | assertThat(result.get(0).getName()).isEqualTo("martin"); 86 | } 87 | 88 | @Test 89 | void getPerson() { 90 | when(personRepository.findById(1L)) 91 | .thenReturn(Optional.of(new Person("martin"))); 92 | 93 | Person person = personService.getPerson(1L); 94 | 95 | assertThat(person.getName()).isEqualTo("martin"); 96 | } 97 | 98 | @Test 99 | void getPersonIfNotFound() { 100 | when(personRepository.findById(1L)) 101 | .thenReturn(Optional.empty()); 102 | 103 | Person person = personService.getPerson(1L); 104 | 105 | assertThat(person).isNull(); 106 | } 107 | 108 | @Test 109 | void put() { 110 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 111 | 112 | personService.put(dto); 113 | 114 | verify(personRepository, times(1)).save(any(Person.class)); 115 | } 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /09-Mock-Test/03-Service-Test-3.md: -------------------------------------------------------------------------------- 1 | # Service Test #3 2 | 3 | ## 이론 4 | 5 | * argThat 6 | * ArgumentMatcher class를 이용해서 결과를 검증하겠다는 의미 7 | 8 | * ArgumentMatcher 9 | * 테스트 결과값을 검증하는 matcher를 custom하게 개발할 수 있도록 제공하는 인터페이스 10 | * matches() 11 | * 해당 method를 override하여 값을 검증할 수 있음 12 | * 검증 후 결과를 boolean으로 리턴하여 테스트를 검증할 수 있도록 함 13 | 14 | ## 실전코드 15 | 16 | ```java 17 | @ExtendWith(MockitoExtension.class) 18 | class PersonServiceTest { 19 | @InjectMocks 20 | private PersonService personService; 21 | @Mock 22 | private PersonRepository personRepository; 23 | 24 | @Test 25 | void getPeopleByName() { 26 | when(personRepository.findByName("martin")) 27 | .thenReturn(Lists.newArrayList(new Person("martin"))); 28 | 29 | List result = personService.getPeopleByName("martin"); 30 | 31 | assertThat(result.size()).isEqualTo(1); 32 | assertThat(result.get(0).getName()).isEqualTo("martin"); 33 | } 34 | 35 | @Test 36 | void getPerson() { 37 | when(personRepository.findById(1L)) 38 | .thenReturn(Optional.of(new Person("martin"))); 39 | 40 | Person person = personService.getPerson(1L); 41 | 42 | assertThat(person.getName()).isEqualTo("martin"); 43 | } 44 | 45 | @Test 46 | void getPersonIfNotFound() { 47 | when(personRepository.findById(1L)) 48 | .thenReturn(Optional.empty()); 49 | 50 | Person person = personService.getPerson(1L); 51 | 52 | assertThat(person).isNull(); 53 | } 54 | 55 | @Test 56 | void put() { 57 | personService.put(mockPersonDto()); 58 | 59 | verify(personRepository, times(1)).save(any(Person.class)); 60 | } 61 | 62 | @Test 63 | void modifyIfPersonNotFound() { 64 | when(personRepository.findById(1L)) 65 | .thenReturn(Optional.empty()); 66 | 67 | assertThrows(RuntimeException.class, () -> personService.modify(1L, mockPersonDto())); 68 | } 69 | 70 | @Test 71 | void modifyIfNameIsDifferent() { 72 | when(personRepository.findById((1L))) 73 | .thenReturn(Optional.of(new Person("tony"))); 74 | 75 | assertThrows(RuntimeException.class, () -> personService.modify(1L, mockPersonDto())); 76 | } 77 | 78 | @Test 79 | void modify() { 80 | when(personRepository.findById(1L)) 81 | .thenReturn(Optional.of(new Person("martin"))); 82 | 83 | personService.modify(1L, mockPersonDto()); 84 | 85 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeUpdated())); 86 | } 87 | 88 | private PersonDto mockPersonDto() { 89 | return PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 90 | } 91 | 92 | private static class IsPersonWillBeUpdated implements ArgumentMatcher { 93 | @Override 94 | public boolean matches(Person person) { 95 | return equals(person.getName(), "martin") 96 | && equals(person.getHobby(), "programming") 97 | && equals(person.getAddress(), "판교") 98 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 99 | && equals(person.getJob(), "programmer") 100 | && equals(person.getPhoneNumber(), "010-1111-2222"); 101 | } 102 | 103 | private boolean equals(Object actual, Object expected) { 104 | return expected.equals(actual); 105 | } 106 | } 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /09-Mock-Test/04-Service-Test-4.md: -------------------------------------------------------------------------------- 1 | # Service Test #4 2 | 3 | ## 실전코드 4 | 5 | ```java 6 | @ExtendWith(MockitoExtension.class) 7 | class PersonServiceTest { 8 | @InjectMocks 9 | private PersonService personService; 10 | @Mock 11 | private PersonRepository personRepository; 12 | 13 | @Test 14 | void getPeopleByName() { 15 | when(personRepository.findByName("martin")) 16 | .thenReturn(Lists.newArrayList(new Person("martin"))); 17 | 18 | List result = personService.getPeopleByName("martin"); 19 | 20 | assertThat(result.size()).isEqualTo(1); 21 | assertThat(result.get(0).getName()).isEqualTo("martin"); 22 | } 23 | 24 | @Test 25 | void getPerson() { 26 | when(personRepository.findById(1L)) 27 | .thenReturn(Optional.of(new Person("martin"))); 28 | 29 | Person person = personService.getPerson(1L); 30 | 31 | assertThat(person.getName()).isEqualTo("martin"); 32 | } 33 | 34 | @Test 35 | void getPersonIfNotFound() { 36 | when(personRepository.findById(1L)) 37 | .thenReturn(Optional.empty()); 38 | 39 | Person person = personService.getPerson(1L); 40 | 41 | assertThat(person).isNull(); 42 | } 43 | 44 | @Test 45 | void put() { 46 | personService.put(mockPersonDto()); 47 | 48 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeInserted())); 49 | } 50 | 51 | @Test 52 | void modifyIfPersonNotFound() { 53 | when(personRepository.findById(1L)) 54 | .thenReturn(Optional.empty()); 55 | 56 | assertThrows(RuntimeException.class, () -> personService.modify(1L, mockPersonDto())); 57 | } 58 | 59 | @Test 60 | void modifyIfNameIsDifferent() { 61 | when(personRepository.findById((1L))) 62 | .thenReturn(Optional.of(new Person("tony"))); 63 | 64 | assertThrows(RuntimeException.class, () -> personService.modify(1L, mockPersonDto())); 65 | } 66 | 67 | @Test 68 | void modify() { 69 | when(personRepository.findById(1L)) 70 | .thenReturn(Optional.of(new Person("martin"))); 71 | 72 | personService.modify(1L, mockPersonDto()); 73 | 74 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeUpdated())); 75 | } 76 | 77 | @Test 78 | void modifyByNameIfPersonNotFound() { 79 | when(personRepository.findById(1L)) 80 | .thenReturn(Optional.empty()); 81 | 82 | assertThrows(RuntimeException.class, () -> personService.modify(1L, "daniel")); 83 | } 84 | 85 | @Test 86 | void modifyByName() { 87 | when(personRepository.findById(1L)) 88 | .thenReturn(Optional.of(new Person("martin"))); 89 | 90 | personService.modify(1L, "daniel"); 91 | 92 | verify(personRepository, times(1)).save(argThat(new IsNameWillBeUpdated())); 93 | } 94 | 95 | @Test 96 | void deleteIfPersonNotFound() { 97 | when(personRepository.findById(1L)) 98 | .thenReturn(Optional.empty()); 99 | 100 | assertThrows(RuntimeException.class, () -> personService.delete(1L)); 101 | } 102 | 103 | @Test 104 | void delete() { 105 | when(personRepository.findById(1L)) 106 | .thenReturn(Optional.of(new Person("martin"))); 107 | 108 | personService.delete(1L); 109 | 110 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeDeleted())); 111 | } 112 | 113 | private PersonDto mockPersonDto() { 114 | return PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 115 | } 116 | 117 | private static class IsPersonWillBeInserted implements ArgumentMatcher { 118 | @Override 119 | public boolean matches(Person person) { 120 | return equals(person.getName(), "martin") 121 | && equals(person.getHobby(), "programming") 122 | && equals(person.getAddress(), "판교") 123 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 124 | && equals(person.getJob(), "programmer") 125 | && equals(person.getPhoneNumber(), "010-1111-2222"); 126 | } 127 | 128 | private boolean equals(Object actual, Object expected) { 129 | return expected.equals(actual); 130 | } 131 | } 132 | 133 | private static class IsPersonWillBeUpdated implements ArgumentMatcher { 134 | @Override 135 | public boolean matches(Person person) { 136 | return equals(person.getName(), "martin") 137 | && equals(person.getHobby(), "programming") 138 | && equals(person.getAddress(), "판교") 139 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 140 | && equals(person.getJob(), "programmer") 141 | && equals(person.getPhoneNumber(), "010-1111-2222"); 142 | } 143 | 144 | private boolean equals(Object actual, Object expected) { 145 | return expected.equals(actual); 146 | } 147 | } 148 | 149 | private static class IsNameWillBeUpdated implements ArgumentMatcher { 150 | @Override 151 | public boolean matches(Person person) { 152 | return person.getName().equals("daniel"); 153 | } 154 | } 155 | 156 | private static class IsPersonWillBeDeleted implements ArgumentMatcher { 157 | @Override 158 | public boolean matches(Person person) { 159 | return person.isDeleted(); 160 | } 161 | } 162 | } 163 | ``` 164 | -------------------------------------------------------------------------------- /10-Exception/01-Exception-Handling-1.md: -------------------------------------------------------------------------------- 1 | # Exception Handling #1 2 | 3 | ## 이론 4 | 5 | * Exception의 종류 6 | * Checked Exception 7 | * Unchecked Exception (Runtime Exception) 8 | * Custom Exception의 필요성 9 | * 시스템의 오류와 분류하여 처리하기 위함 10 | * 구체화된 테스트를 만들기 용이함 11 | 12 | ## 실전코드 13 | 14 | ```java 15 | @Slf4j 16 | public class PersonNotFoundException extends RuntimeException { 17 | private static final String MESSAGE = "Person Entity가 존재하지 않습니다"; 18 | 19 | public PersonNotFoundException() { 20 | super(MESSAGE); 21 | log.error(MESSAGE); 22 | } 23 | } 24 | ``` 25 | 26 | ```java 27 | @Slf4j 28 | public class RenameIsNotPermittedException extends RuntimeException { 29 | private static final String MESSAGE = "이름 변경이 허용되지 않습니다"; 30 | 31 | public RenameIsNotPermittedException() { 32 | super(MESSAGE); 33 | log.error(MESSAGE); 34 | } 35 | } 36 | ``` 37 | 38 | ```java 39 | @Service 40 | @Slf4j 41 | public class PersonService { 42 | @Autowired 43 | private PersonRepository personRepository; 44 | 45 | public List getPeopleByName(String name) { 46 | return personRepository.findByName(name); 47 | } 48 | 49 | @Transactional(readOnly = true) 50 | public Person getPerson(Long id) { 51 | return personRepository.findById(id).orElse(null); 52 | } 53 | 54 | @Transactional 55 | public void put(PersonDto personDto) { 56 | Person person = new Person(); 57 | person.set(personDto); 58 | person.setName(personDto.getName()); 59 | 60 | personRepository.save(person); 61 | } 62 | 63 | @Transactional 64 | public void modify(Long id, PersonDto personDto) { 65 | Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new); 66 | 67 | if (!person.getName().equals(personDto.getName())) { 68 | throw new RenameIsNotPermittedException(); 69 | } 70 | 71 | person.set(personDto); 72 | 73 | personRepository.save(person); 74 | } 75 | 76 | @Transactional 77 | public void modify(Long id, String name) { 78 | Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new); 79 | 80 | person.setName(name); 81 | 82 | personRepository.save(person); 83 | } 84 | 85 | @Transactional 86 | public void delete(Long id) { 87 | Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new); 88 | 89 | person.setDeleted(true); 90 | 91 | personRepository.save(person); 92 | } 93 | } 94 | ``` 95 | 96 | ```java 97 | @ExtendWith(MockitoExtension.class) 98 | class PersonServiceTest { 99 | @InjectMocks 100 | private PersonService personService; 101 | @Mock 102 | private PersonRepository personRepository; 103 | 104 | @Test 105 | void getPeopleByName() { 106 | when(personRepository.findByName("martin")) 107 | .thenReturn(Lists.newArrayList(new Person("martin"))); 108 | 109 | List result = personService.getPeopleByName("martin"); 110 | 111 | assertThat(result.size()).isEqualTo(1); 112 | assertThat(result.get(0).getName()).isEqualTo("martin"); 113 | } 114 | 115 | @Test 116 | void getPerson() { 117 | when(personRepository.findById(1L)) 118 | .thenReturn(Optional.of(new Person("martin"))); 119 | 120 | Person person = personService.getPerson(1L); 121 | 122 | assertThat(person.getName()).isEqualTo("martin"); 123 | } 124 | 125 | @Test 126 | void getPersonIfNotFound() { 127 | when(personRepository.findById(1L)) 128 | .thenReturn(Optional.empty()); 129 | 130 | Person person = personService.getPerson(1L); 131 | 132 | assertThat(person).isNull(); 133 | } 134 | 135 | @Test 136 | void put() { 137 | personService.put(mockPersonDto()); 138 | 139 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeInserted())); 140 | } 141 | 142 | @Test 143 | void modifyIfPersonNotFound() { 144 | when(personRepository.findById(1L)) 145 | .thenReturn(Optional.empty()); 146 | 147 | assertThrows(PersonNotFoundException.class, () -> personService.modify(1L, mockPersonDto())); 148 | } 149 | 150 | @Test 151 | void modifyIfNameIsDifferent() { 152 | when(personRepository.findById((1L))) 153 | .thenReturn(Optional.of(new Person("tony"))); 154 | 155 | assertThrows(RenameIsNotPermittedException.class, () -> personService.modify(1L, mockPersonDto())); 156 | } 157 | 158 | @Test 159 | void modify() { 160 | when(personRepository.findById(1L)) 161 | .thenReturn(Optional.of(new Person("martin"))); 162 | 163 | personService.modify(1L, mockPersonDto()); 164 | 165 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeUpdated())); 166 | } 167 | 168 | @Test 169 | void modifyByNameIfPersonNotFound() { 170 | when(personRepository.findById(1L)) 171 | .thenReturn(Optional.empty()); 172 | 173 | assertThrows(PersonNotFoundException.class, () -> personService.modify(1L, "daniel")); 174 | } 175 | 176 | @Test 177 | void modifyByName() { 178 | when(personRepository.findById(1L)) 179 | .thenReturn(Optional.of(new Person("martin"))); 180 | 181 | personService.modify(1L, "daniel"); 182 | 183 | verify(personRepository, times(1)).save(argThat(new IsNameWillBeUpdated())); 184 | } 185 | 186 | @Test 187 | void deleteIfPersonNotFound() { 188 | when(personRepository.findById(1L)) 189 | .thenReturn(Optional.empty()); 190 | 191 | assertThrows(PersonNotFoundException.class, () -> personService.delete(1L)); 192 | } 193 | 194 | @Test 195 | void delete() { 196 | when(personRepository.findById(1L)) 197 | .thenReturn(Optional.of(new Person("martin"))); 198 | 199 | personService.delete(1L); 200 | 201 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeDeleted())); 202 | } 203 | 204 | private PersonDto mockPersonDto() { 205 | return PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 206 | } 207 | 208 | private static class IsPersonWillBeInserted implements ArgumentMatcher { 209 | @Override 210 | public boolean matches(Person person) { 211 | return equals(person.getName(), "martin") 212 | && equals(person.getHobby(), "programming") 213 | && equals(person.getAddress(), "판교") 214 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 215 | && equals(person.getJob(), "programmer") 216 | && equals(person.getPhoneNumber(), "010-1111-2222"); 217 | } 218 | 219 | private boolean equals(Object actual, Object expected) { 220 | return expected.equals(actual); 221 | } 222 | } 223 | 224 | private static class IsPersonWillBeUpdated implements ArgumentMatcher { 225 | @Override 226 | public boolean matches(Person person) { 227 | return equals(person.getName(), "martin") 228 | && equals(person.getHobby(), "programming") 229 | && equals(person.getAddress(), "판교") 230 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 231 | && equals(person.getJob(), "programmer") 232 | && equals(person.getPhoneNumber(), "010-1111-2222"); 233 | } 234 | 235 | private boolean equals(Object actual, Object expected) { 236 | return expected.equals(actual); 237 | } 238 | } 239 | 240 | private static class IsNameWillBeUpdated implements ArgumentMatcher { 241 | @Override 242 | public boolean matches(Person person) { 243 | return person.getName().equals("daniel"); 244 | } 245 | } 246 | 247 | private static class IsPersonWillBeDeleted implements ArgumentMatcher { 248 | @Override 249 | public boolean matches(Person person) { 250 | return person.isDeleted(); 251 | } 252 | } 253 | } 254 | ``` 255 | -------------------------------------------------------------------------------- /10-Exception/02-Exception-Handling-2.md: -------------------------------------------------------------------------------- 1 | # Exception Handling #2 2 | 3 | ## 이론 4 | 5 | * @ExceptionHandler 6 | * Controller에서 발생하는 오류를 처리하여 필요한 로그를 남기고, 응답을 커스마이징할 수 있도록 지원함 7 | 8 | ## 실전코드 9 | 10 | ```java 11 | @RequestMapping(value = "/api/person") 12 | @RestController 13 | @Slf4j 14 | public class PersonController { 15 | @Autowired 16 | private PersonService personService; 17 | 18 | @GetMapping("/{id}") 19 | public Person getPerson(@PathVariable Long id) { 20 | return personService.getPerson(id); 21 | } 22 | 23 | @PostMapping 24 | @ResponseStatus(HttpStatus.CREATED) 25 | public void postPerson(@RequestBody PersonDto personDto) { 26 | personService.put(personDto); 27 | } 28 | 29 | @PutMapping("/{id}") 30 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 31 | personService.modify(id, personDto); 32 | } 33 | 34 | @PatchMapping("/{id}") 35 | public void modifyPerson(@PathVariable Long id, String name) { 36 | personService.modify(id, name); 37 | } 38 | 39 | @DeleteMapping("/{id}") 40 | public void deletePerson(@PathVariable Long id) { 41 | personService.delete(id); 42 | } 43 | 44 | @ExceptionHandler(value = RenameIsNotPermittedException.class) 45 | public ResponseEntity handleRenameNoPermittedException(RenameIsNotPermittedException ex) { 46 | return new ResponseEntity<>(ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getMessage()), HttpStatus.BAD_REQUEST); 47 | } 48 | 49 | @ExceptionHandler(value = PersonNotFoundException.class) 50 | public ResponseEntity handlePersonNotFoundException(PersonNotFoundException ex) { 51 | return new ResponseEntity<>(ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getMessage()), HttpStatus.BAD_REQUEST); 52 | } 53 | 54 | @ExceptionHandler(value = RuntimeException.class) 55 | public ResponseEntity handleRuntimeException(RuntimeException ex) { 56 | log.error("서버오류 : {}", ex.getMessage(), ex); 57 | return new ResponseEntity<>(ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, "알 수 없는 서버 오류가 발생하였습니다"), HttpStatus.INTERNAL_SERVER_ERROR); 58 | } 59 | } 60 | ``` 61 | 62 | ```java 63 | @Data 64 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 65 | public class ErrorResponse { 66 | private int code; 67 | private String message; 68 | 69 | public static ErrorResponse of(HttpStatus httpStatus, String message) { 70 | return new ErrorResponse(httpStatus.value(), message); 71 | } 72 | } 73 | ``` 74 | 75 | ```java 76 | @Slf4j 77 | @SpringBootTest 78 | @Transactional 79 | class PersonControllerTest { 80 | @Autowired 81 | private PersonController personController; 82 | @Autowired 83 | private PersonRepository personRepository; 84 | @Autowired 85 | private ObjectMapper objectMapper; 86 | @Autowired 87 | private MappingJackson2HttpMessageConverter messageConverter; 88 | 89 | private MockMvc mockMvc; 90 | 91 | @BeforeEach 92 | void beforeEach() { 93 | mockMvc = MockMvcBuilders 94 | .standaloneSetup(personController) 95 | .setMessageConverters(messageConverter) 96 | .alwaysDo(print()) 97 | .build(); 98 | } 99 | 100 | @Test 101 | void getPerson() throws Exception { 102 | mockMvc.perform( 103 | MockMvcRequestBuilders.get("/api/person/1")) 104 | .andExpect(status().isOk()) 105 | .andExpect(jsonPath("$.name").value("martin")) 106 | .andExpect(jsonPath("$.hobby").isEmpty()) 107 | .andExpect(jsonPath("$.address").isEmpty()) 108 | .andExpect(jsonPath("$.birthday").value("1991-08-15")) 109 | .andExpect(jsonPath("$.job").isEmpty()) 110 | .andExpect(jsonPath("$.phoneNumber").isEmpty()) 111 | .andExpect(jsonPath("$.deleted").value(false)) 112 | .andExpect(jsonPath("$.age").isNumber()) 113 | .andExpect(jsonPath("$.birthdayToday").isBoolean()); 114 | } 115 | 116 | @Test 117 | void postPerson() throws Exception { 118 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 119 | 120 | mockMvc.perform( 121 | MockMvcRequestBuilders.post("/api/person") 122 | .contentType(MediaType.APPLICATION_JSON_UTF8) 123 | .content(toJsonString(dto))) 124 | .andExpect(status().isCreated()); 125 | 126 | Person result = personRepository.findAll(Sort.by(Direction.DESC, "id")).get(0); 127 | 128 | assertAll( 129 | () -> assertThat(result.getName()).isEqualTo("martin"), 130 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 131 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 132 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 133 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 134 | () -> assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 135 | ); 136 | } 137 | 138 | @Test 139 | void postPersonIfNameIsNull() throws Exception { 140 | PersonDto dto = new PersonDto(); 141 | 142 | mockMvc.perform( 143 | MockMvcRequestBuilders.post("/api/person") 144 | .contentType(MediaType.APPLICATION_JSON_UTF8) 145 | .content(toJsonString(dto))) 146 | .andExpect(jsonPath("$.code").value(500)) 147 | .andExpect(jsonPath("$.message").value("알 수 없는 서버 오류가 발생하였습니다")); 148 | } 149 | 150 | @Test 151 | void modifyPerson() throws Exception { 152 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 153 | 154 | mockMvc.perform( 155 | MockMvcRequestBuilders.put("/api/person/1") 156 | .contentType(MediaType.APPLICATION_JSON_UTF8) 157 | .content(toJsonString(dto))) 158 | .andExpect(status().isOk()); 159 | 160 | Person result = personRepository.findById(1L).get(); 161 | 162 | assertAll( 163 | () -> assertThat(result.getName()).isEqualTo("martin"), 164 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 165 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 166 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 167 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 168 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 169 | ); 170 | } 171 | 172 | @Test 173 | void modifyPersonIfNameIsDifferent() throws Exception { 174 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 175 | 176 | mockMvc.perform( 177 | MockMvcRequestBuilders.put("/api/person/1") 178 | .contentType(MediaType.APPLICATION_JSON_UTF8) 179 | .content(toJsonString(dto))) 180 | .andExpect(status().isBadRequest()) 181 | .andExpect(jsonPath("$.code").value(400)) 182 | .andExpect(jsonPath("$.message").value("이름 변경이 허용되지 않습니다")); 183 | } 184 | 185 | @Test 186 | void modifyPersonIfPersonNotFound() throws Exception { 187 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 188 | 189 | mockMvc.perform( 190 | MockMvcRequestBuilders.put("/api/person/10") 191 | .contentType(MediaType.APPLICATION_JSON_UTF8) 192 | .content(toJsonString(dto))) 193 | .andExpect(status().isBadRequest()) 194 | .andExpect(jsonPath("$.code").value(400)) 195 | .andExpect(jsonPath("$.message").value("Person Entity가 존재하지 않습니다")); 196 | } 197 | 198 | @Test 199 | void modifyName() throws Exception { 200 | mockMvc.perform( 201 | MockMvcRequestBuilders.patch("/api/person/1") 202 | .param("name", "martinModified")) 203 | .andExpect(status().isOk()); 204 | 205 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 206 | } 207 | 208 | @Test 209 | void deletePerson() throws Exception { 210 | mockMvc.perform( 211 | MockMvcRequestBuilders.delete("/api/person/1")) 212 | .andExpect(status().isOk()); 213 | 214 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 215 | } 216 | 217 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 218 | return objectMapper.writeValueAsString(personDto); 219 | } 220 | } 221 | ``` 222 | -------------------------------------------------------------------------------- /10-Exception/03-Exception-Handling-3.md: -------------------------------------------------------------------------------- 1 | # Exception Handling #3 2 | 3 | ## 이론 4 | 5 | * 전역 예외처리 6 | * @RestControllerAdvice와 @ExceptionHandler를 조합하여 전체 RestController의 예외에 대해서 처리할 수 있음 7 | * @RestControllerAdvice 8 | * RestController에 전역적으로 동작하는 AOP 어노테이션 9 | * @ResponseStatus 10 | * Controller에서 응답하는 Http Response Code를 지정할 수 있음 11 | 12 | ## 실전코드 13 | 14 | ```java 15 | @RestController 16 | public class HelloWorldController { 17 | @GetMapping(value = "/api/helloWorld") 18 | public String helloWorld() { 19 | return "HelloWorld"; 20 | } 21 | 22 | @GetMapping(value = "/api/helloException") 23 | public String helloException() { 24 | throw new RuntimeException("Hello RuntimeException"); 25 | } 26 | } 27 | ``` 28 | 29 | ```java 30 | @RequestMapping(value = "/api/person") 31 | @RestController 32 | @Slf4j 33 | public class PersonController { 34 | @Autowired 35 | private PersonService personService; 36 | 37 | @GetMapping("/{id}") 38 | public Person getPerson(@PathVariable Long id) { 39 | return personService.getPerson(id); 40 | } 41 | 42 | @PostMapping 43 | @ResponseStatus(HttpStatus.CREATED) 44 | public void postPerson(@RequestBody PersonDto personDto) { 45 | personService.put(personDto); 46 | } 47 | 48 | @PutMapping("/{id}") 49 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 50 | personService.modify(id, personDto); 51 | } 52 | 53 | @PatchMapping("/{id}") 54 | public void modifyPerson(@PathVariable Long id, String name) { 55 | personService.modify(id, name); 56 | } 57 | 58 | @DeleteMapping("/{id}") 59 | public void deletePerson(@PathVariable Long id) { 60 | personService.delete(id); 61 | } 62 | } 63 | ``` 64 | 65 | ```java 66 | @Slf4j 67 | @RestControllerAdvice 68 | public class GlobalExceptionHandler { 69 | @ExceptionHandler(RenameIsNotPermittedException.class) 70 | @ResponseStatus(HttpStatus.BAD_REQUEST) 71 | public ErrorResponse handleRenameNoPermittedException(RenameIsNotPermittedException ex) { 72 | return ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getMessage()); 73 | } 74 | 75 | @ExceptionHandler(PersonNotFoundException.class) 76 | @ResponseStatus(HttpStatus.BAD_REQUEST) 77 | public ErrorResponse handlePersonNotFoundException(PersonNotFoundException ex) { 78 | return ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getMessage()); 79 | } 80 | 81 | @ExceptionHandler(RuntimeException.class) 82 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 83 | public ErrorResponse handleRuntimeException(RuntimeException ex) { 84 | log.error("서버오류 : {}", ex.getMessage(), ex); 85 | return ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, "알 수 없는 서버 오류가 발생하였습니다"); 86 | } 87 | } 88 | ``` 89 | 90 | ```java 91 | @SpringBootTest 92 | class HelloWorldControllerTest { 93 | @Autowired 94 | private WebApplicationContext wac; 95 | 96 | private MockMvc mockMvc; 97 | 98 | @BeforeEach 99 | void beforeEach() { 100 | mockMvc = MockMvcBuilders 101 | .webAppContextSetup(wac) 102 | .alwaysDo(print()) 103 | .build(); 104 | } 105 | 106 | @Test 107 | void helloWorld() throws Exception { 108 | mockMvc.perform( 109 | MockMvcRequestBuilders.get("/api/helloWorld")) 110 | .andExpect(status().isOk()) 111 | .andExpect(MockMvcResultMatchers.content().string("HelloWorld")); 112 | } 113 | 114 | @Test 115 | void helloException() throws Exception { 116 | mockMvc.perform( 117 | MockMvcRequestBuilders.get("/api/helloException")) 118 | .andExpect(status().isInternalServerError()) 119 | .andExpect(jsonPath("$.code").value(500)) 120 | .andExpect(jsonPath("$.message").value("알 수 없는 서버 오류가 발생하였습니다")); 121 | } 122 | } 123 | ``` 124 | 125 | ```java 126 | @Slf4j 127 | @SpringBootTest 128 | @Transactional 129 | class PersonControllerTest { 130 | @Autowired 131 | private PersonRepository personRepository; 132 | @Autowired 133 | private ObjectMapper objectMapper; 134 | @Autowired 135 | private WebApplicationContext wac; 136 | 137 | private MockMvc mockMvc; 138 | 139 | @BeforeEach 140 | void beforeEach() { 141 | mockMvc = MockMvcBuilders 142 | .webAppContextSetup(wac) 143 | .alwaysDo(print()) 144 | .build(); 145 | } 146 | 147 | @Test 148 | void getPerson() throws Exception { 149 | mockMvc.perform( 150 | MockMvcRequestBuilders.get("/api/person/1")) 151 | .andExpect(status().isOk()) 152 | .andExpect(jsonPath("$.name").value("martin")) 153 | .andExpect(jsonPath("$.hobby").isEmpty()) 154 | .andExpect(jsonPath("$.address").isEmpty()) 155 | .andExpect(jsonPath("$.birthday").value("1991-08-15")) 156 | .andExpect(jsonPath("$.job").isEmpty()) 157 | .andExpect(jsonPath("$.phoneNumber").isEmpty()) 158 | .andExpect(jsonPath("$.deleted").value(false)) 159 | .andExpect(jsonPath("$.age").isNumber()) 160 | .andExpect(jsonPath("$.birthdayToday").isBoolean()); 161 | } 162 | 163 | @Test 164 | void postPerson() throws Exception { 165 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 166 | 167 | mockMvc.perform( 168 | MockMvcRequestBuilders.post("/api/person") 169 | .contentType(MediaType.APPLICATION_JSON_UTF8) 170 | .content(toJsonString(dto))) 171 | .andExpect(status().isCreated()); 172 | 173 | Person result = personRepository.findAll(Sort.by(Direction.DESC, "id")).get(0); 174 | 175 | assertAll( 176 | () -> assertThat(result.getName()).isEqualTo("martin"), 177 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 178 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 179 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 180 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 181 | () -> assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 182 | ); 183 | } 184 | 185 | @Test 186 | void postPersonIfNameIsNull() throws Exception { 187 | PersonDto dto = new PersonDto(); 188 | 189 | mockMvc.perform( 190 | MockMvcRequestBuilders.post("/api/person") 191 | .contentType(MediaType.APPLICATION_JSON_UTF8) 192 | .content(toJsonString(dto))) 193 | .andExpect(jsonPath("$.code").value(500)) 194 | .andExpect(jsonPath("$.message").value("알 수 없는 서버 오류가 발생하였습니다")); 195 | } 196 | 197 | @Test 198 | void modifyPerson() throws Exception { 199 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 200 | 201 | mockMvc.perform( 202 | MockMvcRequestBuilders.put("/api/person/1") 203 | .contentType(MediaType.APPLICATION_JSON_UTF8) 204 | .content(toJsonString(dto))) 205 | .andExpect(status().isOk()); 206 | 207 | Person result = personRepository.findById(1L).get(); 208 | 209 | assertAll( 210 | () -> assertThat(result.getName()).isEqualTo("martin"), 211 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 212 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 213 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 214 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 215 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 216 | ); 217 | } 218 | 219 | @Test 220 | void modifyPersonIfNameIsDifferent() throws Exception { 221 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 222 | 223 | mockMvc.perform( 224 | MockMvcRequestBuilders.put("/api/person/1") 225 | .contentType(MediaType.APPLICATION_JSON_UTF8) 226 | .content(toJsonString(dto))) 227 | .andExpect(status().isBadRequest()) 228 | .andExpect(jsonPath("$.code").value(400)) 229 | .andExpect(jsonPath("$.message").value("이름 변경이 허용되지 않습니다")); 230 | } 231 | 232 | @Test 233 | void modifyPersonIfPersonNotFound() throws Exception { 234 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 235 | 236 | mockMvc.perform( 237 | MockMvcRequestBuilders.put("/api/person/10") 238 | .contentType(MediaType.APPLICATION_JSON_UTF8) 239 | .content(toJsonString(dto))) 240 | .andExpect(status().isBadRequest()) 241 | .andExpect(jsonPath("$.code").value(400)) 242 | .andExpect(jsonPath("$.message").value("Person Entity가 존재하지 않습니다")); 243 | } 244 | 245 | @Test 246 | void modifyName() throws Exception { 247 | mockMvc.perform( 248 | MockMvcRequestBuilders.patch("/api/person/1") 249 | .param("name", "martinModified")) 250 | .andExpect(status().isOk()); 251 | 252 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 253 | } 254 | 255 | @Test 256 | void deletePerson() throws Exception { 257 | mockMvc.perform( 258 | MockMvcRequestBuilders.delete("/api/person/1")) 259 | .andExpect(status().isOk()); 260 | 261 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 262 | } 263 | 264 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 265 | return objectMapper.writeValueAsString(personDto); 266 | } 267 | } 268 | ``` 269 | -------------------------------------------------------------------------------- /11-Validator/01-Parameter-Validator.md: -------------------------------------------------------------------------------- 1 | # Parameter Validator 2 | 3 | ## 이론 4 | 5 | * Parameter Validator란? 6 | * 내부 로직에서 처리할 수 없는 입력값을 사전에 검증하고, 필요한 오류 및 메시지로 매핑해서 응답하는 것 7 | * @NotEmpty 8 | * 해당 값이 null이거나 empty string("")에 대해서 검증하는 어노테이션 9 | * 속성 10 | * message : 해당 validation을 통과하지 못할 경우 표시할 오류 메시지 11 | * @NotBlank 12 | * 해당 값이 null이거나 empty string("") 및 공백 문자열(" ")까지 검증하는 어노테이션 13 | * @Valid 14 | * 일반적으로 validator는 해당 인자에 대해서만 검증하므로, 검증 대상이 객체이면 recursive하게 검증할 수 있도록 표시해주는 어노테이션 15 | 16 | ## 실전코드 17 | 18 | ```java 19 | @Data 20 | @NoArgsConstructor 21 | @AllArgsConstructor(staticName = "of") 22 | public class PersonDto { 23 | @NotBlank(message = "이름은 필수값입니다") 24 | private String name; 25 | private String hobby; 26 | private String address; 27 | private LocalDate birthday; 28 | private String job; 29 | private String phoneNumber; 30 | } 31 | ``` 32 | 33 | ```java 34 | @RequestMapping(value = "/api/person") 35 | @RestController 36 | @Slf4j 37 | public class PersonController { 38 | @Autowired 39 | private PersonService personService; 40 | 41 | @GetMapping("/{id}") 42 | public Person getPerson(@PathVariable Long id) { 43 | return personService.getPerson(id); 44 | } 45 | 46 | @PostMapping 47 | @ResponseStatus(HttpStatus.CREATED) 48 | public void postPerson(@RequestBody @Valid PersonDto personDto) { 49 | personService.put(personDto); 50 | } 51 | 52 | @PutMapping("/{id}") 53 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 54 | personService.modify(id, personDto); 55 | } 56 | 57 | @PatchMapping("/{id}") 58 | public void modifyPerson(@PathVariable Long id, String name) { 59 | personService.modify(id, name); 60 | } 61 | 62 | @DeleteMapping("/{id}") 63 | public void deletePerson(@PathVariable Long id) { 64 | personService.delete(id); 65 | } 66 | } 67 | ``` 68 | 69 | ```java 70 | @Data 71 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 72 | public class ErrorResponse { 73 | private int code; 74 | private String message; 75 | 76 | public static ErrorResponse of(HttpStatus httpStatus, String message) { 77 | return new ErrorResponse(httpStatus.value(), message); 78 | } 79 | 80 | public static ErrorResponse of(HttpStatus httpStatus, FieldError fieldError) { 81 | if (fieldError == null) { 82 | return new ErrorResponse(httpStatus.value(), "invalid params"); 83 | } else { 84 | return new ErrorResponse(httpStatus.value(), fieldError.getDefaultMessage()); 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | ```java 91 | @Slf4j 92 | @RestControllerAdvice 93 | public class GlobalExceptionHandler { 94 | @ExceptionHandler(RenameIsNotPermittedException.class) 95 | @ResponseStatus(HttpStatus.BAD_REQUEST) 96 | public ErrorResponse handleRenameNoPermittedException(RenameIsNotPermittedException ex) { 97 | return ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getMessage()); 98 | } 99 | 100 | @ExceptionHandler(PersonNotFoundException.class) 101 | @ResponseStatus(HttpStatus.BAD_REQUEST) 102 | public ErrorResponse handlePersonNotFoundException(PersonNotFoundException ex) { 103 | return ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getMessage()); 104 | } 105 | 106 | @ExceptionHandler(MethodArgumentNotValidException.class) 107 | @ResponseStatus(HttpStatus.BAD_REQUEST) 108 | public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { 109 | return ErrorResponse.of(HttpStatus.BAD_REQUEST, ex.getBindingResult().getFieldError().getDefaultMessage()); 110 | } 111 | 112 | @ExceptionHandler(RuntimeException.class) 113 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 114 | public ErrorResponse handleRuntimeException(RuntimeException ex) { 115 | log.error("서버오류 : {}", ex.getMessage(), ex); 116 | return ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, "알 수 없는 서버 오류가 발생하였습니다"); 117 | } 118 | } 119 | ``` 120 | 121 | ```java 122 | @Slf4j 123 | @SpringBootTest 124 | @Transactional 125 | class PersonControllerTest { 126 | @Autowired 127 | private PersonRepository personRepository; 128 | @Autowired 129 | private ObjectMapper objectMapper; 130 | @Autowired 131 | private WebApplicationContext wac; 132 | 133 | private MockMvc mockMvc; 134 | 135 | @BeforeEach 136 | void beforeEach() { 137 | mockMvc = MockMvcBuilders 138 | .webAppContextSetup(wac) 139 | .alwaysDo(print()) 140 | .build(); 141 | } 142 | 143 | @Test 144 | void getPerson() throws Exception { 145 | mockMvc.perform( 146 | MockMvcRequestBuilders.get("/api/person/1")) 147 | .andExpect(status().isOk()) 148 | .andExpect(jsonPath("$.name").value("martin")) 149 | .andExpect(jsonPath("$.hobby").isEmpty()) 150 | .andExpect(jsonPath("$.address").isEmpty()) 151 | .andExpect(jsonPath("$.birthday").value("1991-08-15")) 152 | .andExpect(jsonPath("$.job").isEmpty()) 153 | .andExpect(jsonPath("$.phoneNumber").isEmpty()) 154 | .andExpect(jsonPath("$.deleted").value(false)) 155 | .andExpect(jsonPath("$.age").isNumber()) 156 | .andExpect(jsonPath("$.birthdayToday").isBoolean()); 157 | } 158 | 159 | @Test 160 | void postPerson() throws Exception { 161 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 162 | 163 | mockMvc.perform( 164 | MockMvcRequestBuilders.post("/api/person") 165 | .contentType(MediaType.APPLICATION_JSON_UTF8) 166 | .content(toJsonString(dto))) 167 | .andExpect(status().isCreated()); 168 | 169 | Person result = personRepository.findAll(Sort.by(Direction.DESC, "id")).get(0); 170 | 171 | assertAll( 172 | () -> assertThat(result.getName()).isEqualTo("martin"), 173 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 174 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 175 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 176 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 177 | () -> assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 178 | ); 179 | } 180 | 181 | @Test 182 | void postPersonIfNameIsNull() throws Exception { 183 | PersonDto dto = new PersonDto(); 184 | 185 | mockMvc.perform( 186 | MockMvcRequestBuilders.post("/api/person") 187 | .contentType(MediaType.APPLICATION_JSON_UTF8) 188 | .content(toJsonString(dto))) 189 | .andExpect(status().isBadRequest()) 190 | .andExpect(jsonPath("$.code").value(400)) 191 | .andExpect(jsonPath("$.message").value("이름은 필수값입니다")); 192 | } 193 | 194 | @Test 195 | void postPersonIfNameIsEmptyString() throws Exception { 196 | PersonDto dto = new PersonDto(); 197 | dto.setName(""); 198 | 199 | mockMvc.perform( 200 | MockMvcRequestBuilders.post("/api/person") 201 | .contentType(MediaType.APPLICATION_JSON_UTF8) 202 | .content(toJsonString(dto))) 203 | .andExpect(status().isBadRequest()) 204 | .andExpect(jsonPath("$.code").value(400)) 205 | .andExpect(jsonPath("$.message").value("이름은 필수값입니다")); 206 | } 207 | 208 | @Test 209 | void postPersonIfNameIsBlankString() throws Exception { 210 | PersonDto dto = new PersonDto(); 211 | dto.setName(" "); 212 | 213 | mockMvc.perform( 214 | MockMvcRequestBuilders.post("/api/person") 215 | .contentType(MediaType.APPLICATION_JSON_UTF8) 216 | .content(toJsonString(dto))) 217 | .andExpect(status().isBadRequest()) 218 | .andExpect(jsonPath("$.code").value(400)) 219 | .andExpect(jsonPath("$.message").value("이름은 필수값입니다")); 220 | } 221 | 222 | @Test 223 | void modifyPerson() throws Exception { 224 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 225 | 226 | mockMvc.perform( 227 | MockMvcRequestBuilders.put("/api/person/1") 228 | .contentType(MediaType.APPLICATION_JSON_UTF8) 229 | .content(toJsonString(dto))) 230 | .andExpect(status().isOk()); 231 | 232 | Person result = personRepository.findById(1L).get(); 233 | 234 | assertAll( 235 | () -> assertThat(result.getName()).isEqualTo("martin"), 236 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 237 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 238 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 239 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 240 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 241 | ); 242 | } 243 | 244 | @Test 245 | void modifyPersonIfNameIsDifferent() throws Exception { 246 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 247 | 248 | mockMvc.perform( 249 | MockMvcRequestBuilders.put("/api/person/1") 250 | .contentType(MediaType.APPLICATION_JSON_UTF8) 251 | .content(toJsonString(dto))) 252 | .andExpect(status().isBadRequest()) 253 | .andExpect(jsonPath("$.code").value(400)) 254 | .andExpect(jsonPath("$.message").value("이름 변경이 허용되지 않습니다")); 255 | } 256 | 257 | @Test 258 | void modifyPersonIfPersonNotFound() throws Exception { 259 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 260 | 261 | mockMvc.perform( 262 | MockMvcRequestBuilders.put("/api/person/10") 263 | .contentType(MediaType.APPLICATION_JSON_UTF8) 264 | .content(toJsonString(dto))) 265 | .andExpect(status().isBadRequest()) 266 | .andExpect(jsonPath("$.code").value(400)) 267 | .andExpect(jsonPath("$.message").value("Person Entity가 존재하지 않습니다")); 268 | } 269 | 270 | @Test 271 | void modifyName() throws Exception { 272 | mockMvc.perform( 273 | MockMvcRequestBuilders.patch("/api/person/1") 274 | .param("name", "martinModified")) 275 | .andExpect(status().isOk()); 276 | 277 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 278 | } 279 | 280 | @Test 281 | void deletePerson() throws Exception { 282 | mockMvc.perform( 283 | MockMvcRequestBuilders.delete("/api/person/1")) 284 | .andExpect(status().isOk()); 285 | 286 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 287 | } 288 | 289 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 290 | return objectMapper.writeValueAsString(personDto); 291 | } 292 | } 293 | ``` 294 | -------------------------------------------------------------------------------- /12-Paging/01-List-Api-And-Paging.md: -------------------------------------------------------------------------------- 1 | # Parameter Validator 2 | 3 | ## 이론 4 | 5 | * Pageable 6 | * JPA에서 정의한 Paging을 위한 인터페이스 7 | * 속성 8 | * content 9 | * totalPages 10 | * totalElements 11 | * numberOfElements 12 | * PageRequest 13 | * Pageable 인터페이스를 구현한 구현체 14 | * @PageableDefault 15 | * API에서 페이징을 위한 파라미터가 존재하지 않을 때, 페이징을 위한 기본값을 제공 16 | 17 | ## 실전코드 18 | 19 | ```java 20 | @RequestMapping(value = "/api/person") 21 | @RestController 22 | @Slf4j 23 | public class PersonController { 24 | @Autowired 25 | private PersonService personService; 26 | 27 | @GetMapping 28 | public Page getAll(@PageableDefault Pageable pageable) { 29 | return personService.getAll(pageable); 30 | } 31 | 32 | @GetMapping("/{id}") 33 | public Person getPerson(@PathVariable Long id) { 34 | return personService.getPerson(id); 35 | } 36 | 37 | @PostMapping 38 | @ResponseStatus(HttpStatus.CREATED) 39 | public void postPerson(@RequestBody @Valid PersonDto personDto) { 40 | personService.put(personDto); 41 | } 42 | 43 | @PutMapping("/{id}") 44 | public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto personDto) { 45 | personService.modify(id, personDto); 46 | } 47 | 48 | @PatchMapping("/{id}") 49 | public void modifyPerson(@PathVariable Long id, String name) { 50 | personService.modify(id, name); 51 | } 52 | 53 | @DeleteMapping("/{id}") 54 | public void deletePerson(@PathVariable Long id) { 55 | personService.delete(id); 56 | } 57 | } 58 | ``` 59 | 60 | ```java 61 | @Service 62 | @Slf4j 63 | public class PersonService { 64 | @Autowired 65 | private PersonRepository personRepository; 66 | 67 | public Page getAll(Pageable pageable) { 68 | return personRepository.findAll(pageable); 69 | } 70 | 71 | public List getPeopleByName(String name) { 72 | return personRepository.findByName(name); 73 | } 74 | 75 | @Transactional(readOnly = true) 76 | public Person getPerson(Long id) { 77 | return personRepository.findById(id).orElse(null); 78 | } 79 | 80 | @Transactional 81 | public void put(PersonDto personDto) { 82 | Person person = new Person(); 83 | person.set(personDto); 84 | person.setName(personDto.getName()); 85 | 86 | personRepository.save(person); 87 | } 88 | 89 | @Transactional 90 | public void modify(Long id, PersonDto personDto) { 91 | Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new); 92 | 93 | if (!person.getName().equals(personDto.getName())) { 94 | throw new RenameIsNotPermittedException(); 95 | } 96 | 97 | person.set(personDto); 98 | 99 | personRepository.save(person); 100 | } 101 | 102 | @Transactional 103 | public void modify(Long id, String name) { 104 | Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new); 105 | 106 | person.setName(name); 107 | 108 | personRepository.save(person); 109 | } 110 | 111 | @Transactional 112 | public void delete(Long id) { 113 | Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new); 114 | 115 | person.setDeleted(true); 116 | 117 | personRepository.save(person); 118 | } 119 | } 120 | ``` 121 | 122 | ```java 123 | @Slf4j 124 | @SpringBootTest 125 | @Transactional 126 | class PersonControllerTest { 127 | @Autowired 128 | private PersonRepository personRepository; 129 | @Autowired 130 | private ObjectMapper objectMapper; 131 | @Autowired 132 | private WebApplicationContext wac; 133 | 134 | private MockMvc mockMvc; 135 | 136 | @BeforeEach 137 | void beforeEach() { 138 | mockMvc = MockMvcBuilders 139 | .webAppContextSetup(wac) 140 | .alwaysDo(print()) 141 | .build(); 142 | } 143 | 144 | @Test 145 | void getAll() throws Exception { 146 | mockMvc.perform( 147 | MockMvcRequestBuilders.get("/api/person") 148 | .param("page", "1") 149 | .param("size", "2")) 150 | .andExpect(status().isOk()) 151 | .andExpect(jsonPath("$.totalPages").value(3)) 152 | .andExpect(jsonPath("$.totalElements").value(6)) 153 | .andExpect(jsonPath("$.numberOfElements").value(2)) 154 | .andExpect(jsonPath("$.content.[0].name").value("dennis")) 155 | .andExpect(jsonPath("$.content.[1].name").value("sophia")); 156 | } 157 | 158 | @Test 159 | void getPerson() throws Exception { 160 | mockMvc.perform( 161 | MockMvcRequestBuilders.get("/api/person/1")) 162 | .andExpect(status().isOk()) 163 | .andExpect(jsonPath("$.name").value("martin")) 164 | .andExpect(jsonPath("$.hobby").isEmpty()) 165 | .andExpect(jsonPath("$.address").isEmpty()) 166 | .andExpect(jsonPath("$.birthday").value("1991-08-15")) 167 | .andExpect(jsonPath("$.job").isEmpty()) 168 | .andExpect(jsonPath("$.phoneNumber").isEmpty()) 169 | .andExpect(jsonPath("$.deleted").value(false)) 170 | .andExpect(jsonPath("$.age").isNumber()) 171 | .andExpect(jsonPath("$.birthdayToday").isBoolean()); 172 | } 173 | 174 | @Test 175 | void postPerson() throws Exception { 176 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 177 | 178 | mockMvc.perform( 179 | MockMvcRequestBuilders.post("/api/person") 180 | .contentType(MediaType.APPLICATION_JSON_UTF8) 181 | .content(toJsonString(dto))) 182 | .andExpect(status().isCreated()); 183 | 184 | Person result = personRepository.findAll(Sort.by(Direction.DESC, "id")).get(0); 185 | 186 | assertAll( 187 | () -> assertThat(result.getName()).isEqualTo("martin"), 188 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 189 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 190 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 191 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 192 | () -> assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 193 | ); 194 | } 195 | 196 | @Test 197 | void postPersonIfNameIsNull() throws Exception { 198 | PersonDto dto = new PersonDto(); 199 | 200 | mockMvc.perform( 201 | MockMvcRequestBuilders.post("/api/person") 202 | .contentType(MediaType.APPLICATION_JSON_UTF8) 203 | .content(toJsonString(dto))) 204 | .andExpect(status().isBadRequest()) 205 | .andExpect(jsonPath("$.code").value(400)) 206 | .andExpect(jsonPath("$.message").value("이름은 필수값입니다")); 207 | } 208 | 209 | @Test 210 | void postPersonIfNameIsEmptyString() throws Exception { 211 | PersonDto dto = new PersonDto(); 212 | dto.setName(""); 213 | 214 | mockMvc.perform( 215 | MockMvcRequestBuilders.post("/api/person") 216 | .contentType(MediaType.APPLICATION_JSON_UTF8) 217 | .content(toJsonString(dto))) 218 | .andExpect(status().isBadRequest()) 219 | .andExpect(jsonPath("$.code").value(400)) 220 | .andExpect(jsonPath("$.message").value("이름은 필수값입니다")); 221 | } 222 | 223 | @Test 224 | void postPersonIfNameIsBlankString() throws Exception { 225 | PersonDto dto = new PersonDto(); 226 | dto.setName(" "); 227 | 228 | mockMvc.perform( 229 | MockMvcRequestBuilders.post("/api/person") 230 | .contentType(MediaType.APPLICATION_JSON_UTF8) 231 | .content(toJsonString(dto))) 232 | .andExpect(status().isBadRequest()) 233 | .andExpect(jsonPath("$.code").value(400)) 234 | .andExpect(jsonPath("$.message").value("이름은 필수값입니다")); 235 | } 236 | 237 | @Test 238 | void modifyPerson() throws Exception { 239 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 240 | 241 | mockMvc.perform( 242 | MockMvcRequestBuilders.put("/api/person/1") 243 | .contentType(MediaType.APPLICATION_JSON_UTF8) 244 | .content(toJsonString(dto))) 245 | .andExpect(status().isOk()); 246 | 247 | Person result = personRepository.findById(1L).get(); 248 | 249 | assertAll( 250 | () -> assertThat(result.getName()).isEqualTo("martin"), 251 | () -> assertThat(result.getHobby()).isEqualTo("programming"), 252 | () -> assertThat(result.getAddress()).isEqualTo("판교"), 253 | () -> assertThat(result.getBirthday()).isEqualTo(Birthday.of(LocalDate.now())), 254 | () -> assertThat(result.getJob()).isEqualTo("programmer"), 255 | () ->assertThat(result.getPhoneNumber()).isEqualTo("010-1111-2222") 256 | ); 257 | } 258 | 259 | @Test 260 | void modifyPersonIfNameIsDifferent() throws Exception { 261 | PersonDto dto = PersonDto.of("james", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 262 | 263 | mockMvc.perform( 264 | MockMvcRequestBuilders.put("/api/person/1") 265 | .contentType(MediaType.APPLICATION_JSON_UTF8) 266 | .content(toJsonString(dto))) 267 | .andExpect(status().isBadRequest()) 268 | .andExpect(jsonPath("$.code").value(400)) 269 | .andExpect(jsonPath("$.message").value("이름 변경이 허용되지 않습니다")); 270 | } 271 | 272 | @Test 273 | void modifyPersonIfPersonNotFound() throws Exception { 274 | PersonDto dto = PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 275 | 276 | mockMvc.perform( 277 | MockMvcRequestBuilders.put("/api/person/10") 278 | .contentType(MediaType.APPLICATION_JSON_UTF8) 279 | .content(toJsonString(dto))) 280 | .andExpect(status().isBadRequest()) 281 | .andExpect(jsonPath("$.code").value(400)) 282 | .andExpect(jsonPath("$.message").value("Person Entity가 존재하지 않습니다")); 283 | } 284 | 285 | @Test 286 | void modifyName() throws Exception { 287 | mockMvc.perform( 288 | MockMvcRequestBuilders.patch("/api/person/1") 289 | .param("name", "martinModified")) 290 | .andExpect(status().isOk()); 291 | 292 | assertThat(personRepository.findById(1L).get().getName()).isEqualTo("martinModified"); 293 | } 294 | 295 | @Test 296 | void deletePerson() throws Exception { 297 | mockMvc.perform( 298 | MockMvcRequestBuilders.delete("/api/person/1")) 299 | .andExpect(status().isOk()); 300 | 301 | assertTrue(personRepository.findPeopleDeleted().stream().anyMatch(person -> person.getId().equals(1L))); 302 | } 303 | 304 | private String toJsonString(PersonDto personDto) throws JsonProcessingException { 305 | return objectMapper.writeValueAsString(personDto); 306 | } 307 | } 308 | ``` 309 | 310 | ```java 311 | @ExtendWith(MockitoExtension.class) 312 | class PersonServiceTest { 313 | @InjectMocks 314 | private PersonService personService; 315 | @Mock 316 | private PersonRepository personRepository; 317 | 318 | @Test 319 | void getAll() { 320 | when(personRepository.findAll(any(Pageable.class))) 321 | .thenReturn(new PageImpl<>(Lists.newArrayList(new Person("martin"), new Person("dennis"), new Person("tony")))); 322 | 323 | Page result = personService.getAll(PageRequest.of(0, 3)); 324 | 325 | assertThat(result.getNumberOfElements()).isEqualTo(3); 326 | assertThat(result.getContent().get(0).getName()).isEqualTo("martin"); 327 | assertThat(result.getContent().get(1).getName()).isEqualTo("dennis"); 328 | assertThat(result.getContent().get(2).getName()).isEqualTo("tony"); 329 | } 330 | 331 | @Test 332 | void getPeopleByName() { 333 | when(personRepository.findByName("martin")) 334 | .thenReturn(Lists.newArrayList(new Person("martin"))); 335 | 336 | List result = personService.getPeopleByName("martin"); 337 | 338 | assertThat(result.size()).isEqualTo(1); 339 | assertThat(result.get(0).getName()).isEqualTo("martin"); 340 | } 341 | 342 | @Test 343 | void getPerson() { 344 | when(personRepository.findById(1L)) 345 | .thenReturn(Optional.of(new Person("martin"))); 346 | 347 | Person person = personService.getPerson(1L); 348 | 349 | assertThat(person.getName()).isEqualTo("martin"); 350 | } 351 | 352 | @Test 353 | void getPersonIfNotFound() { 354 | when(personRepository.findById(1L)) 355 | .thenReturn(Optional.empty()); 356 | 357 | Person person = personService.getPerson(1L); 358 | 359 | assertThat(person).isNull(); 360 | } 361 | 362 | @Test 363 | void put() { 364 | personService.put(mockPersonDto()); 365 | 366 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeInserted())); 367 | } 368 | 369 | @Test 370 | void modifyIfPersonNotFound() { 371 | when(personRepository.findById(1L)) 372 | .thenReturn(Optional.empty()); 373 | 374 | assertThrows(PersonNotFoundException.class, () -> personService.modify(1L, mockPersonDto())); 375 | } 376 | 377 | @Test 378 | void modifyIfNameIsDifferent() { 379 | when(personRepository.findById((1L))) 380 | .thenReturn(Optional.of(new Person("tony"))); 381 | 382 | assertThrows(RenameIsNotPermittedException.class, () -> personService.modify(1L, mockPersonDto())); 383 | } 384 | 385 | @Test 386 | void modify() { 387 | when(personRepository.findById(1L)) 388 | .thenReturn(Optional.of(new Person("martin"))); 389 | 390 | personService.modify(1L, mockPersonDto()); 391 | 392 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeUpdated())); 393 | } 394 | 395 | @Test 396 | void modifyByNameIfPersonNotFound() { 397 | when(personRepository.findById(1L)) 398 | .thenReturn(Optional.empty()); 399 | 400 | assertThrows(PersonNotFoundException.class, () -> personService.modify(1L, "daniel")); 401 | } 402 | 403 | @Test 404 | void modifyByName() { 405 | when(personRepository.findById(1L)) 406 | .thenReturn(Optional.of(new Person("martin"))); 407 | 408 | personService.modify(1L, "daniel"); 409 | 410 | verify(personRepository, times(1)).save(argThat(new IsNameWillBeUpdated())); 411 | } 412 | 413 | @Test 414 | void deleteIfPersonNotFound() { 415 | when(personRepository.findById(1L)) 416 | .thenReturn(Optional.empty()); 417 | 418 | assertThrows(PersonNotFoundException.class, () -> personService.delete(1L)); 419 | } 420 | 421 | @Test 422 | void delete() { 423 | when(personRepository.findById(1L)) 424 | .thenReturn(Optional.of(new Person("martin"))); 425 | 426 | personService.delete(1L); 427 | 428 | verify(personRepository, times(1)).save(argThat(new IsPersonWillBeDeleted())); 429 | } 430 | 431 | private PersonDto mockPersonDto() { 432 | return PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222"); 433 | } 434 | 435 | private static class IsPersonWillBeInserted implements ArgumentMatcher { 436 | @Override 437 | public boolean matches(Person person) { 438 | return equals(person.getName(), "martin") 439 | && equals(person.getHobby(), "programming") 440 | && equals(person.getAddress(), "판교") 441 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 442 | && equals(person.getJob(), "programmer") 443 | && equals(person.getPhoneNumber(), "010-1111-2222"); 444 | } 445 | 446 | private boolean equals(Object actual, Object expected) { 447 | return expected.equals(actual); 448 | } 449 | } 450 | 451 | private static class IsPersonWillBeUpdated implements ArgumentMatcher { 452 | @Override 453 | public boolean matches(Person person) { 454 | return equals(person.getName(), "martin") 455 | && equals(person.getHobby(), "programming") 456 | && equals(person.getAddress(), "판교") 457 | && equals(person.getBirthday(), Birthday.of(LocalDate.now())) 458 | && equals(person.getJob(), "programmer") 459 | && equals(person.getPhoneNumber(), "010-1111-2222"); 460 | } 461 | 462 | private boolean equals(Object actual, Object expected) { 463 | return expected.equals(actual); 464 | } 465 | } 466 | 467 | private static class IsNameWillBeUpdated implements ArgumentMatcher { 468 | @Override 469 | public boolean matches(Person person) { 470 | return person.getName().equals("daniel"); 471 | } 472 | } 473 | 474 | private static class IsPersonWillBeDeleted implements ArgumentMatcher { 475 | @Override 476 | public boolean matches(Person person) { 477 | return person.isDeleted(); 478 | } 479 | } 480 | } 481 | ``` 482 | -------------------------------------------------------------------------------- /13-Summary/01-SpringBoot-Project-학습정리.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | ## SpringBoot Project 학습정리 4 | 5 | ### SpringBoot 특성 6 | 7 | * 스프링의 생산성 8 | * Coding By Convention 활용 (CoC : Convention over Configuration) 9 | 10 | ### 학습했던 내용 정리 11 | 12 | * 스프링부트 프로젝트 생성 13 | * Gradle을 이용한 의존성 관리 14 | * Iteration(반복주기) 개발로 2-cycle 개발 진행 15 | 16 | #### 1-cycle 내용정리 17 | 18 | * JPA 19 | * Entity 생성 20 | * @OneToOne Relation 21 | * CascadeType 22 | * FetchType 23 | * Optional, orphanRemoval 24 | * QueryMethod 25 | * @Embedded 26 | * @Valid 27 | * @Query 28 | * @Where (for soft-delete) 29 | * Data.sql 30 | 31 | * SpringMvc 32 | * @GetMapping 33 | * @PostMapping 34 | * @PutMapping 35 | * @PatchMapping 36 | * @DeleteMapping 37 | * @PathVariable 38 | * @RequestBody 39 | 40 | * Lombok 41 | * @Getter 42 | * @Setter 43 | * @ToString 44 | * @Constructor 45 | * @EqualsAndHashCode 46 | * @Data 47 | 48 | * SpringTest 49 | 50 | * Java8 51 | * Stream 52 | * Fileter 53 | * Map 54 | 55 | #### 2-cycle 내용정리 56 | 57 | * SpringMvc 58 | * CustomJsonSerializer 59 | 60 | * SpringTest 61 | * MockMvc Test 62 | * Matcher 63 | * Junit5 64 | 65 | * MockTest 66 | * Mockito 67 | * CustomArgumentMatcher 68 | 69 | * Exception Handling 70 | * CustomException 71 | * ExceptionalHandler 72 | * GlobalExceptionHandler 73 | 74 | * Parameter Validator 75 | * @NotEmpty 76 | * @NotBlank 77 | * @Valid 78 | 79 | * Paging 80 | * Pageable 81 | * Page 82 | 83 | ### 추가로 학습할 것 84 | 85 | * FrontEnd 개발 86 | * Web 87 | * VueJs 88 | * ReactJs 89 | * App 90 | * Android App 91 | * IOS App 92 | 93 | * DB 연동 94 | * MySQL 95 | * MongDB 96 | 97 | * Spring(Boot)의 중급활용 98 | * 추가적인 설정 99 | * Customizing 설정 100 | 101 | * JPA 중급활용 102 | * 다양한 Relation 103 | * QueryDSL 104 | * Jooq 105 | 106 | * 로직의 확장 107 | * 추가적인 스펙 108 | 109 | ```java 110 | @Entity 111 | @NoArgsConstructor 112 | @AllArgsConstructor 113 | @RequiredArgsConstructor 114 | @Data 115 | public class Group { 116 | @Id 117 | @GeneratedValue(strategy = GenerationType.IDENTITY) 118 | private Long id; 119 | 120 | private String description; 121 | 122 | @OneToMany 123 | private List personList; 124 | } 125 | ``` 126 | 127 | ```java 128 | @RequestMapping(value = "/api/group") 129 | @RestController 130 | public class GroupController { 131 | @GetMapping 132 | public List