├── .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 | 
8 | 
9 | 
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