[Database/JDBC] Account Mini Project
Account Mini Project
- 계좌 관리 프로그램
0. 사용 목적
- 우리가 이용하는 은행 서비스와 유사하게 통장(계좌)의 개설 및 계좌를 사용해서 서비스를 이용할 수 있는 프로그램을 만들어본다.
1. 요구사항
-
사용자는 계좌(통장) 개설이 가능하다.
-
계좌 개설시
2.1 계좌번호, 계좌주명, 비밀번호, 잔액정보가 저장되어야 한다.
2.2 최초 계좌 개설시 초기 납입액이 1000원 이상이 되어야 한다.
2.3 계좌번호는 유일해야 하고 시스템에서 자동 발급한다.
-
잔액조회시에 계좌번호가 존재해야 하고 계좌번호에 해당하는 비밀번호가 일치해야 한다.
-
계좌에서 입금, 출금, 계좌이체가 가능하며, 입금액, 출금액, 계좌이체액은 모두 0원을 초과해야 한다.
4.1 입금시 계좌번호, 비밀번호 일치
4.2 출금시 계좌번호, 비밀번호 일치 및 잔액확인
4.3 계좌이체시 입금자 및 예금주의 계좌가 각각 존재
4.3.1 송금자의 잔액확인, 비밀번호 일치여부 확인
4.3.2 송금자에게서 출금 및 예금주에게 입금 처리가 정상적으로 수행될 경우에만 계좌이체 처리한다.
-
계좌 잔액이 가장 높은 계좌주명과 잔액을 조회한다.
2. 요구사항 분석(주요 핵심기능 추출 )
-
위 요구사항에서 필요한 핵심기능들을 추출하면 코드에서 메서드를 구현할 때 더욱 편리해진다.
- 계좌 개설 - createAccount()
- 잔액조회 - findBalanceByAccountNo()
- 입금 - deposit()
- 출금 - withdraw()
- 계좌이체 - trasfer()
- 최고 잔액 계좌정보 0 fingHigestBalanceAccount()
-
이렇게 6가지의 주요기능(메서드)으로 분류할 수 있으며, 요구사항의 조건들은 예외 생성 및 해당 메서드 내에서 예외처리를 통해 해결할 수 있다.
-
핵심기능 메서드 외에 getConnection(), checkAccount(), existsAccountNo() 메서드는 핵심 메서드를 구현하면서 반복되는 작업을 재사용하기 위하여 편리를 위해 구현한 메서드다. (+ DbInfo는 데이터와 연동을 위해 DB 정보를 담은 인터페이스)
-
요구사항을 확인해보면 총 5개의 조건을 확인할 수 있다. 이 조건들은 5개의 예외처리를 통해 해결할 수 있다.(모두 사용자 정의 예외)
- CreateAccountException : 최초 계좌 개설시 초기 납입액 1000원 이상
- AccountNotFoundException : 계좌의 존재 여부 확인
- NotMatchedPasswordException : 계좌번호의 비밀번호 일치여부 확인
- NoMoneyException : 입금액, 출금액, 이체액 0원 초과
- InsufficientBalanceException : 계좌이체시 송금자의 잔액이 이체액보다 적을 경우 이체 불가
-
프로젝트의 전반적인 구조를 UML(StarUML사용)을 통해 도식화하면 아래과 같다.

3. 구현
3 - 0. Database
3 - 1. DbInfo Interface
- OracleDriver class 경로, Oracle url, username, password 와 같이 자주 재사용될 데이터들은 인터페이스에서 상수로 정의한다.
3 - 2. AccountVO class : 계좌의 정보가 담긴 클래스 ( VO : Value Object)
-
객체(계좌)의 정보를 담는 AccountVO 클래스에는 정보가 담길 instance variable, Constructor, getter/setter method, toString method가 필요하다.
– 인스턴스 변수에는 accountVO: String, name: String, password: String, balance: int 총 4개가 있다.
-
필요에 의해 기본생성자를 비롯하여 다양한 매개변수를 가진 생성자가 필요하다.
– 기본생성자는 setter/getter 메서드 이용시 사용.
– primary key 인 accountVo를 제외한 3개의 매개변수를 가진 생성자는 insert/ update/ delete시에 사용
– 모든 iv가 들어있는 생성자는 select 조회시에 사용되는 등 다양하다.
-
다음 코드에서는 편의를 위해 getter/setter 메서드와 toString 메서드는 생략했다. sql 구문에 따라 필요한 생성자가 다른 것에 주의하자
3 -3. AccountDAO class : DB에서 데이터를 조회, 조작하는 기능을 가진 클래스(DAO : Date Access Object)
-
상단의 요구사항 분석에 서술된 핵심 기능 위주로 메서드를 구현해본다
-
jdbc를 통해 DB에 연결하는 코드들은 중복이 되므로 편의상 생략했다.(Connection, PreparedStatement, ResultSet, closeAll()… etc)
3 - 3. 1 계좌 개설 메서드 (createAccount())
- 예외처리 : CreateAccountException
- main 메서드를 통해 AccountVO객체가 전달이 되면 insert sql 구문을 통해 DB에 데이터를 넣어준다
- 초기 납입액 1000원 이상의 조건은 메서드 상단부에서 먼저 처리하고 진행한다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public void createAccount(AccountVO accountVO) throws SQLException, CreateAccountException { //if 조건문을 먼저 상단에 해줘야 조건에 맞지 않을 시 아래 구문을 수행하지 않고 //바로 예외처리를 하여 벗어난다 (예외 던지고 main에서 catch로 처리하고 끝) if(accountVO.getBalance() < 1000) throw new CreateAccountException("계좌 개설시 초기 납입금은 1000원 이상이어야 합니다"); ... try { con = getConnection(); String sql = "insert into account(account_no, name, password, balance) values(account_seq.nextval, ?, ?, ?)"; pstmt = con.prepareStatement(sql); pstmt.setString(1, accountVO.getName()); //.... 값에 할당하는 과정 생략 pstmt.executeUpdate(); }finally { closeAll(pstmt, getConnection()); } }
3 - 3. 2 잔액 조회 메서드 (findBalanceByAccountNo())
- 예외처리 : NotMatchedPasswordException, AccountNotFoundException
- 잔액 조회는 2가지 예외상황(계좌 존재유무, 비밀번호 일치여부)이기 때문에 1행을 먼저 뽑고 그 안에서 다시 예외를 확인한다는 점에 주의하자.
- sql문과 예외처리 if 조건문 확인
- 뒤에 나올 checkAccountNoAndPassword()는 잔액조회 메서드와 모두 동일하나 return 값만 없다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public int findBalanceByAccountNo(String accountNo, String password) throws SQLException, AccountNotFoundException, NotMatchedPasswordException { ... String sql = "select password, balance from account where account_no = ?"; ... //exception이 발생하면 return되지 않고 바로 catch로 날아간다 if(rs.next()) {//계좌번호에 해당하는 계좌가 있는 경우 if(rs.getString(1).equals(password)) {//비밀번호가 일치하면 //2번째 값인 balance 반환 balance = rs.getInt(2); }else { throw new NotMatchedPasswordException("계좌의 패스워드가 일치하지 않습니다"); } }else {//계좌번호에 해당하는 계좌가 존재하지 않는 경우 //사용자 정의 예외 발생시킨다 throw new AccountNotFoundException(accountNo + "계좌정보에 해당하는 계좌가 존재하지 않습니다"); ... return balance; }
3 - 3. 4 계좌 입금 메서드 (deposit())
- 예외 처리 : checkAccountNoAndPassword을 통해 2가지 예외(계좌번호 확인, 패스워드 일치)를 처리한다.
- 원래 구현했던 코드는 balance라는 변수에 findBalanceByAccount()메서드의 잔액을 넣어주고 다음과 같이 구현했었다.
1 2 3
String sql = "update account set balance = ? where account_no = ?"; pstmt = con.prepareStatement(sql); pstmt.setInt(1, balance + money);
-
하지만 이 코드는 값을 넣어줄 때 연산이 되기 때문에 여러 명의 사용자가 입금을 할 때 잔액의 변동사항이 생길 수 있기 때문에 완전히 무결성을 충족하는 코드라고 보기는 어렵다.
따라서 아래와 같이 update sql 구문 안에서 연산을 처리하는 코드로 리팩토링한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
public void deposit(String accountNo, String password, int money) throws NoMoneyException, SQLException, AccountNotFoundException, NotMatchedPasswordException { if(money <= 0) { throw new NoMoneyException("입금액은 0원을 초과해야 합니다"); } checkAccountNoAndPassword(accountNo, password); ... try { con = getConnection(); String sql = "update account set balance = balance + ? where account_no = ?"; ... }finally { closeAll(pstmt, con); } }
3 - 3. 5 계좌 출금 메서드 (withdraw())
-
출금메서드는 sql 구문에서 money를 빼주는 것과 입금 메서드에서 예외처리를 InsufficientBalanceException을 추가하는 것 외에 코드는 거의 동일하다 .
-
이체하려는 금액이 잔액보다 많을 경우에 발생하는 예외를 입금메서드의 예외에서 추가한다 (총 4가지 예외)
1 2 3 4 5 6 7
if(money <= 0) { throw new NoMoneyException("출금액은 0원을 초과해야 합니다"); } int balance = findBalanceByAccountNo(accountNo, password); if(balance < money) { throw new InsufficientBalanceException("잔액보다 출금액이 커서 출금할 수 없습니다"); }
####
3 - 3. 6 계좌이체 메서드(transfer())
- 주의할 점>
- 계좌이체에서는 입금자 계좌에서의 출금과 예금주 계좌에 입금 과정이 한 몸처럼 같이 진행돼야 하기 때문에 transaction 처리가 필요하다.
- withdraw() 출금 메서드를 사용해도 sql 을 2번 작성한 결과와 동일하게 나올 수 있지만 transaction의 진행은 오직 1개의 Connection에서만 1개의 Session에서만 진행되어야 하기 때문에 좋은 코드라고 할 수 없다.
- withdraw를 사용되도 정상적으로 결과값이 나오는 이유? Connection 안의 withdraw() Connection이 있기 때문에 겉으로는 1개의 Connection이라고 인식하는 것으로 예상해본다..(추후 Update필요!! )
- 예외처리가 많아지면 가장 간단한 예외부터 처리한다.(자원낭비 최소화의 목적) - NoMoneyException
- transaction 처리를 위해 필요한 것은 무엇일까?
- setAutoCommit(false) - 자동 커밋모드 해제
- commit() - 정상적으로 수행이 완료됐으면 db 연동
- rollback() - 하나라도 정상적으로 수행이 안됐으면 원래 상태로 복귀
- throw - 예외가 발생한 상황을 구현부 안에서만 처리하는 것이 아니라 사용자에게 알려주기 위해 예외 던진다.
-
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 32 33
public void transfer(String senderAccountNo, String password, int money, String receiverAccountNo) throws NoMoneyException, SQLException, AccountNotFoundException, NotMatchedPasswordException, InsufficientBalanceException { //0원 초과 예외확인 if(money <= 0) { throw new NoMoneyException("이체액은 0원을 초과해야 합니다"); } //수금자 계좌번호 확인 if(existsAccountNo(receiverAccountNo) == false) {//앞에 ! 연산자 사용가능 throw new AccountNotFoundException("이체받을 계좌가 존재하지 않습니다"); } //송금자 비밀번호, 계좌 확인 int balance = findBalanceByAccountNo(senderAccountNo, password); if(balance < money) {//잔액확인 throw new InsufficientBalanceException("잔액 부족으로 이체할 수 없습니다"); } ... try { con = getConnection(); //트랜잭션 제어를 위해 수동커밋모드로 설정 con.setAutoCommit(false); //송금자 계좌에서 출금 String withdrawSql = "update account set balance = balance - ? where account_no = ?"; ... //예금주의 계좌에 입금 String depositSql = "update account set balance = balance + ? where account_no = ?"; ... con.commit(); } catch(Exception e) {//예외 발생시 rollback을 통해 원상태로 복귀 & 예외 전파 //문제가 발생하면 작업을 취소하고 원상태로 되돌린다 con.rollback(); System.out.println("계좌이체 transaction 내에서 예외 발생"); throw e; ... }
####
3 - 3. 7 가장 높은 잔액을 보유한 계좌를 조회하는 메서드(findHighestBalanceAccount())
- 이 메서드는 sql 구문 연습을 해보기 위한 메서드라고 할 수 있다. 해당 sql은 subquery임을 유의하자.
- 또한 가장 높은 잔액을 보유한 계좌가 1개 뿐만이 아닌 여러 개일 수 있기 때문에 다수의 객체를 담기 위해 ArrayList 자료구조체를 사용한다.
- 코드는 지금까지 작성해왔던 코드들과 유사하므로 sql 구문만 확인해본다.
1 2 3 4 5 6 7 8
public ArrayList<AccountVO> findHighestBalanceAccount() throws SQLException { ArrayList<AccountVO> list = new ArrayList<AccountVO>(); ... StringBuilder sql = new StringBuilder("select account_no, name, balance "); sql.append("from account "); sql.append("where balance = (select max(balance) from account)"); ... }
- 잔액의 값이 최고 잔액일 때, 즉 최고 잔액을 따로 구한 다음 where 조건절 안에 넣어줘야 한다.
1 2 3 4 5 6 7 8
public ArrayList<AccountVO> findHighestBalanceAccount() throws SQLException { ArrayList<AccountVO> list = new ArrayList<AccountVO>(); ... StringBuilder sql = new StringBuilder("select account_no, name, balance "); sql.append("from account "); sql.append("where balance = (select max(balance) from account)"); ... }
4. 테스트
-
가장 핵심적이였던 계좌이체 기능을 확인해본다면 다음과 같이 이체가 이루어 진 것을 확인해 볼 수 있다

-
아래는 DB에서도 변경된 값을 확인한 것이다.
