💡 학습 목표
- withdrawal.jsp 파일 생성 및 코드 추가
- 출금 화면 요청 및 기능 구현
- 전체 코드 확인
- 디버그 모드 동작 시켜 보기
withdrawal.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- header.jsp -->
<%@ include file="/WEB-INF/view/layout/header.jsp"%>
<!-- start of content.jsp(xxx.jsp) -->
<div class="col-sm-8">
<h2>출금 요청(인증)</h2>
<h5>Bank App에 오신걸 환영합니다</h5>
<!-- 예외적으로 로그인은 보안 때문에 post로 던지자 -->
<!--
insert into account_tb(number, password, balance, user_id, created_at)
-->
<form action="/account/withdrawal" method="post">
<div class="form-group">
<label for="amount">출금 금액:</label>
<input type="number" class="form-control" placeholder="Enter amount" id="amount" name="amount" value="1000" >
</div>
<div class="form-group">
<label for="wAccountNumber">출금 계좌 번호:</label>
<input type="text" class="form-control" placeholder="Enter account number" id="wAccountNumber" name="wAccountNumber" value="1111">
</div>
<div class="form-group">
<label for="pwd">출금 계좌 비밀 번호 :</label>
<input type="password" class="form-control" placeholder="Enter password" id="pwd" name="wAccountPassword" value="1234" >
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">출금 요청</button>
</div>
</form>
</div>
<!-- end of col-sm-8 -->
</div>
</div>
<!-- end of content.jsp(xxx.jsp) -->
<!-- footer.jsp -->
<%@ include file="/WEB-INF/view/layout/footer.jsp"%>
결과 화면 확인
WithdrawalDTO
package com.tenco.bank.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class WithdrawalDTO {
private Long amount;
private String wAccountNumber;
private String wAccountPassword;
}
AccountController
package com.tenco.bank.controller;
import java.util.List;
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.SaveDTO;
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 // IoC 대상(싱글톤으로 관리)
@RequestMapping("/account")
public class AccountController {
// 계좌 생성 화면 요청 - DI 처리
private final HttpSession session;
private final AccountService accountService;
// @Autowired
public AccountController(HttpSession session, AccountService accountService) {
this.session = session;
this.accountService = accountService;
}
/**
* 계좌 생성 페이지 요청
* 주소 설계 : <http://localhost:8080/account/save>
* @return save.jsp
*/
@GetMapping("/save")
public String savePage() {
// 1. 인증 검사가 필요(account 전체가 필요함)
User principal = (User)session.getAttribute(Define.PRINCIPAL);
if(principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
return "account/save";
}
/**
* 계좌 생성 기능 요청
* 주소 설계 : <http://localhost:8080/account/save>
* @return : 추후 계좌 목록 페이지 이동 처리
*/
@PostMapping("/save")
public String saveProc(SaveDTO dto) {
// 1. form 데이터 추출 (파싱 전략)
// 2. 인증 검사
// 3. 유효성 검사
// 4. 서비스 호출
User principal = (User)session.getAttribute(Define.PRINCIPAL);
if(principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, 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.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
}
accountService.createAccount(dto, principal.getId());
return "redirect:/index";
}
/**
* 계좌 목록 화면 요청
* 주소설계 : <http://localhost:8080/account/list>, ..../
* @return list.jsp
*/
@GetMapping({"/list", "/"})
public String listPage(Model model) {
// 1. 인증검사
User principal = (User)session.getAttribute(Define.PRINCIPAL);
if(principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
// 2. 유효성 검사
// 3. 서비스 호출
List<Account> accountList = accountService.readAccountListByUserId(principal.getId());
if(accountList.isEmpty()) {
model.addAttribute("accountList", null);
} else {
model.addAttribute("accountList", accountList);
}
// JSP 데이트를 넣어 주는 방법
return "account/list";
}
/**
* 출금 페이지 요청
* @return withdrawal.jsp
*/
@GetMapping("/withdrawal")
public String withdrawalPage() {
// 1. 인증검사
User principal = (User)session.getAttribute(Define.PRINCIPAL);
if(principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
return "account/withdrawal";
}
@PostMapping("/withdrawal")
public String withdrawalProc(WithdrawalDTO dto) {
// 1. 인증검사
User principal = (User)session.getAttribute(Define.PRINCIPAL);
if(principal == null) {
throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
}
// 유효성 검사 (자바 코드를 개발) --> 스프링 부트 @Valid 라이브러리가 존재
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";
}
}
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.SaveDTO;
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;
@Service
public class AccountService {
private final AccountRepository accountRepository;
private final HistoryRepository historyRepository;
@Autowired // 생략 가능 - DI 처리
public AccountService(AccountRepository accountRepository, HistoryRepository historyRepository) {
this.accountRepository = accountRepository;
this.historyRepository = historyRepository;
}
/**
* 계좌 생성 기능
* @param dto
* @param id
*/
// 트랜 잭션 처리
@Transactional
public void createAccount(SaveDTO dto, Integer principalId) {
int result = 0;
try {
result = accountRepository.insert(dto.toAccount(principalId));
} catch (DataAccessException e) {
throw new DataDeliveryException("잘못된 요청입니다", HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
throw new RedirectException("알 수 없는 오류 입니다", HttpStatus.SERVICE_UNAVAILABLE);
}
if(result == 0) {
throw new DataDeliveryException("정상 처리 되지 않았습니다", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
public List<Account> readAccountListByUserId(Integer userId) {
List<Account> accountListEntity = null;
try {
accountListEntity = accountRepository.findByUserId(userId);
} catch (DataAccessException e) {
throw new DataDeliveryException("잘못된 처리 입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
throw new RedirectException("알 수 없는 오류", 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 accoutEntity = accountRepository.findByNumber(dto.getWAccountNumber());
if(accoutEntity == null) {
throw new DataDeliveryException(Define.NOT_EXIST_ACCOUNT, HttpStatus.BAD_REQUEST);
}
// 2
accoutEntity.checkOwner(principalId);
// 3
accoutEntity.checkPassword(dto.getWAccountPassword());
// 4
accoutEntity.checkBalance(dto.getAmount());
// 5
// accoutEntity 객체의 잔액을 변경하고 업데이트 처리해야 한다.
accoutEntity.withdraw(dto.getAmount());
// update 처리
accountRepository.updateById(accoutEntity);
// 6 - 거래 내역 등록
History history = new History();
history.setAmount(dto.getAmount());
history.setWBalance(accoutEntity.getBalance());
history.setDBalance(null);
history.setWAccountId(accoutEntity.getId());
history.setDAccountId(null);
int rowResultCount = historyRepository.insert(history);
if(rowResultCount != 1) {
throw new DataDeliveryException(Define.FAILED_PROCESSING, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
: History 등록 시 입금,출금,이체 3가지 형태가 존재 합니다. 따로 이력의 형태를 따로 컬럼을 추가해서 생성하지 않고 ROW 들어간 값에 형태로 구분해 낼 수 있습니다. DB 에서 데이터의 형태를 보고 의미를 추론 할 수 있도록 연습하는 과정도 반드시 필요 합니다
Account
package com.tenco.bank.repository.model;
import java.sql.Timestamp;
import org.springframework.http.HttpStatus;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.utils.Define;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class Account {
private Integer id;
private String number;
private String password;
private Long balance;
private Integer userId;
private Timestamp createdAt;
// 출금 기능
public void withdraw(Long amount) {
// 방어적 코드
this.balance -= amount;
}
// 입금 기능
public void deposit(Long amount) {
this.balance += amount;
}
// 패스워드 체크
public void checkPassword(String password) {
// f == f 일때 ---> true
if(this.password.equals(password) == false) {
throw new DataDeliveryException(Define.FAIL_ACCOUNT_PASSWROD, HttpStatus.BAD_REQUEST);
}
}
// 잔액 여부 확인 - checkBalance
public void checkBalance(Long amount) {
if(this.balance < amount) {
throw new DataDeliveryException(Define.LACK_Of_BALANCE, HttpStatus.BAD_REQUEST);
}
}
// 계좌 소유자 확인 기능 - checkOwner
public void checkOwner(Integer principalId) {
if(this.userId != principalId) {
throw new DataDeliveryException(Define.NOT_ACCOUNT_OWNER, HttpStatus.BAD_REQUEST);
}
}
}
AccountRepository
package com.tenco.bank.repository.interfaces;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.tenco.bank.repository.model.Account;
//AccountRepository 인터페이스와 account.xml 파일을 매칭 시킨다.
@Mapper
public interface AccountRepository {
public int insert(Account account);
public int updateById(Account account);
public int deleteById(Integer id, String name);
// interface 파마리터명과 xml 에 사용할 변수명을 다르게 사용해야 된다면 @param 애노테이션을
// 사용할 수 있다. 그리고 2개 이상에 파라미터를 사용할 경우 반드시 사용하자!
public List<Account> findByUserId(@Param("userId") Integer principalId);
// --> account id 값으로 계좌 정보 조회
public Account findByNumber(@Param("number") String id);
// 코드 추가 예정
}
account.xml
http://mybatis.org/dtd/mybatis-3-mapper.dtd>">
insert into account_tb(number, password, balance, user_id, created_at)
values(#{number}, #{password}, #{balance}, #{userId}, now())
update account_tb set number = #{number}, password = #{password},
balance = #{balance}, user_id = #{userId} where id = #{id}
delete from account_tb where id = #{id}
select * from account_tb where user_id = #{userId}
select * from account_tb where number = #{number}
history.xml
http://mybatis.org/dtd/mybatis-3-mapper.dtd>">
insert into history_tb(amount, w_balance, d_balance, w_account_id, d_account_id)
values(#{amount}, #{wBalance}, #{dBalance}, #{wAccountId}, #{dAccountId} )
update history_tb
set amount = #{amount},
w_balance = #{wBalance},
d_balance = #{dBalance},
w_account_id = #{wAccountId},
d_account_id = #{dAccountId}
where id = #{id}
delete from history_tb where id = #{id}
select * from history_tb where id = #{id}
select * from history_tb
4. 디버그 모드 동작 시켜 보기
먼저 서버가 실행되어 있다면 모두 종료 해주세요
오른쪽 마우스를 클릭하고 원하는 곳(검증하는 곳)에서 브레이크 포인트를 선택해보세요
프로그램을 동작 시켜 보세요
브레이크 포인트에서 실행에 흐름이 멈추는 것을 확인하고 오른쪽에 넘겨 받은 값이 정확한지 디버그 모드에서 확인할 수 있습니다.
'Spring boot > Bank App 만들기' 카테고리의 다른 글
21. 이체 기능 만들기 (0) | 2024.08.08 |
---|---|
20. 입금 기능 만들기 (0) | 2024.08.08 |
18. 중간 리팩토링 (0) | 2024.08.08 |
17. 계좌 목록 만들기(1단계) (0) | 2024.08.08 |
16. 계좌 생성(유효성, 인증검사 중 누가 먼저 일까?) (0) | 2024.08.08 |