[Spring Boot] 7. Spring Boot Project (Bank v1)_3-5.Fuctions Design_Account transfer
Mar 27, 2025
Account는 History를 저장

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

⬇ ssar의 계좌
→ 이체 전 ‘1111’ 계좌 잔액 900원 확인됨

2️⃣ 계좌 이체

⬇
이체하기
버튼을 누르면 redirection 되서 home으로 이동
‼ ssar의 계좌 목록을 확인하면 ‘1111’ 계좌의 잔액이 900원에서 800원으로 바뀐 것을 확인할 수 있다

3️⃣ cos의 ‘3333’ 계좌 잔액 확인

⬇ ssar의 ‘1111’ 계좌에서 cos의 ‘3333’ 계좌로 100원을 보내기 전 금액은 1000원

‼ 이체 후 잔액이 1000원에서 1100원으로 바뀐 것을 확인할 수 있다

Share article