diff --git a/build.gradle b/build.gradle index f3158f0b0f..1b750e01f2 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + runtimeOnly 'com.mysql:mysql-connector-j' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java b/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java index 4b2592fc94..b1bf3e45fa 100644 --- a/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java +++ b/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java @@ -2,4 +2,6 @@ public class CommonConstant { public static final String CSV_PATTERN = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)"; + public static final int INPUT_FIXED_AMOUNT_VOUCHER = 1; + public static final int INPUT_PERCENT_DISCOUNT_VOUCHER = 2; } diff --git a/src/main/java/org/prgms/springbootbasic/common/Console.java b/src/main/java/org/prgms/springbootbasic/common/Console.java deleted file mode 100644 index 8ba68b94fd..0000000000 --- a/src/main/java/org/prgms/springbootbasic/common/Console.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.prgms.springbootbasic.common; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.domain.VoucherType; -import org.springframework.stereotype.Component; - -import java.util.*; - -@Component -@Slf4j -public class Console { // view - domain 분리 필. Controller를 따로 만들고, 거기서 View, Service 호출. - private static final Scanner CONSOLE_INPUT = new Scanner(System.in); - - public static String readCommand() { - System.out.println("=== Voucher Program ==="); - System.out.println("Type 'exit' to exit the program."); - System.out.println("Type 'create' to create a new voucher."); - System.out.println("Type 'list' to list all vouchers."); - System.out.println("Type 'black' to list customers blacked."); - - return CONSOLE_INPUT.next(); - } - - public static int selectCreateType() { - System.out.println(); - System.out.println("Which voucher would you like to create? Just type number."); - System.out.println("1. FixedAmountVoucher"); - System.out.println("2. PercentDiscountVoucher"); - - return CONSOLE_INPUT.nextInt(); - } - - public static int putDiscountDegree(VoucherType type) { - switch (type){ - case FIXED_AMOUNT -> System.out.println("Enter the fixed discount amount."); - case PERCENT_DISCOUNT -> System.out.println("Enter the discount percentage."); - } - return CONSOLE_INPUT.nextInt(); - } - - - public static String ignoreLine() { - return CONSOLE_INPUT.nextLine(); - } - - public static void printList(Collection collection) { - System.out.println(); - collection.forEach(System.out::println); - System.out.println(); - } - - public static void printArgException() { - System.out.println("Invalid argument. Type command again."); - } - - public static void printRuntimeException() { - System.out.println("Invalid input or system error. redirect to beginning."); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/common/UtilMethod.java b/src/main/java/org/prgms/springbootbasic/common/UtilMethod.java new file mode 100644 index 0000000000..882359e8b2 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/common/UtilMethod.java @@ -0,0 +1,12 @@ +package org.prgms.springbootbasic.common; + +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UtilMethod { + public static UUID bytesToUUID(byte[] bytes) { + ByteBuffer wrappedByte = ByteBuffer.wrap(bytes); + + return new UUID(wrappedByte.getLong(), wrappedByte.getLong()); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java b/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java index fb2fe2b1ec..a715da97df 100644 --- a/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java +++ b/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import java.io.*; @@ -11,6 +12,7 @@ import java.util.function.Function; @Component +@Profile("test") public class CsvFileTemplate { private static final Logger log = LoggerFactory.getLogger(CsvFileTemplate.class); diff --git a/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java b/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java index 7af4e6de13..6d5ba4ab48 100644 --- a/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java +++ b/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java @@ -13,7 +13,7 @@ import static org.prgms.springbootbasic.common.CommonConstant.CSV_PATTERN; @Component -@Profile({"dev", "prod"}) +@Profile({"test"}) @Slf4j public class CustomerCsvFileManager { private static final String BLACK_PATH = "./src/main/resources/customer_blacklist.csv"; @@ -31,10 +31,10 @@ public CustomerCsvFileManager(CsvFileTemplate csvFileTemplate) { public List readBlack(){ - return csvFileTemplate.read(BLACK_PATH, this::lineToBlack); + return csvFileTemplate.read(BLACK_PATH, this::convertToBlack); } - private Customer lineToBlack(String line){ + private Customer convertToBlack(String line){ log.debug("line = {}", line); List csvFields = diff --git a/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java b/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java index fd57545ca7..eff7509aa8 100644 --- a/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java +++ b/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java @@ -2,7 +2,9 @@ import lombok.extern.slf4j.Slf4j; import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.voucher.Voucher; import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -14,13 +16,14 @@ @Component @Slf4j -@Profile({"dev", "prod"}) -public class VoucherCsvFileManager { // CsvFileManager 하나로 합쳐서. domain은 최대한 순수하게 유지. 외부 의존성이 들어간다? 이게 도메인에 들어가면 변경이 취약. -> 분리 - private static final String FILE_PATH = "./src/main/resources/voucher.csv"; +@Profile({"test"}) +public class VoucherCsvFileManager { + @Value("${basic.file.path}") + private String FILE_PATH; private static final String CSV_FIRST_LINE = "UUID,Type,DiscountValue"; private static final int UUID_IDX = 0; private static final int TYPE_IDX = 1; - private static final int DISCOUNT_VALUE_IDX = 2; + private static final int DISCOUNT_DEGREE_IDX = 2; private final CsvFileTemplate csvFileTemplate; @@ -28,15 +31,15 @@ public VoucherCsvFileManager(CsvFileTemplate csvFileTemplate) { this.csvFileTemplate = csvFileTemplate; } - public List read(){ - return csvFileTemplate.read(FILE_PATH, this::lineToVoucher); + public List read(){ + return csvFileTemplate.read(FILE_PATH, this::convertToVoucher); } - public void write(List voucherPolicies){ - csvFileTemplate.write(FILE_PATH, voucherPolicies, this::voucherToString, CSV_FIRST_LINE); + public void write(List voucherPolicies){ + csvFileTemplate.write(FILE_PATH, voucherPolicies, this::convertToString, CSV_FIRST_LINE); } - private VoucherPolicy lineToVoucher(String line){ + private Voucher convertToVoucher(String line){ log.debug("line = {}", line); List splitLine = Arrays.stream(line.split(CSV_PATTERN)) @@ -44,32 +47,31 @@ private VoucherPolicy lineToVoucher(String line){ .toList(); VoucherType[] voucherTypes = VoucherType.values(); - for (VoucherType type : voucherTypes) { - String voucherType = type.getDisplayName(); - String curStringType = splitLine.get(TYPE_IDX); + VoucherType thisVoucherType = + Arrays.stream(voucherTypes) + .filter(type -> type.getDisplayName().equals(splitLine.get(TYPE_IDX))) + .findAny() + .orElseThrow(() -> { + log.error("Invalid voucher type."); + return new IllegalArgumentException("Invalid voucher type"); + }); - if (curStringType.equals(voucherType)) { - log.info("This voucher type is {}", voucherType); + VoucherPolicy voucherPolicy = thisVoucherType.create(); + UUID voucherId = UUID.fromString(splitLine.get(UUID_IDX)); + long discountDegree = Long.parseLong(splitLine.get(DISCOUNT_DEGREE_IDX)); - return type.create( - UUID.fromString(splitLine.get(UUID_IDX)), - Long.parseLong(splitLine.get(DISCOUNT_VALUE_IDX)) - ); - } - } - - log.error("Invalid voucher type."); - throw new IllegalArgumentException("Invalid voucher type."); + return new Voucher(voucherId, discountDegree, voucherPolicy); } - private String voucherToString(VoucherPolicy voucherPolicy){ + private String convertToString(Voucher voucher){ // 외부에 도메인을 맞추면 안됨. -> DB 의존적 클래스랑 실제 내부 도메인 분리. + // 이거를 위해서 VoucherPolicy가 getter를 들고있는게 말이 안됨. 얘를 도메인에 맞춰야지 얘때문에 도메인이 망가지면 안된다. StringBuilder sb = new StringBuilder(); - sb.append(voucherPolicy.getVoucherId()); + sb.append(voucher.getVoucherId()); sb.append(","); - sb.append(voucherPolicy.getClass().getSimpleName()); + sb.append(voucher.getVoucherPolicy().getClass().getSimpleName()); sb.append(","); - sb.append(voucherPolicy.getDiscountAmount()); + sb.append(voucher.getDiscountDegree()); sb.append(System.lineSeparator()); return sb.toString(); diff --git a/src/main/java/org/prgms/springbootbasic/console/Console.java b/src/main/java/org/prgms/springbootbasic/console/Console.java new file mode 100644 index 0000000000..dcbd5793d5 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/console/Console.java @@ -0,0 +1,106 @@ +package org.prgms.springbootbasic.console; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.VoucherType; +import org.springframework.stereotype.Component; + +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Scanner; +import java.util.UUID; + +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_FIXED_AMOUNT_VOUCHER; +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_PERCENT_DISCOUNT_VOUCHER; + +@Component +@Slf4j +public class Console { // Console이 common인가? MVC 생각하며 고민하자. View에 해당한다고 보이는데... MVC가 뭔지 대답 못한 것을 두번째 걸렸다. 공부하자... + public static final Scanner consoleInput = new Scanner(System.in); + + public static String readCommand() { + System.out.println("=== Voucher Program ==="); + System.out.println("Type 'createVoucher' to create a new voucher."); + System.out.println("Type 'createCustomer' to create a new customer."); + System.out.println("Type 'listVoucher' to list all vouchers."); + System.out.println("Type 'listCustomer' to list all customers."); + System.out.println("Type 'black' to list customers blacked."); + System.out.println("Type 'wallet' to enter wallet service."); + System.out.println("Type 'exit' to exit the program."); + + return consoleInput.next(); + } + + public static int selectPolicyType() { + System.out.println(); + System.out.println("Which voucher would you like to create? Just type number."); + System.out.println(MessageFormat.format("{0}. with fixed amount discount policy", INPUT_FIXED_AMOUNT_VOUCHER)); + System.out.println(MessageFormat.format("{0}. with percent discount policy", INPUT_PERCENT_DISCOUNT_VOUCHER)); + + return consoleInput.nextInt(); + } + + public static String putCustomerInfo() { + ignoreLine(); + + System.out.println(); + System.out.println("Please enter the name and email of the customer you want to create, separated by a space on a single line."); + + return consoleInput.nextLine(); + } + + public static int putDiscountDegree(VoucherType type) { + switch (type){ + case FIXED_AMOUNT -> System.out.println("Enter the fixed discount amount."); + case PERCENT_DISCOUNT -> System.out.println("Enter the discount percentage."); + } + return consoleInput.nextInt(); + } + + + public static String ignoreLine() { + return consoleInput.nextLine(); + } + + public static void printArgException() { + System.out.println("Invalid argument. Type command again."); + } + + public static void printRuntimeException() { + System.out.println("Invalid input or system error. redirect to beginning."); + } + + public static String readWalletCommand() { + System.out.println(); + System.out.println("Welcome to our wallet service."); + System.out.println("Type 'allocate' if you want to allocate voucher to a customer." ); + System.out.println("Type 'delete' if you want to delete voucher from a customer."); + System.out.println("Type 'showVoucherByCustomer' to view customers with a voucher."); + System.out.println("Type 'showCustomerByVoucher' to view vouchers that a customer has."); + System.out.println("Type 'back' to go back."); + + return consoleInput.next(); + } + + public static UUID typeCustomerId(){ + System.out.println("Type customer Id."); + + return UUID.fromString(consoleInput.next()); + } + + public static UUID typeVoucherId(){ + System.out.println("Type voucher Id."); + + return UUID.fromString(consoleInput.next()); + } + + public static void success(String command){ + System.out.println("'" + command + "' command successfully executed."); + System.out.println(); + } + + public static void printList(Collection collection) { + System.out.println(); + collection.forEach(System.out::println); + System.out.println(); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/MainController.java b/src/main/java/org/prgms/springbootbasic/controller/MainController.java index 3db9514923..7abe4e3fb8 100644 --- a/src/main/java/org/prgms/springbootbasic/controller/MainController.java +++ b/src/main/java/org/prgms/springbootbasic/controller/MainController.java @@ -1,43 +1,63 @@ package org.prgms.springbootbasic.controller; import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.common.Console; import org.prgms.springbootbasic.domain.VoucherType; import org.prgms.springbootbasic.domain.customer.Customer; -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; import org.prgms.springbootbasic.service.CustomerService; +import org.prgms.springbootbasic.service.CustomerVoucherManagementService; import org.prgms.springbootbasic.service.VoucherService; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Controller; import java.util.InputMismatchException; import java.util.List; import java.util.NoSuchElementException; +import java.util.UUID; + +import static org.prgms.springbootbasic.console.Console.*; @Controller @Slf4j public class MainController { private static final String EXIT = "exit"; - private static final String LIST = "list"; - private static final String CREATE = "create"; + private static final String LIST_VOUCHER = "listVoucher"; + private static final String LIST_CUSTOMER = "listCustomer"; + private static final String CREATE_VOUCHER = "createVoucher"; + private static final String CREATE_CUSTOMER = "createCustomer"; private static final String BLACK = "black"; + private static final String WALLET = "wallet"; + private static final String ALLOCATE = "allocate"; + private static final String DELETE = "delete"; + private static final String SHOW_CUSTOMER_BY_VOUCHER = "showVoucherByCustomer"; + private static final String SHOW_VOUCHER_BY_CUSTOMER = "showCustomerByVoucher"; + private static final String BACK = "back"; private final VoucherService voucherService; private final CustomerService customerService; + private final CustomerVoucherManagementService managementService; - public MainController(VoucherService voucherService, CustomerService customerService) { + public MainController(VoucherService voucherService, CustomerService customerService, CustomerVoucherManagementService managementService) { this.voucherService = voucherService; this.customerService = customerService; + this.managementService = managementService; } public void run() { String command = ""; while (!command.equals(EXIT)) { try { - command = Console.readCommand(); + command = readCommand(); executeCommand(command); + success(command); + } catch (IllegalArgumentException e) { + log.error("{}", e.toString()); + + printArgException(); } catch (InputMismatchException e) { - String invalidVal = Console.ignoreLine(); + String invalidVal = ignoreLine(); log.warn("User input = {}", invalidVal); throw new IllegalArgumentException("Not integer."); @@ -47,46 +67,126 @@ public void run() { } catch (IllegalStateException e) { log.error("Scanner is closed"); throw new RuntimeException("Scanner is closed."); - } catch (IllegalArgumentException e) { - Console.printArgException(); + } catch (DuplicateKeyException e) { + log.error("Key duplicate error.", e); + printDuplicateKeyException(); + } catch (DataAccessException e) { + log.error("Database error.", e); + printRuntimeException(); } catch (RuntimeException e) { - Console.printRuntimeException(); + printRuntimeException(); } } + } // 프론트 컨트롤러 패턴에 대해. 공부해보자. 컨트롤러 일관성 없음. 왜 wallet만 따로 존재하는? 고민... -> 그냥 통일합시다. + + private void printDuplicateKeyException() { + System.out.println("There is duplicate key in the values."); } - public void create(){ - int voucherSeq = Console.selectCreateType(); + private void executeCommand(String command) { + switch (command) { + case CREATE_VOUCHER -> createVoucher(); + case CREATE_CUSTOMER -> createCustomer(); + case LIST_VOUCHER -> listVoucher(); + case LIST_CUSTOMER -> listCustomer(); + case BLACK -> black(); + case WALLET -> runWallet(); + case EXIT -> {} + default -> { + log.warn("invalid command. now command = {}", command); + throw new IllegalArgumentException("Invalid command. Type command again."); + } + } + } + + private void createVoucher() { + int voucherSeq = selectPolicyType(); VoucherType voucherType = voucherService.seqToType(voucherSeq); - int discountDegree = Console.putDiscountDegree(voucherType); + int discountDegree = putDiscountDegree(voucherType); - voucherService.create(voucherType, discountDegree); + voucherService.upsert(voucherType, discountDegree); } - private void list(){ - List voucherPolicies = voucherService.findAll(); // 뷰가 다른 클래스에 의존 + private void createCustomer() { + String[] info = putCustomerInfo().split(" "); + + if (info.length != 2) { + throw new IllegalArgumentException("Customer info is not valid."); + } - Console.printList(voucherPolicies); + String name = info[0]; + String email = info[1]; + + customerService.upsert(name, email); } - private void black(){ - List blacklist = customerService.findBlackAll(); // customerRepository에 의존 + private void listVoucher() { + List vouchers = voucherService.findAll(); - Console.printList(blacklist); + printList(vouchers); } - private void executeCommand(String command) { - switch (command) { - case CREATE -> create(); - case LIST -> list(); - case BLACK -> black(); - case EXIT -> {} + private void listCustomer() { + List customers = customerService.findAll(); + + printList(customers); + } + + private void black() { + List blacklist = customerService.findBlackAll(); + + printList(blacklist); + } + + + private void runWallet() { + String command = readWalletCommand(); + + executeWalletCommand(command); + success(command); + } + + private void executeWalletCommand(String command) { + switch (command){ + case ALLOCATE -> allocate(); + case DELETE -> delete(); + case SHOW_CUSTOMER_BY_VOUCHER -> showVoucherByCustomer(); + case SHOW_VOUCHER_BY_CUSTOMER -> showCustomerByVoucher(); + case BACK -> {} default -> { log.warn("invalid command. now command = {}", command); throw new IllegalArgumentException("Invalid command. Type command again."); } } } + + private void allocate() { + UUID customerId = typeCustomerId(); + UUID voucherId = typeVoucherId(); + + managementService.allocate(customerId, voucherId); + } + + private void delete() { + UUID customerId = typeCustomerId(); + UUID voucherId = typeVoucherId(); + + managementService.delete(customerId, voucherId); + } + + private void showVoucherByCustomer() { + UUID customerId = typeCustomerId(); + List vouchers = managementService.searchVouchersFromCustomer(customerId); + + printList(vouchers); + } + + private void showCustomerByVoucher() { + UUID voucherId = typeVoucherId(); + List customers = managementService.searchCustomerFromVoucher(voucherId); + + printList(customers); + } } diff --git a/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java b/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java index 5e951eb9e1..af7ffc191d 100644 --- a/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java +++ b/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java @@ -2,26 +2,28 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.domain.voucher.FixedAmountVoucher; -import org.prgms.springbootbasic.domain.voucher.PercentDiscountVoucher; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import java.util.UUID; -import java.util.function.BiFunction; +import java.util.function.Supplier; + +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_FIXED_AMOUNT_VOUCHER; +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_PERCENT_DISCOUNT_VOUCHER; @Slf4j public enum VoucherType { - FIXED_AMOUNT(1, FixedAmountVoucher::new, "FixedAmountVoucher"), - PERCENT_DISCOUNT(2, PercentDiscountVoucher::new, "PercentDiscountVoucher"); + FIXED_AMOUNT(INPUT_FIXED_AMOUNT_VOUCHER, FixedAmountPolicy::new, "FixedAmountPolicy"), + PERCENT_DISCOUNT(INPUT_PERCENT_DISCOUNT_VOUCHER, PercentDiscountPolicy::new, "PercentDiscountPolicy"); private final int seq; - private final BiFunction biFunction; + private final Supplier createVoucherPolicy; @Getter private final String displayName; - VoucherType(int seq, BiFunction biFunction, String displayName) { + VoucherType(int seq, Supplier createVoucherPolicy, String displayName) { this.seq = seq; - this.biFunction = biFunction; + this.createVoucherPolicy = createVoucherPolicy; this.displayName = displayName; } @@ -36,11 +38,7 @@ public static VoucherType getTypeFromSeq(int seq){ throw new IllegalArgumentException("Invalid seq"); } - public VoucherPolicy create(long discountDegree){ - return this.biFunction.apply(UUID.randomUUID(), discountDegree); - } - - public VoucherPolicy create(UUID uuid, long discountDegree){ - return this.biFunction.apply(uuid, discountDegree); + public VoucherPolicy create(){ + return this.createVoucherPolicy.get(); } } diff --git a/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java b/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java index cf42b9750e..b57aa21d89 100644 --- a/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java +++ b/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java @@ -4,15 +4,23 @@ import java.util.UUID; public class Customer { - private UUID id; + private final UUID customerId; private String name; - private String email; - private LocalDateTime createdAt; + private final String email; + private final LocalDateTime createdAt; private LocalDateTime lastLoginAt; private boolean isBlacked; - public Customer(UUID id, String name, String email, LocalDateTime createdAt, LocalDateTime lastLoginAt, boolean isBlacked) { - this.id = id; + public Customer(UUID customerId, String name, String email, LocalDateTime createdAt) { + this.customerId = customerId; + this.name = name; + this.email = email; + this.createdAt = createdAt; + this.isBlacked = false; + } + + public Customer(UUID customerId, String name, String email, LocalDateTime createdAt, LocalDateTime lastLoginAt, boolean isBlacked) { + this.customerId = customerId; this.name = name; this.email = email; this.createdAt = createdAt; @@ -20,10 +28,33 @@ public Customer(UUID id, String name, String email, LocalDateTime createdAt, Loc this.isBlacked = isBlacked; } + public UUID getCustomerId() { + return customerId; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public boolean isBlacked() { + return isBlacked; + } + + public Customer changeInfo(String name, boolean isBlacked){ + this.name = name; + this.isBlacked = isBlacked; + + return this; + } + @Override public String toString() { return "Customer{" + - "id=" + id + + "id=" + customerId + ", name='" + name + '\'' + ", email='" + email + '\'' + ", createdAt=" + createdAt + diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountPolicy.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountPolicy.java new file mode 100644 index 0000000000..b1fe39730c --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountPolicy.java @@ -0,0 +1,18 @@ +package org.prgms.springbootbasic.domain.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.exception.OutOfRangeException; + +import static java.lang.Math.max; + +@Slf4j +public class FixedAmountPolicy implements VoucherPolicy { + @Override + public long discount(long beforeDiscount, long discountDegree) { + if (beforeDiscount < 0) { + throw new OutOfRangeException("beforeDiscount is less than 0."); + } + + return max(0, beforeDiscount - discountDegree); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountVoucher.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountVoucher.java deleted file mode 100644 index 9eea6ea667..0000000000 --- a/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountVoucher.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.prgms.springbootbasic.domain.voucher; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.exception.OutOfRangeException; - -import java.util.UUID; - -import static java.lang.Math.max; - -@Slf4j -public class FixedAmountVoucher implements VoucherPolicy { - private final UUID voucherId; - private final long amount; - - public FixedAmountVoucher(UUID voucherId, long amount) { - this.voucherId = voucherId; - this.amount = amount; - } - - @Override - public UUID getVoucherId() { - return this.voucherId; - } - - @Override - public long getDiscountAmount() { - return this.amount; - } - - @Override - public long discount(long beforeDiscount) { - if (beforeDiscount < 0) { - throw new OutOfRangeException("beforeDiscount is less than 0."); - } - - return max(0, beforeDiscount - this.amount); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountPolicy.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountPolicy.java new file mode 100644 index 0000000000..542d923e8a --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountPolicy.java @@ -0,0 +1,16 @@ +package org.prgms.springbootbasic.domain.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.exception.OutOfRangeException; + +@Slf4j +public class PercentDiscountPolicy implements VoucherPolicy { + @Override + public long discount(long beforeDiscount, long discountDegree) { + if (beforeDiscount < 0) { + throw new OutOfRangeException("beforeDiscount is less than 0."); + } + + return beforeDiscount * discountDegree / 100L; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountVoucher.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountVoucher.java deleted file mode 100644 index 43ea04aa3f..0000000000 --- a/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountVoucher.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.prgms.springbootbasic.domain.voucher; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.exception.OutOfRangeException; - -import java.util.UUID; - -@Slf4j -public class PercentDiscountVoucher implements VoucherPolicy { - private final UUID voucherId; - private final long percent; - - public PercentDiscountVoucher(UUID voucherId, long percent) { - if (percent > 100 || percent <= 0) { - log.warn("percent value is out of range."); - throw new OutOfRangeException("percent value is out of range."); - } - - this.voucherId = voucherId; - this.percent = percent; - } - - @Override - public UUID getVoucherId() { - return this.voucherId; - } - - @Override - public long getDiscountAmount() { - return this.percent; - } - - @Override - public long discount(long beforeDiscount) { // 음수 고려 - if (beforeDiscount < 0) - throw new OutOfRangeException("beforeDiscount is less than 0."); - return beforeDiscount * percent / 100L; - } -} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/Voucher.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/Voucher.java new file mode 100644 index 0000000000..c5a536e744 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/Voucher.java @@ -0,0 +1,51 @@ +package org.prgms.springbootbasic.domain.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.exception.OutOfRangeException; + +import java.util.UUID; + +@Slf4j +public class Voucher { + private final UUID voucherId; + private final long discountDegree; + private final VoucherPolicy voucherPolicy; + + public Voucher(UUID voucherId, long discountDegree, VoucherPolicy voucherPolicy) { + if (voucherPolicy instanceof PercentDiscountPolicy) { + if (discountDegree <= 0 || discountDegree > 100) { + log.error("percent value is out of range."); + throw new OutOfRangeException("percent value is out of range."); + } + } + + this.voucherId = voucherId; + this.discountDegree = discountDegree; + this.voucherPolicy = voucherPolicy; + } + + public UUID getVoucherId() { + return voucherId; + } + + public long getDiscountDegree() { + return discountDegree; + } + + public VoucherPolicy getVoucherPolicy() { + return voucherPolicy; + } + + public long discount(long beforeDiscount){ + return voucherPolicy.discount(beforeDiscount, this.discountDegree); + } + + @Override + public String toString() { + return "Voucher{" + + "voucherId=" + voucherId + + ", discountDegree=" + discountDegree + + ", voucherPolicy=" + voucherPolicy.getClass().getSimpleName() + "@" + voucherPolicy.hashCode() + + '}'; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java index 51dd756124..72e073810d 100644 --- a/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java @@ -1,9 +1,5 @@ package org.prgms.springbootbasic.domain.voucher; -import java.util.UUID; - public interface VoucherPolicy { - UUID getVoucherId(); // VoucherPolicy를 file로 쓸 때 id와 discount 정도를 알아야 함. - long discount(long beforeDiscount); - long getDiscountAmount(); // VoucherPolicy를 file로 쓸 때 id와 discount 정도를 알아야 함. + long discount(long beforeDiscount, long discountDegree); } diff --git a/src/main/java/org/prgms/springbootbasic/exception/EntityNotFoundException.java b/src/main/java/org/prgms/springbootbasic/exception/EntityNotFoundException.java new file mode 100644 index 0000000000..046b3083f4 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/exception/EntityNotFoundException.java @@ -0,0 +1,6 @@ +package org.prgms.springbootbasic.exception; + +public class EntityNotFoundException extends RuntimeException{ + public EntityNotFoundException() { + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/CustomerFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/CustomerFileRepository.java deleted file mode 100644 index 7db1b08835..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/CustomerFileRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.common.file.CustomerCsvFileManager; -import org.prgms.springbootbasic.domain.customer.Customer; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -@Profile({"dev", "prod"}) -public class CustomerFileRepository implements CustomerRepository { - private final CustomerCsvFileManager customerCsvFileManager; - - public CustomerFileRepository(CustomerCsvFileManager customerCsvFileManager) { - this.customerCsvFileManager = customerCsvFileManager; - } - - @Override - public List findBlackAll() { - return customerCsvFileManager.readBlack(); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/CustomerMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/CustomerMemoryRepository.java deleted file mode 100644 index 7c6caebd51..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/CustomerMemoryRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.domain.customer.Customer; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import java.util.ArrayList; -import java.util.List; - -@Repository -@Profile({"local", "test"}) -public class CustomerMemoryRepository implements CustomerRepository { - @Override - public List findBlackAll() { - return new ArrayList<>(); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/CustomerRepository.java b/src/main/java/org/prgms/springbootbasic/repository/CustomerRepository.java deleted file mode 100644 index 09a3731d46..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/CustomerRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.domain.customer.Customer; - -import java.util.List; - -public interface CustomerRepository { - List findBlackAll(); -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/VoucherFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/VoucherFileRepository.java deleted file mode 100644 index 6bcbd999d6..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/VoucherFileRepository.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.common.file.VoucherCsvFileManager; -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -@Repository -@Profile({"dev", "prod"}) -@Primary -@Slf4j -public class VoucherFileRepository implements VoucherRepository { // csv를 다루니 이름은 Csv를 붙여 더 명확히 해야 하지 않나 싶다. - private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); - private final VoucherCsvFileManager voucherCsvFileManager; - - public VoucherFileRepository(VoucherCsvFileManager voucherCsvFileManager) { - log.debug("FileVoucherRepository started."); - - this.voucherCsvFileManager = voucherCsvFileManager; - } - - @Override - public VoucherPolicy findById(UUID voucherId) { - return Optional.ofNullable(vouchers.get(voucherId)) - .orElseThrow(NoSuchElementException::new); - } - - @Override - public List findAll() { - return new ArrayList<>(vouchers.values()); - } - - @Override - public VoucherPolicy create(VoucherPolicy voucherPolicy) { - vouchers.putIfAbsent(voucherPolicy.getVoucherId(), voucherPolicy); - return voucherPolicy; - } - - @PostConstruct - private void fileRead(){ - List voucherPolicies = voucherCsvFileManager.read(); - voucherPolicies.forEach(this::create); - } - - @PreDestroy - private void fileWrite(){ - voucherCsvFileManager.write(this.findAll()); - } -} - -// 데코레이터 패턴: 기능의 확장. 이것이 기능의 확장이라 보기는 어렵다. -// 빈은 싱글톤이다. 만약 미래에 VoucherFileRepository와 VoucherMemoryRepository를 둘 다 쓴다면 -// VoucherFileRepository에서 VoucherMemoryRepository 내 HashMap을 쓸 건데 싱글톤이라 결국 둘을 공존해 쓰게 되고 이로 인한 이슈가 있을 수 있다. -// 둘을 분리하려면 new로 따로 인스턴스 생성해서 넣어줘도 해결을 할 수 있지만 이를 인지하면서 개발하는 것부터 스트레스임. -// 그냥 분리하자. diff --git a/src/main/java/org/prgms/springbootbasic/repository/VoucherMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/VoucherMemoryRepository.java deleted file mode 100644 index 458f3ce52f..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/VoucherMemoryRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -@Repository -@Profile({"dev", "prod", "local", "test"}) -@Slf4j -public class VoucherMemoryRepository implements VoucherRepository{ - ConcurrentHashMap mem = new ConcurrentHashMap<>(); - - @Override - public VoucherPolicy findById(UUID voucherId) { - return Optional.ofNullable(mem.get(voucherId)) - .orElseThrow(NoSuchElementException::new); - } - - @Override - public List findAll() { - return new ArrayList<>(mem.values()); - } - - @Override - public VoucherPolicy create(VoucherPolicy voucherPolicy) { - mem.putIfAbsent(voucherPolicy.getVoucherId(), voucherPolicy); - return voucherPolicy; - } -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/VoucherRepository.java b/src/main/java/org/prgms/springbootbasic/repository/VoucherRepository.java deleted file mode 100644 index 2c77f22364..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/VoucherRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; - -import java.util.List; -import java.util.UUID; - -public interface VoucherRepository { - VoucherPolicy findById(UUID voucherId); - List findAll(); - VoucherPolicy create(VoucherPolicy voucherPolicy); -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerFileRepository.java new file mode 100644 index 0000000000..6595734bf2 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerFileRepository.java @@ -0,0 +1,55 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.prgms.springbootbasic.common.file.CustomerCsvFileManager; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@Profile({"test"}) +public class CustomerFileRepository implements CustomerRepository { // 추후 구현 + private final CustomerCsvFileManager customerCsvFileManager; + + public CustomerFileRepository(CustomerCsvFileManager customerCsvFileManager) { + this.customerCsvFileManager = customerCsvFileManager; + } + + @Override + public Customer upsert(Customer customer) { + return null; + } + + @Override + public Optional findById(UUID customerId) { + return Optional.empty(); + } + + @Override + public Optional findByEmail(String email) { + return Optional.empty(); + } + + @Override + public List findAll() { + return null; + } + + @Override + public List findBlackAll() { + return customerCsvFileManager.readBlack(); + } + + @Override + public void deleteById(UUID customerId) { + + } + + @Override + public int deleteAll() { + return 0; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepository.java new file mode 100644 index 0000000000..63db16a8c2 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepository.java @@ -0,0 +1,107 @@ +package org.prgms.springbootbasic.repository.customer; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.common.UtilMethod; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.*; + +@Repository +@Slf4j +@Profile({"dev", "prod"}) +public class CustomerJdbcRepository implements CustomerRepository{ + private final NamedParameterJdbcTemplate jdbcTemplate; + + public CustomerJdbcRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Customer upsert(Customer customer) { + Optional foundCustomer = findById(customer.getCustomerId()); + + if (foundCustomer.isPresent()){ + jdbcTemplate.update("UPDATE customers SET name = :name WHERE customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + toParamMap(customer)); + } else { + jdbcTemplate.update("INSERT INTO customers(customer_id, name, email, is_blacked) VALUES (UNHEX(REPLACE(:customerId, '-', '')), :name, :email, :isBlacked)", + toParamMap(customer)); + } + + return customer; + } + + @Override + public Optional findById(UUID customerId) { + try { + return Optional.ofNullable( + jdbcTemplate.queryForObject("SELECT * FROM customers WHERE customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + Collections.singletonMap("customerId", customerId.toString().getBytes()), + mapToCustomer)); + } catch (EmptyResultDataAccessException e) { + log.info("customerId에 해당하는 customer가 DB에 없음."); + return Optional.empty(); + } + } + + @Override + public Optional findByEmail(String email) { + try { + return Optional.ofNullable( + jdbcTemplate.queryForObject("SELECT * FROM customers WHERE email = :email", + Collections.singletonMap("email", email), + mapToCustomer)); + } catch (EmptyResultDataAccessException e) { + log.info("email에 해당하는 customer가 DB에 없음."); + return Optional.empty(); + } + } + + @Override + public List findAll() { + return jdbcTemplate.query("SELECT * FROM customers", mapToCustomer); + } + + @Override + public List findBlackAll() { + return jdbcTemplate.query("SELECT * FROM customers WHERE is_blacked = true", mapToCustomer); + } + + @Override + public void deleteById(UUID customerId) { + jdbcTemplate.update("DELETE FROM customers WHERE customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + Collections.singletonMap("customerId", customerId.toString().getBytes())); + } + + @Override + public int deleteAll() { + return jdbcTemplate.update("DELETE FROM customers", Collections.emptyMap()); + } + + private static Map toParamMap(Customer customer) { + return new HashMap<>(){{ + put("customerId", customer.getCustomerId().toString().getBytes()); + put("name", customer.getName()); + put("email", customer.getEmail()); + put("isBlacked", customer.isBlacked()); + }}; + } + + private static RowMapper mapToCustomer = (rs, rowNum) -> { + String customerName = rs.getString("name"); + UUID customerId = UtilMethod.bytesToUUID(rs.getBytes("customer_id")); + String email = rs.getString("email"); + LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime(); + LocalDateTime lastLoginAt = rs.getTimestamp("last_login_at") != null + ? rs.getTimestamp("last_login_at").toLocalDateTime() : null; + boolean isBlacked = rs.getBoolean("is_blacked"); + + return new Customer(customerId, customerName, email, lastLoginAt, createdAt, isBlacked); + }; +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerMemoryRepository.java new file mode 100644 index 0000000000..580d1bd51f --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerMemoryRepository.java @@ -0,0 +1,49 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.prgms.springbootbasic.domain.customer.Customer; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@Profile({"local"}) +public class CustomerMemoryRepository implements CustomerRepository { // 추후 구현 + @Override + public Customer upsert(Customer customer) { + return null; + } + + @Override + public Optional findById(UUID customerId) { + return Optional.empty(); + } + + @Override + public Optional findByEmail(String email) { + return Optional.empty(); + } + + @Override + public List findAll() { + return null; + } + + @Override + public List findBlackAll() { + return new ArrayList<>(); + } + + @Override + public void deleteById(UUID customerId) { + + } + + @Override + public int deleteAll() { + return 0; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerRepository.java new file mode 100644 index 0000000000..f01227e877 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerRepository.java @@ -0,0 +1,17 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.prgms.springbootbasic.domain.customer.Customer; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface CustomerRepository { + Customer upsert(Customer customer); + Optional findById(UUID customerId); + Optional findByEmail(String email); + List findAll(); + List findBlackAll(); + void deleteById(UUID customerId); + int deleteAll(); +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepository.java new file mode 100644 index 0000000000..2e376dd00c --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepository.java @@ -0,0 +1,94 @@ +package org.prgms.springbootbasic.repository.customervouchermanagement; + +import org.prgms.springbootbasic.common.UtilMethod; +import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.prgms.springbootbasic.common.UtilMethod.bytesToUUID; + +@Repository +public class CustomerVoucherManagementJdbcRepository implements CustomerVoucherManagementRepository { + private final NamedParameterJdbcTemplate jdbcTemplate; + + public CustomerVoucherManagementJdbcRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void allocateVoucherById(UUID customerId, UUID voucherId) { + jdbcTemplate.update("INSERT INTO customers_vouchers VALUES (UNHEX(REPLACE(:customerId, '-', '')), UNHEX(REPLACE(:voucherId, '-', '')))", + toParamMap(customerId, voucherId)); + } + + @Override + public void deleteVoucherById(UUID customerId, UUID voucherId) { + jdbcTemplate.update("DELETE FROM customers_vouchers " + + "WHERE customer_id = UNHEX(REPLACE(:customerId, '-', '')) AND voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + toParamMap(customerId, voucherId)); + } + + @Override + public void deleteAll() { + jdbcTemplate.update("DELETE FROM customers_vouchers", Collections.emptyMap()); + } + + @Override + public List searchVouchersByCustomerId(UUID customerId) { + return jdbcTemplate.query("SELECT v.voucher_id, v.discount_degree, v.voucher_type " + + "FROM vouchers v JOIN customers_vouchers w ON v.voucher_id = w.voucher_id " + + "WHERE w.customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + Collections.singletonMap("customerId", customerId.toString().getBytes()), + mapToVoucher); + } + + @Override + public List searchCustomersByVoucherId(UUID voucherId) { + return jdbcTemplate.query("SELECT c.customer_id, c.name, c.email, c.last_login_at, c.created_at, c.is_blacked " + + "FROM customers c " + + "JOIN customers_vouchers w ON c.customer_id = w.customer_id " + + "WHERE w.voucher_id = UUID_TO_BIN(:voucherId)", + Collections.singletonMap("voucherId", voucherId.toString().getBytes()), + mapToCustomer); + } + + private static RowMapper mapToVoucher = (rs, rowNum) -> { + UUID voucherId = bytesToUUID(rs.getBytes("voucher_id")); + long discountDegree = rs.getLong("discount_degree"); + String voucherTypeString = rs.getString("voucher_type"); + VoucherType voucherType = Arrays.stream(VoucherType.values()) + .filter(vt -> vt.getDisplayName().equals(voucherTypeString)) + .findAny() + .orElseThrow(() -> new NoSuchElementException("해당 VoucherType이 존재하지 않음.")); + VoucherPolicy voucherPolicy = voucherType.create(); + + return new Voucher(voucherId, discountDegree, voucherPolicy); + }; + + private static RowMapper mapToCustomer = (rs, rowNum) -> { + String customerName = rs.getString("name"); + UUID customerId = UtilMethod.bytesToUUID(rs.getBytes("customer_id")); + String email = rs.getString("email"); + LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime(); + LocalDateTime lastLoginAt = rs.getTimestamp("last_login_at") != null + ? rs.getTimestamp("last_login_at").toLocalDateTime() : null; + boolean isBlacked = rs.getBoolean("is_blacked"); + + return new Customer(customerId, customerName, email, lastLoginAt, createdAt, isBlacked); + }; // static 메서드의 위치 조정. + + private Map toParamMap(UUID customerId, UUID voucherId){ + return new HashMap<>(){{ + put("customerId", customerId.toString().getBytes()); + put("voucherId", voucherId.toString().getBytes()); + }}; + } + +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementRepository.java new file mode 100644 index 0000000000..a6ca57401f --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementRepository.java @@ -0,0 +1,15 @@ +package org.prgms.springbootbasic.repository.customervouchermanagement; + +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; + +import java.util.List; +import java.util.UUID; + +public interface CustomerVoucherManagementRepository { + void allocateVoucherById(UUID customerId, UUID voucherId); + void deleteVoucherById(UUID customerId, UUID voucherId); + void deleteAll(); + List searchVouchersByCustomerId(UUID customerId); + List searchCustomersByVoucherId(UUID voucherId); +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepository.java new file mode 100644 index 0000000000..24ba617d8d --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepository.java @@ -0,0 +1,72 @@ +package org.prgms.springbootbasic.repository.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.common.file.VoucherCsvFileManager; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@Profile({"test"}) +@Primary +@Slf4j +public class VoucherCsvFileRepository implements VoucherRepository { + private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); + private final VoucherCsvFileManager voucherCsvFileManager; + + public VoucherCsvFileRepository(VoucherCsvFileManager voucherCsvFileManager) { + log.debug("FileVoucherRepository started."); + + this.voucherCsvFileManager = voucherCsvFileManager; + } + + @Override + public Optional findById(UUID voucherId) { + return Optional.ofNullable(vouchers.get(voucherId)); + } + + @Override + public List findAll() { + return new ArrayList<>(vouchers.values()); + } + + @Override + public Voucher upsert(Voucher voucher) { + if (Optional.ofNullable(vouchers.get(voucher.getVoucherId())).isPresent()) { + vouchers.replace(voucher.getVoucherId(), voucher); + } else { + vouchers.put(voucher.getVoucherId(), voucher); + } + return voucher; + } + + @Override + public void deleteById(UUID voucherId) { + vouchers.remove(voucherId); + } + + @Override + public void deleteAll() { + vouchers.clear(); + } + + @PostConstruct + private void fileRead(){ + List voucherPolicies = voucherCsvFileManager.read(); + voucherPolicies.forEach(this::upsert); + } + + @PreDestroy + private void fileWrite(){ + voucherCsvFileManager.write(this.findAll()); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepository.java new file mode 100644 index 0000000000..e8847bfff1 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepository.java @@ -0,0 +1,94 @@ +package org.prgms.springbootbasic.repository.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.*; + +import static org.prgms.springbootbasic.common.UtilMethod.bytesToUUID; + +@Repository +@Slf4j +@Profile({"dev", "prod"}) +public class VoucherJdbcRepository implements VoucherRepository { // 네이밍 고민 + private final NamedParameterJdbcTemplate jdbcTemplate; + + public VoucherJdbcRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Voucher upsert(Voucher voucher) { + Optional foundVoucher = findById(voucher.getVoucherId()); // 재사용 지양 + + if (foundVoucher.isPresent()){ + jdbcTemplate.update("UPDATE vouchers SET discount_degree = :discountDegree, voucher_type = :voucherType WHERE voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + toParamMap(voucher)); + } else { + jdbcTemplate.update("INSERT INTO vouchers(voucher_id, discount_degree, voucher_type) " + + "VALUES (UNHEX(REPLACE(:voucherId, '-', '')), :discountDegree, :voucherType)", + toParamMap(voucher)); + } + + return voucher; + } + + @Override + public Optional findById(UUID voucherId) { + try { + return Optional.ofNullable( + jdbcTemplate.queryForObject("SELECT * FROM vouchers WHERE voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + Collections.singletonMap("voucherId", voucherId.toString().getBytes()), + mapToVoucher)); + } catch (EmptyResultDataAccessException e) { + log.info("voucherId에 해당하는 Voucher가 DB에 없음."); + return Optional.empty(); + } + } + + @Override + public List findAll() { + return jdbcTemplate.query("SELECT * FROM vouchers", mapToVoucher); + } + + @Override + public void deleteById(UUID voucherId) { + jdbcTemplate.update("DELETE FROM vouchers WHERE voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + Collections.singletonMap("voucherId", voucherId.toString().getBytes())); // 예외 명시적으로 던지기. update가 던져주는 예외는 이해하기 어려울 수도. + } + + @Override + public void deleteAll() { + jdbcTemplate.update("DELETE FROM vouchers", Collections.emptyMap()); + } + + private static Map toParamMap(Voucher voucher) { + return new HashMap<>(){{ + put("voucherId", voucher.getVoucherId().toString().getBytes()); + put("discountDegree", voucher.getDiscountDegree()); + put("voucherType", voucher.getVoucherPolicy().getClass().getSimpleName()); + }}; + } + + private static RowMapper mapToVoucher = (rs, rowNum) -> { + UUID voucherId = bytesToUUID(rs.getBytes("voucher_id")); + long discountDegree = rs.getLong("discount_degree"); + String voucherTypeString = rs.getString("voucher_type"); + VoucherType voucherType = Arrays.stream(VoucherType.values()) + .filter(vt -> vt.getDisplayName().equals(voucherTypeString)) + .findAny() + .orElseThrow(() -> new NoSuchElementException("해당 VoucherType이 존재하지 않음.")); + VoucherPolicy voucherPolicy = voucherType.create(); + + return new Voucher(voucherId, discountDegree, voucherPolicy); + }; + + +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepository.java new file mode 100644 index 0000000000..e100d1b8e1 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepository.java @@ -0,0 +1,49 @@ +package org.prgms.springbootbasic.repository.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@Profile({"local"}) +@Slf4j +public class VoucherMemoryRepository implements VoucherRepository{ + private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); + + @Override + public Optional findById(UUID voucherId) { + return Optional.ofNullable(vouchers.get(voucherId)); + } + + @Override + public List findAll() { + return new ArrayList<>(vouchers.values()); + } + + @Override + public Voucher upsert(Voucher voucher) { + if (Optional.ofNullable(vouchers.get(voucher.getVoucherId())).isPresent()) { + vouchers.replace(voucher.getVoucherId(), voucher); + } else { + vouchers.put(voucher.getVoucherId(), voucher); + } + return voucher; + } + + @Override + public void deleteById(UUID voucherId) { + vouchers.remove(voucherId); + } + + @Override + public void deleteAll() { + vouchers.clear(); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherRepository.java new file mode 100644 index 0000000000..23c68afd9d --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherRepository.java @@ -0,0 +1,15 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.prgms.springbootbasic.domain.voucher.Voucher; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface VoucherRepository { + Optional findById(UUID voucherId); + List findAll(); + Voucher upsert(Voucher voucher); + void deleteById(UUID voucherId); + void deleteAll(); +} diff --git a/src/main/java/org/prgms/springbootbasic/service/CustomerService.java b/src/main/java/org/prgms/springbootbasic/service/CustomerService.java index 35bd35fc46..82fac0e6ef 100644 --- a/src/main/java/org/prgms/springbootbasic/service/CustomerService.java +++ b/src/main/java/org/prgms/springbootbasic/service/CustomerService.java @@ -2,10 +2,13 @@ import lombok.extern.slf4j.Slf4j; import org.prgms.springbootbasic.domain.customer.Customer; -import org.prgms.springbootbasic.repository.CustomerRepository; +import org.prgms.springbootbasic.repository.customer.CustomerRepository; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service @Slf4j @@ -16,7 +19,26 @@ public CustomerService(CustomerRepository customerRepository) { this.customerRepository = customerRepository; } + public Customer upsert(String name, String email) { + Customer customer = new Customer(UUID.randomUUID(), name, email, LocalDateTime.now()); + + return this.customerRepository.upsert(customer); + } + + public Optional findById(UUID customerId){ + return customerRepository.findById(customerId); + } + + public List findAll() { + return customerRepository.findAll(); + } + + public void deleteAll(){ + customerRepository.deleteAll(); + } + public List findBlackAll(){ return customerRepository.findBlackAll(); } } +// 전체적으로 피드백 반영한 후에, 반영 생각과 왜 그렇게 반영했는지. PR에. 테스트 코드 보완도 좀 하고. diff --git a/src/main/java/org/prgms/springbootbasic/service/CustomerVoucherManagementService.java b/src/main/java/org/prgms/springbootbasic/service/CustomerVoucherManagementService.java new file mode 100644 index 0000000000..7dc1b42b03 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/CustomerVoucherManagementService.java @@ -0,0 +1,36 @@ +package org.prgms.springbootbasic.service; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.repository.customervouchermanagement.CustomerVoucherManagementRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +@Slf4j +public class CustomerVoucherManagementService { + private final CustomerVoucherManagementRepository managementRepository; + + public CustomerVoucherManagementService(CustomerVoucherManagementRepository managementRepository) { + this.managementRepository = managementRepository; + } + + public void allocate(UUID customerId, UUID voucherId){ + managementRepository.allocateVoucherById(customerId, voucherId); + } + + public void delete(UUID customerId, UUID voucherId){ + managementRepository.deleteVoucherById(customerId, voucherId); + } + + public List searchVouchersFromCustomer(UUID customerId){ + return managementRepository.searchVouchersByCustomerId(customerId); + } + + public List searchCustomerFromVoucher(UUID voucherId){ + return managementRepository.searchCustomersByVoucherId(voucherId); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/service/VoucherService.java b/src/main/java/org/prgms/springbootbasic/service/VoucherService.java index 96d5246de9..48b17a3105 100644 --- a/src/main/java/org/prgms/springbootbasic/service/VoucherService.java +++ b/src/main/java/org/prgms/springbootbasic/service/VoucherService.java @@ -2,11 +2,14 @@ import lombok.extern.slf4j.Slf4j; import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.voucher.Voucher; import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.prgms.springbootbasic.repository.VoucherRepository; +import org.prgms.springbootbasic.repository.voucher.VoucherRepository; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service @Slf4j @@ -21,13 +24,18 @@ public VoucherType seqToType(int voucherSeq) { return VoucherType.getTypeFromSeq(voucherSeq); } - public void create(VoucherType voucherType, int discountDegree) { - VoucherPolicy voucherPolicy = voucherType.create(discountDegree); // 생성 비즈니스 로직이 있다. + public void upsert(VoucherType voucherType, int discountDegree) { + VoucherPolicy voucherPolicy = voucherType.create(); + Voucher voucher = new Voucher(UUID.randomUUID(), discountDegree, voucherPolicy); - voucherRepository.create(voucherPolicy); + voucherRepository.upsert(voucher); } - public List findAll(){ + public Optional findById(UUID voucherId){ + return voucherRepository.findById(voucherId); + } + + public List findAll(){ return voucherRepository.findAll(); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000000..057274fbff --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,6 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/test + username: test + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000000..afd1b972b9 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,3 @@ +basic: + file: + path: ./src/main/resources/voucher.csv diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000000..64c9e21ad4 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,6 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/prod + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000000..46d45d35fd --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,9 @@ +basic: + file: + path: ./src/test/resources/voucher.csv +spring: + datasource: + url: jdbc:mysql://localhost:3306/test + username: test + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000000..f6ed7761a3 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,22 @@ +CREATE TABLE IF NOT EXISTS customers ( + customer_id BINARY(16) PRIMARY KEY, + name VARCHAR(26) NOT NULL, + email VARCHAR(56) NOT NULL , + last_login_at DATETIME(6) DEFAULT NULL, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + is_blacked BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT unq_user_email UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS vouchers ( + voucher_id BINARY(16) PRIMARY KEY, + discount_degree bigint NOT NULL, + voucher_type varchar(64) NOT NULL +); + +CREATE TABLE IF NOT EXISTS customers_vouchers ( -- 이 이름이 맞는지? 이건 wallet이라기 보다는 그냥 고객-바우처 매핑 테이블인데? + customer_id BINARY(16) NOT NULL, + voucher_id BINARY(16) NOT NULL, + FOREIGN KEY (customer_id) references customers(customer_id), + FOREIGN KEY (voucher_id) references vouchers(voucher_id) +); diff --git a/src/test/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepositoryTest.java new file mode 100644 index 0000000000..318363cb74 --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepositoryTest.java @@ -0,0 +1,138 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("dev") +class CustomerJdbcRepositoryTest { // @Transactional은 위험할 수 있다. "A"CID. 트랜잭션은 전파 전략이 부모 레벨을 따라간다. 상위도 롤백될 가능성이. 테스트가 부모, 운영 코드가 자식. 트랜잭션 처리 공부. + // 즉, 테스트에서 @Transactional을 사용하고, 운영 코드에서도 @Transactional을 사용한다면 기본 전파 전략에 따라 테스트 @Transactional을 우선시한다. + // 하지만 이를 인지하고 잘 사용한다면 @Transactional을 사용해도 괜찮다. + + @Autowired + private CustomerRepository customerRepository; + + private Customer setUpCustomer; + + @BeforeEach + void setUp() { + setUpCustomer = new Customer(UUID.randomUUID(), + "test", + "test@gmail.com", + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + customerRepository.upsert(setUpCustomer); + } + + @AfterEach + void clean() { + customerRepository.deleteAll(); + } + + @Test + void insertNewCustomerInDB() { + Customer customer = new Customer(UUID.randomUUID(), + "test2", + "test2@gmail.com", + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + customerRepository.upsert(customer); + Optional retrievedCustomer = customerRepository.findById(customer.getCustomerId()); + + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), is(samePropertyValuesAs(customer))); + } + + @Test + void updateExistingCustomerInDB() { + Customer customer = customerRepository.findById(setUpCustomer.getCustomerId()).orElseThrow(EntityNotFoundException::new); + + Customer changedCustomer = customer.changeInfo("updated", customer.isBlacked()); + + customerRepository.upsert(changedCustomer); + + Optional retrievedCustomer = customerRepository.findById(setUpCustomer.getCustomerId()); + + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(changedCustomer)); + } + + @Test + void findCustomerByCustomerIdInDB() { + Optional retrievedCustomer = customerRepository.findById(setUpCustomer.getCustomerId()); + Optional notExistingCustomer = customerRepository.findById(UUID.randomUUID()); + + assertThat(notExistingCustomer.isPresent(), is(false)); + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(setUpCustomer)); + } + + @Test + void findCustomerByEmailInDB() { + Optional retrievedCustomer = customerRepository.findByEmail(setUpCustomer.getEmail()); + Optional notExistingCustomer = customerRepository.findByEmail("blahblah@naver.com"); + + assertThat(notExistingCustomer.isPresent(), is(false)); + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(setUpCustomer)); + } + + @Test + void findAllCustomersInDB(){ + customerRepository.upsert(new Customer(UUID.randomUUID(), "test2", "test2@gmail.com", LocalDateTime.now())); + + List customers = customerRepository.findAll(); + + assertThat(customers, hasSize(2)); + } + + @Test + void findAllBlackedCustomersInDB() { + Customer blackCustomer = new Customer(UUID.randomUUID(), + "black", + "black@gmail.com", + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + blackCustomer.changeInfo(blackCustomer.getName(), true); + customerRepository.upsert(blackCustomer); + + List blackList = customerRepository.findBlackAll(); + + assertThat(blackList, hasSize(1)); + } + + @Test + void deleteCustomerByCustomerIdInDB() { + customerRepository.deleteById(setUpCustomer.getCustomerId()); + + var deletedCustomer = customerRepository.findById(setUpCustomer.getCustomerId()); + + assertThat(deletedCustomer.isPresent(), is(false)); + } + + @Test + void deleteAllCustomersInDB() { + customerRepository.upsert(new Customer(UUID.randomUUID(), "test2", "test2@gmail.com", LocalDateTime.now())); + + customerRepository.deleteAll(); + + List customers = customerRepository.findAll(); + + assertThat(customers, hasSize(0)); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepositoryTest.java new file mode 100644 index 0000000000..38f4df68ac --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepositoryTest.java @@ -0,0 +1,110 @@ +package org.prgms.springbootbasic.repository.customervouchermanagement; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.repository.customer.CustomerRepository; +import org.prgms.springbootbasic.repository.voucher.VoucherRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("dev") +class CustomerVoucherManagementJdbcRepositoryTest { + @Autowired + private CustomerVoucherManagementRepository managementRepository; + @Autowired + private CustomerRepository customerRepository; + @Autowired + private VoucherRepository voucherRepository; + + private UUID setUpCustomerId; + private UUID setUpVoucherId; + + @BeforeEach + void setUp() { + setUpCustomerId = UUID.randomUUID(); + setUpVoucherId = UUID.randomUUID(); + customerRepository.upsert(new Customer(setUpCustomerId, "name", "email", LocalDateTime.now())); + voucherRepository.upsert(new Voucher(setUpVoucherId, 1000, new FixedAmountPolicy())); + } + + @AfterEach + void clean() { + managementRepository.deleteAll(); + customerRepository.deleteAll(); + voucherRepository.deleteAll(); + } + + @Test + @DisplayName("고객과 바우처가 제대로 매핑되는지 확인") + void allocateVoucherToCustomerById() { + UUID anotherVoucherId = UUID.randomUUID(); + voucherRepository.upsert(new Voucher(anotherVoucherId, 20, new PercentDiscountPolicy())); + + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + managementRepository.allocateVoucherById(setUpCustomerId, anotherVoucherId); + + List customers = managementRepository.searchCustomersByVoucherId(setUpVoucherId); + List vouchers = managementRepository.searchVouchersByCustomerId(setUpCustomerId); + + assertThat(customers, hasSize(1)); + assertThat(vouchers, hasSize(2)); + } + + @Test + @DisplayName("특정 고객과 특정 바우처 간 관계 제거") + void deleteRelationBetweenCustomerAndVoucherById() { + UUID anotherVoucherId = UUID.randomUUID(); + voucherRepository.upsert(new Voucher(anotherVoucherId, 100, new FixedAmountPolicy())); + + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + managementRepository.allocateVoucherById(setUpCustomerId, anotherVoucherId); + managementRepository.deleteVoucherById(setUpCustomerId, setUpVoucherId); + + List vouchers = managementRepository.searchVouchersByCustomerId(setUpCustomerId); + List customers = managementRepository.searchCustomersByVoucherId(setUpVoucherId); + + assertThat(vouchers, hasSize(1)); + assertThat(customers, hasSize(0)); + } + + @Test + @DisplayName("고객 id를 이용해 바우처들을 조회") + void searchVouchersRelatedToACustomerByCustomerId() { + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + + List vouchers = managementRepository.searchVouchersByCustomerId(setUpCustomerId); + List noVouchers = managementRepository.searchVouchersByCustomerId(UUID.randomUUID()); + + assertThat(vouchers, hasSize(1)); + assertThat(vouchers.get(0).getVoucherId(), is(setUpVoucherId)); + assertThat(noVouchers, hasSize(0)); + } + + @Test + @DisplayName("바우처 id를 통해 연관 고객들을 조회") + void searchCustomersRelatedToAVoucherByVoucherId() { + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + + List customers = managementRepository.searchCustomersByVoucherId(setUpVoucherId); + List noCustomers = managementRepository.searchCustomersByVoucherId(UUID.randomUUID()); + + assertThat(customers, hasSize(1)); + assertThat(customers.get(0).getCustomerId(), is(setUpCustomerId)); + assertThat(noCustomers, hasSize(0)); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepositoryTest.java new file mode 100644 index 0000000000..93406f8dba --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepositoryTest.java @@ -0,0 +1,106 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("test") +class VoucherCsvFileRepositoryTest { + + @Autowired + private VoucherCsvFileRepository voucherCsvFileRepository; + + @BeforeEach + void cleanUp(){ + voucherCsvFileRepository.deleteAll(); + } + + @Test + void findVoucherByIdFromFile() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + + Optional retrievedVoucher = voucherCsvFileRepository.findById(fixedVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), fixedVoucher); + } + + @Test + void findAllVoucherFromFile() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + Voucher percentVoucher = new Voucher(UUID.randomUUID(), 10, new PercentDiscountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + voucherCsvFileRepository.upsert(percentVoucher); + + List vouchers = voucherCsvFileRepository.findAll(); + List voucherUUIDs = vouchers.stream().map(Voucher::getVoucherId).toList(); + + assertThat(vouchers, hasSize(2)); + assertThat(voucherUUIDs, hasItem(fixedVoucher.getVoucherId())); + assertThat(voucherUUIDs, hasItem(percentVoucher.getVoucherId())); + } + + @Test + void createVoucherToFile() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 2000, new FixedAmountPolicy()); + Voucher percentVoucher = new Voucher(UUID.randomUUID(), 20, new PercentDiscountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + voucherCsvFileRepository.upsert(percentVoucher); + + List vouchers = voucherCsvFileRepository.findAll(); + List voucherUUIDs = vouchers.stream().map(Voucher::getVoucherId).toList(); + + assertThat(vouchers, hasSize(2)); + assertThat(voucherUUIDs, hasItem(fixedVoucher.getVoucherId())); + assertThat(voucherUUIDs, hasItem(percentVoucher.getVoucherId())); + assertThat(vouchers, + not(hasItem(samePropertyValuesAs(new Voucher(UUID.randomUUID(), 2000, new FixedAmountPolicy()))))); + } + + @Test + void deleteVoucherById() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + + assertThat(voucherCsvFileRepository.findById(fixedVoucher.getVoucherId()).isPresent(), is(true)); + + voucherCsvFileRepository.deleteById(UUID.randomUUID()); + assertThat(voucherCsvFileRepository.findAll(), hasSize(1)); + + voucherCsvFileRepository.deleteById(fixedVoucher.getVoucherId()); + assertThat(voucherCsvFileRepository.findAll(), hasSize(0)); + } + + @Test + void deleteAllVoucher() { + voucherCsvFileRepository.upsert(new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy())); + + voucherCsvFileRepository.deleteAll(); + + assertThat(voucherCsvFileRepository.findAll(), hasSize(0)); + } + + private void compareVoucher(Voucher v1, Voucher v2){ + assertThat(v1.getVoucherId(), is(v2.getVoucherId())); + assertThat(v1.getDiscountDegree(), is(v2.getDiscountDegree())); + assertThat(v1.getVoucherPolicy().getClass().getSimpleName(), is(v2.getVoucherPolicy().getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepositoryTest.java new file mode 100644 index 0000000000..3463057883 --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepositoryTest.java @@ -0,0 +1,111 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + + +@SpringBootTest +@ActiveProfiles("dev") +class VoucherJdbcRepositoryTest { + @Autowired + private VoucherJdbcRepository voucherJdbcRepository; + + private Voucher setUpVoucher; + @BeforeEach + void setUp() { + setUpVoucher = new Voucher(UUID.randomUUID(), 40, new PercentDiscountPolicy()); + voucherJdbcRepository.upsert(setUpVoucher); + } + + @AfterEach + void clean() { + voucherJdbcRepository.deleteAll(); + } + + @Test + void saveNewVoucherToDB() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(fixedVoucher); + + Optional retrievedVoucher = voucherJdbcRepository.findById(fixedVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), fixedVoucher); + } + + @Test + void updateVoucherInDB() { + Voucher updateVoucher = new Voucher(setUpVoucher.getVoucherId(), 2000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(updateVoucher); + + Optional retrievedVoucher = voucherJdbcRepository.findById(setUpVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), updateVoucher); + } + + @Test + void findVoucherByIdInDB() { + Optional retrievedVoucher = voucherJdbcRepository.findById(setUpVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), setUpVoucher); + } + + @Test + void findAllVouchersInDB() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 3000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(fixedVoucher); + + List vouchers = voucherJdbcRepository.findAll(); + List voucherUUIDs = vouchers.stream().map(Voucher::getVoucherId).toList(); + + assertThat(vouchers, hasSize(2)); + assertThat(voucherUUIDs, hasItem(fixedVoucher.getVoucherId())); + assertThat(voucherUUIDs, hasItem(setUpVoucher.getVoucherId())); + } + + @Test + void deleteVoucherByIdInDB() { + voucherJdbcRepository.deleteById(setUpVoucher.getVoucherId()); + + List vouchers = voucherJdbcRepository.findAll(); + + assertThat(vouchers, hasSize(0)); + } + + @Test + void deleteAllVouchersInDB() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 3000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(fixedVoucher); + voucherJdbcRepository.deleteAll(); + + List vouchers = voucherJdbcRepository.findAll(); + + assertThat(vouchers.size(), is(0)); + } + + private void compareVoucher(Voucher v1, Voucher v2){ + assertThat(v1.getVoucherId(), is(v2.getVoucherId())); + assertThat(v1.getDiscountDegree(), is(v2.getDiscountDegree())); + assertThat(v1.getVoucherPolicy().getClass().getSimpleName(), is(v2.getVoucherPolicy().getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepositoryTest.java new file mode 100644 index 0000000000..1845a29fdc --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepositoryTest.java @@ -0,0 +1,82 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringJUnitConfig(VoucherMemoryRepository.class) +@ActiveProfiles("local") +class VoucherMemoryRepositoryTest { + + @Autowired + private VoucherMemoryRepository voucherMemoryRepository; + + @BeforeEach + void cleanUp(){ + voucherMemoryRepository.deleteAll(); + } + + @Test + void findVoucherByIdFromMemory() { + UUID voucherId = UUID.randomUUID(); + Voucher fixedVoucher = new Voucher(voucherId, 1000, new FixedAmountPolicy()); + + voucherMemoryRepository.upsert(fixedVoucher); + + Optional retrievedVoucher = voucherMemoryRepository.findById(voucherId); + Optional randomRetrievedVoucher = voucherMemoryRepository.findById(UUID.randomUUID()); + + assertThat(randomRetrievedVoucher.isEmpty(), is(true)); + assertThat(retrievedVoucher.isEmpty(), is(false)); + assertThat(retrievedVoucher.get(), samePropertyValuesAs(fixedVoucher)); + } + + @Test + void findAllVoucherFromMemory() { + Voucher percentDiscountVoucher = new Voucher(UUID.randomUUID(), 20, new PercentDiscountPolicy()); + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 6000, new FixedAmountPolicy()); + + voucherMemoryRepository.upsert(percentDiscountVoucher); + voucherMemoryRepository.upsert(fixedVoucher); + + assertThat(voucherMemoryRepository.findAll(), hasSize(2)); + assertThat(voucherMemoryRepository.findAll(), hasItem(samePropertyValuesAs(percentDiscountVoucher))); + assertThat(voucherMemoryRepository.findAll(), hasItem(samePropertyValuesAs(fixedVoucher))); + } + + @Test + void createNewVouchersToMemory() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + Voucher percentDiscountVoucher = new Voucher(UUID.randomUUID(), 30, new PercentDiscountPolicy()); + + this.voucherMemoryRepository.upsert(fixedVoucher); + this.voucherMemoryRepository.upsert(percentDiscountVoucher); + + assertThat(voucherMemoryRepository.findAll(), hasSize(2)); + assertThrows(IllegalArgumentException.class, + () -> voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 0, new PercentDiscountPolicy()))); // 여기에 이걸 넣는게 맞나? + } + + @Test + void deleteAllVouchersFromMemory() { + voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 10, new PercentDiscountPolicy())); + voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 20, new PercentDiscountPolicy())); + voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy())); + + voucherMemoryRepository.deleteAll(); + + assertThat(voucherMemoryRepository.findAll(), hasSize(0)); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/service/CustomerServiceTest.java b/src/test/java/org/prgms/springbootbasic/service/CustomerServiceTest.java new file mode 100644 index 0000000000..4dbd2849af --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/service/CustomerServiceTest.java @@ -0,0 +1,95 @@ +package org.prgms.springbootbasic.service; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.repository.customer.CustomerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ActiveProfiles("dev") +class CustomerServiceTest { + @Autowired + private CustomerService customerService; + @MockBean + private CustomerRepository customerRepository; + + @Test + @DisplayName("upsert가 제대로 동작하는지 확인") + void upsertCustomer() { + Customer customer = new Customer(UUID.randomUUID(), "name", "email", LocalDateTime.now()); + + when(customerRepository.upsert(any())).thenReturn(customer); + + Customer createdCustomer = customerService.upsert("name", "email"); + + assertThat(createdCustomer).isEqualTo(customer); + + verify(customerRepository).upsert(any()); + } + + @Test + @DisplayName("findById로 제대로 고객을 가져오는지 확인") + void findById(){ + UUID customerId = UUID.randomUUID(); + Customer customer = new Customer(customerId, "name", "email", LocalDateTime.now()); + + when(customerRepository.findById(customerId)).thenReturn(Optional.of(customer)); + + Optional retrievedCustomer = customerService.findById(customerId); + Optional noCustomer = customerService.findById(UUID.randomUUID()); + + assertThat(retrievedCustomer.isPresent()).isTrue(); + assertThat(retrievedCustomer.get()).isEqualTo(customer); + assertThat(noCustomer.isPresent()).isFalse(); + } + + @Test + @DisplayName("findAll로 모든 고객을 가져오는지 확인") + void findAllCustomers() { + when(customerRepository.findAll()) + .thenReturn(List.of(new Customer(UUID.randomUUID(), "c1", "cmail1", LocalDateTime.now()), + new Customer(UUID.randomUUID(), "c2", "cmail2", LocalDateTime.now()))); + List customers = customerService.findAll(); + + assertThat(customers).hasSize(2); + + verify(customerRepository).findAll(); + } + + @Test + @DisplayName("CustomerService 내 deleteAll 동작 확인") + void deleteAllCustomers() { + this.customerService.deleteAll(); + + verify(customerRepository).deleteAll(); + } + + @Test + @DisplayName("CustomerService 내 findAllBlack 동작 확인") + void findAllBlackedCustomers() { + Customer customer = new Customer(UUID.randomUUID(), "c", "cmail", LocalDateTime.now()); + customer.changeInfo(customer.getName(), true); + + when(customerRepository.findBlackAll()).thenReturn(List.of(customer)); + + List blackCustomers = customerService.findBlackAll(); + + assertThat(blackCustomers).hasSize(1); + + verify(customerRepository).findBlackAll(); + } +}