자바은행
테이블과 트리거 생성
계좌 정보와 입출금 내역 정보를 데이터베이스에 저장하는 은행 예제를 실습해 보자.
SQL*PLUS에서 soctt/tiger로 접속하여 계좌 테이블과 입출금 명세 테이블을 다음 SQL문을 사용해 생성한다.
create table bankaccount ( accountno varchar2(50), owner varchar2(20) not null, balance number, kind varchar2(10), constraint PK_BANKACCOUNT primary key(accountno), constraint CK_BANKACCOUNT_NORMAL CHECK (balance >= CASE WHEN kind='NORMAL' THEN 0 END), constraint CK_BANKACCOUNT_KIND CHECK (kind in ('NORMAL', 'MINUS')) ); create table transaction ( transactiondate timestamp, kind varchar2(10), amount number, balance number, accountno varchar2(50), constraint FK_TRANSACTION FOREIGN KEY(accountno) REFERENCES bankaccount(accountno) );
계좌 테이블에서 잔액이 변할 때마다 입출금 명세 테이블에 거래 내역을 쌓는 트리거를 다음을 참조해 생성한다.
create or replace trigger bank_trigger after insert or update of balance on bankaccount for each row begin if :new.balance > :old.balance then insert into transaction values ( systimestamp, 'DEPOSIT', :new.balance - :old.balance, :new.balance, :old.accountno ); end if; if :new.balance < :old.balance then insert into transaction values ( systimestamp, 'WITHDRAW', :old.balance - :new.balance, :new.balance, :old.accountno ); end if; end; /
트리거는 지정된 이벤트가 발생할 때마다 오라클 데이터베이스가 자동으로 호출하는 저장 프로 시저와 같다.
다음은 트리거에 대한 이해를 돕은 예제다.
create table a (name varchar2(10)); create table b (name varchar2(10)); CREATE OR REPLACE TRIGGER test_tri AFTER INSERT OR UPDATE ON a FOR EACH ROW BEGIN insert into b values (:new.name); END; /
테이블 a에 데이터를 인서트하면, 트리거에 의해 테이블 b에도 같은 데이터가 자동으로 인서트된다.
insert into a values ('홍길동'); insert into a values ('임꺽정'); insert into a values ('장길산'); select * from b; NAME ---------- 홍길동 임꺽정 장길산
자바 빈
Transaction.java
package net.java_school.bank; import java.io.Serializable; public class Transaction implements Serializable { private String transactionDate; private String transactionTime; private String kind; private double amount; private double balance; public Transaction() {} public Transaction(String transactionDate, String transactionTime, String kind, double amount, double balance) { this.transactionDate = transactionDate; this.transactionTime = transactionTime; this.kind = kind; this.amount = amount; this.balance = balance; } public String getTransactionDate() { return transactionDate; } public void setTransactionDate(String transactionDate) { this.transactionDate = transactionDate; } public String getTransactionTime() { return transactionTime; } public void setTransactionTime(String transactionTime) { this.transactionTime = transactionTime; } public String getKind() { return kind; } public void setKind(String kind) { this.kind = kind; } public long getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public long getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(transactionDate); sb.append("|"); sb.append(transactionTime); sb.append("|"); sb.append(kind); sb.append("|"); sb.append(amount); sb.append("|"); sb.append(balance); return sb.toString(); } }
Account.java
package net.java_school.bank; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; public class Account implements Serializable { private String accountNo; private String name; protected double balance; private String kind; protected List<Transaction> transactions = new ArrayList<Transaction>(); static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy"); static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); static final String DEPOSIT = "DEPOSIT"; static final String WITHDRAW = "WITHDRAW"; static final String NORMAL = "NORMAL"; static final String MINUS = "MINUS"; public Account() {} public Account(String accountNo, String name, String kind) { this.accountNo = accountNo; this.name = name; this.kind = kind; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public long getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public String getKind() { return kind; } public void setKind(String kind) { this.kind = kind; } public List<Transaction> getTransactions() { return transactions; } public void setTransactions(List<Transaction> transactions) { this.transactions = transactions; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(accountNo); sb.append("|"); sb.append(name); sb.append("|"); sb.append(balance); sb.append("|"); sb.append(kind); return sb.toString(); } }
은행 컴포넌트의 모든 기능을 다음 클래스에 정의한다.
Bank.java
package net.java_school.bank; import java.io.Serializable; import java.util.List; public interface Bank extends Serializable { //계좌를 생성한다. public void addAccount(String accountNo, String name, String kind); //계좌번호로 계좌를 찾는다. public Account getAccount(String accountNo); //소유자 명으로 계좌를 찾는다. public List<Account> findAccountByName(String name); //모든 계좌를 반환한다. public List<Account> getAccounts(); //입금 public void deposit(String accountNo, double amount); //출금 public void withdraw(String accountNo, double amount); //이체 public void transfer(String from, String to, double amount); //입출금 명세 public List<Transaction> getTransactions(String accountNo); }
데이터베이스 관련 기능을 다음 인터페이스에 정의한다.
BankDao.java
package net.java_school.bank; import java.util.List; public interface BankDao { //계좌 생성 public void insertAccount(String accountNo, String name, String kind); //계좌번호로 계좌 찾기 public Account selectOneAccount(String accountNo); //소유자로 계좌 찾기 public List<Account> selectAccountsByName(String name); //계좌 목록 public List<Account> selectAllAccounts(); //입금 public void deposit(String accountNo, double amount); //출금 public void withdraw(String accountNo, double amount); //입출금 명세 public List<Transaction> selectAllTransactions(String accountNo); }
외부 라이브러리 추가
로깅 라이브러리와 오라클 JDBC 드라이버를 클래스 패스에 추가한다.
http://www.slf4j.org/download.html에서 최신 배포본을 내려받는다.
압축을 풀고 slf4j-api-1.7.xx.jar 와 slf4j-simple-1.7.xx.jar 파일과 오라클 JDBC 드라이버를 클래스 패스에 추가한다.
이클립스에서 작업한다면 다음 그림을 참조한다.
BankDao 인터페이스의 구현 클래스를 작성한다.
MyBankDao.java
package net.java_school.bank; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyBankDao implements BankDao { Logger logger = LoggerFactory.getLogger(MyBankDao.class); static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:XE"; static final String USER = "scott"; static final String PASSWORD = "tiger"; //생성자에서 JDBC 드라이버 로딩 public MyBankDao() { try { Class.forName("oracle.jdbc.driver.OracleDriver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } //커넥션 얻기 private Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } //자원 반환 private void close(ResultSet rs, PreparedStatement pstmt, Connection con) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } @Override public void insertAccount(String accountNo, String name, String kind) { Connection con = null; PreparedStatement pstmt = null; String sql = "INSERT INTO bankaccount VALUES (?, ?, 0, ?)"; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1, accountNo); pstmt.setString(2, name); pstmt.setString(3, kind); pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { close(null, pstmt, con); } } @Override public Account selectOneAccount(String accountNo) { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; Account account = null; String sql = "SELECT accountNo,owner,balance,kind " + "FROM bankaccount " + "WHERE accountNo = ?"; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1, accountNo); rs = pstmt.executeQuery(); if (rs.next()) { account = new Account(); account.setAccountNo(rs.getString("accountNo")); account.setName(rs.getString("owner")); account.setBalance(rs.getDouble("balance")); account.setKind(rs.getString("kind")); return account; } } catch (SQLException e) { e.printStackTrace(); } finally { close(rs, pstmt, con); } return null; } @Override public List<Account> selectAccountsByName(String name) { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; List<Account> matched = new ArrayList<Account>(); Account account = null; String sql = "SELECT accountNo,owner,balance,kind " + "FROM bankaccount " + "WHERE owner = ? " + "ORDER By accountNo DESC"; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1, name); rs = pstmt.executeQuery(); while (rs.next()) { account = new Account(); account.setAccountNo(rs.getString("accountNo")); account.setName(rs.getString("owner")); account.setBalance(rs.getDouble("balance")); account.setKind(rs.getString("kind")); matched.add(account); } } catch (SQLException e) { e.printStackTrace(); } finally { close(rs, pstmt, con); } return matched; } @Override public List<Account> selectAllAccounts() { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; List<Account> all = new ArrayList<Account>(); Account account = null; String sql = "SELECT accountNo,owner,balance,kind " + "FROM bankaccount " + "ORDER By accountNo DESC"; try { con = getConnection(); pstmt = con.prepareStatement(sql); rs = pstmt.executeQuery(); while (rs.next()) { account = new Account(); account.setAccountNo(rs.getString("accountNo")); account.setName(rs.getString("owner")); account.setBalance(rs.getDouble("balance")); account.setKind(rs.getString("kind")); all.add(account); } } catch (SQLException e) { e.printStackTrace(); } finally { close(rs, pstmt, con); } return all; } @Override public void deposit(String accountNo, double amount) { Connection con = null; PreparedStatement pstmt = null; String sql = "UPDATE bankaccount " + "SET balance = balance + ? " + "WHERE accountNo = ?"; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setLong(1, amount); pstmt.setString(2, accountNo); pstmt.executeUpdate(); logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{}", accountNo, amount, Account.DEPOSIT); } catch (SQLException e) { e.printStackTrace(); } finally { close(null, pstmt, con); } } @Override public void withdraw(String accountNo, double amount) { Connection con = null; PreparedStatement pstmt = null; String sql = "UPDATE bankaccount " + "SET balance = balance - ? " + "WHERE accountNo = ?"; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setLong(1, amount); pstmt.setString(2, accountNo); pstmt.executeUpdate(); logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{}", accountNo, amount, Account.DEPOSIT); } catch (SQLException e) { e.printStackTrace(); } finally { close(null, pstmt, con); } } @Override public List<Transaction> selectAllTransactions(String accountNo) { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; List<Transaction> all = new ArrayList<Transaction>(); Transaction transaction = null; String sql = "SELECT transactionDate,kind,amount,balance " + "FROM transaction " + "WHERE accountNo = ? " + "ORDER By transactionDate ASC"; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1, accountNo); rs = pstmt.executeQuery(); while (rs.next()) { transaction = new Transaction(); String date = Account.DATE_FORMAT.format(rs.getTimestamp("transactionDate")); String time = Account.TIME_FORMAT.format(rs.getTimestamp("transactionDate")); transaction.setTransactionDate(date); transaction.setTransactionTime(time); transaction.setKind(rs.getString("kind")); transaction.setAmount(rs.getDouble("amount")); transaction.setBalance(rs.getDouble("balance")); all.add(transaction); } } catch (SQLException e) { e.printStackTrace(); } finally { close(rs, pstmt, con); } return all; } }
Bank 인터페이스의 구현 클래스를 작성한다.
MyBank.java
package net.java_school.bank; import java.util.List; public class MyBank implements Bank { private BankDao dao = new MyBankDao(); @Override public void addAccount(String accountNo, String name, String kind) { dao.insertAccount(accountNo, name, kind); } @Override public Account getAccount(String accountNo) { return dao.selectOneAccount(accountNo); } @Override public List<Account> findAccountByName(String name) { return dao.selectAccountsByName(name); } @Override public List<Account> getAccounts() { return dao.selectAllAccounts(); } @Override public void deposit(String accountNo, double amount) { dao.deposit(accountNo, amount); } @Override public void withdraw(String accountNo, double amount) { dao.withdraw(accountNo, amount); } @Override public void transfer(String from, String to, double amount) { dao.withdraw(from, amount); dao.deposit(to, amount); } @Override public List<Transaction> getTransactions(String accountNo) { return dao.selectAllTransactions(accountNo); } }
은행 프로그램에서 사용자와 대화를 담당하는 클래스를 작성한다.
BankUi.java
package net.java_school.bank; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; public class BankUi { private Bank bank = new MyBank(); private String readCommandLine() throws IOException { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); String input = br.readLine(); return input; } public void startWork() { String menu = null; do { System.out.println(" ** 메뉴를 선택하세요 ** "); System.out.println(" 1 ** 계좌 등록 "); System.out.println(" 2 ** 계좌 목록 "); System.out.println(" 3 ** 입금 "); System.out.println(" 4 ** 출금 "); System.out.println(" 5 ** 이체 "); System.out.println(" 6 ** 입출금 명세 "); System.out.println(" q ** 종료 "); System.out.println(" ********************** "); System.out.println(">>"); try { menu = readCommandLine(); String accountNo = null; String name = null; String kind = null; double amount = 0; if (menu.equals("1")) { //TODO 계좌등록 System.out.println("계좌 번호를 입력하세요: "); accountNo = this.readCommandLine(); System.out.println("소유자 이름을 입력하세요: "); name = this.readCommandLine(); System.out.println("계좌 종류를 선택하세요. 일반(n), 마이너스(m): 일반(n): : "); kind = this.readCommandLine(); if (kind != null && kind.equals("m")) { bank.addAccount(accountNo, name, Account.MINUS); } else { bank.addAccount(accountNo, name, Account.NORMAL); } } else if (menu.equals("2")) { //TODO 계좌목록 List<Account> accounts = bank.getAccounts(); for (Account account : accounts) { System.out.println(account); } } else if (menu.equals("3")) { //TODO 입금 System.out.println("계좌 번호를 입력하세요: "); accountNo = this.readCommandLine(); System.out.println("입금 액을 입력하세요: "); amount = Double.parseDouble(this.readCommandLine()); bank.deposit(accountNo, amount); } else if (menu.equals("4")) { //TODO 출금 System.out.println("계좌 번호를 입력하세요: "); accountNo = this.readCommandLine(); System.out.println("출금 액을 입력하세요: "); amount = Double.parseDouble(this.readCommandLine()); bank.withdraw(accountNo, amount); } else if (menu.equals("5")) { //TODO 이체 System.out.println("송금 계좌(From) 번호를 입력하세요: "); String from = this.readCommandLine(); System.out.println("입금 계좌(To) 번호를 입력하세요: "); String to = this.readCommandLine(); System.out.println("이체 금액을 입력하세요: "); amount = Double.parseDouble(this.readCommandLine()); bank.transfer(from, to, amount); } else if (menu.equals("6")) { //TODO 입출금 명세 System.out.println("계좌 번호를 입력하세요: "); accountNo = this.readCommandLine(); List<Transaction> transactions = bank.getTransactions(accountNo); for (Transaction transaction : transactions) { System.out.println(transaction); } } } catch (Exception e) { e.printStackTrace(); } System.out.println(); } while (!menu.equals("q")); } public static void main(String[] args) throws Exception { BankUi ui = new BankUi(); ui.startWork(); } }