├── 10 ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── hanbit │ │ │ └── product │ │ │ └── management │ │ │ ├── Application.java │ │ │ ├── application │ │ │ ├── SimpleProductService.java │ │ │ └── ValidationService.java │ │ │ ├── domain │ │ │ ├── EntityNotFoundException.java │ │ │ └── Product.java │ │ │ ├── infrastructure │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── ErrorMessage.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── ProductController.java │ │ │ └── ProductDto.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── hanbit │ └── product │ └── management │ └── ApplicationTests.java ├── 11 ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── hanbit │ │ │ └── product │ │ │ └── management │ │ │ ├── Application.java │ │ │ ├── application │ │ │ ├── SimpleProductService.java │ │ │ └── ValidationService.java │ │ │ ├── domain │ │ │ ├── EntityNotFoundException.java │ │ │ └── Product.java │ │ │ ├── infrastructure │ │ │ ├── DatabaseProductRepository.java │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── ErrorMessage.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── ProductController.java │ │ │ └── ProductDto.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── hanbit │ └── product │ └── management │ └── ApplicationTests.java ├── 13 ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── shortenurlservice │ │ │ ├── ShortenurlserviceApplication.java │ │ │ ├── application │ │ │ └── SimpleShortenUrlService.java │ │ │ ├── domain │ │ │ ├── LackOfShortenUrlKeyException.java │ │ │ ├── NotFoundShortenUrlException.java │ │ │ ├── ShortenUrl.java │ │ │ └── ShortenUrlRepository.java │ │ │ ├── infrastructure │ │ │ └── MapShortenUrlRepository.java │ │ │ └── presentation │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── ShortenUrlCreateRequestDto.java │ │ │ ├── ShortenUrlCreateResponseDto.java │ │ │ ├── ShortenUrlInformationDto.java │ │ │ └── ShortenUrlRestController.java │ └── resources │ │ ├── application.properties │ │ └── static │ │ └── index.html │ └── test │ └── java │ └── kr │ └── co │ └── shortenurlservice │ ├── ShortenurlserviceApplicationTests.java │ ├── application │ ├── SimpleShortenUrlServiceTest.java │ └── SimpleShortenUrlServiceUnitTest.java │ └── presentation │ └── ShortenUrlRestControllerTest.java ├── 03 ├── 3-2-1.java ├── 3-2-2.java ├── 3-2-3.java ├── 3-2-4.java ├── 3-2-5.java ├── 3-2-6.java ├── 3-2-7.java ├── 3-2-8.java ├── 3-3-1.java ├── 3-3-10.java ├── 3-3-11.java ├── 3-3-12.java ├── 3-3-13.java ├── 3-3-14.java ├── 3-3-2.java ├── 3-3-3.java ├── 3-3-4.java ├── 3-3-5.java ├── 3-3-6.java ├── 3-3-7.java ├── 3-3-8.java └── 3-3-9.java ├── 05 ├── 5-2-1.java ├── 5-2-2.java ├── 5-2-3.java ├── 5-2-4.java └── backend.html ├── 06 ├── 6-2-1.html ├── 6-2-2.html ├── 6-2-3-1.html ├── 6-2-3.html ├── 6-2-4.html ├── 6-2-5-1.html ├── 6-2-5.html ├── 6-4-1.html ├── 6-4-2.html ├── 6-4-3.html └── SimpleRestController.java ├── 07 ├── 7-3-1.html ├── 7-3-2.html ├── Bookmark.java ├── BookmarkAjaxRestController.java └── NoParameterAjaxRestController.java ├── 08 ├── ContentTypeRestController.java ├── RedirectRestController.java └── ServerErrorRestController.java ├── 09 ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── hanbit │ │ │ └── product │ │ │ └── management │ │ │ ├── Application.java │ │ │ ├── application │ │ │ └── SimpleProductService.java │ │ │ ├── domain │ │ │ └── Product.java │ │ │ ├── infrastructure │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── ProductController.java │ │ │ └── ProductDto.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── hanbit │ └── product │ └── management │ └── ApplicationTests.java ├── 12-1 ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── hanbit │ │ │ └── product │ │ │ └── management │ │ │ ├── Application.java │ │ │ ├── application │ │ │ ├── SimpleProductService.java │ │ │ └── ValidationService.java │ │ │ ├── domain │ │ │ ├── EntityNotFoundException.java │ │ │ ├── Product.java │ │ │ └── ProductRepository.java │ │ │ ├── infrastructure │ │ │ ├── DatabaseProductRepository.java │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── ErrorMessage.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── ProductController.java │ │ │ └── ProductDto.java │ └── resources │ │ ├── application-prod.properties │ │ ├── application-test.properties │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── hanbit │ └── product │ └── management │ └── ApplicationTests.java ├── 12-2 ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── hanbit │ │ │ └── product │ │ │ └── management │ │ │ ├── Application.java │ │ │ ├── application │ │ │ ├── SimpleProductService.java │ │ │ └── ValidationService.java │ │ │ ├── domain │ │ │ ├── EntityNotFoundException.java │ │ │ ├── Product.java │ │ │ └── ProductRepository.java │ │ │ ├── infrastructure │ │ │ ├── DatabaseProductRepository.java │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── ErrorMessage.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── ProductController.java │ │ │ └── ProductDto.java │ └── resources │ │ ├── application-prod.properties │ │ ├── application-test.properties │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── hanbit │ └── product │ └── management │ ├── ApplicationTests.java │ └── application │ └── SimpleProductServiceTest.java ├── 12-3 ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── hanbit │ │ │ └── product │ │ │ └── management │ │ │ ├── Application.java │ │ │ ├── application │ │ │ ├── SimpleProductService.java │ │ │ └── ValidationService.java │ │ │ ├── domain │ │ │ ├── EntityNotFoundException.java │ │ │ ├── Product.java │ │ │ └── ProductRepository.java │ │ │ ├── infrastructure │ │ │ ├── DatabaseProductRepository.java │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── ErrorMessage.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── ProductController.java │ │ │ └── ProductDto.java │ └── resources │ │ ├── application-prod.properties │ │ ├── application-test.properties │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── hanbit │ └── product │ └── management │ ├── ApplicationTests.java │ └── application │ ├── SimpleProductServiceTest.java │ └── SimpleProductServiceUnitTest.java ├── 14-1 ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── ordermanagement │ │ │ ├── OrdermanagementApplication.java │ │ │ ├── application │ │ │ ├── SimpleOrderService.java │ │ │ └── SimpleProductService.java │ │ │ ├── domain │ │ │ ├── exception │ │ │ │ └── EntityNotFoundException.java │ │ │ ├── order │ │ │ │ ├── Order.java │ │ │ │ └── OrderRepository.java │ │ │ └── product │ │ │ │ ├── Product.java │ │ │ │ └── ProductRepository.java │ │ │ ├── infrastructure │ │ │ ├── ListOrderRepository.java │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── controller │ │ │ └── ProductRestController.java │ │ │ └── dto │ │ │ └── ProductDto.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── ordermanagement │ └── OrdermanagementApplicationTests.java ├── 14-2 ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── kr │ │ │ └── co │ │ │ └── ordermanagement │ │ │ ├── OrdermanagementApplication.java │ │ │ ├── application │ │ │ ├── SimpleOrderService.java │ │ │ └── SimpleProductService.java │ │ │ ├── domain │ │ │ ├── exception │ │ │ │ ├── CanNotCancellableStateException.java │ │ │ │ ├── EntityNotFoundException.java │ │ │ │ └── NotEnoughAmountException.java │ │ │ ├── order │ │ │ │ ├── Order.java │ │ │ │ ├── OrderRepository.java │ │ │ │ ├── OrderedProduct.java │ │ │ │ └── State.java │ │ │ └── product │ │ │ │ ├── Product.java │ │ │ │ └── ProductRepository.java │ │ │ ├── infrastructure │ │ │ ├── ListOrderRepository.java │ │ │ └── ListProductRepository.java │ │ │ └── presentation │ │ │ ├── controller │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── OrderRestController.java │ │ │ └── ProductRestController.java │ │ │ └── dto │ │ │ ├── ChangeStateRequestDto.java │ │ │ ├── ErrorMessageDto.java │ │ │ ├── OrderProductRequestDto.java │ │ │ ├── OrderResponseDto.java │ │ │ ├── OrderedProductDto.java │ │ │ └── ProductDto.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── kr │ └── co │ └── ordermanagement │ └── OrdermanagementApplicationTests.java ├── APPENDIX B └── README.md ├── README.md └── reference_for_test.md /03/3-2-1.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static void main(String[] args) { 3 | int number = 1; 4 | 5 | if (number == 1) { 6 | System.out.println("if 블록입니다."); 7 | } else if (number == 2) { 8 | System.out.println("else if 블록입니다."); 9 | } else { 10 | System.out.println("else 블록입니다."); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /03/3-2-2.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static void main(String[] args) { 3 | int[] array = {1, 2, 3, 4, 5}; 4 | 5 | for (int i = 0; i < array.length; i++) { 6 | System.out.println("i = " + array[i]); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /03/3-2-3.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static void main(String[] args) { 3 | int[] array = {1, 2, 3, 4, 5}; 4 | int i = 0; 5 | 6 | while (i < array.length) { 7 | System.out.println("i = "+ array[i]); 8 | i++; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /03/3-2-4.java: -------------------------------------------------------------------------------- 1 | public interface Car {} 2 | 3 | public class Sonata implements Car {} 4 | 5 | public class K5 implements Car {} 6 | 7 | public class Main { 8 | public static void main(String[] args) { 9 | Car car1 = new Sonata(); 10 | Car car2 = new K5(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /03/3-2-5.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | public class Main { 5 | public static void main(String[] args) { 6 | List list = new ArrayList(); 7 | // 는 ArrayList에 Integer 타입이 저장될 수 있다는 것을 의미한다. 8 | 9 | list.add(1); 10 | list.add(2); 11 | list.add(3); 12 | 13 | System.out.println(list.get(1)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /03/3-2-6.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | public class Main { 5 | 6 | public static void main(String[] args) { 7 | List list = new ArrayList(); 8 | 9 | list.add("public"); // ["public"] 10 | list.add("static"); // ["public", "static"] 11 | list.add("void"); // ["public", "static", "void"] 12 | 13 | // for 문으로 List를 순회할 수 있다. 14 | for (int i = 0; i < list.size(); i++) { // list.size()는 리스트의 크기를 반환한다. 15 | System.out.println(list.get(i)); // i번째 요소가 출력된다. 16 | } 17 | 18 | list.remove(1); // 1번째 요소인 "static"이 제거된다. -> ["public", "void"] 19 | int voidIndex = list.indexOf("void"); // void의 인덱스인 1이 반환된다. 20 | System.out.println("void의 index = " + voidIndex); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /03/3-2-7.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static void main(String[] args) { 3 | String str1 = new String("is same?"); 4 | String str2 = new String("is same?"); 5 | 6 | System.out.println(str1 == str2); // true or false? 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /03/3-2-8.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static void main(String[] args) { 3 | String str1 = new String("is same?"); 4 | String str2 = new String("is same?"); 5 | 6 | System.out.println(str1.equals(str2)); // true or false? 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /03/3-3-1.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.Comparator; 3 | import java.util.List; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | List list = new ArrayList(); 8 | 9 | list.add("public"); 10 | list.add("static"); 11 | list.add("void"); 12 | 13 | // 익명 클래스 코드 14 | list.sort(new Comparator() { 15 | @Override 16 | public int compare(String str1, String str2) { 17 | return str1.compareTo(str2); 18 | } 19 | }); 20 | 21 | // 람다 표현식 코드 22 | list.sort((Comparator) (str1, str2) -> str1.compareTo(str2)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /03/3-3-10.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | private static String getSomeString() { 3 | return null; // 이 메서드는 항상 null을 반환한다. 4 | } 5 | 6 | public static void main(String[] args) { 7 | String isThisNull = getSomeString(); 8 | 9 | System.out.println(isThisNull.toUpperCase()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /03/3-3-11.java: -------------------------------------------------------------------------------- 1 | import java.util.Optional; 2 | 3 | public class Main { 4 | private static Optional getSomeString() { 5 | return Optional.empty(); // null을 반환하는 것이 아니라 비어있는 Optional을 반환한다. 6 | } 7 | 8 | public static void main(String[] args) { 9 | Optional isThisNull = getSomeString(); 10 | 11 | isThisNull.ifPresent(str -> System.out.println(str.toUpperCase())); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /03/3-3-12.java: -------------------------------------------------------------------------------- 1 | import java.util.Optional; 2 | 3 | public class Main { 4 | private static Optional getSomeString() { 5 | return Optional.ofNullable("public static void"); 6 | } 7 | 8 | public static void main(String[] args) { 9 | Optional isThisNull = getSomeString(); 10 | 11 | isThisNull.ifPresent(str -> System.out.println(str.toUpperCase())); // PUBLIC STATIC VOID가 출력된다. 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /03/3-3-13.java: -------------------------------------------------------------------------------- 1 | import java.util.Optional; 2 | 3 | public class Main { 4 | private static Optional getSomeString() { 5 | return Optional.ofNullable("public static void"); 6 | } 7 | 8 | public static void main(String[] args) { 9 | Optional str = getSomeString(); 10 | 11 | if(str.isPresent()) { 12 | System.out.println(str.get().toUpperCase()); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /03/3-3-14.java: -------------------------------------------------------------------------------- 1 | import java.util.Optional; 2 | 3 | public class Main { 4 | private static Optional getSomeString() { 5 | return Optional.ofNullable("public static void"); 6 | } 7 | 8 | public static void main(String[] args) { 9 | Optional str = getSomeString(); 10 | 11 | str.ifPresent((string) -> System.out.println(string.toUpperCase())); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /03/3-3-2.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | public class Main { 5 | 6 | public static void main(String[] args) { 7 | List list = new ArrayList(); 8 | 9 | list.add("public"); 10 | list.add("static"); 11 | list.add("void"); 12 | 13 | list.stream().forEach(str -> System.out.println(str)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /03/3-3-3.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.Arrays; 3 | import java.util.List; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Integer[] integerArray = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 8 | List list = Arrays.asList(integerArray); 9 | 10 | List evenList = new ArrayList(); 11 | 12 | for (int i = 0; i < list.size(); i++) { 13 | Integer number = list.get(i); 14 | if (number % 2 == 0) { // 2로 나눴을 때의 나머지가 0이면 2의 배수다. 15 | evenList.add(number); 16 | } 17 | } 18 | 19 | for (int i = 0; i < evenList.size(); i++) { 20 | System.out.println(evenList.get(i)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /03/3-3-4.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.List; 3 | import java.util.stream.Collectors; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Integer[] integerArray = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 8 | List list = Arrays.asList(integerArray); 9 | 10 | List evenList = list.stream() 11 | .filter(value -> value % 2 == 0).collect(Collectors.toList()); 12 | 13 | evenList.stream().forEach(value -> System.out.println(value)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /03/3-3-5.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.List; 3 | 4 | public class Main { 5 | public static void main(String[] args) { 6 | Integer[] integerArray = new Integer[]{1, 2, 3, 4, 5}; 7 | List list = Arrays.asList(integerArray); 8 | list.stream().forEach(value -> System.out.println(value)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /03/3-3-6.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.List; 3 | import java.util.stream.Collectors; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Integer[] integerArray = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 8 | List list = Arrays.asList(integerArray); 9 | List evenList = list.stream() 10 | .filter(value -> value % 2 == 0).collect(Collectors.toList()); 11 | evenList.stream().forEach(value -> System.out.println(value)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /03/3-3-7.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.List; 3 | 4 | public class Main { 5 | public static void main(String[] args) { 6 | Integer[] integerArray = new Integer[]{1, 1, 1, 1, 2, 2, 2, 3, 3, 4}; 7 | List list = Arrays.asList(integerArray); 8 | List distinctList = list.stream().distinct().toList(); 9 | distinctList.stream().forEach(value -> System.out.println(value)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /03/3-3-8.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.List; 3 | 4 | public class Main { 5 | public static void main(String[] args) { 6 | String[] lowercaseArray = new String[]{"public", "static", "void"}; 7 | List lowercaseList = Arrays.asList(lowercaseArray); 8 | List uppercaseList = lowercaseList.stream() 9 | .map(value -> value.toUpperCase()).toList(); 10 | uppercaseList.stream().forEach(value -> System.out.println(value)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /03/3-3-9.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | private static String getSomeString() { 3 | return null; // 이 메서드는 항상 null을 반환한다. 4 | } 5 | 6 | public static void main(String[] args) { 7 | String isThisNull = getSomeString(); 8 | 9 | if(null != isThisNull) { 10 | System.out.println(isThisNull.toUpperCase()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /05/5-2-1.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class SimpleRestController { 8 | 9 | @RequestMapping("/") 10 | public String hello() { 11 | return "Hello"; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /05/5-2-2.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class SimpleRestController { 8 | 9 | @RequestMapping("/") 10 | public String hello() { 11 | return "Hello"; 12 | } 13 | 14 | @RequestMapping("/bye") 15 | public String bye() { 16 | return "Bye"; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /05/5-2-3.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class SimpleRestController { 8 | 9 | @RequestMapping("/bye") 10 | public String hello() { 11 | return "Hello"; 12 | } 13 | 14 | @RequestMapping("/bye") 15 | public String bye() { 16 | return "Bye"; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /05/5-2-4.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class SimpleRestController { 8 | 9 | @RequestMapping("/") 10 | public String hello() { 11 | return "Hello Backend"; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /05/backend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 이 HTML의 제목입니다. 4 | 5 | 6 | 7 | 안녕하세요. resources/static 폴더에 추가된 backend.html입니다. 8 | 9 | 10 | -------------------------------------------------------------------------------- /06/6-2-1.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /06/6-2-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 헬로 월드, hello world 7 | 8 | 9 | -------------------------------------------------------------------------------- /06/6-2-3-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 구글로 이동 7 | 8 | 9 | -------------------------------------------------------------------------------- /06/6-2-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 구글로 이동 7 | 8 | 9 | -------------------------------------------------------------------------------- /06/6-2-4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 이미지 넣어보기 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /06/6-2-5-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 어떤 링크 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /06/6-2-5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 어떤 링크 10 | 11 | 12 | -------------------------------------------------------------------------------- /06/6-4-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /06/6-4-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /06/6-4-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /06/SimpleRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RequestParam; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class SimpleRestController { 9 | 10 | @RequestMapping("/article") 11 | public String createArticle(@RequestParam("title") String title, 12 | @RequestParam("content") String content) { 13 | return String.format("title=%s / content=%s", title, content); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /07/7-3-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /07/7-3-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 | 12 |
    13 | 14 |
15 | 16 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /07/Bookmark.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | public class Bookmark { 4 | public String name; 5 | public String url; 6 | } 7 | -------------------------------------------------------------------------------- /07/BookmarkAjaxRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestBody; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @RestController 12 | public class BookmarkAjaxRestController { 13 | 14 | private List bookmarks = new ArrayList<>(); 15 | 16 | @RequestMapping(method = RequestMethod.POST, path = "/bookmark") 17 | public String registerBookmark(@RequestBody Bookmark bookmark) { 18 | bookmarks.add(bookmark); 19 | return "registered"; 20 | } 21 | 22 | @RequestMapping(method = RequestMethod.GET, path = "/bookmarks") 23 | public List getBookmarks() { 24 | return bookmarks; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /07/NoParameterAjaxRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class NoParameterAjaxRestController { 8 | 9 | @RequestMapping("/get-with-no-parameter") 10 | public String getWithNoParameter() { 11 | return "파라미터가 없는 GET 요청"; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /08/ContentTypeRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class ContentTypeRestController { 8 | @RequestMapping("/returnString") 9 | public String returnString() { 10 | return "문자열을 리턴"; 11 | } 12 | @RequestMapping("/returnBookmark") 13 | public Bookmark returnBookmark() { 14 | return new Bookmark(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /08/RedirectRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.net.URI; 10 | 11 | @RestController 12 | public class RedirectRestController { 13 | @RequestMapping("/redirectToTarget") 14 | public ResponseEntity redirectToTarget() { 15 | HttpHeaders headers = new HttpHeaders(); 16 | headers.setLocation(URI.create("/targetOfRedirect")); 17 | return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY); 18 | } 19 | @RequestMapping("/targetOfRedirect") 20 | public String targetOfRedirect() { 21 | return "This is Redirect!"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /08/ServerErrorRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class ServerErrorRestController { 8 | @RequestMapping("/throwServerError") 9 | public void throwServerError() { 10 | throw new RuntimeException(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /09/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.7/reference/htmlsingle/#boot-features-developing-web-applications) 10 | 11 | ### Guides 12 | The following guides illustrate how to use some features concretely: 13 | 14 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 15 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 16 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 17 | 18 | -------------------------------------------------------------------------------- /09/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co.hanbit 12 | product.management 13 | 0.0.1-SNAPSHOT 14 | product.management 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.modelmapper 26 | modelmapper 27 | 3.1.0 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-maven-plugin 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /09/src/main/java/kr/co/hanbit/product/management/Application.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.modelmapper.config.Configuration; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | @Bean 17 | public ModelMapper modelMapper() { 18 | ModelMapper modelMapper = new ModelMapper(); 19 | modelMapper.getConfiguration() 20 | .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) 21 | .setFieldMatchingEnabled(true); 22 | return modelMapper; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /09/src/main/java/kr/co/hanbit/product/management/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.infrastructure.ListProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.modelmapper.ModelMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class SimpleProductService { 14 | 15 | private ListProductRepository listProductRepository; 16 | private ModelMapper modelMapper; 17 | 18 | @Autowired 19 | SimpleProductService(ListProductRepository listProductRepository, ModelMapper modelMapper) { 20 | this.listProductRepository = listProductRepository; 21 | this.modelMapper = modelMapper; 22 | } 23 | 24 | public ProductDto add(ProductDto productDto) { 25 | Product product = modelMapper.map(productDto, Product.class); 26 | Product savedProduct = listProductRepository.add(product); 27 | ProductDto savedProductDto = modelMapper.map(savedProduct, ProductDto.class); 28 | return savedProductDto; 29 | } 30 | 31 | public ProductDto findById(Long id) { 32 | Product product = listProductRepository.findById(id); 33 | ProductDto productDto = modelMapper.map(product, ProductDto.class); 34 | return productDto; 35 | } 36 | 37 | public List findAll() { 38 | List products = listProductRepository.findAll(); 39 | List productDtos = products.stream() 40 | .map(product -> modelMapper.map(product, ProductDto.class)) 41 | .toList(); 42 | return productDtos; 43 | } 44 | 45 | public List findByNameContaining(String name) { 46 | List products = listProductRepository.findByNameContaining(name); 47 | List productDtos = products.stream() 48 | .map(product -> modelMapper.map(product, ProductDto.class)) 49 | .toList(); 50 | return productDtos; 51 | } 52 | 53 | public ProductDto update(ProductDto productDto) { 54 | Product product = modelMapper.map(productDto, Product.class); 55 | Product updatedProduct = listProductRepository.update(product); 56 | ProductDto updatedProductDto = modelMapper.map(updatedProduct, ProductDto.class); 57 | return updatedProductDto; 58 | } 59 | 60 | public void delete(Long id) { 61 | listProductRepository.delete(id); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /09/src/main/java/kr/co/hanbit/product/management/domain/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import java.util.Objects; 4 | 5 | public class Product { 6 | private Long id; 7 | private String name; 8 | private Integer price; 9 | private Integer amount; 10 | 11 | public void setId(Long id) { 12 | this.id = id; 13 | } 14 | 15 | public Boolean sameId(Long id) { 16 | return this.id.equals(id); 17 | } 18 | 19 | public Boolean containsName(String name) { 20 | return this.name.contains(name); 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | Product product = (Product) o; 28 | return Objects.equals(id, product.id); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /09/src/main/java/kr/co/hanbit/product/management/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | import java.util.concurrent.CopyOnWriteArrayList; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | 10 | @Repository 11 | public class ListProductRepository { 12 | 13 | private List products = new CopyOnWriteArrayList<>(); 14 | private AtomicLong sequence = new AtomicLong(1L); 15 | 16 | public Product add(Product product) { 17 | product.setId(sequence.getAndAdd(1L)); 18 | 19 | products.add(product); 20 | return product; 21 | } 22 | 23 | public Product findById(Long id) { 24 | return products.stream() 25 | .filter(product -> product.sameId(id)) 26 | .findFirst() 27 | .orElseThrow(); 28 | } 29 | 30 | public List findAll() { 31 | return products; 32 | } 33 | 34 | public List findByNameContaining(String name) { 35 | return products.stream() 36 | .filter(product -> product.containsName(name)) 37 | .toList(); 38 | } 39 | 40 | public Product update(Product product) { 41 | Integer indexToModify = products.indexOf(product); 42 | products.set(indexToModify, product); 43 | return product; 44 | } 45 | 46 | public void delete(Long id) { 47 | Product product = this.findById(id); 48 | products.remove(product); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /09/src/main/java/kr/co/hanbit/product/management/presentation/ProductController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.application.SimpleProductService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import java.util.List; 8 | 9 | @RestController 10 | public class ProductController { 11 | 12 | private SimpleProductService simpleProductService; 13 | 14 | @Autowired 15 | ProductController(SimpleProductService simpleProductService) { 16 | this.simpleProductService = simpleProductService; 17 | } 18 | 19 | @RequestMapping(value = "/products", method = RequestMethod.POST) 20 | public ProductDto createProduct(@RequestBody ProductDto productDto) { 21 | return simpleProductService.add(productDto); 22 | } 23 | 24 | @RequestMapping(value = "/products/{id}", method = RequestMethod.GET) 25 | public ProductDto findProductById(@PathVariable Long id) { 26 | return simpleProductService.findById(id); 27 | } 28 | 29 | @RequestMapping(value = "/products", method = RequestMethod.GET) 30 | public List findProducts( 31 | @RequestParam(required = false) String name 32 | ) { 33 | if (null == name) 34 | return simpleProductService.findAll(); 35 | 36 | return simpleProductService.findByNameContaining(name); 37 | } 38 | 39 | @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) 40 | public ProductDto updateProduct( 41 | @PathVariable Long id, 42 | @RequestBody ProductDto productDto 43 | ) { 44 | productDto.setId(id); 45 | return simpleProductService.update(productDto); 46 | } 47 | 48 | @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) 49 | public void deleteProduct(@PathVariable Long id) { 50 | simpleProductService.delete(id); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /09/src/main/java/kr/co/hanbit/product/management/presentation/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | public class ProductDto { 4 | private Long id; 5 | private String name; 6 | private Integer price; 7 | private Integer amount; 8 | 9 | public Long getId() { 10 | return id; 11 | } 12 | 13 | public void setId(Long id) { 14 | this.id = id; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public Integer getPrice() { 22 | return price; 23 | } 24 | 25 | public Integer getAmount() { 26 | return amount; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /09/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /09/src/test/java/kr/co/hanbit/product/management/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /10/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.7/reference/htmlsingle/#boot-features-developing-web-applications) 10 | 11 | ### Guides 12 | The following guides illustrate how to use some features concretely: 13 | 14 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 15 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 16 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 17 | 18 | -------------------------------------------------------------------------------- /10/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co.hanbit 12 | product.management 13 | 0.0.1-SNAPSHOT 14 | product.management 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | org.modelmapper 32 | modelmapper 33 | 3.1.0 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-maven-plugin 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/Application.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.modelmapper.config.Configuration; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | @Bean 17 | public ModelMapper modelMapper() { 18 | ModelMapper modelMapper = new ModelMapper(); 19 | modelMapper.getConfiguration() 20 | .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) 21 | .setFieldMatchingEnabled(true); 22 | return modelMapper; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.infrastructure.ListProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.modelmapper.ModelMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class SimpleProductService { 14 | 15 | private ListProductRepository listProductRepository; 16 | private ModelMapper modelMapper; 17 | private ValidationService validationService; 18 | 19 | @Autowired 20 | SimpleProductService(ListProductRepository listProductRepository, ModelMapper modelMapper, ValidationService validationService 21 | ) { 22 | this.listProductRepository = listProductRepository; 23 | this.modelMapper = modelMapper; 24 | this.validationService = validationService; 25 | } 26 | 27 | public ProductDto add(ProductDto productDto) { 28 | Product product = modelMapper.map(productDto, Product.class); 29 | validationService.checkValid(product); 30 | 31 | Product savedProduct = listProductRepository.add(product); 32 | ProductDto savedProductDto = modelMapper.map(savedProduct, ProductDto.class); 33 | return savedProductDto; 34 | } 35 | 36 | public ProductDto findById(Long id) { 37 | Product product = listProductRepository.findById(id); 38 | ProductDto productDto = modelMapper.map(product, ProductDto.class); 39 | return productDto; 40 | } 41 | 42 | public List findAll() { 43 | List products = listProductRepository.findAll(); 44 | List productDtos = products.stream() 45 | .map(product -> modelMapper.map(product, ProductDto.class)) 46 | .toList(); 47 | return productDtos; 48 | } 49 | 50 | public List findByNameContaining(String name) { 51 | List products = listProductRepository.findByNameContaining(name); 52 | List productDtos = products.stream() 53 | .map(product -> modelMapper.map(product, ProductDto.class)) 54 | .toList(); 55 | return productDtos; 56 | } 57 | 58 | public ProductDto update(ProductDto productDto) { 59 | Product product = modelMapper.map(productDto, Product.class); 60 | Product updatedProduct = listProductRepository.update(product); 61 | ProductDto updatedProductDto = modelMapper.map(updatedProduct, ProductDto.class); 62 | return updatedProductDto; 63 | } 64 | 65 | public void delete(Long id) { 66 | listProductRepository.delete(id); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/application/ValidationService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.validation.annotation.Validated; 5 | 6 | import jakarta.validation.Valid; 7 | 8 | @Service 9 | @Validated 10 | public class ValidationService { 11 | public void checkValid(@Valid T validationTarget) { 12 | // do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/domain/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/domain/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.Size; 6 | import java.util.Objects; 7 | 8 | public class Product { 9 | private Long id; 10 | 11 | @Size(min = 1, max = 100) 12 | private String name; 13 | 14 | @Max(1_000_000) 15 | @Min(0) 16 | private Integer price; 17 | 18 | @Max(9_999) 19 | @Min(0) 20 | private Integer amount; 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public Boolean sameId(Long id) { 27 | return this.id.equals(id); 28 | } 29 | 30 | public Boolean containsName(String name) { 31 | return this.name.contains(name); 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | Product product = (Product) o; 39 | return Objects.equals(id, product.id); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.domain.Product; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | import java.util.concurrent.CopyOnWriteArrayList; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | @Repository 12 | public class ListProductRepository { 13 | 14 | private List products = new CopyOnWriteArrayList<>(); 15 | private AtomicLong sequence = new AtomicLong(1L); 16 | 17 | public Product add(Product product) { 18 | product.setId(sequence.getAndAdd(1L)); 19 | 20 | products.add(product); 21 | return product; 22 | } 23 | 24 | public Product findById(Long id) { 25 | return products.stream() 26 | .filter(product -> product.sameId(id)) 27 | .findFirst() 28 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 29 | } 30 | 31 | public List findAll() { 32 | return products; 33 | } 34 | 35 | public List findByNameContaining(String name) { 36 | return products.stream() 37 | .filter(product -> product.containsName(name)) 38 | .toList(); 39 | } 40 | 41 | public Product update(Product product) { 42 | Integer indexToModify = products.indexOf(product); 43 | products.set(indexToModify, product); 44 | return product; 45 | } 46 | 47 | public void delete(Long id) { 48 | Product product = this.findById(id); 49 | products.remove(product); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/presentation/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import java.util.List; 4 | 5 | public class ErrorMessage { 6 | 7 | private List errors; 8 | 9 | public ErrorMessage(List errors) { 10 | this.errors = errors; 11 | } 12 | 13 | public List getErrors() { 14 | return errors; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/presentation/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import jakarta.validation.ConstraintViolation; 12 | import jakarta.validation.ConstraintViolationException; 13 | import jakarta.validation.Path; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | @RestControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | @ExceptionHandler(ConstraintViolationException.class) 22 | public ResponseEntity handleConstraintViolatedException( 23 | ConstraintViolationException ex 24 | ) { 25 | Set> constraintViolations = ex.getConstraintViolations(); 26 | List errors = constraintViolations.stream() 27 | .map( 28 | constraintViolation -> 29 | extractField(constraintViolation.getPropertyPath()) + ", " + constraintViolation.getMessage() 30 | ) 31 | .toList(); 32 | 33 | ErrorMessage errorMessage = new ErrorMessage(errors); 34 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 35 | } 36 | 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public ResponseEntity handleMethodArgumentNotValidException( 39 | MethodArgumentNotValidException ex 40 | ) { 41 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 42 | List errors = fieldErrors.stream() 43 | .map( 44 | fieldError -> 45 | fieldError.getField() + ", " + fieldError.getDefaultMessage() 46 | ) 47 | .toList(); 48 | 49 | ErrorMessage errorMessage = new ErrorMessage(errors); 50 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 51 | } 52 | 53 | @ExceptionHandler(EntityNotFoundException.class) 54 | public ResponseEntity handleEntityNotFoundExceptionException( 55 | EntityNotFoundException ex 56 | ) { 57 | List errors = new ArrayList<>(); 58 | errors.add(ex.getMessage()); 59 | 60 | ErrorMessage errorMessage = new ErrorMessage(errors); 61 | return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND); 62 | } 63 | 64 | private String extractField(Path path) { 65 | String[] splittedArray = path.toString().split("[.]"); 66 | int lastIndex = splittedArray.length - 1; 67 | return splittedArray[lastIndex]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/presentation/ProductController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.application.SimpleProductService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import jakarta.validation.Valid; 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | 13 | private SimpleProductService simpleProductService; 14 | 15 | @Autowired 16 | ProductController(SimpleProductService simpleProductService) { 17 | this.simpleProductService = simpleProductService; 18 | } 19 | 20 | @RequestMapping(value = "/products", method = RequestMethod.POST) 21 | public ProductDto createProduct(@Valid @RequestBody ProductDto productDto) { 22 | return simpleProductService.add(productDto); 23 | } 24 | 25 | @RequestMapping(value = "/products/{id}", method = RequestMethod.GET) 26 | public ProductDto findProductById(@PathVariable Long id) { 27 | return simpleProductService.findById(id); 28 | } 29 | 30 | @RequestMapping(value = "/products", method = RequestMethod.GET) 31 | public List findProducts( 32 | @RequestParam(required = false) String name 33 | ) { 34 | if (null == name) 35 | return simpleProductService.findAll(); 36 | 37 | return simpleProductService.findByNameContaining(name); 38 | } 39 | 40 | @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) 41 | public ProductDto updateProduct( 42 | @PathVariable Long id, 43 | @RequestBody ProductDto productDto 44 | ) { 45 | productDto.setId(id); 46 | return simpleProductService.update(productDto); 47 | } 48 | 49 | @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) 50 | public void deleteProduct(@PathVariable Long id) { 51 | simpleProductService.delete(id); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /10/src/main/java/kr/co/hanbit/product/management/presentation/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | public class ProductDto { 6 | private Long id; 7 | 8 | @NotNull 9 | private String name; 10 | 11 | @NotNull 12 | private Integer price; 13 | 14 | @NotNull 15 | private Integer amount; 16 | 17 | public Long getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public Integer getPrice() { 30 | return price; 31 | } 32 | 33 | public Integer getAmount() { 34 | return amount; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /10/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /10/src/test/java/kr/co/hanbit/product/management/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /11/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.7/reference/htmlsingle/#boot-features-developing-web-applications) 10 | 11 | ### Guides 12 | The following guides illustrate how to use some features concretely: 13 | 14 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 15 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 16 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 17 | 18 | -------------------------------------------------------------------------------- /11/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co.hanbit 12 | product.management 13 | 0.0.1-SNAPSHOT 14 | product.management 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | org.modelmapper 32 | modelmapper 33 | 3.1.0 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-jdbc 39 | 40 | 41 | mysql 42 | mysql-connector-java 43 | runtime 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/Application.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.modelmapper.config.Configuration; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | 10 | import javax.sql.DataSource; 11 | import java.sql.Connection; 12 | 13 | @SpringBootApplication 14 | public class Application { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(Application.class, args); 18 | } 19 | 20 | @Bean 21 | public ModelMapper modelMapper() { 22 | ModelMapper modelMapper = new ModelMapper(); 23 | modelMapper.getConfiguration() 24 | .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) 25 | .setFieldMatchingEnabled(true); 26 | return modelMapper; 27 | } 28 | 29 | @Bean 30 | public ApplicationRunner runner(DataSource dataSource) { 31 | return args -> { 32 | // 이 부분에 실행할 코드를 넣으면 된다. 33 | Connection connection = dataSource.getConnection(); 34 | }; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.infrastructure.DatabaseProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.modelmapper.ModelMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class SimpleProductService { 14 | 15 | private DatabaseProductRepository databaseProductRepository; 16 | private ModelMapper modelMapper; 17 | private ValidationService validationService; 18 | 19 | @Autowired 20 | SimpleProductService(DatabaseProductRepository productRepository, ModelMapper modelMapper, ValidationService validationService 21 | ) { 22 | this.databaseProductRepository = productRepository; 23 | this.modelMapper = modelMapper; 24 | this.validationService = validationService; 25 | } 26 | 27 | public ProductDto add(ProductDto productDto) { 28 | Product product = modelMapper.map(productDto, Product.class); 29 | validationService.checkValid(product); 30 | 31 | Product savedProduct = databaseProductRepository.add(product); 32 | ProductDto savedProductDto = modelMapper.map(savedProduct, ProductDto.class); 33 | return savedProductDto; 34 | } 35 | 36 | public ProductDto findById(Long id) { 37 | Product product = databaseProductRepository.findById(id); 38 | ProductDto productDto = modelMapper.map(product, ProductDto.class); 39 | return productDto; 40 | } 41 | 42 | public List findAll() { 43 | List products = databaseProductRepository.findAll(); 44 | List productDtos = products.stream() 45 | .map(product -> modelMapper.map(product, ProductDto.class)) 46 | .toList(); 47 | return productDtos; 48 | } 49 | 50 | public List findByNameContaining(String name) { 51 | List products = databaseProductRepository.findByNameContaining(name); 52 | List productDtos = products.stream() 53 | .map(product -> modelMapper.map(product, ProductDto.class)) 54 | .toList(); 55 | return productDtos; 56 | } 57 | 58 | public ProductDto update(ProductDto productDto) { 59 | Product product = modelMapper.map(productDto, Product.class); 60 | Product updatedProduct = databaseProductRepository.update(product); 61 | ProductDto updatedProductDto = modelMapper.map(updatedProduct, ProductDto.class); 62 | return updatedProductDto; 63 | } 64 | 65 | public void delete(Long id) { 66 | databaseProductRepository.delete(id); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/application/ValidationService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.validation.annotation.Validated; 5 | 6 | import jakarta.validation.Valid; 7 | 8 | @Service 9 | @Validated 10 | public class ValidationService { 11 | public void checkValid(@Valid T validationTarget) { 12 | // do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/domain/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/domain/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.Size; 6 | import java.util.Objects; 7 | 8 | public class Product { 9 | private Long id; 10 | 11 | @Size(min = 1, max = 100) 12 | private String name; 13 | 14 | @Max(1_000_000) 15 | @Min(0) 16 | private Integer price; 17 | 18 | @Max(9_999) 19 | @Min(0) 20 | private Integer amount; 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public Integer getPrice() { 31 | return price; 32 | } 33 | 34 | public Integer getAmount() { 35 | return amount; 36 | } 37 | 38 | public void setId(Long id) { 39 | this.id = id; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public void setPrice(Integer price) { 47 | this.price = price; 48 | } 49 | 50 | public void setAmount(Integer amount) { 51 | this.amount = amount; 52 | } 53 | 54 | public Boolean sameId(Long id) { 55 | return this.id.equals(id); 56 | } 57 | 58 | public Boolean containsName(String name) { 59 | return this.name.contains(name); 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | Product product = (Product) o; 67 | return Objects.equals(id, product.id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/infrastructure/DatabaseProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 6 | import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; 7 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 8 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 9 | import org.springframework.jdbc.core.namedparam.SqlParameterSource; 10 | import org.springframework.jdbc.support.GeneratedKeyHolder; 11 | import org.springframework.jdbc.support.KeyHolder; 12 | import org.springframework.stereotype.Repository; 13 | 14 | import java.util.List; 15 | 16 | @Repository 17 | public class DatabaseProductRepository { 18 | 19 | private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 20 | 21 | @Autowired 22 | public DatabaseProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { 23 | this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; 24 | } 25 | 26 | public Product add(Product product) { 27 | KeyHolder keyHolder = new GeneratedKeyHolder(); 28 | SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product); 29 | 30 | namedParameterJdbcTemplate.update("INSERT INTO products (name, price, amount) VALUES (:name, :price, :amount)", namedParameter, keyHolder); 31 | 32 | Long generatedId = keyHolder.getKey().longValue(); 33 | product.setId(generatedId); 34 | 35 | return product; 36 | } 37 | 38 | public Product findById(Long id) { 39 | SqlParameterSource namedParameter = new MapSqlParameterSource("id", id); 40 | 41 | Product product = namedParameterJdbcTemplate.queryForObject( 42 | "SELECT id, name, price, amount FROM products WHERE id=:id", 43 | namedParameter, 44 | new BeanPropertyRowMapper<>(Product.class) 45 | ); 46 | 47 | return product; 48 | } 49 | 50 | public List findAll() { 51 | List products = namedParameterJdbcTemplate.query( 52 | "SELECT * FROM products", 53 | new BeanPropertyRowMapper<>(Product.class) 54 | ); 55 | 56 | return products; 57 | } 58 | 59 | public List findByNameContaining(String name) { 60 | SqlParameterSource namedParameter = new MapSqlParameterSource("name", "%" + name + "%"); 61 | 62 | List products = namedParameterJdbcTemplate.query( 63 | "SELECT * FROM products WHERE name LIKE :name", 64 | namedParameter, 65 | new BeanPropertyRowMapper<>(Product.class) 66 | ); 67 | 68 | return products; 69 | } 70 | 71 | public Product update(Product product) { 72 | SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product); 73 | 74 | namedParameterJdbcTemplate.update( 75 | "UPDATE products SET name=:name, price=:price, amount=:amount WHERE id=:id", 76 | namedParameter 77 | ); 78 | 79 | return product; 80 | } 81 | 82 | public void delete(Long id) { 83 | SqlParameterSource namedParameter = new MapSqlParameterSource("id", id); 84 | 85 | namedParameterJdbcTemplate.update( 86 | "DELETE FROM products WHERE id=:id", 87 | namedParameter 88 | ); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.domain.Product; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | import java.util.concurrent.CopyOnWriteArrayList; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | @Repository 12 | public class ListProductRepository { 13 | 14 | private List products = new CopyOnWriteArrayList<>(); 15 | private AtomicLong sequence = new AtomicLong(1L); 16 | 17 | public Product add(Product product) { 18 | product.setId(sequence.getAndAdd(1L)); 19 | 20 | products.add(product); 21 | return product; 22 | } 23 | 24 | public Product findById(Long id) { 25 | return products.stream() 26 | .filter(product -> product.sameId(id)) 27 | .findFirst() 28 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 29 | } 30 | 31 | public List findAll() { 32 | return products; 33 | } 34 | 35 | public List findByNameContaining(String name) { 36 | return products.stream() 37 | .filter(product -> product.containsName(name)) 38 | .toList(); 39 | } 40 | 41 | public Product update(Product product) { 42 | Integer indexToModify = products.indexOf(product); 43 | products.set(indexToModify, product); 44 | return product; 45 | } 46 | 47 | public void delete(Long id) { 48 | Product product = this.findById(id); 49 | products.remove(product); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/presentation/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import java.util.List; 4 | 5 | public class ErrorMessage { 6 | 7 | private List errors; 8 | 9 | public ErrorMessage(List errors) { 10 | this.errors = errors; 11 | } 12 | 13 | public List getErrors() { 14 | return errors; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/presentation/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import jakarta.validation.ConstraintViolation; 12 | import jakarta.validation.ConstraintViolationException; 13 | import jakarta.validation.Path; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | @RestControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | @ExceptionHandler(ConstraintViolationException.class) 22 | public ResponseEntity handleConstraintViolatedException( 23 | ConstraintViolationException ex 24 | ) { 25 | Set> constraintViolations = ex.getConstraintViolations(); 26 | List errors = constraintViolations.stream() 27 | .map( 28 | constraintViolation -> 29 | extractField(constraintViolation.getPropertyPath()) + ", " + constraintViolation.getMessage() 30 | ) 31 | .toList(); 32 | 33 | ErrorMessage errorMessage = new ErrorMessage(errors); 34 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 35 | } 36 | 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public ResponseEntity handleMethodArgumentNotValidException( 39 | MethodArgumentNotValidException ex 40 | ) { 41 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 42 | List errors = fieldErrors.stream() 43 | .map( 44 | fieldError -> 45 | fieldError.getField() + ", " + fieldError.getDefaultMessage() 46 | ) 47 | .toList(); 48 | 49 | ErrorMessage errorMessage = new ErrorMessage(errors); 50 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 51 | } 52 | 53 | @ExceptionHandler(EntityNotFoundException.class) 54 | public ResponseEntity handleEntityNotFoundExceptionException( 55 | EntityNotFoundException ex 56 | ) { 57 | List errors = new ArrayList<>(); 58 | errors.add(ex.getMessage()); 59 | 60 | ErrorMessage errorMessage = new ErrorMessage(errors); 61 | return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND); 62 | } 63 | 64 | private String extractField(Path path) { 65 | String[] splittedArray = path.toString().split("[.]"); 66 | int lastIndex = splittedArray.length - 1; 67 | return splittedArray[lastIndex]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/presentation/ProductController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.application.SimpleProductService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import jakarta.validation.Valid; 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | 13 | private SimpleProductService simpleProductService; 14 | 15 | @Autowired 16 | ProductController(SimpleProductService simpleProductService) { 17 | this.simpleProductService = simpleProductService; 18 | } 19 | 20 | @RequestMapping(value = "/products", method = RequestMethod.POST) 21 | public ProductDto createProduct(@Valid @RequestBody ProductDto productDto) { 22 | return simpleProductService.add(productDto); 23 | } 24 | 25 | @RequestMapping(value = "/products/{id}", method = RequestMethod.GET) 26 | public ProductDto findProductById(@PathVariable Long id) { 27 | return simpleProductService.findById(id); 28 | } 29 | 30 | @RequestMapping(value = "/products", method = RequestMethod.GET) 31 | public List findProducts( 32 | @RequestParam(required = false) String name 33 | ) { 34 | if (null == name) 35 | return simpleProductService.findAll(); 36 | 37 | return simpleProductService.findByNameContaining(name); 38 | } 39 | 40 | @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) 41 | public ProductDto updateProduct( 42 | @PathVariable Long id, 43 | @RequestBody ProductDto productDto 44 | ) { 45 | productDto.setId(id); 46 | return simpleProductService.update(productDto); 47 | } 48 | 49 | @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) 50 | public void deleteProduct(@PathVariable Long id) { 51 | simpleProductService.delete(id); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /11/src/main/java/kr/co/hanbit/product/management/presentation/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | public class ProductDto { 6 | private Long id; 7 | 8 | @NotNull 9 | private String name; 10 | 11 | @NotNull 12 | private Integer price; 13 | 14 | @NotNull 15 | private Integer amount; 16 | 17 | public Long getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public Integer getPrice() { 30 | return price; 31 | } 32 | 33 | public Integer getAmount() { 34 | return amount; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /11/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/product_management 2 | spring.datasource.username=root 3 | spring.datasource.password=hanbit 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -------------------------------------------------------------------------------- /11/src/test/java/kr/co/hanbit/product/management/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /12-1/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.7/reference/htmlsingle/#boot-features-developing-web-applications) 10 | 11 | ### Guides 12 | The following guides illustrate how to use some features concretely: 13 | 14 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 15 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 16 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 17 | 18 | -------------------------------------------------------------------------------- /12-1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co.hanbit 12 | product.management 13 | 0.0.1-SNAPSHOT 14 | product.management 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | org.modelmapper 32 | modelmapper 33 | 3.1.0 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-jdbc 39 | 40 | 41 | mysql 42 | mysql-connector-java 43 | runtime 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/Application.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.modelmapper.config.Configuration; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Profile; 10 | 11 | import javax.sql.DataSource; 12 | import java.sql.Connection; 13 | 14 | @SpringBootApplication 15 | public class Application { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | 21 | @Bean 22 | public ModelMapper modelMapper() { 23 | ModelMapper modelMapper = new ModelMapper(); 24 | modelMapper.getConfiguration() 25 | .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) 26 | .setFieldMatchingEnabled(true); 27 | return modelMapper; 28 | } 29 | 30 | @Bean 31 | @Profile("prod") 32 | public ApplicationRunner runner(DataSource dataSource) { 33 | return args -> { 34 | // 이 부분에 실행할 코드를 넣으면 된다. 35 | Connection connection = dataSource.getConnection(); 36 | }; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.domain.ProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.modelmapper.ModelMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class SimpleProductService { 14 | 15 | private ProductRepository productRepository; 16 | private ModelMapper modelMapper; 17 | private ValidationService validationService; 18 | 19 | @Autowired 20 | SimpleProductService(ProductRepository productRepository, ModelMapper modelMapper, ValidationService validationService 21 | ) { 22 | this.productRepository = productRepository; 23 | this.modelMapper = modelMapper; 24 | this.validationService = validationService; 25 | } 26 | 27 | public ProductDto add(ProductDto productDto) { 28 | Product product = modelMapper.map(productDto, Product.class); 29 | validationService.checkValid(product); 30 | 31 | Product savedProduct = productRepository.add(product); 32 | ProductDto savedProductDto = modelMapper.map(savedProduct, ProductDto.class); 33 | return savedProductDto; 34 | } 35 | 36 | public ProductDto findById(Long id) { 37 | Product product = productRepository.findById(id); 38 | ProductDto productDto = modelMapper.map(product, ProductDto.class); 39 | return productDto; 40 | } 41 | 42 | public List findAll() { 43 | List products = productRepository.findAll(); 44 | List productDtos = products.stream() 45 | .map(product -> modelMapper.map(product, ProductDto.class)) 46 | .toList(); 47 | return productDtos; 48 | } 49 | 50 | public List findByNameContaining(String name) { 51 | List products = productRepository.findByNameContaining(name); 52 | List productDtos = products.stream() 53 | .map(product -> modelMapper.map(product, ProductDto.class)) 54 | .toList(); 55 | return productDtos; 56 | } 57 | 58 | public ProductDto update(ProductDto productDto) { 59 | Product product = modelMapper.map(productDto, Product.class); 60 | Product updatedProduct = productRepository.update(product); 61 | ProductDto updatedProductDto = modelMapper.map(updatedProduct, ProductDto.class); 62 | return updatedProductDto; 63 | } 64 | 65 | public void delete(Long id) { 66 | productRepository.delete(id); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/application/ValidationService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.validation.annotation.Validated; 5 | 6 | import jakarta.validation.Valid; 7 | 8 | @Service 9 | @Validated 10 | public class ValidationService { 11 | public void checkValid(@Valid T validationTarget) { 12 | // do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/domain/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/domain/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.Size; 6 | import java.util.Objects; 7 | 8 | public class Product { 9 | private Long id; 10 | 11 | @Size(min = 1, max = 100) 12 | private String name; 13 | 14 | @Max(1_000_000) 15 | @Min(0) 16 | private Integer price; 17 | 18 | @Max(9_999) 19 | @Min(0) 20 | private Integer amount; 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public Integer getPrice() { 31 | return price; 32 | } 33 | 34 | public Integer getAmount() { 35 | return amount; 36 | } 37 | 38 | public void setId(Long id) { 39 | this.id = id; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public void setPrice(Integer price) { 47 | this.price = price; 48 | } 49 | 50 | public void setAmount(Integer amount) { 51 | this.amount = amount; 52 | } 53 | 54 | public Boolean sameId(Long id) { 55 | return this.id.equals(id); 56 | } 57 | 58 | public Boolean containsName(String name) { 59 | return this.name.contains(name); 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | Product product = (Product) o; 67 | return Objects.equals(id, product.id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import java.util.List; 4 | 5 | public interface ProductRepository { 6 | Product add(Product product); 7 | Product findById(Long id); 8 | List findAll(); 9 | List findByNameContaining(String name); 10 | Product update(Product product); 11 | void delete(Long id); 12 | } 13 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/infrastructure/DatabaseProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.domain.ProductRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 8 | import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; 9 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 10 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 11 | import org.springframework.jdbc.core.namedparam.SqlParameterSource; 12 | import org.springframework.jdbc.support.GeneratedKeyHolder; 13 | import org.springframework.jdbc.support.KeyHolder; 14 | import org.springframework.stereotype.Repository; 15 | 16 | import java.util.List; 17 | 18 | @Repository 19 | @Profile("prod") 20 | public class DatabaseProductRepository implements ProductRepository { 21 | 22 | private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 23 | 24 | @Autowired 25 | public DatabaseProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { 26 | this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; 27 | } 28 | 29 | public Product add(Product product) { 30 | KeyHolder keyHolder = new GeneratedKeyHolder(); 31 | SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product); 32 | 33 | namedParameterJdbcTemplate.update("INSERT INTO products (name, price, amount) VALUES (:name, :price, :amount)", namedParameter, keyHolder); 34 | 35 | Long generatedId = keyHolder.getKey().longValue(); 36 | product.setId(generatedId); 37 | 38 | return product; 39 | } 40 | 41 | public Product findById(Long id) { 42 | SqlParameterSource namedParameter = new MapSqlParameterSource("id", id); 43 | 44 | Product product = namedParameterJdbcTemplate.queryForObject( 45 | "SELECT id, name, price, amount FROM products WHERE id=:id", 46 | namedParameter, 47 | new BeanPropertyRowMapper<>(Product.class) 48 | ); 49 | 50 | return product; 51 | } 52 | 53 | public List findAll() { 54 | List products = namedParameterJdbcTemplate.query( 55 | "SELECT * FROM products", 56 | new BeanPropertyRowMapper<>(Product.class) 57 | ); 58 | 59 | return products; 60 | } 61 | 62 | public List findByNameContaining(String name) { 63 | SqlParameterSource namedParameter = new MapSqlParameterSource("name", "%" + name + "%"); 64 | 65 | List products = namedParameterJdbcTemplate.query( 66 | "SELECT * FROM products WHERE name LIKE :name", 67 | namedParameter, 68 | new BeanPropertyRowMapper<>(Product.class) 69 | ); 70 | 71 | return products; 72 | } 73 | 74 | public Product update(Product product) { 75 | SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product); 76 | 77 | namedParameterJdbcTemplate.update( 78 | "UPDATE products SET name=:name, price=:price, amount=:amount WHERE id=:id", 79 | namedParameter 80 | ); 81 | 82 | return product; 83 | } 84 | 85 | public void delete(Long id) { 86 | SqlParameterSource namedParameter = new MapSqlParameterSource("id", id); 87 | 88 | namedParameterJdbcTemplate.update( 89 | "DELETE FROM products WHERE id=:id", 90 | namedParameter 91 | ); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.domain.Product; 5 | import kr.co.hanbit.product.management.domain.ProductRepository; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | import java.util.concurrent.atomic.AtomicLong; 12 | 13 | @Repository 14 | @Profile("test") 15 | public class ListProductRepository implements ProductRepository { 16 | 17 | private List products = new CopyOnWriteArrayList<>(); 18 | private AtomicLong sequence = new AtomicLong(1L); 19 | 20 | public Product add(Product product) { 21 | product.setId(sequence.getAndAdd(1L)); 22 | 23 | products.add(product); 24 | return product; 25 | } 26 | 27 | public Product findById(Long id) { 28 | return products.stream() 29 | .filter(product -> product.sameId(id)) 30 | .findFirst() 31 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 32 | } 33 | 34 | public List findAll() { 35 | return products; 36 | } 37 | 38 | public List findByNameContaining(String name) { 39 | return products.stream() 40 | .filter(product -> product.containsName(name)) 41 | .toList(); 42 | } 43 | 44 | public Product update(Product product) { 45 | Integer indexToModify = products.indexOf(product); 46 | products.set(indexToModify, product); 47 | return product; 48 | } 49 | 50 | public void delete(Long id) { 51 | Product product = this.findById(id); 52 | products.remove(product); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/presentation/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import java.util.List; 4 | 5 | public class ErrorMessage { 6 | 7 | private List errors; 8 | 9 | public ErrorMessage(List errors) { 10 | this.errors = errors; 11 | } 12 | 13 | public List getErrors() { 14 | return errors; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/presentation/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import jakarta.validation.ConstraintViolation; 12 | import jakarta.validation.ConstraintViolationException; 13 | import jakarta.validation.Path; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | @RestControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | @ExceptionHandler(ConstraintViolationException.class) 22 | public ResponseEntity handleConstraintViolatedException( 23 | ConstraintViolationException ex 24 | ) { 25 | Set> constraintViolations = ex.getConstraintViolations(); 26 | List errors = constraintViolations.stream() 27 | .map( 28 | constraintViolation -> 29 | extractField(constraintViolation.getPropertyPath()) + ", " + constraintViolation.getMessage() 30 | ) 31 | .toList(); 32 | 33 | ErrorMessage errorMessage = new ErrorMessage(errors); 34 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 35 | } 36 | 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public ResponseEntity handleMethodArgumentNotValidException( 39 | MethodArgumentNotValidException ex 40 | ) { 41 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 42 | List errors = fieldErrors.stream() 43 | .map( 44 | fieldError -> 45 | fieldError.getField() + ", " + fieldError.getDefaultMessage() 46 | ) 47 | .toList(); 48 | 49 | ErrorMessage errorMessage = new ErrorMessage(errors); 50 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 51 | } 52 | 53 | @ExceptionHandler(EntityNotFoundException.class) 54 | public ResponseEntity handleEntityNotFoundExceptionException( 55 | EntityNotFoundException ex 56 | ) { 57 | List errors = new ArrayList<>(); 58 | errors.add(ex.getMessage()); 59 | 60 | ErrorMessage errorMessage = new ErrorMessage(errors); 61 | return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND); 62 | } 63 | 64 | private String extractField(Path path) { 65 | String[] splittedArray = path.toString().split("[.]"); 66 | int lastIndex = splittedArray.length - 1; 67 | return splittedArray[lastIndex]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/presentation/ProductController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.application.SimpleProductService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import jakarta.validation.Valid; 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | 13 | private SimpleProductService simpleProductService; 14 | 15 | @Autowired 16 | ProductController(SimpleProductService simpleProductService) { 17 | this.simpleProductService = simpleProductService; 18 | } 19 | 20 | @RequestMapping(value = "/products", method = RequestMethod.POST) 21 | public ProductDto createProduct(@Valid @RequestBody ProductDto productDto) { 22 | return simpleProductService.add(productDto); 23 | } 24 | 25 | @RequestMapping(value = "/products/{id}", method = RequestMethod.GET) 26 | public ProductDto findProductById(@PathVariable Long id) { 27 | return simpleProductService.findById(id); 28 | } 29 | 30 | @RequestMapping(value = "/products", method = RequestMethod.GET) 31 | public List findProducts( 32 | @RequestParam(required = false) String name 33 | ) { 34 | if (null == name) 35 | return simpleProductService.findAll(); 36 | 37 | return simpleProductService.findByNameContaining(name); 38 | } 39 | 40 | @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) 41 | public ProductDto updateProduct( 42 | @PathVariable Long id, 43 | @RequestBody ProductDto productDto 44 | ) { 45 | productDto.setId(id); 46 | return simpleProductService.update(productDto); 47 | } 48 | 49 | @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) 50 | public void deleteProduct(@PathVariable Long id) { 51 | simpleProductService.delete(id); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /12-1/src/main/java/kr/co/hanbit/product/management/presentation/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | public class ProductDto { 6 | private Long id; 7 | 8 | @NotNull 9 | private String name; 10 | 11 | @NotNull 12 | private Integer price; 13 | 14 | @NotNull 15 | private Integer amount; 16 | 17 | public Long getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public Integer getPrice() { 30 | return price; 31 | } 32 | 33 | public Integer getAmount() { 34 | return amount; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /12-1/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/product_management 2 | spring.datasource.username=root 3 | spring.datasource.password=hanbit 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -------------------------------------------------------------------------------- /12-1/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 2 | -------------------------------------------------------------------------------- /12-1/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=test 2 | -------------------------------------------------------------------------------- /12-1/src/test/java/kr/co/hanbit/product/management/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /12-2/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.7/reference/htmlsingle/#boot-features-developing-web-applications) 10 | 11 | ### Guides 12 | The following guides illustrate how to use some features concretely: 13 | 14 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 15 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 16 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 17 | 18 | -------------------------------------------------------------------------------- /12-2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co.hanbit 12 | product.management 13 | 0.0.1-SNAPSHOT 14 | product.management 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-jdbc 39 | 40 | 41 | mysql 42 | mysql-connector-java 43 | runtime 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/Application.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | //import org.modelmapper.ModelMapper; 4 | //import org.modelmapper.config.Configuration; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Profile; 10 | 11 | import javax.sql.DataSource; 12 | import java.sql.Connection; 13 | 14 | @SpringBootApplication 15 | public class Application { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | 21 | // @Bean 22 | // public ModelMapper modelMapper() { 23 | // ModelMapper modelMapper = new ModelMapper(); 24 | // modelMapper.getConfiguration() 25 | // .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) 26 | // .setFieldMatchingEnabled(true); 27 | // return modelMapper; 28 | // } 29 | 30 | @Bean 31 | @Profile("prod") 32 | public ApplicationRunner runner(DataSource dataSource) { 33 | return args -> { 34 | // 이 부분에 실행할 코드를 넣으면 된다. 35 | Connection connection = dataSource.getConnection(); 36 | }; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.domain.ProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class SimpleProductService { 13 | 14 | private ProductRepository productRepository; 15 | private ValidationService validationService; 16 | 17 | @Autowired 18 | SimpleProductService(ProductRepository productRepository, ValidationService validationService 19 | ) { 20 | this.productRepository = productRepository; 21 | this.validationService = validationService; 22 | } 23 | 24 | public ProductDto add(ProductDto productDto) { 25 | Product product = ProductDto.toEntity(productDto); 26 | validationService.checkValid(product); 27 | 28 | Product savedProduct = productRepository.add(product); 29 | ProductDto savedProductDto = ProductDto.toDto(savedProduct); 30 | return savedProductDto; 31 | } 32 | 33 | public ProductDto findById(Long id) { 34 | Product product = productRepository.findById(id); 35 | ProductDto productDto = ProductDto.toDto(product); 36 | return productDto; 37 | } 38 | 39 | public List findAll() { 40 | List products = productRepository.findAll(); 41 | List productDtos = products.stream() 42 | .map(product -> ProductDto.toDto(product)) 43 | .toList(); 44 | return productDtos; 45 | } 46 | 47 | public List findByNameContaining(String name) { 48 | List products = productRepository.findByNameContaining(name); 49 | List productDtos = products.stream() 50 | .map(product -> ProductDto.toDto(product)) 51 | .toList(); 52 | return productDtos; 53 | } 54 | 55 | public ProductDto update(ProductDto productDto) { 56 | Product product = ProductDto.toEntity(productDto); 57 | Product updatedProduct = productRepository.update(product); 58 | ProductDto updatedProductDto = ProductDto.toDto(updatedProduct); 59 | return updatedProductDto; 60 | } 61 | 62 | public void delete(Long id) { 63 | productRepository.delete(id); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/application/ValidationService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.validation.annotation.Validated; 5 | 6 | import jakarta.validation.Valid; 7 | 8 | @Service 9 | @Validated 10 | public class ValidationService { 11 | public void checkValid(@Valid T validationTarget) { 12 | // do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/domain/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/domain/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.Size; 6 | import java.util.Objects; 7 | 8 | public class Product { 9 | private Long id; 10 | 11 | @Size(min = 1, max = 100) 12 | private String name; 13 | 14 | @Max(1_000_000) 15 | @Min(0) 16 | private Integer price; 17 | 18 | @Max(9_999) 19 | @Min(0) 20 | private Integer amount; 21 | 22 | public Product() { 23 | } 24 | 25 | public Product(Long id, String name, Integer price, Integer amount) { 26 | this.id = id; 27 | this.name = name; 28 | this.price = price; 29 | this.amount = amount; 30 | } 31 | 32 | public Long getId() { 33 | return id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public Integer getPrice() { 41 | return price; 42 | } 43 | 44 | public Integer getAmount() { 45 | return amount; 46 | } 47 | 48 | public void setId(Long id) { 49 | this.id = id; 50 | } 51 | 52 | public void setName(String name) { 53 | this.name = name; 54 | } 55 | 56 | public void setPrice(Integer price) { 57 | this.price = price; 58 | } 59 | 60 | public void setAmount(Integer amount) { 61 | this.amount = amount; 62 | } 63 | 64 | public Boolean sameId(Long id) { 65 | return this.id.equals(id); 66 | } 67 | 68 | public Boolean containsName(String name) { 69 | return this.name.contains(name); 70 | } 71 | 72 | @Override 73 | public boolean equals(Object o) { 74 | if (this == o) return true; 75 | if (o == null || getClass() != o.getClass()) return false; 76 | Product product = (Product) o; 77 | return Objects.equals(id, product.id); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import java.util.List; 4 | 5 | public interface ProductRepository { 6 | Product add(Product product); 7 | Product findById(Long id); 8 | List findAll(); 9 | List findByNameContaining(String name); 10 | Product update(Product product); 11 | void delete(Long id); 12 | } 13 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.domain.Product; 5 | import kr.co.hanbit.product.management.domain.ProductRepository; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | import java.util.concurrent.atomic.AtomicLong; 12 | 13 | @Repository 14 | @Profile("test") 15 | public class ListProductRepository implements ProductRepository { 16 | 17 | private List products = new CopyOnWriteArrayList<>(); 18 | private AtomicLong sequence = new AtomicLong(1L); 19 | 20 | public Product add(Product product) { 21 | product.setId(sequence.getAndAdd(1L)); 22 | 23 | products.add(product); 24 | return product; 25 | } 26 | 27 | public Product findById(Long id) { 28 | return products.stream() 29 | .filter(product -> product.sameId(id)) 30 | .findFirst() 31 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 32 | } 33 | 34 | public List findAll() { 35 | return products; 36 | } 37 | 38 | public List findByNameContaining(String name) { 39 | return products.stream() 40 | .filter(product -> product.containsName(name)) 41 | .toList(); 42 | } 43 | 44 | public Product update(Product product) { 45 | Integer indexToModify = products.indexOf(product); 46 | products.set(indexToModify, product); 47 | return product; 48 | } 49 | 50 | public void delete(Long id) { 51 | Product product = this.findById(id); 52 | products.remove(product); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/presentation/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import java.util.List; 4 | 5 | public class ErrorMessage { 6 | 7 | private List errors; 8 | 9 | public ErrorMessage(List errors) { 10 | this.errors = errors; 11 | } 12 | 13 | public List getErrors() { 14 | return errors; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/presentation/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import jakarta.validation.ConstraintViolation; 12 | import jakarta.validation.ConstraintViolationException; 13 | import jakarta.validation.Path; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | @RestControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | @ExceptionHandler(ConstraintViolationException.class) 22 | public ResponseEntity handleConstraintViolatedException( 23 | ConstraintViolationException ex 24 | ) { 25 | Set> constraintViolations = ex.getConstraintViolations(); 26 | List errors = constraintViolations.stream() 27 | .map( 28 | constraintViolation -> 29 | extractField(constraintViolation.getPropertyPath()) + ", " + constraintViolation.getMessage() 30 | ) 31 | .toList(); 32 | 33 | ErrorMessage errorMessage = new ErrorMessage(errors); 34 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 35 | } 36 | 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public ResponseEntity handleMethodArgumentNotValidException( 39 | MethodArgumentNotValidException ex 40 | ) { 41 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 42 | List errors = fieldErrors.stream() 43 | .map( 44 | fieldError -> 45 | fieldError.getField() + ", " + fieldError.getDefaultMessage() 46 | ) 47 | .toList(); 48 | 49 | ErrorMessage errorMessage = new ErrorMessage(errors); 50 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 51 | } 52 | 53 | @ExceptionHandler(EntityNotFoundException.class) 54 | public ResponseEntity handleEntityNotFoundExceptionException( 55 | EntityNotFoundException ex 56 | ) { 57 | List errors = new ArrayList<>(); 58 | errors.add(ex.getMessage()); 59 | 60 | ErrorMessage errorMessage = new ErrorMessage(errors); 61 | return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND); 62 | } 63 | 64 | private String extractField(Path path) { 65 | String[] splittedArray = path.toString().split("[.]"); 66 | int lastIndex = splittedArray.length - 1; 67 | return splittedArray[lastIndex]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/presentation/ProductController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.application.SimpleProductService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import jakarta.validation.Valid; 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | 13 | private SimpleProductService simpleProductService; 14 | 15 | @Autowired 16 | ProductController(SimpleProductService simpleProductService) { 17 | this.simpleProductService = simpleProductService; 18 | } 19 | 20 | @RequestMapping(value = "/products", method = RequestMethod.POST) 21 | public ProductDto createProduct(@Valid @RequestBody ProductDto productDto) { 22 | return simpleProductService.add(productDto); 23 | } 24 | 25 | @RequestMapping(value = "/products/{id}", method = RequestMethod.GET) 26 | public ProductDto findProductById(@PathVariable Long id) { 27 | return simpleProductService.findById(id); 28 | } 29 | 30 | @RequestMapping(value = "/products", method = RequestMethod.GET) 31 | public List findProducts( 32 | @RequestParam(required = false) String name 33 | ) { 34 | if (null == name) 35 | return simpleProductService.findAll(); 36 | 37 | return simpleProductService.findByNameContaining(name); 38 | } 39 | 40 | @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) 41 | public ProductDto updateProduct( 42 | @PathVariable Long id, 43 | @RequestBody ProductDto productDto 44 | ) { 45 | productDto.setId(id); 46 | return simpleProductService.update(productDto); 47 | } 48 | 49 | @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) 50 | public void deleteProduct(@PathVariable Long id) { 51 | simpleProductService.delete(id); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /12-2/src/main/java/kr/co/hanbit/product/management/presentation/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | public class ProductDto { 8 | private Long id; 9 | 10 | @NotNull 11 | private String name; 12 | 13 | @NotNull 14 | private Integer price; 15 | 16 | @NotNull 17 | private Integer amount; 18 | 19 | public ProductDto() { 20 | } 21 | 22 | public ProductDto(String name, Integer price, Integer amount) { 23 | this.name = name; 24 | this.price = price; 25 | this.amount = amount; 26 | } 27 | 28 | public ProductDto(Long id, String name, Integer price, Integer amount) { 29 | this.id = id; 30 | this.name = name; 31 | this.price = price; 32 | this.amount = amount; 33 | } 34 | 35 | public Long getId() { 36 | return id; 37 | } 38 | 39 | public void setId(Long id) { 40 | this.id = id; 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | public Integer getPrice() { 48 | return price; 49 | } 50 | 51 | public Integer getAmount() { 52 | return amount; 53 | } 54 | 55 | public static Product toEntity(ProductDto productDto) { 56 | Product product = new Product( 57 | productDto.getId(), 58 | productDto.getName(), 59 | productDto.getPrice(), 60 | productDto.getAmount() 61 | ); 62 | 63 | return product; 64 | } 65 | 66 | public static ProductDto toDto(Product product) { 67 | ProductDto productDto = new ProductDto( 68 | product.getId(), 69 | product.getName(), 70 | product.getPrice(), 71 | product.getAmount() 72 | ); 73 | 74 | return productDto; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /12-2/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/product_management 2 | spring.datasource.username=root 3 | spring.datasource.password=hanbit 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -------------------------------------------------------------------------------- /12-2/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 2 | -------------------------------------------------------------------------------- /12-2/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=test 2 | -------------------------------------------------------------------------------- /12-2/src/test/java/kr/co/hanbit/product/management/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /12-2/src/test/java/kr/co/hanbit/product/management/application/SimpleProductServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.presentation.ProductDto; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | @SpringBootTest 15 | @ActiveProfiles("test") 16 | class SimpleProductServiceTest { 17 | 18 | @Autowired 19 | SimpleProductService simpleProductService; 20 | 21 | // @Transactional 22 | @Test 23 | @DisplayName("상품을 추가한 후 id로 조회하면 해당 상품이 조회되어야한다.") 24 | void productAddAndFindByIdTest () { 25 | ProductDto productDto = new ProductDto("연필", 300, 20); 26 | 27 | ProductDto savedProductDto = simpleProductService.add(productDto); 28 | Long savedProductId = savedProductDto.getId(); 29 | 30 | ProductDto foundProductDto = simpleProductService.findById(savedProductId); 31 | 32 | assertTrue(savedProductDto.getId().equals(foundProductDto.getId())); 33 | assertTrue(savedProductDto.getName().equals(foundProductDto.getName())); 34 | assertTrue(savedProductDto.getPrice().equals(foundProductDto.getPrice())); 35 | assertTrue(savedProductDto.getAmount().equals(foundProductDto.getAmount())); 36 | } 37 | 38 | @Test 39 | @DisplayName("존재하지 않는 상품 id로 조회하면 EntityNotFoundException이 발생해야한다.") 40 | void findProductNotExistIdTest () { 41 | Long notExistId = -1L; 42 | 43 | assertThrows(EntityNotFoundException.class, () -> { 44 | simpleProductService.findById(notExistId); 45 | }); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /12-3/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.7/reference/htmlsingle/#boot-features-developing-web-applications) 10 | 11 | ### Guides 12 | The following guides illustrate how to use some features concretely: 13 | 14 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 15 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 16 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 17 | 18 | -------------------------------------------------------------------------------- /12-3/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co.hanbit 12 | product.management 13 | 0.0.1-SNAPSHOT 14 | product.management 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-jdbc 39 | 40 | 41 | mysql 42 | mysql-connector-java 43 | runtime 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/Application.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | //import org.modelmapper.ModelMapper; 4 | //import org.modelmapper.config.Configuration; 5 | import org.springframework.boot.ApplicationRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Profile; 10 | 11 | import javax.sql.DataSource; 12 | import java.sql.Connection; 13 | 14 | @SpringBootApplication 15 | public class Application { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | 21 | // @Bean 22 | // public ModelMapper modelMapper() { 23 | // ModelMapper modelMapper = new ModelMapper(); 24 | // modelMapper.getConfiguration() 25 | // .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) 26 | // .setFieldMatchingEnabled(true); 27 | // return modelMapper; 28 | // } 29 | 30 | @Bean 31 | @Profile("prod") 32 | public ApplicationRunner runner(DataSource dataSource) { 33 | return args -> { 34 | // 이 부분에 실행할 코드를 넣으면 된다. 35 | Connection connection = dataSource.getConnection(); 36 | }; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.domain.ProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class SimpleProductService { 13 | 14 | private ProductRepository productRepository; 15 | private ValidationService validationService; 16 | 17 | @Autowired 18 | SimpleProductService(ProductRepository productRepository, ValidationService validationService 19 | ) { 20 | this.productRepository = productRepository; 21 | this.validationService = validationService; 22 | } 23 | 24 | public ProductDto add(ProductDto productDto) { 25 | Product product = ProductDto.toEntity(productDto); 26 | validationService.checkValid(product); 27 | 28 | Product savedProduct = productRepository.add(product); 29 | ProductDto savedProductDto = ProductDto.toDto(savedProduct); 30 | return savedProductDto; 31 | } 32 | 33 | public ProductDto findById(Long id) { 34 | Product product = productRepository.findById(id); 35 | ProductDto productDto = ProductDto.toDto(product); 36 | return productDto; 37 | } 38 | 39 | public List findAll() { 40 | List products = productRepository.findAll(); 41 | List productDtos = products.stream() 42 | .map(product -> ProductDto.toDto(product)) 43 | .toList(); 44 | return productDtos; 45 | } 46 | 47 | public List findByNameContaining(String name) { 48 | List products = productRepository.findByNameContaining(name); 49 | List productDtos = products.stream() 50 | .map(product -> ProductDto.toDto(product)) 51 | .toList(); 52 | return productDtos; 53 | } 54 | 55 | public ProductDto update(ProductDto productDto) { 56 | Product product = ProductDto.toEntity(productDto); 57 | Product updatedProduct = productRepository.update(product); 58 | ProductDto updatedProductDto = ProductDto.toDto(updatedProduct); 59 | return updatedProductDto; 60 | } 61 | 62 | public void delete(Long id) { 63 | productRepository.delete(id); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/application/ValidationService.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.validation.annotation.Validated; 5 | 6 | import jakarta.validation.Valid; 7 | 8 | @Service 9 | @Validated 10 | public class ValidationService { 11 | public void checkValid(@Valid T validationTarget) { 12 | // do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/domain/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/domain/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.Size; 6 | import java.util.Objects; 7 | 8 | public class Product { 9 | private Long id; 10 | 11 | @Size(min = 1, max = 100) 12 | private String name; 13 | 14 | @Max(1_000_000) 15 | @Min(0) 16 | private Integer price; 17 | 18 | @Max(9_999) 19 | @Min(0) 20 | private Integer amount; 21 | 22 | public Product() { 23 | } 24 | 25 | public Product(Long id, String name, Integer price, Integer amount) { 26 | this.id = id; 27 | this.name = name; 28 | this.price = price; 29 | this.amount = amount; 30 | } 31 | 32 | public Long getId() { 33 | return id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public Integer getPrice() { 41 | return price; 42 | } 43 | 44 | public Integer getAmount() { 45 | return amount; 46 | } 47 | 48 | public void setId(Long id) { 49 | this.id = id; 50 | } 51 | 52 | public void setName(String name) { 53 | this.name = name; 54 | } 55 | 56 | public void setPrice(Integer price) { 57 | this.price = price; 58 | } 59 | 60 | public void setAmount(Integer amount) { 61 | this.amount = amount; 62 | } 63 | 64 | public Boolean sameId(Long id) { 65 | return this.id.equals(id); 66 | } 67 | 68 | public Boolean containsName(String name) { 69 | return this.name.contains(name); 70 | } 71 | 72 | @Override 73 | public boolean equals(Object o) { 74 | if (this == o) return true; 75 | if (o == null || getClass() != o.getClass()) return false; 76 | Product product = (Product) o; 77 | return Objects.equals(id, product.id); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.domain; 2 | 3 | import java.util.List; 4 | 5 | public interface ProductRepository { 6 | Product add(Product product); 7 | Product findById(Long id); 8 | List findAll(); 9 | List findByNameContaining(String name); 10 | Product update(Product product); 11 | void delete(Long id); 12 | } 13 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.infrastructure; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.domain.Product; 5 | import kr.co.hanbit.product.management.domain.ProductRepository; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | import java.util.concurrent.atomic.AtomicLong; 12 | 13 | @Repository 14 | @Profile("test") 15 | public class ListProductRepository implements ProductRepository { 16 | 17 | private List products = new CopyOnWriteArrayList<>(); 18 | private AtomicLong sequence = new AtomicLong(1L); 19 | 20 | public Product add(Product product) { 21 | product.setId(sequence.getAndAdd(1L)); 22 | 23 | products.add(product); 24 | return product; 25 | } 26 | 27 | public Product findById(Long id) { 28 | return products.stream() 29 | .filter(product -> product.sameId(id)) 30 | .findFirst() 31 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 32 | } 33 | 34 | public List findAll() { 35 | return products; 36 | } 37 | 38 | public List findByNameContaining(String name) { 39 | return products.stream() 40 | .filter(product -> product.containsName(name)) 41 | .toList(); 42 | } 43 | 44 | public Product update(Product product) { 45 | Integer indexToModify = products.indexOf(product); 46 | products.set(indexToModify, product); 47 | return product; 48 | } 49 | 50 | public void delete(Long id) { 51 | Product product = this.findById(id); 52 | products.remove(product); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/presentation/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import java.util.List; 4 | 5 | public class ErrorMessage { 6 | 7 | private List errors; 8 | 9 | public ErrorMessage(List errors) { 10 | this.errors = errors; 11 | } 12 | 13 | public List getErrors() { 14 | return errors; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/presentation/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import jakarta.validation.ConstraintViolation; 12 | import jakarta.validation.ConstraintViolationException; 13 | import jakarta.validation.Path; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | @RestControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | @ExceptionHandler(ConstraintViolationException.class) 22 | public ResponseEntity handleConstraintViolatedException( 23 | ConstraintViolationException ex 24 | ) { 25 | Set> constraintViolations = ex.getConstraintViolations(); 26 | List errors = constraintViolations.stream() 27 | .map( 28 | constraintViolation -> 29 | extractField(constraintViolation.getPropertyPath()) + ", " + constraintViolation.getMessage() 30 | ) 31 | .toList(); 32 | 33 | ErrorMessage errorMessage = new ErrorMessage(errors); 34 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 35 | } 36 | 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public ResponseEntity handleMethodArgumentNotValidException( 39 | MethodArgumentNotValidException ex 40 | ) { 41 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 42 | List errors = fieldErrors.stream() 43 | .map( 44 | fieldError -> 45 | fieldError.getField() + ", " + fieldError.getDefaultMessage() 46 | ) 47 | .toList(); 48 | 49 | ErrorMessage errorMessage = new ErrorMessage(errors); 50 | return new ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST); 51 | } 52 | 53 | @ExceptionHandler(EntityNotFoundException.class) 54 | public ResponseEntity handleEntityNotFoundExceptionException( 55 | EntityNotFoundException ex 56 | ) { 57 | List errors = new ArrayList<>(); 58 | errors.add(ex.getMessage()); 59 | 60 | ErrorMessage errorMessage = new ErrorMessage(errors); 61 | return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND); 62 | } 63 | 64 | private String extractField(Path path) { 65 | String[] splittedArray = path.toString().split("[.]"); 66 | int lastIndex = splittedArray.length - 1; 67 | return splittedArray[lastIndex]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/presentation/ProductController.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.application.SimpleProductService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import jakarta.validation.Valid; 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | 13 | private SimpleProductService simpleProductService; 14 | 15 | @Autowired 16 | ProductController(SimpleProductService simpleProductService) { 17 | this.simpleProductService = simpleProductService; 18 | } 19 | 20 | @RequestMapping(value = "/products", method = RequestMethod.POST) 21 | public ProductDto createProduct(@Valid @RequestBody ProductDto productDto) { 22 | return simpleProductService.add(productDto); 23 | } 24 | 25 | @RequestMapping(value = "/products/{id}", method = RequestMethod.GET) 26 | public ProductDto findProductById(@PathVariable Long id) { 27 | return simpleProductService.findById(id); 28 | } 29 | 30 | @RequestMapping(value = "/products", method = RequestMethod.GET) 31 | public List findProducts( 32 | @RequestParam(required = false) String name 33 | ) { 34 | if (null == name) 35 | return simpleProductService.findAll(); 36 | 37 | return simpleProductService.findByNameContaining(name); 38 | } 39 | 40 | @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) 41 | public ProductDto updateProduct( 42 | @PathVariable Long id, 43 | @RequestBody ProductDto productDto 44 | ) { 45 | productDto.setId(id); 46 | return simpleProductService.update(productDto); 47 | } 48 | 49 | @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) 50 | public void deleteProduct(@PathVariable Long id) { 51 | simpleProductService.delete(id); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /12-3/src/main/java/kr/co/hanbit/product/management/presentation/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.presentation; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | public class ProductDto { 8 | private Long id; 9 | 10 | @NotNull 11 | private String name; 12 | 13 | @NotNull 14 | private Integer price; 15 | 16 | @NotNull 17 | private Integer amount; 18 | 19 | public ProductDto() { 20 | } 21 | 22 | public ProductDto(String name, Integer price, Integer amount) { 23 | this.name = name; 24 | this.price = price; 25 | this.amount = amount; 26 | } 27 | 28 | public ProductDto(Long id, String name, Integer price, Integer amount) { 29 | this.id = id; 30 | this.name = name; 31 | this.price = price; 32 | this.amount = amount; 33 | } 34 | 35 | public Long getId() { 36 | return id; 37 | } 38 | 39 | public void setId(Long id) { 40 | this.id = id; 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | public Integer getPrice() { 48 | return price; 49 | } 50 | 51 | public Integer getAmount() { 52 | return amount; 53 | } 54 | 55 | public static Product toEntity(ProductDto productDto) { 56 | Product product = new Product( 57 | productDto.getId(), 58 | productDto.getName(), 59 | productDto.getPrice(), 60 | productDto.getAmount() 61 | ); 62 | 63 | return product; 64 | } 65 | 66 | public static ProductDto toDto(Product product) { 67 | ProductDto productDto = new ProductDto( 68 | product.getId(), 69 | product.getName(), 70 | product.getPrice(), 71 | product.getAmount() 72 | ); 73 | 74 | return productDto; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /12-3/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/product_management 2 | spring.datasource.username=root 3 | spring.datasource.password=hanbit 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -------------------------------------------------------------------------------- /12-3/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 2 | -------------------------------------------------------------------------------- /12-3/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=test 2 | -------------------------------------------------------------------------------- /12-3/src/test/java/kr/co/hanbit/product/management/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /12-3/src/test/java/kr/co/hanbit/product/management/application/SimpleProductServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.EntityNotFoundException; 4 | import kr.co.hanbit.product.management.presentation.ProductDto; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | @SpringBootTest 15 | @ActiveProfiles("test") 16 | class SimpleProductServiceTest { 17 | 18 | @Autowired 19 | SimpleProductService simpleProductService; 20 | 21 | // @Transactional 22 | @Test 23 | @DisplayName("상품을 추가한 후 id로 조회하면 해당 상품이 조회되어야한다.") 24 | void productAddAndFindByIdTest () { 25 | ProductDto productDto = new ProductDto("연필", 300, 20); 26 | 27 | ProductDto savedProductDto = simpleProductService.add(productDto); 28 | Long savedProductId = savedProductDto.getId(); 29 | 30 | ProductDto foundProductDto = simpleProductService.findById(savedProductId); 31 | 32 | assertTrue(savedProductDto.getId().equals(foundProductDto.getId())); 33 | assertTrue(savedProductDto.getName().equals(foundProductDto.getName())); 34 | assertTrue(savedProductDto.getPrice().equals(foundProductDto.getPrice())); 35 | assertTrue(savedProductDto.getAmount().equals(foundProductDto.getAmount())); 36 | } 37 | 38 | @Test 39 | @DisplayName("존재하지 않는 상품 id로 조회하면 EntityNotFoundException이 발생해야한다.") 40 | void findProductNotExistIdTest () { 41 | Long notExistId = -1L; 42 | 43 | assertThrows(EntityNotFoundException.class, () -> { 44 | simpleProductService.findById(notExistId); 45 | }); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /12-3/src/test/java/kr/co/hanbit/product/management/application/SimpleProductServiceUnitTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.hanbit.product.management.application; 2 | 3 | import kr.co.hanbit.product.management.domain.Product; 4 | import kr.co.hanbit.product.management.domain.ProductRepository; 5 | import kr.co.hanbit.product.management.presentation.ProductDto; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | import static org.mockito.Mockito.*; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | @ExtendWith(MockitoExtension.class) 17 | public class SimpleProductServiceUnitTest { 18 | 19 | @Mock 20 | private ProductRepository productRepository; 21 | 22 | @Mock 23 | private ValidationService validationService; 24 | 25 | @InjectMocks 26 | private SimpleProductService simpleProductService; 27 | 28 | @Test 29 | @DisplayName("상품 추가 후에는 추가된 상품이 반환되어야한다.") 30 | void productAddTest () { 31 | ProductDto productDto = new ProductDto("연필", 300, 20); 32 | Long PRODUCT_ID = 1L; 33 | 34 | Product product = ProductDto.toEntity(productDto); 35 | product.setId(PRODUCT_ID); 36 | when(productRepository.add(any())).thenReturn(product); 37 | 38 | ProductDto savedProductDto = simpleProductService.add(productDto); 39 | 40 | assertTrue(savedProductDto.getId().equals(PRODUCT_ID)); 41 | assertTrue(savedProductDto.getName().equals(productDto.getName())); 42 | assertTrue(savedProductDto.getPrice().equals(productDto.getPrice())); 43 | assertTrue(savedProductDto.getAmount().equals(productDto.getAmount())); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /13/README.md: -------------------------------------------------------------------------------- 1 | # shorten-url-service 2 | 단축 URL 서비스 레포지토리 3 | 4 | ### 요구사항 5 | 1. [bitly](https://bitly.com/) 같은 단축 URL 서비스를 만들어야 합니다. 6 | 2. 단축된 URL의 키(Key)는 8글자로 생성되어야 합니다. '단축된 URL의 키'는 'https://bit.ly/3onGWgK' 에서 경로(Path)에 해당하는 '3onGWgK'를 의미합니다. bitly에서는 7글자의 키를 사용합니다. 7 | 3. 키 생성 알고리즘은 자유롭게 구현하시면 됩니다. 8 | 4. 단축된 URL로 사용자가 요청하면 원래의 URL로 리다이렉트(Redirect)되어야 합니다. 9 | 5. 원래의 URL로 다시 단축 URL을 생성해도 항상 새로운 단축 URL이 생성되어야 합니다. 이때 기존에 생성되었던 단축 URL도 여전히 동작해야 합니다. 10 | 6. 단축된 URL -> 원본 URL로 리다이렉트 될 때마다 카운트가 증가되어야하고, 해당 정보를 확인할 수 있는 API가 있어야합니다. 11 | 7. 데이터베이스 없이 컬렉션을 활용하여 데이터를 저장해야합니다. 12 | 8. 기능이 정상 동작하는 것을 확인할 수 있는 적절한 테스트 코드가 있어야 합니다. 13 | 9. (선택) 해당 서비스를 사용할 수 있는 UI 페이지를 구현해주세요. 14 | 15 | ### 필요 API 16 | **경로, HTTP 메서드, 파라미터 전달은 여러분의 의도에 맞게 적절히 구현해주세요.** 17 | 18 | 1. 단축 URL 생성 API 19 | 2. 단축 URL 리다이렉트 API 20 | 3. 단축 URL 정보 조회 API 21 | -------------------------------------------------------------------------------- /13/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co 12 | shortenurlservice 13 | 0.0.1-SNAPSHOT 14 | shortenurlservice 15 | Shorten Url Service 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-validation 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-test 32 | test 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-maven-plugin 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/ShortenurlserviceApplication.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ShortenurlserviceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ShortenurlserviceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/application/SimpleShortenUrlService.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.application; 2 | 3 | import kr.co.shortenurlservice.domain.LackOfShortenUrlKeyException; 4 | import kr.co.shortenurlservice.domain.NotFoundShortenUrlException; 5 | import kr.co.shortenurlservice.domain.ShortenUrl; 6 | import kr.co.shortenurlservice.domain.ShortenUrlRepository; 7 | import kr.co.shortenurlservice.presentation.ShortenUrlCreateRequestDto; 8 | import kr.co.shortenurlservice.presentation.ShortenUrlCreateResponseDto; 9 | import kr.co.shortenurlservice.presentation.ShortenUrlInformationDto; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class SimpleShortenUrlService { 15 | 16 | private ShortenUrlRepository shortenUrlRepository; 17 | 18 | @Autowired 19 | SimpleShortenUrlService(ShortenUrlRepository shortenUrlRepository) { 20 | this.shortenUrlRepository = shortenUrlRepository; 21 | } 22 | 23 | public ShortenUrlCreateResponseDto generateShortenUrl(ShortenUrlCreateRequestDto shortenUrlCreateRequestDto) { 24 | String originalUrl = shortenUrlCreateRequestDto.getOriginalUrl(); 25 | String shortenUrlKey = getUniqueShortenUrlKey(); 26 | 27 | ShortenUrl shortenUrl = new ShortenUrl(originalUrl, shortenUrlKey); 28 | shortenUrlRepository.saveShortenUrl(shortenUrl); 29 | 30 | ShortenUrlCreateResponseDto shortenUrlCreateResponseDto = new ShortenUrlCreateResponseDto(shortenUrl); 31 | return shortenUrlCreateResponseDto; 32 | } 33 | 34 | public String getOriginalUrlByShortenUrlKey(String shortenUrlKey) { 35 | ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey); 36 | 37 | if(null == shortenUrl) 38 | throw new NotFoundShortenUrlException(); 39 | 40 | shortenUrl.increaseRedirectCount(); 41 | shortenUrlRepository.saveShortenUrl(shortenUrl); 42 | 43 | String originalUrl = shortenUrl.getOriginalUrl(); 44 | 45 | return originalUrl; 46 | } 47 | 48 | public ShortenUrlInformationDto getShortenUrlInformationByShortenUrlKey(String shortenUrlKey) { 49 | ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey); 50 | 51 | if(null == shortenUrl) 52 | throw new NotFoundShortenUrlException(); 53 | 54 | ShortenUrlInformationDto shortenUrlInformationDto = new ShortenUrlInformationDto(shortenUrl); 55 | 56 | return shortenUrlInformationDto; 57 | } 58 | 59 | private String getUniqueShortenUrlKey() { 60 | final int MAX_RETRY_COUNT = 5; 61 | int count = 0; 62 | 63 | while(count++ < MAX_RETRY_COUNT) { 64 | String shortenUrlKey = ShortenUrl.generateShortenUrlKey(); 65 | ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey); 66 | 67 | if(null == shortenUrl) 68 | return shortenUrlKey; 69 | } 70 | 71 | throw new LackOfShortenUrlKeyException(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/domain/LackOfShortenUrlKeyException.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.domain; 2 | 3 | public class LackOfShortenUrlKeyException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/domain/NotFoundShortenUrlException.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.domain; 2 | 3 | public class NotFoundShortenUrlException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/domain/ShortenUrl.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.domain; 2 | 3 | import java.util.Random; 4 | 5 | public class ShortenUrl { 6 | private String originalUrl; 7 | private String shortenUrlKey; 8 | private Long redirectCount; 9 | 10 | public ShortenUrl(String originalUrl, String shortenUrlKey) { 11 | this.originalUrl = originalUrl; 12 | this.shortenUrlKey = shortenUrlKey; 13 | this.redirectCount = 0L; 14 | } 15 | 16 | public String getOriginalUrl() { 17 | return originalUrl; 18 | } 19 | 20 | public String getShortenUrlKey() { 21 | return shortenUrlKey; 22 | } 23 | 24 | public Long getRedirectCount() { 25 | return redirectCount; 26 | } 27 | 28 | public void increaseRedirectCount() { 29 | this.redirectCount = this.redirectCount + 1; 30 | } 31 | 32 | public static String generateShortenUrlKey() { 33 | String base56Characters = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz"; 34 | Random random = new Random(); 35 | StringBuilder shortenUrlKey = new StringBuilder(); 36 | 37 | for(int count = 0; count < 8; count++) { 38 | int base56CharactersIndex = random.nextInt(0, base56Characters.length()); 39 | char base56Character = base56Characters.charAt(base56CharactersIndex); 40 | 41 | shortenUrlKey.append(base56Character); 42 | } 43 | 44 | return shortenUrlKey.toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/domain/ShortenUrlRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.domain; 2 | 3 | public interface ShortenUrlRepository { 4 | void saveShortenUrl(ShortenUrl shortenUrl); 5 | ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey); 6 | } 7 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/infrastructure/MapShortenUrlRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.infrastructure; 2 | 3 | import kr.co.shortenurlservice.domain.ShortenUrl; 4 | import kr.co.shortenurlservice.domain.ShortenUrlRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | @Repository 11 | public class MapShortenUrlRepository implements ShortenUrlRepository { 12 | 13 | private Map shortenUrls = new ConcurrentHashMap<>(); 14 | 15 | @Override 16 | public void saveShortenUrl(ShortenUrl shortenUrl) { 17 | shortenUrls.put(shortenUrl.getShortenUrlKey(), shortenUrl); 18 | } 19 | 20 | @Override 21 | public ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey) { 22 | ShortenUrl shortenUrl = shortenUrls.get(shortenUrlKey); 23 | return shortenUrl; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/presentation/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.presentation; 2 | 3 | import kr.co.shortenurlservice.domain.LackOfShortenUrlKeyException; 4 | import kr.co.shortenurlservice.domain.NotFoundShortenUrlException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | 10 | @RestControllerAdvice 11 | public class GlobalExceptionHandler { 12 | 13 | @ExceptionHandler(LackOfShortenUrlKeyException.class) 14 | public ResponseEntity handleLackOfShortenUrlKeyException( 15 | LackOfShortenUrlKeyException ex 16 | ) { 17 | // 개발자에게 알려줄 수 있는 수단 필요 18 | return new ResponseEntity<>("단축 URL 자원이 부족합니다.", HttpStatus.INTERNAL_SERVER_ERROR); 19 | } 20 | 21 | @ExceptionHandler(NotFoundShortenUrlException.class) 22 | public ResponseEntity handleNotFoundShortenUrlException( 23 | NotFoundShortenUrlException ex 24 | ) { 25 | return new ResponseEntity<>("단축 URL을 찾지 못했습니다.", HttpStatus.NOT_FOUND); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/presentation/ShortenUrlCreateRequestDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.presentation; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import org.hibernate.validator.constraints.URL; 5 | 6 | public class ShortenUrlCreateRequestDto { 7 | @NotNull 8 | @URL(regexp = "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)") 9 | private String originalUrl; 10 | 11 | public ShortenUrlCreateRequestDto() { 12 | } 13 | 14 | public ShortenUrlCreateRequestDto(String originalUrl) { 15 | this.originalUrl = originalUrl; 16 | } 17 | 18 | public String getOriginalUrl() { 19 | return originalUrl; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/presentation/ShortenUrlCreateResponseDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.presentation; 2 | 3 | import kr.co.shortenurlservice.domain.ShortenUrl; 4 | 5 | public class ShortenUrlCreateResponseDto { 6 | private String originalUrl; 7 | private String shortenUrlKey; 8 | 9 | public ShortenUrlCreateResponseDto(ShortenUrl shortenUrl) { 10 | this.originalUrl = shortenUrl.getOriginalUrl(); 11 | this.shortenUrlKey = shortenUrl.getShortenUrlKey(); 12 | } 13 | 14 | public String getOriginalUrl() { 15 | return originalUrl; 16 | } 17 | 18 | public String getShortenUrlKey() { 19 | return shortenUrlKey; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/presentation/ShortenUrlInformationDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.presentation; 2 | 3 | import kr.co.shortenurlservice.domain.ShortenUrl; 4 | 5 | public class ShortenUrlInformationDto { 6 | private String originalUrl; 7 | private String shortenUrlKey; 8 | private Long redirectCount; 9 | 10 | public ShortenUrlInformationDto(ShortenUrl shortenUrl) { 11 | this.originalUrl = shortenUrl.getOriginalUrl(); 12 | this.shortenUrlKey = shortenUrl.getShortenUrlKey(); 13 | this.redirectCount = shortenUrl.getRedirectCount(); 14 | } 15 | 16 | public String getOriginalUrl() { 17 | return originalUrl; 18 | } 19 | 20 | public String getShortenUrlKey() { 21 | return shortenUrlKey; 22 | } 23 | 24 | public Long getRedirectCount() { 25 | return redirectCount; 26 | } 27 | } -------------------------------------------------------------------------------- /13/src/main/java/kr/co/shortenurlservice/presentation/ShortenUrlRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.presentation; 2 | 3 | import kr.co.shortenurlservice.application.SimpleShortenUrlService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.http.HttpHeaders; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import jakarta.validation.Valid; 11 | import java.net.URI; 12 | import java.net.URISyntaxException; 13 | 14 | @RestController 15 | public class ShortenUrlRestController { 16 | 17 | private SimpleShortenUrlService simpleShortenUrlService; 18 | 19 | @Autowired 20 | ShortenUrlRestController(SimpleShortenUrlService simpleShortenUrlService) { 21 | this.simpleShortenUrlService = simpleShortenUrlService; 22 | } 23 | 24 | @RequestMapping(value = "/shortenUrl", method = RequestMethod.POST) 25 | public ResponseEntity createShortenUrl( 26 | @Valid @RequestBody ShortenUrlCreateRequestDto shortenUrlCreateRequestDto 27 | ) { 28 | ShortenUrlCreateResponseDto shortenUrlCreateResponseDto = 29 | simpleShortenUrlService.generateShortenUrl(shortenUrlCreateRequestDto); 30 | return ResponseEntity.ok(shortenUrlCreateResponseDto); 31 | } 32 | 33 | @RequestMapping(value = "/{shortenUrlKey}", method = RequestMethod.GET) 34 | public ResponseEntity redirectShortenUrl( 35 | @PathVariable String shortenUrlKey 36 | ) throws URISyntaxException { 37 | String originalUrl = simpleShortenUrlService.getOriginalUrlByShortenUrlKey(shortenUrlKey); 38 | 39 | URI redirectUri = new URI(originalUrl); 40 | HttpHeaders httpHeaders = new HttpHeaders(); 41 | httpHeaders.setLocation(redirectUri); 42 | 43 | return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY); 44 | } 45 | 46 | @RequestMapping(value = "/shortenUrl/{shortenUrlKey}", method = RequestMethod.GET) 47 | public ResponseEntity getShortenUrlInformation( 48 | @PathVariable String shortenUrlKey 49 | ) { 50 | ShortenUrlInformationDto shortenUrlInformationDto = 51 | simpleShortenUrlService.getShortenUrlInformationByShortenUrlKey(shortenUrlKey); 52 | return ResponseEntity.ok(shortenUrlInformationDto); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /13/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.mvc.static-path-pattern=/ui/** -------------------------------------------------------------------------------- /13/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | URL 단축 서비스 5 | 6 | 7 |

URL 단축 서비스

8 |
9 |
10 | 11 | 12 |
13 | 단축된 URL : 14 |
15 |
16 |
17 |
18 |

단축 URL 정보 조회

19 |
20 |
21 | 22 | 23 |
24 | 단축 URL 정보 : 25 |
26 | 83 | 84 | -------------------------------------------------------------------------------- /13/src/test/java/kr/co/shortenurlservice/ShortenurlserviceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ShortenurlserviceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /13/src/test/java/kr/co/shortenurlservice/application/SimpleShortenUrlServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.application; 2 | 3 | import kr.co.shortenurlservice.presentation.ShortenUrlCreateRequestDto; 4 | import kr.co.shortenurlservice.presentation.ShortenUrlCreateResponseDto; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | @SpringBootTest 13 | class SimpleShortenUrlServiceTest { 14 | 15 | @Autowired 16 | private SimpleShortenUrlService simpleShortenUrlService; 17 | 18 | @Test 19 | @DisplayName("URL을 단축한 후 단축된 URL 키로 조회하면 원래 URL이 조회되어야 한다.") 20 | void shortenUrlAddTest() { 21 | String expectedOriginalUrl = "https://www.hanbit.co.kr/"; 22 | ShortenUrlCreateRequestDto shortenUrlCreateRequestDto = new ShortenUrlCreateRequestDto(expectedOriginalUrl); 23 | 24 | ShortenUrlCreateResponseDto shortenUrlCreateResponseDto = simpleShortenUrlService.generateShortenUrl(shortenUrlCreateRequestDto); 25 | String shortenUrlKey = shortenUrlCreateResponseDto.getShortenUrlKey(); 26 | 27 | String originalUrl = simpleShortenUrlService.getOriginalUrlByShortenUrlKey(shortenUrlKey); 28 | 29 | assertTrue(originalUrl.equals(expectedOriginalUrl)); 30 | } 31 | 32 | // 존재하지 않는 단축 URL을 조회하는 경우는 여러분들이 테스트 작성 해보기 33 | 34 | } 35 | -------------------------------------------------------------------------------- /13/src/test/java/kr/co/shortenurlservice/application/SimpleShortenUrlServiceUnitTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.application; 2 | 3 | import kr.co.shortenurlservice.domain.LackOfShortenUrlKeyException; 4 | import kr.co.shortenurlservice.domain.ShortenUrl; 5 | import kr.co.shortenurlservice.domain.ShortenUrlRepository; 6 | import kr.co.shortenurlservice.presentation.ShortenUrlCreateRequestDto; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.mockito.junit.jupiter.MockitoExtension; 14 | 15 | import static org.mockito.Mockito.*; 16 | 17 | @ExtendWith(MockitoExtension.class) 18 | public class SimpleShortenUrlServiceUnitTest { 19 | 20 | @Mock 21 | private ShortenUrlRepository shortenUrlRepository; 22 | 23 | @InjectMocks 24 | private SimpleShortenUrlService simpleShortenUrlService; 25 | 26 | @Test 27 | @DisplayName("단축 URL이 계속 중복되면 LackOfShortenUrlKeyException 예외가 발생해야한다.") 28 | void throwLackOfShortenUrlKeyExceptionTest() { 29 | ShortenUrlCreateRequestDto shortenUrlCreateRequestDto = new ShortenUrlCreateRequestDto(null); 30 | 31 | when(shortenUrlRepository.findShortenUrlByShortenUrlKey(any())).thenReturn(new ShortenUrl(null, null)); 32 | 33 | Assertions.assertThrows(LackOfShortenUrlKeyException.class, () -> { 34 | simpleShortenUrlService.generateShortenUrl(shortenUrlCreateRequestDto); 35 | }); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /13/src/test/java/kr/co/shortenurlservice/presentation/ShortenUrlRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.shortenurlservice.presentation; 2 | 3 | import kr.co.shortenurlservice.application.SimpleShortenUrlService; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import static org.mockito.Mockito.any; 12 | import static org.mockito.Mockito.when; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | @WebMvcTest(controllers = ShortenUrlRestController.class) 18 | class ShortenUrlRestControllerTest { 19 | 20 | @MockBean 21 | private SimpleShortenUrlService simpleShortenUrlService; 22 | 23 | @Autowired 24 | private MockMvc mockMvc; 25 | 26 | @Test 27 | @DisplayName("원래의 URL로 리다이렉트 되어야한다.") 28 | void redirectTest() throws Exception { 29 | String expectedOriginalUrl = "https://www.hanbit.co.kr/"; 30 | 31 | when(simpleShortenUrlService.getOriginalUrlByShortenUrlKey(any())).thenReturn(expectedOriginalUrl); 32 | 33 | mockMvc.perform(get("/any-key")) 34 | .andExpect(status().isMovedPermanently()) 35 | .andExpect(header().string("Location", expectedOriginalUrl)); 36 | } 37 | 38 | // 없는 단축 URL을 조회하는 경우는 여러분들이 테스트 작성 해보기 (404로 떨어지나?) 39 | 40 | } -------------------------------------------------------------------------------- /14-1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co 12 | ordermanagement 13 | 0.0.1-SNAPSHOT 14 | ordermanagement 15 | 주문 관리 API 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/OrdermanagementApplication.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class OrdermanagementApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(OrdermanagementApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/application/SimpleOrderService.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.application; 2 | 3 | import kr.co.ordermanagement.domain.order.OrderRepository; 4 | import kr.co.ordermanagement.domain.product.ProductRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class SimpleOrderService { 10 | 11 | private ProductRepository productRepository; 12 | private OrderRepository orderRepository; 13 | 14 | @Autowired 15 | public SimpleOrderService(ProductRepository productRepository, OrderRepository orderRepository) { 16 | this.productRepository = productRepository; 17 | this.orderRepository = orderRepository; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.application; 2 | 3 | import kr.co.ordermanagement.domain.product.Product; 4 | import kr.co.ordermanagement.domain.product.ProductRepository; 5 | import kr.co.ordermanagement.presentation.dto.ProductDto; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class SimpleProductService { 13 | 14 | private ProductRepository productRepository; 15 | 16 | @Autowired 17 | SimpleProductService(ProductRepository productRepository) { 18 | this.productRepository = productRepository; 19 | } 20 | 21 | public List findAll() { 22 | List products = productRepository.findAll(); 23 | List productDtos = products.stream() 24 | .map(product -> ProductDto.toDto(product)) 25 | .toList(); 26 | return productDtos; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/domain/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/domain/order/Order.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.order; 2 | 3 | public class Order { 4 | } 5 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/domain/order/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.order; 2 | 3 | public interface OrderRepository { 4 | } 5 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/domain/product/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.product; 2 | 3 | import java.util.Objects; 4 | 5 | public class Product { 6 | private Long id; 7 | private String name; 8 | private Integer price; 9 | private Integer amount; 10 | 11 | public Product(Long id, String name, Integer price, Integer amount) { 12 | this.id = id; 13 | this.name = name; 14 | this.price = price; 15 | this.amount = amount; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public Integer getPrice() { 27 | return price; 28 | } 29 | 30 | public Integer getAmount() { 31 | return amount; 32 | } 33 | 34 | public Boolean sameId(Long id) { 35 | return this.id.equals(id); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | Product product = (Product) o; 43 | return Objects.equals(id, product.id); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/domain/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.product; 2 | 3 | import java.util.List; 4 | 5 | public interface ProductRepository { 6 | Product findById(Long id); 7 | List findAll(); 8 | void update(Product product); 9 | } 10 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/infrastructure/ListOrderRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.infrastructure; 2 | 3 | import kr.co.ordermanagement.domain.order.OrderRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public class ListOrderRepository implements OrderRepository { 8 | } 9 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.infrastructure; 2 | 3 | import kr.co.ordermanagement.domain.exception.EntityNotFoundException; 4 | import kr.co.ordermanagement.domain.product.Product; 5 | import kr.co.ordermanagement.domain.product.ProductRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import jakarta.annotation.PostConstruct; 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | 12 | @Repository 13 | public class ListProductRepository implements ProductRepository { 14 | 15 | private List products = new CopyOnWriteArrayList<>(); 16 | 17 | @PostConstruct 18 | void initProducts() { 19 | Product product1 = new Product(1L, "상품1", 10000, 100); 20 | Product product2 = new Product(2L, "상품2", 25000, 300); 21 | Product product3 = new Product(3L, "상품3", 30000, 500); 22 | 23 | products.add(product1); 24 | products.add(product2); 25 | products.add(product3); 26 | } 27 | 28 | @Override 29 | public Product findById(Long id) { 30 | return products.stream() 31 | .filter(product -> product.sameId(id)) 32 | .findFirst() 33 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 34 | } 35 | 36 | @Override 37 | public List findAll() { 38 | return products; 39 | } 40 | 41 | @Override 42 | public void update(Product product) { 43 | Integer indexToModify = products.indexOf(product); 44 | products.set(indexToModify, product); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/presentation/controller/ProductRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.controller; 2 | 3 | import kr.co.ordermanagement.application.SimpleProductService; 4 | import kr.co.ordermanagement.presentation.dto.ProductDto; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.List; 11 | 12 | @RestController 13 | public class ProductRestController { 14 | 15 | private SimpleProductService simpleProductService; 16 | 17 | @Autowired 18 | ProductRestController(SimpleProductService simpleProductService) { 19 | this.simpleProductService = simpleProductService; 20 | } 21 | 22 | @RequestMapping(value = "/products", method = RequestMethod.GET) 23 | public List findProducts() { 24 | return simpleProductService.findAll(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /14-1/src/main/java/kr/co/ordermanagement/presentation/dto/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | import kr.co.ordermanagement.domain.product.Product; 4 | 5 | public class ProductDto { 6 | private Long id; 7 | private String name; 8 | private Integer price; 9 | private Integer amount; 10 | 11 | public ProductDto(Long id, String name, Integer price, Integer amount) { 12 | this.id = id; 13 | this.name = name; 14 | this.price = price; 15 | this.amount = amount; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public Integer getPrice() { 27 | return price; 28 | } 29 | 30 | public Integer getAmount() { 31 | return amount; 32 | } 33 | 34 | public static ProductDto toDto(Product product) { 35 | ProductDto productDto = new ProductDto( 36 | product.getId(), 37 | product.getName(), 38 | product.getPrice(), 39 | product.getAmount() 40 | ); 41 | 42 | return productDto; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /14-1/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /14-2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | kr.co 12 | ordermanagement 13 | 0.0.1-SNAPSHOT 14 | ordermanagement 15 | 주문 관리 API 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/OrdermanagementApplication.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class OrdermanagementApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(OrdermanagementApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/application/SimpleProductService.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.application; 2 | 3 | import kr.co.ordermanagement.domain.product.Product; 4 | import kr.co.ordermanagement.domain.product.ProductRepository; 5 | import kr.co.ordermanagement.presentation.dto.ProductDto; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class SimpleProductService { 13 | 14 | private ProductRepository productRepository; 15 | 16 | @Autowired 17 | SimpleProductService(ProductRepository productRepository) { 18 | this.productRepository = productRepository; 19 | } 20 | 21 | public List findAll() { 22 | List products = productRepository.findAll(); 23 | List productDtos = products.stream() 24 | .map(product -> ProductDto.toDto(product)) 25 | .toList(); 26 | return productDtos; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/exception/CanNotCancellableStateException.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.exception; 2 | 3 | public class CanNotCancellableStateException extends RuntimeException { 4 | public CanNotCancellableStateException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/exception/NotEnoughAmountException.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.exception; 2 | 3 | public class NotEnoughAmountException extends RuntimeException { 4 | public NotEnoughAmountException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/order/Order.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.order; 2 | 3 | import java.util.List; 4 | 5 | public class Order { 6 | private Long id; 7 | private List orderedProducts; 8 | private Integer totalPrice; 9 | private State state; 10 | 11 | public Order(List orderedProducts) { 12 | this.orderedProducts = orderedProducts; 13 | this.totalPrice = calculateTotalPrice(orderedProducts); 14 | this.state = State.CREATED; 15 | } 16 | 17 | public Long getId() { 18 | return id; 19 | } 20 | 21 | public List getOrderedProducts() { 22 | return orderedProducts; 23 | } 24 | 25 | public Integer getTotalPrice() { 26 | return totalPrice; 27 | } 28 | 29 | public State getState() { 30 | return state; 31 | } 32 | 33 | public void setId(Long id) { 34 | this.id = id; 35 | } 36 | 37 | public Boolean sameId(Long id) { 38 | return this.id.equals(id); 39 | } 40 | 41 | public Boolean sameState(State state) { 42 | return this.state.equals(state); 43 | } 44 | 45 | public void changeStateForce(State state) { 46 | this.state = state; 47 | } 48 | 49 | public void cancel() { 50 | this.state.checkCancellable(); 51 | this.state = State.CANCELED; 52 | } 53 | 54 | private Integer calculateTotalPrice(List orderedProducts) { 55 | return orderedProducts 56 | .stream() 57 | .mapToInt(orderedProduct -> orderedProduct.getPrice() * orderedProduct.getAmount()) 58 | .sum(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/order/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.order; 2 | 3 | import java.util.List; 4 | 5 | public interface OrderRepository { 6 | Order add(Order order); 7 | Order findById(Long id); 8 | List findByState(State state); 9 | } 10 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/order/OrderedProduct.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.order; 2 | 3 | public class OrderedProduct { 4 | private Long id; 5 | private String name; 6 | private Integer price; 7 | private Integer amount; 8 | 9 | public OrderedProduct(Long id, String name, Integer price, Integer amount) { 10 | this.id = id; 11 | this.name = name; 12 | this.price = price; 13 | this.amount = amount; 14 | } 15 | 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public Integer getPrice() { 25 | return price; 26 | } 27 | 28 | public Integer getAmount() { 29 | return amount; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/order/State.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.order; 2 | 3 | import kr.co.ordermanagement.domain.exception.CanNotCancellableStateException; 4 | 5 | public enum State { 6 | CREATED { 7 | @Override 8 | void checkCancellable() {} 9 | }, 10 | SHIPPING, 11 | COMPLETED, 12 | CANCELED; 13 | 14 | void checkCancellable() { 15 | throw new CanNotCancellableStateException("이미 취소되었거나 취소할 수 없는 주문상태입니다."); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/product/Product.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.product; 2 | 3 | import kr.co.ordermanagement.domain.exception.NotEnoughAmountException; 4 | 5 | import java.util.Objects; 6 | 7 | public class Product { 8 | private Long id; 9 | private String name; 10 | private Integer price; 11 | private Integer amount; 12 | 13 | public Product(Long id, String name, Integer price, Integer amount) { 14 | this.id = id; 15 | this.name = name; 16 | this.price = price; 17 | this.amount = amount; 18 | } 19 | 20 | public Long getId() { 21 | return id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public Integer getPrice() { 29 | return price; 30 | } 31 | 32 | public Integer getAmount() { 33 | return amount; 34 | } 35 | 36 | public Boolean sameId(Long id) { 37 | return this.id.equals(id); 38 | } 39 | 40 | public void checkEnoughAmount(Integer orderedAmount) { 41 | if(this.amount < orderedAmount) 42 | throw new NotEnoughAmountException(this.id + "번 상품의 수량이 부족합니다."); 43 | } 44 | 45 | public void decreaseAmount(Integer orderedAmount) { 46 | this.amount = this.amount - orderedAmount; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) return true; 52 | if (o == null || getClass() != o.getClass()) return false; 53 | Product product = (Product) o; 54 | return Objects.equals(id, product.id); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/domain/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.domain.product; 2 | 3 | import java.util.List; 4 | 5 | public interface ProductRepository { 6 | Product findById(Long id); 7 | List findAll(); 8 | void update(Product product); 9 | } 10 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/infrastructure/ListOrderRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.infrastructure; 2 | 3 | import kr.co.ordermanagement.domain.exception.EntityNotFoundException; 4 | import kr.co.ordermanagement.domain.order.Order; 5 | import kr.co.ordermanagement.domain.order.OrderRepository; 6 | import kr.co.ordermanagement.domain.order.State; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | import java.util.concurrent.atomic.AtomicLong; 12 | 13 | @Repository 14 | public class ListOrderRepository implements OrderRepository { 15 | 16 | private List orders = new CopyOnWriteArrayList<>(); 17 | private AtomicLong sequence = new AtomicLong(1L); 18 | 19 | @Override 20 | public Order add(Order order) { 21 | order.setId(sequence.getAndAdd(1L)); 22 | 23 | orders.add(order); 24 | return order; 25 | } 26 | 27 | @Override 28 | public Order findById(Long id) { 29 | return orders.stream() 30 | .filter(order -> order.sameId(id)) 31 | .findFirst() 32 | .orElseThrow(() -> new EntityNotFoundException("Order를 찾지 못했습니다.")); 33 | } 34 | 35 | @Override 36 | public List findByState(State state) { 37 | return orders.stream() 38 | .filter(order -> order.sameState(state)) 39 | .toList(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/infrastructure/ListProductRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.infrastructure; 2 | 3 | import kr.co.ordermanagement.domain.exception.EntityNotFoundException; 4 | import kr.co.ordermanagement.domain.product.Product; 5 | import kr.co.ordermanagement.domain.product.ProductRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import jakarta.annotation.PostConstruct; 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | 12 | @Repository 13 | public class ListProductRepository implements ProductRepository { 14 | 15 | private List products = new CopyOnWriteArrayList<>(); 16 | 17 | @PostConstruct 18 | void initProducts() { 19 | Product product1 = new Product(1L, "상품1", 10000, 100); 20 | Product product2 = new Product(2L, "상품2", 25000, 300); 21 | Product product3 = new Product(3L, "상품3", 30000, 500); 22 | 23 | products.add(product1); 24 | products.add(product2); 25 | products.add(product3); 26 | } 27 | 28 | @Override 29 | public Product findById(Long id) { 30 | return products.stream() 31 | .filter(product -> product.sameId(id)) 32 | .findFirst() 33 | .orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다.")); 34 | } 35 | 36 | @Override 37 | public List findAll() { 38 | return products; 39 | } 40 | 41 | @Override 42 | public void update(Product product) { 43 | Integer indexToModify = products.indexOf(product); 44 | products.set(indexToModify, product); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/controller/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.controller; 2 | 3 | import kr.co.ordermanagement.domain.exception.CanNotCancellableStateException; 4 | import kr.co.ordermanagement.domain.exception.EntityNotFoundException; 5 | import kr.co.ordermanagement.domain.exception.NotEnoughAmountException; 6 | import kr.co.ordermanagement.presentation.dto.ErrorMessageDto; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | @RestControllerAdvice 13 | public class GlobalExceptionHandler { 14 | 15 | @ExceptionHandler(CanNotCancellableStateException.class) 16 | public ResponseEntity handleCanNotCancellableState( 17 | CanNotCancellableStateException ex 18 | ) { 19 | ErrorMessageDto errorMessageDto = new ErrorMessageDto(ex.getMessage()); 20 | return new ResponseEntity<>(errorMessageDto, HttpStatus.INTERNAL_SERVER_ERROR); 21 | } 22 | 23 | @ExceptionHandler(EntityNotFoundException.class) 24 | public ResponseEntity handleEntityNotFoundException( 25 | EntityNotFoundException ex 26 | ) { 27 | ErrorMessageDto errorMessageDto = new ErrorMessageDto(ex.getMessage()); 28 | return new ResponseEntity<>(errorMessageDto, HttpStatus.NOT_FOUND); 29 | } 30 | 31 | @ExceptionHandler(NotEnoughAmountException.class) 32 | public ResponseEntity handleNotEnoughAmountException( 33 | NotEnoughAmountException ex 34 | ) { 35 | ErrorMessageDto errorMessageDto = new ErrorMessageDto(ex.getMessage()); 36 | return new ResponseEntity<>(errorMessageDto, HttpStatus.INTERNAL_SERVER_ERROR); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/controller/OrderRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.controller; 2 | 3 | import kr.co.ordermanagement.application.SimpleOrderService; 4 | import kr.co.ordermanagement.domain.order.State; 5 | import kr.co.ordermanagement.presentation.dto.ChangeStateRequestDto; 6 | import kr.co.ordermanagement.presentation.dto.OrderProductRequestDto; 7 | import kr.co.ordermanagement.presentation.dto.OrderResponseDto; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.List; 13 | 14 | @RestController 15 | public class OrderRestController { 16 | 17 | private SimpleOrderService simpleOrderService; 18 | 19 | @Autowired 20 | OrderRestController(SimpleOrderService simpleOrderService) { 21 | this.simpleOrderService = simpleOrderService; 22 | } 23 | 24 | // 상품 주문 API 25 | @RequestMapping(value = "/orders", method = RequestMethod.POST) 26 | public ResponseEntity createOrder(@RequestBody List orderProductRequestDtos) { 27 | OrderResponseDto orderResponseDto = simpleOrderService.createOrder(orderProductRequestDtos); 28 | 29 | return ResponseEntity.ok(orderResponseDto); 30 | } 31 | 32 | // 주문번호로 조회 API 33 | @RequestMapping(value = "/orders/{orderId}", method = RequestMethod.GET) 34 | public ResponseEntity getOrderById(@PathVariable Long orderId) { 35 | OrderResponseDto orderResponseDto = simpleOrderService.findById(orderId); 36 | 37 | return ResponseEntity.ok(orderResponseDto); 38 | } 39 | 40 | // 주문상태 강제 변경 API 41 | @RequestMapping(value = "/orders/{orderId}", method = RequestMethod.PATCH) 42 | public ResponseEntity changeOrderState( 43 | @PathVariable Long orderId, 44 | @RequestBody ChangeStateRequestDto changeStateRequestDto 45 | ) { 46 | OrderResponseDto orderResponseDto = simpleOrderService.changeState(orderId, changeStateRequestDto); 47 | 48 | return ResponseEntity.ok(orderResponseDto); 49 | } 50 | 51 | // 주문상태로 조회 API 52 | @RequestMapping(value = "/orders", method = RequestMethod.GET) 53 | public ResponseEntity> getOrdersByState(@RequestParam State state) { 54 | List orderResponseDtos = simpleOrderService.findByState(state); 55 | 56 | return ResponseEntity.ok(orderResponseDtos); 57 | } 58 | 59 | // 주문 취소 API 60 | @RequestMapping(value = "/orders/{orderId}/cancel", method = RequestMethod.PATCH) 61 | public ResponseEntity cancelOrderById(@PathVariable Long orderId) { 62 | OrderResponseDto orderResponseDto = simpleOrderService.cancelOrderById(orderId); 63 | 64 | return ResponseEntity.ok(orderResponseDto); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/controller/ProductRestController.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.controller; 2 | 3 | import kr.co.ordermanagement.application.SimpleProductService; 4 | import kr.co.ordermanagement.presentation.dto.ProductDto; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.List; 11 | 12 | @RestController 13 | public class ProductRestController { 14 | 15 | private SimpleProductService simpleProductService; 16 | 17 | @Autowired 18 | ProductRestController(SimpleProductService simpleProductService) { 19 | this.simpleProductService = simpleProductService; 20 | } 21 | 22 | @RequestMapping(value = "/products", method = RequestMethod.GET) 23 | public List findProducts() { 24 | return simpleProductService.findAll(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/dto/ChangeStateRequestDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | import kr.co.ordermanagement.domain.order.State; 4 | 5 | public class ChangeStateRequestDto { 6 | private State state; 7 | 8 | public State getState() { 9 | return state; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/dto/ErrorMessageDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | public class ErrorMessageDto { 4 | 5 | private String message; 6 | 7 | public ErrorMessageDto(String message) { 8 | this.message = message; 9 | } 10 | 11 | public String getMessage() { 12 | return message; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/dto/OrderProductRequestDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | public class OrderProductRequestDto { 4 | private Long id; 5 | private Integer amount; 6 | 7 | public Long getId() { 8 | return id; 9 | } 10 | 11 | public Integer getAmount() { 12 | return amount; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/dto/OrderResponseDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | import kr.co.ordermanagement.domain.order.Order; 4 | import kr.co.ordermanagement.domain.order.State; 5 | 6 | import java.util.List; 7 | 8 | public class OrderResponseDto { 9 | private Long id; 10 | private List orderedProducts; 11 | private Integer totalPrice; 12 | private State state; 13 | 14 | public OrderResponseDto(Long id, List orderedProducts, Integer totalPrice, State state) { 15 | this.id = id; 16 | this.orderedProducts = orderedProducts; 17 | this.totalPrice = totalPrice; 18 | this.state = state; 19 | } 20 | 21 | public Long getId() { 22 | return id; 23 | } 24 | 25 | public List getOrderedProducts() { 26 | return orderedProducts; 27 | } 28 | 29 | public Integer getTotalPrice() { 30 | return totalPrice; 31 | } 32 | 33 | public State getState() { 34 | return state; 35 | } 36 | 37 | public static OrderResponseDto toDto(Order order) { 38 | List orderedProductDtos = order.getOrderedProducts() 39 | .stream() 40 | .map(orderedProduct -> OrderedProductDto.toDto(orderedProduct)) 41 | .toList(); 42 | 43 | OrderResponseDto orderResponseDto = new OrderResponseDto( 44 | order.getId(), 45 | orderedProductDtos, 46 | order.getTotalPrice(), 47 | order.getState() 48 | ); 49 | 50 | return orderResponseDto; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/dto/OrderedProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | import kr.co.ordermanagement.domain.order.OrderedProduct; 4 | 5 | public class OrderedProductDto { 6 | private Long id; 7 | private String name; 8 | private Integer price; 9 | private Integer amount; 10 | 11 | public OrderedProductDto(Long id, String name, Integer price, Integer amount) { 12 | this.id = id; 13 | this.name = name; 14 | this.price = price; 15 | this.amount = amount; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public Integer getPrice() { 27 | return price; 28 | } 29 | 30 | public Integer getAmount() { 31 | return amount; 32 | } 33 | 34 | public static OrderedProductDto toDto(OrderedProduct orderedProduct) { 35 | OrderedProductDto orderedProductDto = new OrderedProductDto( 36 | orderedProduct.getId(), 37 | orderedProduct.getName(), 38 | orderedProduct.getPrice(), 39 | orderedProduct.getAmount() 40 | ); 41 | 42 | return orderedProductDto; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /14-2/src/main/java/kr/co/ordermanagement/presentation/dto/ProductDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.ordermanagement.presentation.dto; 2 | 3 | import kr.co.ordermanagement.domain.product.Product; 4 | 5 | public class ProductDto { 6 | private Long id; 7 | private String name; 8 | private Integer price; 9 | private Integer amount; 10 | 11 | public ProductDto(Long id, String name, Integer price, Integer amount) { 12 | this.id = id; 13 | this.name = name; 14 | this.price = price; 15 | this.amount = amount; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public Integer getPrice() { 27 | return price; 28 | } 29 | 30 | public Integer getAmount() { 31 | return amount; 32 | } 33 | 34 | public static ProductDto toDto(Product product) { 35 | ProductDto productDto = new ProductDto( 36 | product.getId(), 37 | product.getName(), 38 | product.getPrice(), 39 | product.getAmount() 40 | ); 41 | 42 | return productDto; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /14-2/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /APPENDIX B/README.md: -------------------------------------------------------------------------------- 1 | # product-management-application 2 | 3 | ### JDK 4 | 자바 버전은 JDK17을 사용했습니다. 5 | 6 | 구체적으론 OpenJDK 중 [Temurin JDK](https://adoptium.net/temurin/releases/)를 사용했습니다. 7 | 8 | ### Profile 9 | Profile은 test Profile로 바로 리스트를 사용하는 상품 관리 애플리케이션을 실행시킬 수 있도록 구성했고, 만약 데이터베이스를 사용하는 Profile로 실행시키려면 application.properties에서 ‘spring.profiles.active’ 값을 prod로 변경해주세요. 10 | 11 | Profile을 prod로 변경하는 경우 데이터베이스를 사용하게 되고, 데이터베이스는 아래와 같이 쉽게 실행시켜 보실 수 있습니다. 12 | 13 | #### 도커로 MySQL 데이터베이스 실행 14 | ``` 15 | docker run --name some-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=hanbit -d mysql:8.0.29 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci 16 | ``` 17 | 18 | #### 데이터베이스 접속 19 | ``` 20 | mysql -u root -p 21 | ``` 22 | 23 | #### 스키마 생성 24 | ``` 25 | CREATE SCHEMA product_management; 26 | ``` 27 | 28 | #### 스키마 사용 29 | ``` 30 | USE product_management; 31 | ``` 32 | 33 | #### products 테이블 생성 34 | ```SQL 35 | CREATE TABLE products ( 36 | id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT, 37 | name VARCHAR(100) NOT NULL, 38 | price INT NOT NULL, 39 | amount INT NOT NULL 40 | ); 41 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-for-backend 2 | [한빛미디어] "이것이 취업을 위한 백엔드 개발이다 with 자바" 전체 소스코드 저장소입니다. 3 | 4 | ![image](https://github.com/lleellee0/java-for-backend/assets/14347593/ac9003d2-ba11-482a-81ee-4dfec0680485) 5 | 6 | 도서 구입 링크 - https://search.shopping.naver.com/book/catalog/45070745618
7 | 도서 강의 - https://www.youtube.com/watch?v=Kp5wo7a4eAo&list=PLVsNizTWUw7FBMFX9pezh5Gxg5AtNmoMv 8 | 9 | ## 정오표 10 | 11 | ### p.402 - [issue](https://github.com/lleellee0/java-for-backend/issues/13) 12 | 코드 블록의 **EntityNotFoundException.java** 중 일부 -> **GlobalExceptionHandler.java** 중 일부 13 | -------------------------------------------------------------------------------- /reference_for_test.md: -------------------------------------------------------------------------------- 1 | 테스트 코드 관련 참고할 내용 정리 2 | --------------------------------------------------------------------------------