19. 출금 기능 만들기

 

목차

    0. 작업 순서

    1. withdrawal.jsp 파일 생성 및 코드 추가
    2. 출금 화면 요청 및 기능 구현
    3. 전체 코드 확인
    4. 디버그 모드 동작 시켜 보기

    1. 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 context.jsp(xxx.jsp) -->
    <div class="col-sm-8">
    	<h2>출금 요청(인증)</h2>
    	<h5>Bank App에 오신걸 환영합니다.</h5>
    
    	<!-- 예외적으로 로그인은 보안때문에 post로 던지자 -->
    	<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="wAccountPassword">출금 계좌 비밀 번호:</label> <input type="password" class="form-control" placeholder="Enter password" id="wAccountPassword" 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 context.jsp(xxx.jsp) -->
    
    <!-- footer.jsp -->
    <%@ include file="/WEB-INF/view/layout/footer.jsp"%>

    결과 화면 확인

    2. 출금 화면 요청 및 기능 구현

    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
    /**
     * 출금 페이지 요청
     * 
     * @return withdrawal.jsp
     */
    @GetMapping("/withdrawal")
    public String withdrawalPage() {
        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) {
        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 || dto.getWAccountNumber().trim().isEmpty()) {
            throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
        }
        if (dto.getWAccountPassword() == null || dto.getWAccountPassword().trim().isEmpty()) {
            throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
        }
        accountService.updateAccountWithdraw(dto, principal.getId());
        return "redirect:/account/list";
    }
    AccountService
    // 한번에 모든 기능을 생각하는건 힘듦
    // 1. 계좌 존재 여부를 확인
    // 2. 본인 계좌 여부를 확인 -- 객체 상태값에서 비교
    // 3. 계좌 비번 확인 		-- 객체 상태값에서 비교
    // 4. 잔액 여부 확인		-- 객체 상태값에서 확인
    // 5. 출금 처리				-- update
    // 6. 거래 내역 등록		-- insert(history)
    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.BAD_REQUEST);
        }
        accountEntity.checkOwner(principalId);
        accountEntity.checkPassword(dto.getWAccountPassword());
        accountEntity.checkBalance(dto.getAmount());
        // 5.
        accountEntity.withdraw(dto.getAmount());
        accountRepository.updateById(accountEntity);
        // 6.
        History history = History.builder()
                        .amount(dto.getAmount())
                        .wAccountId(accountEntity.getId())
                        .wBalance(accountEntity.getBalance())
                        .dAccountId(null)
                        .dBalance(null)
                        .build();
        int rowResultCount = historyRepository.insert(history);
        if (rowResultCount != 1) {
            throw new DataDeliveryException(Define.FAILED_PROCESSING, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    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 checkOwner(Integer userId) {
    		if (this.userId != userId) {
    			throw new DataDeliveryException(Define.NOT_ACCOUNT_OWNER, HttpStatus.BAD_REQUEST);
    		}
    	}
    	// 패스워드 체크
    	public void checkPassword(String password) {
    		if (!this.password.equals(password)) {
    			throw new DataDeliveryException(Define.FAIL_ACCOUNT_PASSWORD, HttpStatus.BAD_REQUEST);
    		}
    	}
    	// 잔액 여부 확인
    	public void checkBalance(Long amount) {
    		if (balance < amount) {
    			throw new DataDeliveryException(Define.LACK_Of_BALANCE, 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);
    	
    	// interface 파라미터명과 xml에 사용할 변수명을 다르게 사용해야 된다면 @param 어노테이션을
    	// 사용할 수 있다. 그리고 2개 이상의 파라미터를 사용할 경우 반드시 @param 어노테이션을 사용하자!
    	public List<Account> findByUserId(@Param("userId") Integer principalId);
    	// --> account number 값으로 계좌 정보 조회
    	public Account findByNumber(@Param("number") String id);
    }
    account.xml
    <select
        id="findByNumber"
        resultType="com.tenco.bank.repository.model.Account">
        select * from account_tb where number = #{number}
    </select>
    <update id="updateById">
        update account_tb set number = #{number}, password = #{password},
        balance = #{balance}, user_id = #{userId} where id = #{id}
    </update>
    history.xml
    <insert id="insert">
        insert into history_tb(amount, w_balance, d_balance, w_account_id, d_account_id)
        values(#{amount},#{wBalance},#{dBalance},#{wAccountId},#{dAccountId})
    </insert>