👨💻 지난 포스팅에서 출금기능을 구현하였습니다. 이번 포스팅에서는 입금 기능 구현 과정을 다뤄보겠습니다. 🚀
Spring Boot를 활용한 입금 기능 🚀
은행 애플리케이션에서 입금 기능은 필수적인 요소입니다. 이번 포스팅에서는 Spring Boot를 활용하여 입금 기능을 구현하는 과정을 소개하겠습니다. 계좌 존재 여부 확인부터 입금 처리, 거래 내역 등록까지의 과정을 하나씩 살펴보겠습니다.
📌 학습 목표
- account/deposit.jsp 파일 생성
- DepositDto 파일 생성
- 입금 기능 구현
- 오류 테스트 확인
생성 될 파일 확인
📁1. account/deposit.jsp 파일 생성
📌 입금 화면 생성
기존 save.jsp 파일을 복사하여 account/deposit.jsp를 생성하고, 아래 코드를 수정하여 적용합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- header.jsp -->
<%@ include file="/WEB-INF/view/layout/header.jsp"%>
<div class="col-sm-8">
<h2>입금 요청(인증)</h2>
<h5>Bank App 오신 걸 환영합니다.</h5>
<form action="/account/deposit" method="post">
<div class="form-group">
<label for="amount">입금 금액:</label> <input type="text" name="amount" class="form-control" placeholder="Enter amount" id="amount" value="1000">
</div>
<div class="form-group">
<label for="dAccountNumber">입금 계좌번호:</label> <input type="text" name="dAccountNumber" class="form-control" placeholder="입금 계좌번호 입력"
id="dAccountNumber" value="1111">
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">입금</button>
</div>
</form>
</div>
</div>
</div>
<!-- footer.jsp -->
<%@ include file="/WEB-INF/view/layout/footer.jsp"%>
📌 설명
- 기존 save.jsp를 참고하여 입금 기능을 위한 화면을 구성합니다.
- 사용자가 입금할 금액과 입금 계좌번호를 입력할 수 있도록 input 필드를 배치합니다.
- form 태그의 action="/account/deposit"을 통해 입금 요청을 처리하는 컨트롤러로 데이터를 전송합니다.
📌 AccountController - 입금 페이지 요청 코드 추가
// 입금 페이지 요청
@GetMapping("/deposit")
public String depositPage() {
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.ENTER_YOUR_LOGIN, HttpStatus.UNAUTHORIZED);
}
return "account/deposit";
}
결과 화면 미리 보기
📁 2. DepositDto 파일 생성
입금 데이터 전송을 위한 DepositDto 파일을 생성합니다.
package com.tenco.bank.dto;
import lombok.Data;
@Data
public class DepositDto {
private Long amount;
private String dAccountNumber;
}
📌 설명
- DepositDto는 클라이언트에서 전달된 입금 요청 데이터를 담는 객체입니다.
- @Data 애노테이션을 사용하여 Getter, Setter, toString()을 자동 생성합니다.
- amount: 입금할 금액
- dAccountNumber: 입금 대상 계좌번호
3. 입금 기능 구현
📌 MyBatis Mapper - 입금 기능 SQL
account.xml 파일에 입금 시 계좌 업데이트를 위한 쿼리를 추가합니다.
<update id="updateById">
UPDATE account_tb
SET number = #{number}, password = #{password},
balance = #{balance}, user_id = #{userId}
WHERE id = #{id}
</update>
📌 설명
- updateById는 계좌의 잔액(balance)을 업데이트하는 SQL 쿼리입니다.
- #{balance}: 입금 후 갱신된 잔액 값이 들어갑니다.
- #{id}: 업데이트할 계좌의 고유 ID를 지정합니다.
📌 AccountRepository - 입금 기능 메서드 추가
@Mapper
public interface AccountRepository {
public int insert(Account account);
public int updateById(Account account);
}
📌 설명
- findByNumber(String accountNumber): 입금할 계좌가 존재하는지 확인하는 메서드입니다.
- updateById(Account account): 입금 후 계좌의 잔액을 업데이트합니다.
📌 AccountService - 입금 기능 구현
package com.tenco.bank.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.tenco.bank.dto.AccountSaveDTO;
import com.tenco.bank.dto.DepositDto;
import com.tenco.bank.dto.WithdrawalDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.RedirectException;
import com.tenco.bank.repository.interfaces.AccountRepository;
import com.tenco.bank.repository.interfaces.HistoryRepository;
import com.tenco.bank.repository.model.Account;
import com.tenco.bank.repository.model.History;
import com.tenco.bank.utils.Define;
import lombok.RequiredArgsConstructor;
@Service
//final 멤버 변수 사용시 직정 생성자를 만들기 않고 @RequiredArgsConstructor 대체 가능 합니다
@RequiredArgsConstructor
public class AccountService {
@Autowired
private final AccountRepository accountRepository;
@Autowired
private final HistoryRepository historyRepository;
// @RequiredArgsConstructor 대체
// public AccountService(AccountRepository accountRepository) {
// this.accountRepository = accountRepository;
// }
/**
* 계좌 생성 기능
*
* @param dto
* @param pricipalId
*/
@Transactional
public void createAccount(AccountSaveDTO dto, Integer pricipalId) {
try {
accountRepository.insert(dto.toAccount(pricipalId));
} catch (DataAccessException e) {
throw new DataDeliveryException(Define.INVALID_INPUT, HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
throw new RedirectException(Define.UNKNOWN, HttpStatus.SERVICE_UNAVAILABLE);
}
}
/**
* 사용자별 계좌 번호 조회 서비스
* @param principalId
* @return List<Account> or Null
*/
public List<Account> readAccountListByUserId(Integer principalId) {
List<Account> accountListEntity = null;
try {
accountListEntity = accountRepository.findAllByUserId(principalId);
} catch (DataAccessException e) {
throw new DataDeliveryException(Define.INVALID_INPUT, HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
throw new RedirectException(Define.UNKNOWN, HttpStatus.SERVICE_UNAVAILABLE);
}
return accountListEntity;
}
// 한번에 모든 기능을 생각하는 것은 힘든 부분 입니다.
// 주석을 활용해서 하나씩 만들어야 하는 기능을 먼저 정의하고
// 코드를 작성해 봅시다.
// 출금 기능 만들기
// 1. 계좌 존재 여부 확인 -- select
// 2. 본인 계좌 여부 확인 -- 객체에서 확인 처리
// 3. 계좌 비번 확인
// 4. 잔액 여부 화인
// 5. 출금 처리 ---> update
// 6. 거래 내역 등록 --> insert(history)
// 7. 트랜잭션 처리
@Transactional
public void updateAccountWithdraw(WithdrawalDTO dto, Integer principalId) {
// 1
Account accountEntity = accountRepository.findByNumber(dto.getWAccountNumber());
if(accountEntity == null) {
throw new DataDeliveryException(Define.NOT_EXIST_ACCOUNT, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 2. 1단계 직접 코드 작성, 2단계 Entity에 메서드를 만들어 두기
accountEntity.checkOwner(principalId);
// 3.
accountEntity.checkPassword(dto.getWAccountPassword());
// 4.
accountEntity.checkBalacne(dto.getAmount());
// 5 --> 출금 기능 (Account) --> 객체 상태값 변경
accountEntity.withdraw(dto.getAmount());
accountRepository.updateById(accountEntity);
// 6 --> 거래 내역 등록
History history = new History();
history.setAmount(dto.getAmount());
history.setWBalance(accountEntity.getBalance());
history.setDBalance(null);
history.setWAccountId(accountEntity.getId());
history.setDAccountId(null);
int rowResultCount = historyRepository.insert(history);
if(rowResultCount != 1) {
throw new DataDeliveryException(Define.FAILED_PROCESSING, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// 입금 기능 만들기
// 1. 계좌 존재여부 확인(select)
// 2. 계좌 존재 -> 본인 계좌 여부 확인(객체)
// 3. 입금 처리 -> update
// 4. 거래 내역 등록 - insert
// 5. 트랜잭션 처리
@Transactional
public void updateAccountDeposit(DepositDto dto, Integer principalId) {
// 1. 계좌 존재 여부 확인
Account accountEntity = accountRepository.findByNumber(dto.getDAccountNumber());
if (accountEntity == null) {
throw new DataDeliveryException(Define.NOT_EXIST_ACCOUNT, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 2. 본인 계좌 여부 확인
accountEntity.checkOwner(principalId);
// 3. 입금처리(객체 상태 변경 후 update 처리)
accountEntity.deposit(dto.getAmount());
accountRepository.updateById(accountEntity);
// 4. history에 거래내역 등록
History history = new History();
history.setAmount(dto.getAmount());
history.setWAccountId(null);
history.setDAccountId(accountEntity.getId());
history.setWBalance(null);
history.setDBalance(accountEntity.getBalance());
int rowResultCount = historyRepository.insert(history);
if (rowResultCount != 1) {
throw new DataDeliveryException(Define.FAILED_PROCESSING, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
📌 설명
- 계좌 존재 여부 확인
- findByNumber(dto.getDAccountNumber())를 호출하여 입금할 계좌가 존재하는지 확인합니다.
- 존재하지 않으면 예외(DataDeliveryException)를 발생시킵니다.
- 본인 계좌 여부 확인
- checkOwner(principalId)를 호출하여 본인의 계좌가 맞는지 확인합니다.
- 이를 통해 타인의 계좌로 임의 입금하는 행위를 방지합니다.
- 입금 처리
- deposit(dto.getAmount())을 호출하여 잔액을 증가시킵니다.
- updateById(accountEntity)를 실행하여 DB의 계좌 잔액을 갱신합니다.
- 거래 내역 등록
- History 객체를 생성하여 거래 내역을 저장합니다.
- historyRepository.insert(history)를 통해 DB에 저장합니다.
- 정상적으로 처리되지 않으면 예외 발생.
📌 Account Controller - 입금 요청 처리
package com.tenco.bank.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.tenco.bank.dto.AccountSaveDTO;
import com.tenco.bank.dto.DepositDto;
import com.tenco.bank.dto.WithdrawalDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.UnAuthorizedException;
import com.tenco.bank.repository.model.Account;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.service.AccountService;
import com.tenco.bank.utils.Define;
import jakarta.servlet.http.HttpSession;
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private final HttpSession session;
@Autowired
private final AccountService accountService;
public AccountController(HttpSession session, AccountService accountService) {
this.session = session;
this.accountService = accountService;
}
/**
* 계좌 목록 페이지
*
* @param model - accountList
* @return list.jsp
*/
@GetMapping({ "/list", "/" })
public String listPage(Model model) {
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
List<Account> accountList = accountService.readAccountListByUserId(principal.getId());
if (accountList.isEmpty()) {
model.addAttribute("accountList", null);
} else {
model.addAttribute("accountList", accountList);
}
return "account/list";
}
/**
* 계좌 생성 화면 요청
* @return account/save.jsp
*/
@GetMapping("/save")
public String savePage() {
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
return "account/save";
}
/**
* 계좌 생성 처리
* @param AccountSaveDTO
* @return 계좌 목록 화면 이동
*/
@PostMapping("/save")
public String saveProc(AccountSaveDTO dto) {
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.ENTER_YOUR_LOGIN, HttpStatus.UNAUTHORIZED);
}
if (dto.getNumber() == null || dto.getNumber().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
}
if (dto.getPassword() == null || dto.getPassword().isEmpty()) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
}
if (dto.getBalance() == null || dto.getBalance() <= 0) {
throw new DataDeliveryException(Define.INVALID_INPUT, HttpStatus.BAD_REQUEST);
}
accountService.createAccount(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 출금 페이지 요청
* @return withdraw.jsp
*/
@GetMapping("/withdrawal")
public String withdrawalPage() {
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.ENTER_YOUR_LOGIN, HttpStatus.UNAUTHORIZED);
}
return "account/withdrawal";
}
/**
* 출금 요청 기능 처리
* @return account/list.jsp
*/
@PostMapping("/withdrawal")
public String withdrawalProc(WithdrawalDTO dto) {
// 1. 인증 검사
User principal = (User) session.getAttribute(Define.PRINCIPAL);
if (principal == null) {
throw new UnAuthorizedException(Define.ENTER_YOUR_LOGIN, HttpStatus.UNAUTHORIZED);
}
// 2. 유효성 검사
// 유효성 검사
if(dto.getAmount() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE,
HttpStatus.BAD_REQUEST);
}
if(dto.getAmount().longValue() <= 0) {
throw new DataDeliveryException(Define.W_BALANCE_VALUE,
HttpStatus.BAD_REQUEST);
}
if(dto.getWAccountNumber() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER,
HttpStatus.BAD_REQUEST);
}
if(dto.getWAccountPassword() == null || dto.getWAccountPassword().isEmpty() ) {
throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD,
HttpStatus.BAD_REQUEST);
}
accountService.updateAccountWithdraw(dto, principal.getId());
return "redirect:/account/list";
}
/**
* 입금 화면 요청
* @return account/deposit.jsp
*/
@GetMapping("/deposit")
public String depositPage() {
// 1. 인증 검사
User principal = (User) session.getAttribute(Define.PRINCIPAL); // 다운 캐스팅
if (principal == null) {
throw new UnAuthorizedException(Define.ENTER_YOUR_LOGIN, HttpStatus.UNAUTHORIZED);
}
return "account/deposit";
}
/**
* 입금 기능 처리
* @param DepositDto
* @return 계좌 목록 페이지
*/
@PostMapping("/deposit")
public String depositProc(DepositDto dto) {
// 1. 인증 검사
User principal = (User) session.getAttribute(Define.PRINCIPAL); // 다운 캐스팅
if (principal == null) {
throw new UnAuthorizedException(Define.ENTER_YOUR_LOGIN, HttpStatus.UNAUTHORIZED);
}
// 2. 유효성 검사
if (dto.getAmount() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
}
if (dto.getAmount().longValue() <= 0) {
throw new DataDeliveryException(Define.D_BALANCE_VALUE, HttpStatus.BAD_REQUEST);
}
if (dto.getDAccountNumber() == null) {
throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
}
accountService.updateAccountDeposit(dto, principal.getId());
return "redirect:/account/list";
}
}
📌 설명
- 계좌 목록 조회 - 로그인한 사용자의 계좌 목록을 불러와서 화면에 출력.
- 계좌 생성 - 계좌 번호, 비밀번호, 초기 잔액 입력 후 계좌 생성.
- 출금 기능 - 계좌 비밀번호와 출금 금액을 입력하여 출금 요청.
- 입금 기능 - 계좌 번호와 입금 금액을 입력하여 입금 요청.
- 인증 검사 - 모든 요청 전에 session을 확인하여 사용자 인증 여부를 검증.
✅ 특징 및 유효성 검사
- 모든 요청에서 로그인한 사용자만 접근 가능하도록 검증.
- 출금 및 입금 금액 검증: 0 이하의 값이 입력되지 않도록 체크.
- 유효한 계좌번호인지 확인하여 잘못된 입금·출금을 방지.
5. 오류 테스트 확인 하기
✅입금 확인(정상 동작)
✅유효성 검사 동작 여부 확인
✅ 예외 처리 확인
🎯 마무리 및 결론
이번 포스팅에서는 Spring Boot를 활용한 입금 기능을 구현하는 과정을 상세히 다뤄보았습니다.
은행 애플리케이션에서 입금 기능은 필수적인 요소로, 안정적이고 신뢰할 수 있는 입금 처리를 위해 계좌 검증, 트랜잭션 관리, 유효성 검사, 거래 내역 등록 등의 핵심 개념을 적용하였습니다.
✅ 이번 포스팅에서 다룬 내용
- 입금 화면 생성 (account/deposit.jsp 파일 작성)
- 입금 데이터 전달을 위한 DTO 생성 (DepositDto)
- 입금 기능 구현 (계좌 확인 → 입금 처리 → 거래 내역 등록)
- 입금 기능의 유효성 검사 및 예외 처리
- 오류 테스트 및 정상 동작 확인
이제 입금 기능이 완성되었으며, 계좌 잔액이 업데이트되고 거래 내역이 정상적으로 저장됨을 확인할 수 있습니다.
이 과정에서 Spring Boot, MyBatis, 트랜잭션 관리, 예외 처리 등 다양한 기술을 활용하여 안정적인 금융 서비스를 구축하는 방법을 배웠습니다. 🚀
📌 다음 포스팅에서는 계좌 이체 기능을 추가하여 더욱 완성도 높은 금융 서비스를 구현하겠습니다.
출금 기능이 궁금하다면??
[Spring boot] Bank App - 출금
👨💻 지난 포스팅에서 계좌목록 조회를 구현했습니다. 이어 이번 포스팅에서는 출금 기능 구현 과정을 다뤄보겠습니다. 🚀Spring Boot를 활용한 출금 기능 및 디버깅 모드 실행 🚀이전 포스
seohong.tistory.com
'Spring' 카테고리의 다른 글
[Spring boot] Bank App - 어노테이션 정리 (0) | 2025.03.06 |
---|---|
[Spring boot] Bank App - 출금 (0) | 2025.02.18 |
[Spring boot] Bank App - 계좌 목록 조회 (0) | 2025.02.18 |
[Spring boot] Bank App - 계좌 생성 (0) | 2025.02.17 |
[Spring boot] Bank App - 로그인 처리 (0) | 2025.02.17 |