2025.03.06 ์์ฑ
OS : Window
๊ฐ๋ฐํ๊ฒฝ: IntelliJ IDEA
๊ฐ๋ฐ์ธ์ด: Java
ํ๋ ์์ํฌ: Spring Boot
2์ 24์ผ๋ถํฐ ์ ํDS ๊ธ์ตSW ์์นด๋ฐ๋ฏธ 4๊ธฐ 2์ฐจ ํ๋ก์ ํธ๊ฐ ์์๋๋ค.
์ด๋ฒ ํ๋ก์ ํธ์์ ํ ๋ฆฌ๋๋ฅผ ๋งก์ ํ์ ์ด๋๊ณ ์๊ณ , ๋๋ฆ ์ ํด๋๊ฐ๊ณ ์๋ค๊ณ ์๊ฐ ์ค...๐
๊ธฐ์กด์๋ ์ฑ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ์ฃผ๋ก ๋ด๋นํ์ง๋ง, ์ด๋ฒ์ ๋ฐฑ์๋๊น์ง ์ง์ ๊ฐ๋ฐํ๊ฒ ๋๋ฉด์ ํ์คํ ๊ฐ๋ฐ์๋ก ์ฑ์ฅํ๋ ๊ณผ์ ์ ๊ฒช๊ณ ์๋ค.
๋ฟ๋ง ์๋๋ผ, ๋์์ธ๊น์ง ๋งก๊ฒ ๋์ด UI/UX ๋์์ธ ์ญ๋๋ ํจ๊ป ํค์ฐ๋ ์ค.
ํ์ง๋ง ๋๋ฌด ํผ๊ณคํด์ ๋ฏธ์ณ๋ฒ๋ฆฌ๊ฒ ์!!
๋ฐฑ์๋๋ฅผ ์ฒ์ ๋ฐฐ์ฐ๋ฉด์ ๋ค์ ์ฝ๋ฆฐ์ด๋ก ๋์๊ฐ ๊ธฐ๋ถ์ด์ง๋ง, ๋จผ ํ๋ ์ ๋ฏ์ฐ ๊ฐ๋ฐ์๊ฐ ๋์ด ์์ ํ ๋ ๊ฑฑ์ ์๋ค. ๐
1) Domain : Account / Method : POST / URI: api/account/set-ratio / CRUD : UPDATE / Description : ์ฆ๊ถ๊ณ์ข์ ์ด์ฒด๋ ๋น์จ์ ์ค์ ํ๋ ๊ธฐ๋ฅ / Response : x
2) Domain : Account / Method : POST / URI: api/account/collect-interest / CRUD : UPDATE / Description : ์ด์ ๋ฐ๊ธฐ ๊ธฐ๋ฅ / Response : ์ ์ ์ ์์ก์ ๋ณด๋ฅผ ๋ฆฌํด
3) Domain : Account / Method : GET / URI: api/account/get-interest / CRUD : READ / Description : ์ด์์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค / Response : ์ ์ ๊ฐ ๋ฐ์ ์ด์๋ฅผ ๋ฆฌํด
4) Domain : Account / Method : GET / URI: api/account/histotry / CRUD : READ / Description : ์ ์ ๊ณ์ข์ ํ์คํ ๋ฆฌ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค / Response : ์ ์ ์ ํํนํต์ฅ ํ์คํ ๋ฆฌ ์ ๋ณด
AccountEntity
package org.team4.sol_server.domain.account.entity;
import jakarta.persistence.*;
import lombok.*;
import java.math.BigInteger;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "account")
public class AccountEntity extends BaseEntity {
// @ManyToOne
// @JoinColumn(name = "user_idx", nullable = false)
// private UserEntity user;
@Id
@Column(name = "account_no", nullable = false, unique = true)
private String accountNumber;
// ์์
@Column(name = "user_idx", nullable = false)
private int userIdx;
@Column(name = "balance", nullable = false)
private Long balance;
@Column(name = "investor_ratio", nullable = false)
private int investorRatio; // ์ด์ฒด ๋น์จ
@Column(name = "interest", nullable = false)
private int interest; // ์ด์ ๊ธ์ก
@Column(name = "interest_ratio", nullable = false)
private double interestRatio; // ์ด์์จ
}
AccountHistoryEntity
package org.team4.sol_server.domain.account.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "account_history")
public class AccountHistoryEntity extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "account_his_idx")
private int id;
@ManyToOne
@JoinColumn(name = "account_no", nullable = false)
private AccountEntity account;
// @ManyToOne
// @JoinColumn(name = "user_idx", nullable = false)
// private UserEntity user;
// ์์
@Column(name = "user_idx", nullable = false)
private int userIdx;
@Column(name = "display_name", nullable = false)
private String displayName;
@Column(name = "pre_balance", nullable = false)
private int preBalance;
}
package org.team4.sol_server.domain.account.dto;
import lombok.*;
import java.math.BigInteger;
@Data
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AccountDTO {
private String accountNumber;
private Long balance;
}
๊ทผ๋ฐ ๊ฐ๋ฐ ์๋ฃํ๊ณ ์๋ฌธ์ ์ด ์๊น...
๊ทธ๋์ ๊ฐ์ฌ๋๊ป ํธ๋ค๋ฅ ๋ฌ๋ ค๊ฐ์ ์ฌ์ญค๋ดค๋ค!
๊ฐ์ฌ๋์ : ์ฌ์ค DTO? ์ ๋ง๋ค์ด๋ ๋๊ธด ํฉ๋๋ค...
์๋ฅผ ๋ค์ด, ์๋ setTransferRatio()์ collectInterest() ๋ ๊ฐ์ API๋ฅผ ๋น๊ตํด๋ณด์.
// ์ฆ๊ถ ๊ณ์ข ์ด์ฒด ๋น์จ ์ค์
@PostMapping("/set-ratio")
// accountNumber --> ๊ณ์ข ๋ฒํธ ๋ฐ์, ratio --> ์ด์ฒด ๋น์จ(%) ๋ฐ์
public ResponseEntity<String> setTransferRatio(@RequestBody TransferRatioDTO requestDTO) {
accountService.setTransferRatio(requestDTO.getAccountNumber(), requestDTO.getRatio()); // ํฌ์ ๋น์จ ์
๋ฐ์ดํธ
return ResponseEntity.ok("์ฆ๊ถ ๊ณ์ข ์ด์ฒด ๋น์จ ์ค์ : " + requestDTO.getRatio());
}
// ์ด์ ๋ฐ๊ธฐ
@PostMapping("/collect-interest")
public ResponseEntity<AccountDTO> collectInterest(@RequestParam String accountNumber) {
AccountDTO updatedAccount = accountService.collectInterest(accountNumber); // ํฌ์ ๋น์จ์ ๊ธฐ์ค์ผ๋ก ์ด์ ๊ณ์ฐ ๋ฐ ๊ณ์ข ์์ก ์ฆ๊ฐ ํจ์
return ResponseEntity.ok(updatedAccount);
}
์ด ๋์ ์ฐจ์ด์ ์ด ์๋ค.
setTransferRatio()์์๋ DTO(TransferRatioDTO)๋ฅผ ์ฌ์ฉํด์ ๋ฐ์ดํฐ๋ฅผ ๋๊ธด๋ค.
collectInterest()์์๋ @RequestParam์ ์ง์ ์ฌ์ฉํ๋ค.
๊ทผ๋ฐ ๋ ๊ฐ์ ๊ฒฝ์ฐ์ ์ค์ ๋ก TransferRatioDTO๋ฅผ ๋ณด๋ฉด
package org.team4.sol_server.domain.account.dto;
import lombok.*;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TransferRatioDTO {
private String accountNumber;
private int ratio;
}
์ด๋ ๊ฒ ๋ณ์๊ฐ 2~3๊ฐ ์ ๋๋ฐ์ ์๋๋ฐ...
๊ตณ์ด DTO๋ฅผ ๋ง๋๋ ๊ฒ ํ์ํ ๊น?๋ผ๋ ์๊ฐ์ด ๋ค์ด ์ด๋ ๊ฒ ์์ฑํ๊ฒ ๋ ๊ฒ์ด๋ค.
๊ทธ๋์ ๊ฐ์ฌ๋ํํ ์ฌ์ญค๋ณธ ๊ฒฐ๊ณผ...
๊ฒฐ๋ก
๋ฐ์ดํฐ๊ฐ ๋จ์ํ ๊ฒฝ์ฐ ์ ์จ๋ ๋ฌธ์ ์์.
์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ์ด๋ ์ ์ง๋ณด์? ์ธก๋ฉด์์๋ ์กด์ฌํ๋ฉด ์ข์ ๊ฒ ๊ฐ๊ธด ํ๋ค๋ง...
๋ค๋ฅธ ์ฌ๋๊ณผ ํ์
ํ ๋ DTO๊ฐ ์์ผ๋ฉด ํ์
์ด ํจ์ฌ ๋น ๋ฅด๊ฒ ๋๋๊น(๊ฐ๋
์ฑ) ๊ทธ๋ฐ ๋ถ๋ถ์์๋ ์ข๊ธด ํ๋ค๋ง...
๊ฐ์ธ์ ์ธ ์๊ฐ์ผ๋ก๋ 5๊ฐ ์ด์์ด ์๋๋ผ๋ฉด ๊ทธ๋ฅ DTO ๋ง๋ค์ง ์๊ณ @RequestParam์ ์จ์ ์ฌ์ฉํ๋ ๊ฒ ๋์ ๊ฒ ๊ฐ๋ค.
ํ
์คํธ๋ฅผ ํ ๋ @RequestParamํ์์
์ฌ์ง๊ณผ ๊ฐ์ด Params์ ๊ฐ์ ์์๋ก ๋ฃ์ด์ฃผ๊ณ ์์ฒญํ๋ฉด Body๋ก 200 ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๊ณ
@RequestBody TransferRatioDTO requestDTO ํ์์
์ด๋ ๊ฒ Body๋ก raw ๊ฐ์ ์์ฑํด์ ์์ฒญํ๋ฉด ์ฌ์ง๊ณผ ๊ฐ์ด ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
(Body > raw JSON ํ์์ผ๋ก ๊ฐ์ ๋ณด๋ด์ผ ํ๋ค.)
์์ง ๋ฐฑ๋ฆฐ์ด๋ผ ์ด๋ฐ ๊ฒ๋ ์์ํ ์์๊ฐ๋ ์ค...
package org.team4.sol_server.domain.account.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.team4.sol_server.domain.account.entity.AccountEntity;
import java.util.Optional;
public interface AccountRepository extends JpaRepository<AccountEntity, Long> {
Optional<AccountEntity> findByAccountNumber(String accountNumber);
}
package org.team4.sol_server.domain.account.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.team4.sol_server.domain.account.entity.AccountHistoryEntity;
import java.util.List;
@Repository
public interface AccountHistoryRepository extends JpaRepository<AccountHistoryEntity, Integer> {
// ํน์ ๊ณ์ข์ ๊ฑฐ๋ ๋ด์ญ ์กฐํ
List<AccountHistoryEntity> findByAccountAccountNumber(String accountNumber);
}
package org.team4.sol_server.domain.account.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.team4.sol_server.domain.account.dto.AccountDTO;
import org.team4.sol_server.domain.account.entity.AccountEntity;
import org.team4.sol_server.domain.account.entity.AccountHistoryEntity;
import org.team4.sol_server.domain.account.repository.AccountHistoryRepository;
import org.team4.sol_server.domain.account.repository.AccountRepository;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class AccountService {
// ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ณ์ข ์กฐํ, ์
๊ธ, ์ถ๊ธ, ์ด์ฒด, ์ด์ฒด ๋น์จ ์ค์ , ์ด์ ๊ณ์ฐ ๋ฑ์ ์ฒ๋ฆฌ
private final AccountRepository accountRepository;
private final AccountHistoryRepository accountHistoryRepository;
public Optional<AccountEntity> getAccountByNumber(String accountNumber) {
return accountRepository.findByAccountNumber(accountNumber);
}
@Transactional
public boolean transfer(String fromAccountNumber, String toAccountNumber, Long amount) {
Optional<AccountEntity> fromAccountOpt = accountRepository.findByAccountNumber(fromAccountNumber); // ์ถ๊ธ ๊ณ์ข
Optional<AccountEntity> toAccountOpt = accountRepository.findByAccountNumber(toAccountNumber); // ์
๊ธ ๊ณ์ข
if (fromAccountOpt.isPresent() && toAccountOpt.isPresent()) {
AccountEntity fromAccount = fromAccountOpt.get();
AccountEntity toAccount = toAccountOpt.get();
if (fromAccount.getBalance() >= amount) {
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
return true;
}
}
return false;
}
@Transactional
public boolean deposit(String accountNumber, Long amount) {
Optional<AccountEntity> accountOpt = accountRepository.findByAccountNumber(accountNumber);
if (accountOpt.isPresent() && amount > 0) {
AccountEntity account = accountOpt.get();
account.setBalance(account.getBalance() + amount); // ์
๊ธ ๋ก์ง
accountRepository.save(account);
return true;
}
return false;
}
// ํํน ํต์ฅ ํฌ์ ๋น์จ ์ค์
@Transactional
public void setTransferRatio(String accountNumber, int ratio) {
Optional<AccountEntity> accountOpt = accountRepository.findByAccountNumber(accountNumber);
accountOpt.ifPresent(account -> {
account.setInvestorRatio(ratio);
accountRepository.save(account);
});
}
// ํํน ํต์ฅ ํฌ์ ๋น์จ ์กฐํ
@Transactional
public Integer getTransferRatio(String accountNumber) {
Optional<AccountEntity> accountOpt = accountRepository.findByAccountNumber(accountNumber);
return accountOpt.map(AccountEntity::getInvestorRatio).orElse(0);
}
// collectInterest() --> ํฌ์ ๋น์จ์ ๊ธฐ์ค์ผ๋ก ์ด์ ๊ณ์ฐ ๋ฐ ๊ณ์ข ์์ก ์ฆ๊ฐ ํจ์
@Transactional
public AccountDTO collectInterest(String accountNumber) {
Optional<AccountEntity> accountOpt = accountRepository.findByAccountNumber(accountNumber);
if (accountOpt.isPresent()) {
AccountEntity account = accountOpt.get();
// ์ด์ ๊ณ์ฐ = ์์ก * (์ด์์จ/100.0)
int interest = (int) (account.getBalance() * (account.getInterestRatio() / 100.0));
account.setInterest(interest);
account.setBalance(account.getBalance() + interest);
accountRepository.save(account);
return AccountDTO.builder()
.accountNumber(account.getAccountNumber())
.balance(account.getBalance())
.build();
}
return null;
}
// ๋ฐ์ ์ด์ ์กฐํ
public int getInterest(String accountNumber) {
Optional<AccountEntity> accountOpt = accountRepository.findByAccountNumber(accountNumber);
return accountOpt.map(AccountEntity::getInterest).orElse(0);
}
// ๊ฑฐ๋ ๋ด์ญ ์กฐํ
@Transactional(readOnly = true)
public List<AccountHistoryEntity> getAccountHistory(String accountNumber) {
return accountHistoryRepository.findByAccountAccountNumber(accountNumber);
}
}
package org.team4.sol_server.domain.account.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.team4.sol_server.domain.account.dto.AccountDTO;
import org.team4.sol_server.domain.account.dto.DepositRequestDTO;
import org.team4.sol_server.domain.account.dto.TransferRatioDTO;
import org.team4.sol_server.domain.account.dto.TransferRequestDTO;
import org.team4.sol_server.domain.account.entity.AccountEntity;
import org.team4.sol_server.domain.account.entity.AccountHistoryEntity;
import org.team4.sol_server.domain.account.repository.AccountRepository;
import org.team4.sol_server.domain.account.service.AccountService;
import java.util.List;
import java.util.Optional;
/*
ํ์ผ๋ช
: AccountController.java
์์ฑ์ : JDeok
๋ ์ง : 2025.03.05
์ ๊ฐ : ์คํ 02:14
๊ธฐ ๋ฅ : ๊ณ์ขํ์ด์ง
Params :
Return :
๋ณ๊ฒฝ์ฌํญ
- 2025.03.05 : JDeok(์ต์ด์์ฑ)
*/
@RestController
@RequestMapping("api/account")
@RequiredArgsConstructor
@CrossOrigin(origins = "http://localhost:3000")
public class AccountController {
private final AccountService accountService;
private final AccountRepository accountRepository;
// ๊ณ์ข ์กฐํ ํ์ด์ง
@GetMapping("/balance")
public ResponseEntity<AccountDTO> getAccount(@RequestParam String accountNumber, Model model) {
Optional<AccountEntity> account = accountService.getAccountByNumber(accountNumber);
account.ifPresent(value -> model.addAttribute("account", value));
AccountDTO dto = AccountDTO.builder()
.accountNumber(account.get().getAccountNumber())
.balance(account.get().getBalance())
.build();
return ResponseEntity.ok().body(dto);
}
// ์ด์ฒด ํผ ํ์ด์ง
// @GetMapping("/transfer")
// public String showTransferForm() {
// return "transfer";
// }
// ์ด์ฒด ๊ธฐ๋ฅ ์ํ
@PostMapping("/transfer")
public String transfer(@RequestBody TransferRequestDTO transferRequestDTO, Model model) {
boolean success = accountService.transfer(transferRequestDTO.getFromAccount()
, transferRequestDTO.getToAccount()
, transferRequestDTO.getAmount());
model.addAttribute("success", success);
return "transfer_result";
}
// ์
๊ธ (Deposit)
@PostMapping("/deposit")
public ResponseEntity<String> deposit(@RequestBody DepositRequestDTO requestDTO) {
boolean success = accountService.deposit(requestDTO.getAccountNumber(), requestDTO.getAmount());
if (success) {
return ResponseEntity.ok("Deposit successful");
}
return ResponseEntity.badRequest().body("Deposit failed: Invalid account or amount");
}
// ์ฆ๊ถ ๊ณ์ข ์ด์ฒด ๋น์จ ์ค์
@PostMapping("/set-ratio")
// accountNumber --> ๊ณ์ข ๋ฒํธ ๋ฐ์, ratio --> ์ด์ฒด ๋น์จ(%) ๋ฐ์
public ResponseEntity<String> setTransferRatio(@RequestBody TransferRatioDTO requestDTO) {
accountService.setTransferRatio(requestDTO.getAccountNumber(), requestDTO.getRatio()); // ํฌ์ ๋น์จ ์
๋ฐ์ดํธ
return ResponseEntity.ok("์ฆ๊ถ ๊ณ์ข ์ด์ฒด ๋น์จ ์ค์ : " + requestDTO.getRatio());
}
// ์ฆ๊ถ ๊ณ์ข ์ด์ฒด ๋น์จ ์กฐํ
@GetMapping("/get-ratio")
public Integer getTransferRatio(@RequestParam String accountNumber) {
return accountService.getTransferRatio(accountNumber);
}
// ์ด์ ๋ฐ๊ธฐ
@PostMapping("/collect-interest")
public ResponseEntity<AccountDTO> collectInterest(@RequestParam String accountNumber) {
AccountDTO updatedAccount = accountService.collectInterest(accountNumber); // ํฌ์ ๋น์จ์ ๊ธฐ์ค์ผ๋ก ์ด์ ๊ณ์ฐ ๋ฐ ๊ณ์ข ์์ก ์ฆ๊ฐ ํจ์
return ResponseEntity.ok(updatedAccount);
}
// ์ด์ ์ ๋ณด ์กฐํ --> ์ฌ์ฉ์๊ฐ ๋ฐ์ ์ด์
// ํ์ฌ ์์ก ๊ธฐ์ค์ผ๋ก ๋ฐ์ ์ด์ ๊ณ์ฐ ํ ๋ฐํ
@GetMapping("/get-interest")
public ResponseEntity<Integer> getInterest(@RequestParam String accountNumber) {
int interest = accountService.getInterest(accountNumber); // ์ด์ ๋น์จ์ ๋ฐ๋ผ ์ด์ ๊ณ์ฐ
return ResponseEntity.ok(interest);
}
// ํด๋น ๊ณ์ข ํต์ฅ ๋ด์ญ ์กฐํ
@GetMapping("/history")
public ResponseEntity<List<AccountHistoryEntity>> getAccountHistory(@RequestParam String accountNumber) {
List<AccountHistoryEntity> historyList = accountService.getAccountHistory(accountNumber);
return ResponseEntity.ok(historyList);
}
}
โ ์ปจํธ๋กค๋ฌ(Controller)
ํด๋ผ์ด์ธํธ ์์ฒญ์ ๋ฐ์ (@PostMapping, @GetMapping)
์์ฒญ ๋ฐ์ดํฐ๋ฅผ ์๋น์ค(Service)๋ก ์ ๋ฌ
์๋น์ค์์ ์ฒ๋ฆฌ๋ ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํ
โ ์๋น์ค(Service)
์๋น์ค๋ ์ค์ ๋น์ฆ๋์ค ๋ก์ง(์
๋ฌด ๋ก์ง)์ ์ฒ๋ฆฌํ๋ ๊ณณ์ด์ผ!
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ ์ฝ์ด์ค๊ฑฐ๋, ๊ฐ์ ๊ณ์ฐํ๊ณ ๋ณ๊ฒฝํ๋ ๊ฑด ๋ชจ๋ ์๋น์ค์์ ์ฒ๋ฆฌํด.
๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌ (DB์์ ์กฐํ/์์ , ๊ณ์ฐ, ๊ฒ์ฆ ๋ฑ)
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ํต์
์ฌ๋ฌ ์ปจํธ๋กค๋ฌ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
โ ์ปจํธ๋กค๋ฌ์ ์๋น์ค๋ฅผ ๋ถ๋ฆฌํ๋ฉด?
์ฝ๋๊ฐ ๊น๋ํ๊ณ , ์ ์ง๋ณด์๊ฐ ์ฌ์
์ฌ์ฌ์ฉ์ฑ ์ฆ๊ฐ (๊ฐ์ ๋ก์ง์ ์ฌ๋ฌ API์์ ์ฌ์ฉ ๊ฐ๋ฅ)
ํ
์คํธ๊ฐ ์ฌ์ (์๋น์ค ๋จ์ ํ
์คํธ, ์ปจํธ๋กค๋ฌ Mock ํ
์คํธ ๊ฐ๋ฅ)
๐ ์ฝ๊ฒ ์ดํดํ๋ ๋น์
โ
์ปจํธ๋กค๋ฌ(Controller) = ์จ์ดํฐ ๐ฝ
๐ ์๋(ํด๋ผ์ด์ธํธ) ์ฃผ๋ฌธ์ ๋ฐ๊ณ , ์๋ฆฌ๋ฅผ ์ฃผ๋ฐฉ(์๋น์ค)์๊ฒ ์ ๋ฌ
โ
์๋น์ค(Service) = ์ฃผ๋ฐฉ ๐ณ
๐ ์จ์ดํฐ๊ฐ ์ ๋ฌํ ์ฃผ๋ฌธ์ ์ฒ๋ฆฌํด์ ์๋ฆฌ๋ฅผ ์์ฑ
โ
Repository = ์ฐฝ๊ณ ๐ช
๐ ์ฃผ๋ฐฉ(์๋น์ค)์์ ์ฌ๋ฃ(DB ๋ฐ์ดํฐ)๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ์ ์ฅ
โ ์ปจํธ๋กค๋ฌ๊ฐ ๋ชจ๋ ๊ฑธ ์ฒ๋ฆฌํ๋ฉด? ๐คฏ (์จ์ดํฐ๊ฐ ์๋ฆฌ๊น์ง ๋ค ํ๋ ค๋ฉด ํ๋ค๊ฒ ์ง?)
โ ์ปจํธ๋กค๋ฌ๋ "์์ฒญ์ ๋ฐ์์ ์ ๋ฌํ๋ ์ญํ "๋ง ํ๊ณ , ์๋ฆฌ๋ "์๋น์ค"์์ ํด์ผ ํด!
AccountRepositoryTest
package org.team4.sol_server.repository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.team4.sol_server.domain.account.entity.AccountEntity;
import org.team4.sol_server.domain.account.repository.AccountRepository;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
public class AccountRepositoryTest {
@Autowired
private AccountRepository accountRepository;
@Test
public void testFindByAccountNumber() {
Optional<AccountEntity> account = accountRepository.findByAccountNumber("ACC123456");
assertThat(account).isPresent();
assertThat(account.get().getBalance()).isEqualTo(1000000);
}
}
AccountServiceTests
package org.team4.sol_server.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import org.team4.sol_server.domain.account.dto.AccountDTO;
import org.team4.sol_server.domain.account.entity.AccountEntity;
import org.team4.sol_server.domain.account.repository.AccountRepository;
import org.team4.sol_server.domain.account.service.AccountService;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional
public class AccountServiceTests {
@Autowired
private AccountRepository accountRepository;
@Autowired
private AccountService accountService;
private String testAccountNumber = "ACC123";
private String testAccountNumber2 = "ACC456";
@BeforeEach
void setUp() {
AccountEntity account1 = AccountEntity.builder()
.accountNumber(testAccountNumber)
.userIdx(1)
.balance(1000000L)
.investorRatio(50)
.interest(25000)
.interestRatio(2.5)
.build();
accountRepository.save(account1);
AccountEntity account2 = AccountEntity.builder()
.accountNumber(testAccountNumber2)
.userIdx(2)
.balance(500000L)
.investorRatio(30)
.interest(0)
.interestRatio(1.2)
.build();
accountRepository.save(account2);
}
@Test
public void testGetAccount() {
// ๊ณ์ข ์ ๋ณด ์กฐํ ํ
์คํธ
Optional<AccountEntity> accountOpt = accountService.getAccountByNumber(testAccountNumber);
assertThat(accountOpt).isPresent();
assertThat(accountOpt.get().getBalance()).isEqualTo(1000000);
System.out.println("ํํน ํต์ฅ ์์ก: " + accountOpt.get().getBalance());
}
@Test
public void testDeposit() {
// ์
๊ธ ํ
์คํธ: 10๋ง์ ์
๊ธ
boolean result = accountService.deposit(testAccountNumber, 100000L);
assertThat(result).isTrue();
// ๋ณ๊ฒฝ๋ ์์ก ํ์ธ
Optional<AccountEntity> updatedAccount = accountService.getAccountByNumber(testAccountNumber);
assertThat(updatedAccount).isPresent();
assertThat(updatedAccount.get().getBalance()).isEqualTo(1100000);
System.out.println("ํํน ํต์ฅ ์์ก: " + updatedAccount.get().getBalance());
}
@Test
public void testTransfer() {
// ๊ณ์ข ๊ฐ ์ก๊ธ ํ
์คํธ (์ถ๊ธ 10๋ง์)
boolean result = accountService.transfer(testAccountNumber, testAccountNumber2, 100000L);
assertThat(result).isTrue();
// ์์ก ํ์ธ
Optional<AccountEntity> fromAccount = accountService.getAccountByNumber(testAccountNumber);
Optional<AccountEntity> toAccount = accountService.getAccountByNumber(testAccountNumber2);
assertThat(fromAccount).isPresent();
assertThat(toAccount).isPresent();
assertThat(fromAccount.get().getBalance()).isEqualTo(900000);
assertThat(toAccount.get().getBalance()).isEqualTo(600000);
System.out.println("ํํน ํต์ฅ ์์ก: " + fromAccount.get().getBalance());
System.out.println("์ฆ๊ถ ๊ณ์ข ์์ก: " + toAccount.get().getBalance());
}
@Test
public void testSetTransferRatio() {
// ํฌ์ ๋น์จ ๋ณ๊ฒฝ ํ
์คํธ
accountService.setTransferRatio(testAccountNumber, 80);
Optional<AccountEntity> updatedAccount = accountService.getAccountByNumber(testAccountNumber);
assertThat(updatedAccount).isPresent();
assertThat(updatedAccount.get().getInvestorRatio()).isEqualTo(80);
System.out.println("ํฌ์ ๋น์จ ๋ณ๊ฒฝ: " + updatedAccount.get().getInvestorRatio());
}
@Test
public void testCollectInterest() {
// ์ด์ ๋ฐ๊ธฐ ๊ธฐ๋ฅ ํ
์คํธ
AccountDTO updatedAccount = accountService.collectInterest(testAccountNumber);
// ์ด์๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐ๋์๋์ง ํ์ธ
assertThat(updatedAccount).isNotNull();
assertThat(updatedAccount.getBalance()).isEqualTo(1025000); // 100๋ง์ + 2.5% (25000์)
System.out.println("์ด์ ๋ฐ๊ธฐ: " + updatedAccount.getBalance());
}
@Test
public void testGetInterest() {
// ๋ฐ์ ์ด์ ์กฐํ ํ
์คํธ
int interest = accountService.getInterest(testAccountNumber);
assertThat(interest).isEqualTo(25000); // 100๋ง์ ร 2.5% = 25000์
System.out.println(interest);
}
}
๋!
์ถ๊ฐ์ ์ผ๋ก MockMvc๋ฅผ ์ฌ์ฉํ์ฌ Spring Boot ์ปจํธ๋กค๋ฌ ํ
์คํธํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
๊ตณ์ด Postman์ ์ฐ์ง ์์๋ ๋๋ ๋ฐฉ๋ฒ์ธ๋ฐ, ํฌ์คํธ๋งจ์จ๊ฐ UI๊ฐ ์ด์๊ณ ํธํ๋๊น ๊ฑ ๊ทธ๊ฑฐ ์ฐ๋ ๊ฒ ๋ซ๊ธด ํ๋ค.
๋ฐฉ์์ ๋์ถฉ
package org.team4.sol_server.domain.account.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.team4.sol_server.domain.account.service.AccountService;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(AccountController.class) // ํน์ ์ปจํธ๋กค๋ฌ๋ง ํ
์คํธ
public class AccountControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean // ์๋น์ค ๊ณ์ธต์ Mocking (์์กด์ฑ ์ฃผ์
)
private AccountService accountService;
@Test
public void testGetBalance() throws Exception {
// ๊ฐ์ง ์๋น์ค ์๋ต ์ค์
when(accountService.getInterest("ACC123")).thenReturn(5000);
// GET ์์ฒญ์ ์คํํ๊ณ ๊ฒฐ๊ณผ ๊ฒ์ฆ
mockMvc.perform(get("/api/account/get-interest")
.param("accountNumber", "ACC123")) // ์์ฒญ ํ๋ผ๋ฏธํฐ ์ค์
.andExpect(status().isOk()) // ์๋ต ์ฝ๋ 200์ธ์ง ํ์ธ
.andExpect(content().string("5000")); // ์๋ต ๋ด์ฉ ๊ฒ์ฆ
}
}
์ด๋ฌํ๋ค.
์ผ๋จ์ ๋๋ฌ์ง๋ง, ์ถ๊ฐ์ ์ผ๋ก ๋ ๊ตฌํํ ์ฌํญ์ด ๋์ด๋ ๊ฒ ๊ฐ๊ธด ํ๋ค.
์ฐ์ ์ ํ๋ก ํธ๋ ๋ฐฑ ์ฐ๋์ ์์ํด ๋ฐ์ดํฐ๋ฅผ ์ถ๋ ฅํด๋ณด๊ณ UI๊น์ง ๋์์ธ ์ธ์
ํ ๊ณํ~~~
โ API ๊ฐ๋ฐํ๋ฉด์ DTO ํ์์ฑ์ ๋ํ ๊ณ ๋ฏผ์ ํด๋ด
โ Postman์ ํ์ฉํด API ํ
์คํธ ๋ฐ ๋ฌธ์ํ ์งํ
โ ๊ณง ํ๋ก ํธ์ ๋ฐฑ์๋ ์ฐ๋ ํ UI๊น์ง ์์ฑํ ๊ณํ!
๐ฅ ์์ผ๋ก๋ ๊ณ์ ์ฑ์ฅํ๋ ํ์คํ ๊ฐ๋ฐ์๊ฐ ๋๊ธฐ ์ํด ๋ฌ๋ ค๊ฐ๋ค! ๐