[Spring Boot] 7. Spring Boot Project (Bank v1)_3-5.Fuctions Design_Account transfer

김미숙's avatar
Mar 27, 2025
[Spring Boot] 7. Spring Boot Project (Bank v1)_3-5.Fuctions Design_Account transfer
‼️
Account는 History를 저장
notion image

transfer-form

{{>layout/header}} <!--마진 : mt,mr,ml,mb (1~5) ex) mt-5--> <div class="container mt-2"> <div class="mt-4 p-5 bg-light text-dark rounded-4"> <h1>계좌이체 페이지</h1> <form action="/account/transfer" method="post"> <div class="mb-3 mt-3"> <input type="text" class="form-control" placeholder="Enter amount" name="amount"> </div> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter withdrawNumber" name="withdrawNumber"> </div> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter depositNumber" name="depositNumber"> </div> <div class="mb-3 mt-3"> <input type="password" class="form-control" placeholder="Enter withdrawPassword" name="withdrawPassword"> </div> <button type="submit" class="btn btn-primary">이체하기</button> </form> </div> </div> {{>layout/footer}}

AccountRequest

package com.metacoding.bankv1.account; import lombok.Data; public class AccountRequest { @Data public static class SaveDTO { private Integer number; private String password; private Integer balance; } @Data public static class TransferDTO { private Integer amount; private Integer withdrawNumber; private Integer depositNumber; private String withdrawPassword; } }

AccountController

package com.metacoding.bankv1.account; import com.metacoding.bankv1.user.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import java.util.List; @RequiredArgsConstructor @Controller public class AccountController { private final AccountService accountService; private final HttpSession session; @GetMapping("/") public String home() { return "home"; } @GetMapping("/account/save-form") public String saveForm() { // 인증체크 (반복되는 부가로직) User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요"); return "account/save-form"; } @PostMapping("/account/save") public String save(AccountRequest.SaveDTO saveDTO) { // 인증체크 (반복되는 부가로직) User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요"); accountService.계좌생성(saveDTO, sessionUser.getId()); return "redirect:/account"; } @GetMapping("/account") public String list(HttpServletRequest request) { // 인증체크 (반복되는 부가로직) User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요"); List<Account> accountList = accountService.나의계좌목록(sessionUser.getId()); request.setAttribute("models", accountList); return "account/list"; } @GetMapping("/account/transfer-form") public String transferForm() { // 인증체크 (반복되는 공통부가로직) User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요"); return "account/transfer-form"; } @PostMapping("/account/transfer") public String transfer(AccountRequest.TransferDTO transferDTO) { // 인증체크 (반복되는 공통부가로직) User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요"); accountService.계좌이체(transferDTO, sessionUser.getId()); return "redirect:/"; // TODO } }

AccountService

package com.metacoding.bankv1.account; import com.metacoding.bankv1.account.history.HistoryRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @RequiredArgsConstructor @Service public class AccountService { private final AccountRepository accountRepository; private final HistoryRepository historyRepository; @Transactional public void 계좌생성(AccountRequest.SaveDTO saveDTO, int userId) { accountRepository.save(saveDTO.getNumber(), saveDTO.getPassword(), saveDTO.getBalance(), userId); } public List<Account> 나의계좌목록(Integer userId) { return accountRepository.findAllByUserId(userId); } @Transactional public void 계좌이체(AccountRequest.TransferDTO transferDTO, int userId) { // 1. 출금 계좌 조회 Account withdrawAccount = accountRepository.findByNumber(transferDTO.getWithdrawNumber()); // 2. 출금 계좌 없으면 RunTimeException if (withdrawAccount == null) throw new RuntimeException("출금 계좌가 존재하지 않습니다"); // 3. 입금계좌 조회 Account depositAccount = accountRepository.findByNumber(transferDTO.getDepositNumber()); // 4. 입금 계좌 없으면 RunTimeException if (depositAccount == null) throw new RuntimeException("입금 계좌가 존재하지 않습니다"); // 5. 출금 계좌의 잔액 검사 if (withdrawAccount.getBalance() < transferDTO.getAmount()) { throw new RuntimeException("출금 계좌의 잔액: " + withdrawAccount.getBalance() + ", 이체하려는 금액: " + transferDTO.getAmount()); } // 6. 출금 계좌의 비밀번호 획인해서 동일인인지 체크 if (!(withdrawAccount.getPassword().equals(transferDTO.getWithdrawPassword()))) { throw new RuntimeException("출금계좌 비밀번호가 틀렸습니다"); } // 7. 출금 계좌의 주인과 로그인 유저가 동일 인물인지 권한 확인 if (!(withdrawAccount.getUserId().equals(userId))) { // Integer는 125이하까지만 값을 비교해주기 때문에 equals로 비교하는게 좋다 throw new RuntimeException("출금계좌의 권한이 없습니다"); } // 8. Account Update -> 출금계좌 int withdrawBalance = withdrawAccount.getBalance(); withdrawBalance = withdrawBalance - transferDTO.getAmount(); accountRepository.updateByNumber(withdrawBalance, withdrawAccount.getPassword(), withdrawAccount.getNumber()); // 9. Account Update -> 입금계좌 int depositBalance = depositAccount.getBalance(); depositBalance = depositBalance + transferDTO.getAmount(); accountRepository.updateByNumber(depositBalance, depositAccount.getPassword(), depositAccount.getNumber()); // 10. History save historyRepository.save(transferDTO.getWithdrawNumber(), transferDTO.getDepositNumber(), transferDTO.getAmount(), withdrawBalance); // 검증이 끝났기 때문에 transferDTO에서 가져온다 } }

AccountRepository

package com.metacoding.bankv1.account; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import java.util.List; @RequiredArgsConstructor @Repository public class AccountRepository { private final EntityManager em; public void save(Integer number, String password, Integer balance, int userId) { Query query = em.createNativeQuery("insert into account_tb(number,password,balance,user_id,created_at) values(?,?,?,?,now())"); query.setParameter(1, number); query.setParameter(2, password); query.setParameter(3, balance); query.setParameter(4, userId); query.executeUpdate(); } public List<Account> findAllByUserId(Integer userId) { Query query = em.createNativeQuery("select * from account_tb where user_id = ? order by created_at desc", Account.class); query.setParameter(1, userId); return query.getResultList(); } public Account findByNumber(Integer number) { Query query = em.createNativeQuery("select * from account_tb where number = ?", Account.class); query.setParameter(1, number); try { return (Account) query.getSingleResult(); } catch (Exception e) { return null; } } // 재사용할 수 있게 만들기 public void updateByNumber(int balance, String password, int number) { Query query = em.createNativeQuery("update account_tb set balance = ?, password = ? where number = ?"); // 변경가능한 건 다 써주는게 좋다 query.setParameter(1, balance); query.setParameter(2, password); query.setParameter(3, number); query.executeUpdate(); } // 재사용 불가능한 코드 예시 // public void updateWithdraw(int amount, int number) { // Query query = em.createNativeQuery("update account_tb set balance = balance - ? where number = ?"); // query.setParameter(1, amount); // query.setParameter(2, number); // query.executeUpdate(); // } // // public void updateDeposit(int amount, int number) { // Query query = em.createNativeQuery("update account_tb set balance = balance + ? where number = ?"); // query.setParameter(1, amount); // query.setParameter(2, number); // query.executeUpdate(); // } // // public void updatePassword(String password, int number) { // Query query = em.createNativeQuery("update account_tb set password = ? where number = ?"); // query.setParameter(1, password); // query.setParameter(2, number); // query.executeUpdate(); // } }

HistoryRepository

package com.metacoding.bankv1.account.history; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @RequiredArgsConstructor @Repository public class HistoryRepository { private final EntityManager em; public void save(int withdrawNumber, int depositNumber, int amount, int withdrawBalance) { Query query = em.createNativeQuery("insert into history_tb(withdraw_number, deposit_number, amount, withdraw_balance, created_at) values (?, ?, ?, ?, now())"); query.setParameter(1, withdrawNumber); query.setParameter(2, depositNumber); query.setParameter(3, amount); query.setParameter(4, withdrawBalance); query.executeUpdate(); } }

ssar의 ‘1111’ 계좌에서 cos의 ‘3333’ 계좌로 100원 이체해보기

1️⃣ ssar login
notion image
⬇ ssar의 계좌
→ 이체 전 ‘1111’ 계좌 잔액 900원 확인됨
notion image
2️⃣ 계좌 이체
notion image
이체하기 버튼을 누르면 redirection 되서 home으로 이동
notion image
‼ ssar의 계좌 목록을 확인하면 ‘1111’ 계좌의 잔액이 900원에서 800원으로 바뀐 것을 확인할 수 있다
notion image
3️⃣ cos의 ‘3333’ 계좌 잔액 확인
notion image
⬇ ssar의 ‘1111’ 계좌에서 cos의 ‘3333’ 계좌로 100원을 보내기 전 금액은 1000원
notion image
‼ 이체 후 잔액이 1000원에서 1100원으로 바뀐 것을 확인할 수 있다
notion image
Share article

parangdajavous