Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

햄발

Bank App 만들기 - 출금 기능 본문

Spring boot

Bank App 만들기 - 출금 기능

햄발자 2024. 9. 27. 15:22

 

 

 

 

학습 목표 

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

 

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="password" 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"%>

 

AccountController.java

더보기
닫기

AccountController.java

	
	/**
	 * 출금 페이지 요청 
	 * @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";
	}

 

header.jsp

header 출금하기
<li class="nav-item"><a class="nav-link" href="/account/withdrawal">출금하기</a></li>

더보기
닫기

header.jsp

<div class="container" style="margin-top: 30px">
				<div class="row">
					<div class="col-sm-4">
						<h2>About Me</h2>
						<h5>Photo of me:</h5>
						<div class="m--profile"></div>
						<p>코린이 개발을 위한 뱅크 앱</p>
						<h3>서비스 목록</h3>
						<p>계좌목록, 생성, 입금, 출금, 이체 페이지를 활용할 수 있어요</p>
						<ul class="nav nav-pills flex-column">
							<li class="nav-item"><a class="nav-link" href="/account/list">나의계좌목록</a></li>
							<li class="nav-item"><a class="nav-link" href="/account/save">신규계좌생성</a></li>
					<%--	<li class="nav-item"><a class="nav-link" href="/account/withdrawl">출금하기</a></li>  --%>
							<li class="nav-item"><a class="nav-link" href="/account/withdrawal">출금하기</a></li>
							<li class="nav-item"><a class="nav-link" href="/account/deposit">입금하기</a></li>
							<li class="nav-item"><a class="nav-link" href="/account/transfer">이체하기</a></li>
						</ul>
						<hr class="d-sm-none">
					</div>

					<!-- end of header.jsp  -->

 

 


 

WithdrawalDTO

더보기
닫기

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.java

더보기
닫기

AccountController.java

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.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
	 */
	@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
	 * 
	 * @retrun : 추후 계좌 목록 페이지 이동 처리
	 */

	@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);
		}

		// 서비스에 dto와 유저아이디를 보낸다.
		accountService.createAccount(dto, principal.getId());

		return "redirect:/index";
	}

	/**
	 * 계좌 목록 화면 요청 주소 설계 : http://localhost:8080/account/list, ..../
	 * 
	 * @return list.jsp
	 */
	// 페이지 리턴해야 되서 string으로 짓는다.
	@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) { // 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.java

더보기
닫기

AccountService.java

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.repository.model.User;
import com.tenco.bank.utils.Define;

@Service // IoC 대상( 싱글톤으로 관리) 
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 integer
	 */
	
	// 트랜 잭션 처리를 해야한다. (한번에 반영되거나, 아예 반영안되거나)  
	@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);
		}
		
	}
	
	/**
	 * 계좌 몇개 있는지 확인
	 * @param principal
	 */
	
	@Transactional
	public List<Account> readAccountListByUserId(Integer userId) {
		// TODO Auto-generated method stub
		List<Account> accountListEntity = null;
		
		try {
			accountListEntity = accountRepository.findByUserId(userId);
		} catch (DataDeliveryException e) {
			// TODO: handle exception
			throw new DataDeliveryException("잘못된 처리 입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			throw new RedirectException("알 수 없는 오류", HttpStatus.SERVICE_UNAVAILABLE);
		}
		
		return accountListEntity; 
		
	}
	
	
	 // 한번에 모든 기능을 생각하는 것은 힘듬
	 // 1. 사용자가 던진 계좌번호가 존재하는지 여부를 확인해야 한다. --> select 
	 // 2. 본인 계좌 여부를 확인해야 한다. --> 객체 상태값에서 비교한다.
	 // 3. 계좌 비번 확인 --> 객체 상태값에서 일치 여부 확인 account에서 userid가 있고 principal에서도 확인가능
	 // 4. 잔액 여부 확인 --> 객체 상태값에서 확인
	 // 5. 출금 처리 --> update 쿼리 발생
	 // 6. history 테이블에 거래내역 등록 --> insert(history)
	 // 7. 트랜잭션 처리 ex) insert 하다가 오류나면 뒤로 update 가야 되기 때문에 트랙잭션 사용
	
	@Transactional
	public void updateAccountWithdraw(WithdrawalDTO dto, Integer principalId) {
		
		// 1. 사용자가 던진 계좌번호가 존재하는지 여부를 확인해야 한다. 
		// (퍼시스턴스 계층에서 긁어서 entity 붙임)
		Account accountEntity = accountRepository.findByNumber(dto.getWAccountNumber());
		if(accountEntity == null) {
			throw new DataDeliveryException(Define.NOT_EXIST_ACCOUNT, HttpStatus.BAD_REQUEST);
		}
		
		// 2.
		accountEntity.checkOwner(principalId);
		
		// 3.
		accountEntity.checkPassword(dto.getWAccountPassword());
		
		// 4.
		accountEntity.checkBalance(dto.getAmount());
		
		// 5. 출금 처리 -- accountRepository 객체의 잔액을 변경하고 업데이트 처리해야 한다.
		accountEntity.withdraw(dto.getAmount());
		
		// update 처리
		accountRepository.updateById(accountEntity);
		
		// 6 - 거래 내역 등록
		/**
		 * <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>
		 */
		
		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);
		}
	}

}

 

Account.java

더보기
닫기

Account.java

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.java

더보기
닫기

AccountRepository.java

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 deleteBtId(Integer id, String name);
	
	// 고민! - 계좌 조회 
	// --> 한 사람의 유저는 여러개의 계좌번호를 가질 수 있다. (리스트로 뽑아야 한다.) 
	// @Param 사용이유 : interface 파라미터명과 xml에 사용할 변수명을 다르게 사용해야 된다면
	// @param 애노테이션을 사용할 수 있다. 그리고 2개 이상에 파라미터를 사용할 경우 반드시 사용.
	public List<Account> findByUserId(@Param("userId") Integer principalId);
	
	// --> account id 값으로 계좌정보 조회. (필요하다.)
	public Account findByNumber(@Param("number") String id);
	
}

 

account.xml

더보기
닫기

account.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.tenco.bank.repository.interfaces.AccountRepository">

	<!-- 반드시 세미콜론을 제거 해야 한다.   -->
	<!-- id는 매칭되어 있는 인터페이스에 메서드 명과 같아야 한다.  -->	
	
	<insert id="insert">
		insert into account_tb(number, password, balance, user_id, created_at)
		values(#{number}, #{password}, #{balance}, #{userId}, now())
	</insert>	
	
	<update id="updateById">
		update account_tb set number = #{number}, password = #{password},
			balance = #{balance}, user_id = #{userId} where id = #{id}
	</update>
	
	<delete id="deleteById">
		delete from account_tb where id = #{id}
	</delete>

	<select id="findByUserId" resultType="com.tenco.bank.repository.model.Account">
		select * from account_tb where user_id = #{userId} 
	</select>
	
	<select id="findByNumber"  resultType="com.tenco.bank.repository.model.Account">
		select * from account_tb where number = #{number}
	</select>

</mapper>

 

history.xml

더보기
닫기

history.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.tenco.bank.repository.interfaces.HistoryRepository">

	<!-- 반드시 세미콜론을 제거 해야 한다.   -->
	<!-- id는 매칭되어 있는 인터페이스에 메서드 명과 같아야 한다.  -->	
	
	<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>
	
	<update id="updateById">
		update history_tb
		set amount = #{amount},
			w_Balance = #{wBalance},
			d_Balance = #{dBalance},
			w_Account_id = #{wAccountId},
			d_account_id = #{dAccountId}
		where id = #{id}
		
	</update>
	
	<delete id="deleteById">
		delete from history_tb where id = #{id}
	
	</delete>
	
	<select id="findById" resultType="com.tenco.bank.repository.model.History">
		select * from history_tb where id = #{id}
	
	</select>
	
	<select id="findAll" resultType="com.tenco.bank.repository.model.History">
		select * from history_tb
	
	</select>
	

</mapper>