银行转账

CREATE TABLE acct(acct_no NUMBER, balance NUMBER);
CREATE TABLE locking(acct_no NUMBER, type VARCHAR2(8));
CREATE TABLE holding(acct_no NUMBER, amount NUMBER);

INSERT INTO acct VALUES(10001, 2000);
INSERT INTO acct VALUES(10002, 500);
INSERT INTO acct VALUES(10003, 1500);
INSERT INTO acct VALUES(10004, 300);

INSERT INTO locking VALUES(10004, 'locked');

INSERT INTO holding VALUES(10001, 1000);
INSERT INTO holding VALUES(10003, 800);
commit;

  /************************************************
   使用源表名称:holding 冻结账户信息表
                 Locking锁定账户信息表
                 acct账户信息表
   功能描述:账户间转账
             a) 确定转出账户没有被锁。
             b) 确定转入账户的存在。
             c) 确定转入账户没有被锁。
             d) 确定转出账户中有足够余额满足转账 
             e) 确定转出账户在转账后满足冻结额的要求
             f) 确定在转出账户上成功地扣去了转账的金额
             g) 确定在转入账户上成功地加上了转账的金额
   参数说明:transfer_in 资金转入账户
             transfer_out 资金转出账户
             transfer_balance 转账金额
             oi_flag 返回标志 0-正常 -1 失败
             os_msg 返回信息 
  ************************************************/
  create or replace 
  PROCEDURE transfer_account(transfer_in      IN NUMBER,
                             transfer_out     IN NUMBER,
                             transfer_balance IN NUMBER,
                             oi_flag          OUT PLS_INTEGER,
                             os_msg           OUT VARCHAR2) AS
    balance_after_transfer acct.balance%TYPE; --转账后的余额
  BEGIN
    oi_flag := 0;
    os_msg  := 'success';
    IF transfer_in = transfer_out THEN
      oi_flag := -1;
      os_msg  := '转入、转出账户相同,不必转账';
      RETURN;
    END IF;
    --a) 确定转出账户没有被锁。
    IF (is_account_lock(transfer_out) = 0) THEN
      oi_flag := -1;
      os_msg  := '转出账户被锁定';
      RETURN;
    END IF;
    --b)  确定转入账户的存在。
    IF (is_account_exist(transfer_in) = -1) THEN
      oi_flag := -1;
      os_msg  := '转入账户不存在';
      RETURN;
    END IF;
    --c)  确定转入账户没有被锁。
    IF (is_account_lock(transfer_in) = 0) THEN
      oi_flag := -1;
      os_msg  := '转入账户被锁定';
      RETURN;
    END IF;
    --d)  确定转出账户中有足够余额满足转账 
    DECLARE
      num_transfer_balance acct.balance%TYPE;
    BEGIN
      SELECT balance
        INTO num_transfer_balance
        FROM acct
       WHERE acct_no = transfer_out;
      IF num_transfer_balance < transfer_balance THEN
        oi_flag := -1;
        os_msg  := '转出账户余额不足,当前余额为' || num_transfer_balance;
        RETURN;
      END IF;
      balance_after_transfer := num_transfer_balance - transfer_balance;
    EXCEPTION
      WHEN no_data_found THEN
        oi_flag := -1;
        os_msg  := '转出账户不存在';
        RETURN;
      WHEN too_many_rows THEN
        oi_flag := -1;
        os_msg  := '转出账户不唯一';
        RETURN;
        --注意这里不加when others,通用异常放外围
    END;
    --e)  确定转出账户在转账后满足冻结额的要求
    --对冻结账户执行判断
    IF is_account_holding(transfer_out) = 0 THEN
      DECLARE
        num_hoding_balance holding.amount%TYPE;
      BEGIN
        SELECT amount
          INTO num_hoding_balance
          FROM holding
         WHERE acct_no = transfer_out;
        IF num_hoding_balance > balance_after_transfer THEN
          oi_flag := -1;
          os_msg  := '该账户为冻结账户,转账后余额低于冻结额,当前冻结额为' || num_hoding_balance;
          RETURN;
        END IF;
      EXCEPTION
        WHEN no_data_found THEN
          oi_flag := -1;
          os_msg  := '冻结账户不存在' || '输入账号为' || transfer_out;
          RETURN;
        WHEN too_many_rows THEN
          oi_flag := -1;
          os_msg  := '冻结账户不唯一' || '输入账号为' || transfer_out;
          RETURN;
      END;
    END IF;
    --f)  确定在转出账户上成功地扣去了转账的金额
    UPDATE acct
       SET balance = balance - transfer_balance
     WHERE acct_no = transfer_out;
    IF SQL%ROWCOUNT <> 1 THEN
      oi_flag := -1;
      os_msg  := '更新转出账户余额失败';
      RETURN;
    END IF;
    --g)  确定在转入账户上成功地加上了转账的金额
    UPDATE acct
       SET balance = balance + transfer_balance
     WHERE acct_no = transfer_in;
    IF SQL%ROWCOUNT <> 1 THEN
      oi_flag := -1;
      os_msg  := '更新转入账户余额失败';
      RETURN;
    END IF;
    
    COMMIT;
  EXCEPTION
    WHEN OTHERS THEN
      oi_flag := -1;
      os_msg  := '转账出现异常,异常信息为' || SQLERRM || '|' || SQLCODE;
      ROLLBACK;
      --此处日志未处理,有兴趣可自行参考前面的例子补充上
  END;
  
  
  
  
  /************************************************
   使用源表名称:locking锁定账户信息表
   功能描述:账号是否被锁,返回0时被锁
   参数说明:acctno 账号
  ************************************************/
  create or replace 
  FUNCTION is_account_lock(acctno acct.acct_no%TYPE) RETURN PLS_INTEGER AS
    lock_acc_num PLS_INTEGER;
  BEGIN
    SELECT COUNT(1) INTO lock_acc_num FROM locking WHERE acct_no = acctno;
    IF lock_acc_num > 0 THEN
      RETURN 0;
    ELSE
      RETURN - 1;
    END IF;
  END is_account_lock;

  /************************************************
   使用源表名称:acct锁定账户信息表
   功能描述:账号是否存在,返回-1不存在
   参数说明:acctno 账号  
  ************************************************/
  create or replace 
  FUNCTION is_account_exist(acctno acct.acct_no%TYPE) RETURN PLS_INTEGER AS
    acc_num PLS_INTEGER;
  BEGIN
    SELECT COUNT(1) INTO acc_num FROM acct WHERE acct.acct_no = acctno;
    IF acc_num > 0 THEN
      RETURN 0;
    ELSE
      RETURN - 1;
    END IF;
  END is_account_exist;
  /************************************************
   使用源表名称:holding 冻结账户信息表
   功能描述:账号是否冻结
   参数说明:acctno 账号
  ************************************************/
  create or replace 
  FUNCTION is_account_holding(acctno acct.acct_no%TYPE) RETURN PLS_INTEGER AS
    acc_num PLS_INTEGER;
  BEGIN
    SELECT COUNT(1) INTO acc_num FROM holding WHERE acct_no = acctno;
    IF acc_num > 0 THEN
      RETURN 0;
    ELSE
      RETURN - 1;
    END IF;
  END is_account_holding;
/

另附测试程序
CREATE OR REPLACE PACKAGE account_test IS
  --转账程序测试集
  PROCEDURE transfer_account_test(oi_flag OUT PLS_INTEGER,
                                  os_msg  OUT VARCHAR2);
  --测试
  PROCEDURE is_account_lock_test(oi_flag OUT PLS_INTEGER,
                                 os_msg  OUT VARCHAR2);
END account_test;
/
CREATE OR REPLACE PACKAGE BODY account_test IS
  --测试案例集
  PROCEDURE transfer_account_test(oi_flag OUT PLS_INTEGER,
                                  os_msg  OUT VARCHAR2) AS
  BEGIN
    oi_flag := 0;
    os_msg  := 'success';
    --a) 确定转出账户没有被锁。
    is_account_lock_test(oi_flag, os_msg); --测试函数is_account_lock
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
    --传入锁定转出账户
    account.transfer_account(10001, 10004, 1, oi_flag, os_msg);
    IF oi_flag = 0 THEN
      RETURN;
    END IF;
    --传入非锁定转出账户
    account.transfer_account(10001, 10002, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
    --b) 确定转入账户的存在。
    --传入非法的转入账户10009
    account.transfer_account(10009, 10002, 1, oi_flag, os_msg);
    IF oi_flag = 0 THEN
      RETURN;
    END IF;
    --传入合法的转入账户10001
    account.transfer_account(10001, 10002, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
    --c) 确定转入账户没有被锁。
    --传入锁定的转入账户
    account.transfer_account(10004, 10002, 1, oi_flag, os_msg);
    IF oi_flag = 0 THEN
      RETURN;
    END IF;
    --传入非锁定的转入账户
    account.transfer_account(10001, 10002, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
    --d) 确定转出账户中有足够余额满足转账 
    --传入超大的金额
    account.transfer_account(10001, 10002, 100000, oi_flag, os_msg);
    IF oi_flag = 0 THEN
      RETURN;
    END IF;
    --传入正常的金额按(f)、(g)的案例覆盖
    /*account.transfer_account(10001, 10002, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;*/
    --e) 确定转出账户在转账后满足冻结额的要求
    --传入冻结账户并且不满足冻结金额要求 10003账户转出1000后将不满足要求
    account.transfer_account(10001, 10003, 1000, oi_flag, os_msg);
    IF oi_flag = 0 THEN
      RETURN;
    END IF;
    --传入冻结账户满足冻结金额要求
    account.transfer_account(10002, 10001, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
    --传入非冻结账户以下案例可覆盖
    --f) 确定在转出账户上成功地扣去了转账的金额
    account.transfer_account(10003, 10002, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
    --g) 确定在转入账户上成功地加上了转账的金额
    account.transfer_account(10003, 10002, 1, oi_flag, os_msg);
    IF oi_flag = -1 THEN
      RETURN;
    END IF;
  END;
  --测试账户是否锁定
  PROCEDURE is_account_lock_test(oi_flag OUT PLS_INTEGER,
                                 os_msg  OUT VARCHAR2) AS
  BEGIN
    /*测试函数is_account_lock
    */
    oi_flag := 0;
    os_msg  := 'success';
    DECLARE
      expect_account locking.acct_no%TYPE := 10004; --已锁定的账户
      expect_result  PLS_INTEGER := 0; --期望为锁定
      real_resutl    PLS_INTEGER;
    BEGIN
      real_resutl := account.is_account_lock(expect_account);
      IF expect_result = real_resutl THEN
        RETURN;
      ELSE
        oi_flag := -1;
        --此处加上拼接,便于在测试集中调用且不会丢失其他测试程序的结果,注意要加上字符数限制
        os_msg := substrb(os_msg || '|bug in is_account_lock,期望值:' ||
                          expect_result || '实际值:' || real_resutl || '|',
                          1,
                          1000);
      END IF;
    END;
  END;
END account_test;
/

猜你喜欢

转载自alvinking.iteye.com/blog/1663378