💡 학습 목표
1. 주소 설계 확인
2. withdrawForm.jsp 주소 설계 및 name 속성 확인
3. AccountController 출금 인증 검사, 유효성 검사 추가
4. AccountService 출금 기능 구현 및 트랜잭션 처리
5. MyBatis 맵퍼 확인 및 쿼리 확인
withdraw.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/view/layout/header.jsp"%>
<div class="col-sm-8">
<h2>출금 페이지(인증)</h2>
<h5>어서오세요 환영합니다.</h5>
<div class="bg-light p-md-5 h-75">
<div class="form-group">
<form action="/account/withdraw" method="post">
<div class="form-group">
<label for="amount">출금 금액</label>
<input type="text" id="amount" class="form-control" placeholder="출금 금액을 입력하세요" name="amount">
</div>
<div class="form-group">
<label for="wAccountNumber">출금 계좌번호</label>
<input type="text" id="wAccountNumber" class="form-control" placeholder="출금 계좌번호를 입력하세요" name="wAccountNumber">
</div>
<div class="form-group">
<label for="wAccountPassword">출금 계좌 비밀번호</label>
<input type="password" id="wAccountPassword" class="form-control" placeholder="출금 계좌 비밀번호를 입력하세요" name="wAccountPassword">
</div>
<button type="submit" class="btn btn-primary">출금</button>
</form>
</div>
</div>
</div>
</div>
</div>
<%@ include file="/WEB-INF/view/layout/footer.jsp"%>
AccountController.java
package com.tencoding.bank.controller;
import java.util.List;
import javax.servlet.http.HttpSession;
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.tencoding.bank.dto.DepositFormDto;
import com.tencoding.bank.dto.SaveFormDto;
import com.tencoding.bank.dto.WithDrawFormDto;
import com.tencoding.bank.handler.exception.CustomRestfulException;
import com.tencoding.bank.handler.exception.UnAuthorizedException;
import com.tencoding.bank.repository.model.Account;
import com.tencoding.bank.repository.model.User;
import com.tencoding.bank.service.AccountService;
import com.tencoding.bank.util.Define;
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private HttpSession session;
@Autowired
private AccountService accountService;
// 계좌 목록 페이지
// http://localhost:80/account/list
@GetMapping("/list")
public String list(Model model) {
User user = (User)session.getAttribute(Define.PRINCIPAL);;
if(user == null) {
throw new UnAuthorizedException("로그인을 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
List<Account> accountList = accountService.readAccountList(user.getId());
if(accountList.isEmpty()) {
model.addAttribute("accountList", null);
} else {
model.addAttribute("accountList", accountList);
}
return "account/list";
}
// 계좌 생성 페이지
// http://localhost:80/account/save
// /account/save - 화면 이동
/**
* 계좌 생성 페이지 이동
*/
@GetMapping("/save")
public String save() {
// 1. 인증 여부 확인
User user = (User)session.getAttribute(Define.PRINCIPAL);;
if(user == null) {
throw new UnAuthorizedException("로그인을 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
return "account/save";
}
/**
* 계좌 생성 로직 구현
* @return
*/
@PostMapping("/save")
public String saveProc(SaveFormDto saveFormDto) {
// 1. 인증 검사
User user = (User)session.getAttribute(Define.PRINCIPAL);;
if(user == null) {
throw new UnAuthorizedException("로그인을 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
// 2. 유효성 검사
if(saveFormDto.getNumber() == null
|| saveFormDto.getNumber().isEmpty()) {
throw new CustomRestfulException("계좌번호를 입력해주세요.", HttpStatus.BAD_REQUEST);
}
if(saveFormDto.getPassword() == null
|| saveFormDto.getPassword().isEmpty()) {
throw new CustomRestfulException("비밀번호를 입력해주세요.", HttpStatus.BAD_REQUEST);
}
if(saveFormDto.getBalance() == null
|| saveFormDto.getBalance() < 0) {
throw new CustomRestfulException("잘못된 입력입니다.", HttpStatus.BAD_REQUEST);
}
// 3. 서비스 호출
accountService.creatAccount(saveFormDto, user.getId());
return "redirect:/account/list";
}
// 출금 페이지
// http://localhost:80/account/withdraw
@GetMapping("/withdraw")
public String withdraw() {
// 1. 인증 여부 확인
User user = (User)session.getAttribute(Define.PRINCIPAL);;
if(user == null) {
throw new UnAuthorizedException("로그인을 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
return "account/withdraw";
}
// body -> String --> amount=1000&wAccountId=10&......
@PostMapping("/withdraw")
public String withdrawProc(WithDrawFormDto withDrawFormDto) {
// 1. 인증 여부 확인
User user = (User)session.getAttribute(Define.PRINCIPAL);;
if(user == null) {
throw new UnAuthorizedException("로그인을 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
// 2. 유효성 검사
if(withDrawFormDto.getAmount() == null) {
throw new CustomRestfulException("금액을 입력해주세요.", HttpStatus.BAD_REQUEST);
}
if(withDrawFormDto.getAmount() <= 0) {
throw new CustomRestfulException("잘못된 금액입니다.", HttpStatus.BAD_REQUEST);
}
if(withDrawFormDto.getWAccountNumber() == null
|| withDrawFormDto.getWAccountNumber().isEmpty()) {
throw new CustomRestfulException("출금 계좌번호를 확인해주세요.", HttpStatus.BAD_REQUEST);
}
if(withDrawFormDto.getWAccountPassword() == null
|| withDrawFormDto.getWAccountPassword().isEmpty()) {
throw new CustomRestfulException("출금 계좌 비밀번호를 확인해주세요.", HttpStatus.BAD_REQUEST);
}
accountService.updateAccountWithdraw(withDrawFormDto, user.getId());
return "redirect:/account/list";
}
// 입금 페이지
// http://localhost:80/account/deposit
@GetMapping("/deposit")
public String deposit() {
// 1. 인증 여부 확인
User user = (User)session.getAttribute(Define.PRINCIPAL);;
if(user == null) {
throw new UnAuthorizedException("로그인을 먼저 해주세요.", HttpStatus.UNAUTHORIZED);
}
return "account/deposit";
}
// 이체 페이지
// http://localhost:80/account/transfer
@GetMapping("/transfer")
public String transfer() {
return "account/transfer";
}
// TODO - 수정하기
// 상세 보기 페이지
// http://localhost:80/account/detail/1
@GetMapping("/detail")
public String detail() {
return "account/detail";
}
}
AccountService.java
package com.tencoding.bank.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.tencoding.bank.dto.DepositFormDto;
import com.tencoding.bank.dto.SaveFormDto;
import com.tencoding.bank.dto.WithDrawFormDto;
import com.tencoding.bank.handler.exception.CustomRestfulException;
import com.tencoding.bank.repository.interfaces.AccountRepository;
import com.tencoding.bank.repository.interfaces.HistoryRepository;
import com.tencoding.bank.repository.model.Account;
import com.tencoding.bank.repository.model.History;
@Service // IoC 대상 + 싱글톤 패턴으로 -> 스프링 컨테이너 메모리에 객체가 생성
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private HistoryRepository historyRepository;
@Transactional
public void creatAccount(SaveFormDto saveFormDto, Integer principalId) {
// 등록 처리 - insert
Account account = new Account();
account.setNumber(saveFormDto.getNumber());
account.setPassword(saveFormDto.getPassword());
account.setBalance(saveFormDto.getBalance());
account.setUserId(principalId);
int resultRowCount = accountRepository.insert(account);
if(resultRowCount != 1) {
throw new CustomRestfulException("계좌 생성 실패", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 계좌 목록 보기 ( 로그인 된 사용자 )
* @param userId
* @return
*/
@Transactional
public List<Account> readAccountList(Integer userId) {
List<Account> list = accountRepository.findByUserId(userId);
return list;
}
// 출금 기능 로직을 고민해보기
// 1. 계좌 존재 여부 확인 - select query
// 2. 본인 계좌 여부 확인 - select query
// 3. 계좌 비밀번호 확인 - select query
// 4. 계좌 잔액 여부 확인 - select query
// 5. 출금 처리 --> update query
// 6. 거래 내역 등록 -> insert query - history_tb
// 7. 트랜잭션 처리
@Transactional
public void updateAccountWithdraw(WithDrawFormDto withDrawFormDto, Integer id) {
Account accountEntity = accountRepository.findByNumber(withDrawFormDto.getWAccountNumber());
// 1
if(accountEntity == null) {
throw new CustomRestfulException("해당 계좌가 없습니다.", HttpStatus.BAD_REQUEST);
}
// 2
if(accountEntity.getUserId() != id) {
throw new CustomRestfulException("본인 소유 계좌가 아닙니다.", HttpStatus.BAD_REQUEST);
}
// 3
if(accountEntity.getPassword().equals(withDrawFormDto.getWAccountPassword()) == false) {
throw new CustomRestfulException("출금 계좌 비밀번호가 일치하지 않습니다.", HttpStatus.BAD_REQUEST);
}
// 4
if(accountEntity.getBalance() < withDrawFormDto.getAmount()) {
throw new CustomRestfulException("계좌 잔액이 부족합니다.", HttpStatus.BAD_REQUEST);
}
// 5 -> update query (모델 객체 상태 변경 -> 객체 다시 던지기)
accountEntity.withdraw(withDrawFormDto.getAmount());
// 쿼리 던지기
accountRepository.updateById(accountEntity);
// 6 - 거래 내역 등록 History 객체 생성
History history = new History();
history.setAmount(withDrawFormDto.getAmount());
// 출금 시점에 해당 계좌에 잔액을 입력
history.setWBalance(accountEntity.getBalance());
history.setDBalance(null);
history.setWAccountId(accountEntity.getId());
history.setDAccountId(null);
int resultRowCount = historyRepository.insert(history);
if(resultRowCount != 1) {
throw new CustomRestfulException("정상적으로 처리되지 않았습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
WithDrawFormDto.java
package com.tencoding.bank.dto;
import lombok.Data;
@Data
public class WithDrawFormDto {
// 화면 name태그 기준
private Long amount;
private String wAccountNumber;
private String wAccountPassword;
}
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.tencoding.bank.repository.interfaces.AccountRepository">
<select id="findByUserId" resultType="com.tencoding.bank.repository.model.Account">
select * from account_tb where user_id = #{userId}
</select>
<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} where id = #{id}
</update>
<delete id="deleteById">
delete from account_tb where id = #{id}
</delete>
<select id="findById" resultType="com.tencoding.bank.repository.model.Account">
select * from account_tb where id = #{id}
</select>
<select id="findAll" resultType="com.tencoding.bank.repository.model.Account">
select * from account_tb
</select>
<select id="findByNumber" resultType="com.tencoding.bank.repository.model.Account">
select * from account_tb where number = #{number}
</select>
</mapper>
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.tencoding.bank.repository.interfaces.HistoryRepository">
<insert id="insert">
insert into history_tb (amount, w_account_id, d_account_id, w_balance, d_balance, created_at)
values(#{amount}, #{wAccountId}, #{dAccountId}, #{wBalance}, #{dBalance}, now())
</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="findAll" resultType="com.tencoding.bank.repository.model.History">
select * from history_tb
</select>
<select id="findById" resultType="com.tencoding.bank.repository.model.History">
select * from history_tb where id = #{id}
</select>
</mapper>