實現一個簡單的銀行轉賬操作


原文發表在我的博客主頁,轉載請注明出處。

前言

在進行一個應用系統的開發過程中,從上到下一般需要四個構件:客戶端-業務邏輯層-數據訪問層-數據庫,其中數據訪問層是一個底層、核心的技術。而且在實際開發中,數據庫的操作也就是說數據訪問層都是嵌套在其他語言中的,其是編程的核心。本文面向的是python語言,即通過python操作數據庫來實現簡單的銀行轉賬操作。

工具

python提供了python DB API用來統一操作數據庫,使訪問數據庫的接口規范化,在沒有python DB API之前,接口程序十分混亂,不同的數據庫需要不同的操作接口,所以這個接口提供了極大的方便。在具體操作的時候,我們需要操作數據庫以及其他邏輯的python代碼,數據庫連接對象connection來建立連接,數據庫交互對象cursor來“運送”數據,一個健壯的系統必不可少的便是數據庫異常類Exceptions。整個訪問數據庫流程如下圖:

接下來分別介紹下兩個主要對象:

  • connection:數據庫連接對象,建立python客戶端與數據庫的網絡連接。
    創建方法:MySQLdb.connect(),包括的主要成員方法:
    • cursor():使用該連接創建並返回游標
    • commit():提交當前事務
    • rollback():回滾當前事務
    • close()關閉連接
  • cursor:游標對象,用於執行查詢與獲取結果,cursor對象支持的主要方法如下:
    • execute():執行SQL語句,將結果從數據庫獲取到客戶端
    • fetchone():取得結果集的下一行
    • fetchmany(size):獲取結果集的下size行
    • fetchall():獲取結果集中剩下的所有行
    • rowcount:最近一次execute返回數據的行數
    • close():關閉游標對象

在上面的方法中提到了一個關鍵名詞:事務,什么是事務呢?他是訪問和更新數據的一個程序執行單元,很多操作的一個集合,有四個特點:

  • 原子性:事物中包括的諸操作要么都做,要么都不做
  • 一致性:事務必須使數據庫從一致性狀態變到另一個一致性狀態
  • 隔離型:一個事務的執行不被其他事務干擾
  • 持久性:事務一旦提交,它對數據庫的改變就是持久性的

事務的上述特點正是我們完成銀行轉賬操作的關鍵。

具體實現

在開發中我們怎么樣使用事務呢?

  1. 關閉自動commit()
  2. 正常結束事務:conn.commit(),
  3. 異常結束事務:conn.rollback()

在銀行轉賬系統中,需要考慮如下需求:比如A給B轉賬,當A賬戶上減少了M錢時,必須在B賬戶上多了M錢,不能A減了B沒加,也不能B加了A還沒有減,當然賬戶必須是有效的,M錢的金額肯定要大於A賬戶上的金額。所以在具體設計的時候,需要將A賬戶的金錢減少和B賬戶的金錢增加作為一個事務,要么同時成功,要么一起失敗。按照這個需求,書寫代碼,詳細代碼見github,代碼復制和數據庫如下,有兩個賬戶,分別擁有金錢110和10,在運行代碼的時候在參數欄輸入1,2,100(source_acctid, target_acctid, tranfer_money)。

整個代碼的邏輯如下:首先連接數據庫,之后執行邏輯,然后斷開數據庫連接,執行的邏輯包括檢查轉賬雙方的賬戶是否有效,轉賬金額是否多於轉賬人的賬戶余額,分別給轉賬雙方的帳號金額發生變化。如果正常結束事務,提交修改數據庫,否則回滾。

#coding:utf-8
import sys
import MySQLdb

class TransferMoney():
    def __init__(self, conn):
        self.conn = conn

    def transfer(self, src, target, money):
        try:
            self.check_acct_available(src)
            self.check_acct_available(target)
            self.has_enough_money(src, money)
            self.reduce_money(src, money)
            self.add_money(target, money)
            self.conn.commit()
        except Exception as e:
            print e
            self.conn.rollback()

    def reduce_money(self, src, money):
        cursor = self.conn.cursor()
        try:
            sql = "update account set money = money - %s where acctid = %s" %(money, src)
            cursor.execute(sql)
            print "reduce_money: " + sql
            #rs = cursor.fetchall()
            if cursor.rowcount != 1:
                raise Exception("the account reduce money fail")
        finally:
            cursor.close()


    def add_money(self, target, money):
        cursor = self.conn.cursor()
        try:
            sql = "update account set money = money + %s where acctid = %s" %(money, target)
            cursor.execute(sql)
            print "add_money: " + sql
            #rs = cursor.fetchall()
            if cursor.rowcount != 1:
                raise Exception("the account add money fail")
        finally:
            cursor.close()


    def check_acct_available(self, accit):
        cursor = self.conn.cursor()
        try:
            sql = "select * from account where acctid =  %s" %accit
            cursor.execute(sql)
            print "check_acct_available: " + sql
            rs = cursor.fetchall()
            if len(rs) != 1:
                raise Exception("the account %s is not exist" %accit)
        finally:
            cursor.close()

    def has_enough_money(self, src, money):
        cursor = self.conn.cursor()
        try:
            sql = "select * from account where acctid =  %s and money >= %s " %(src, money)
            cursor.execute(sql)
            print "has_enough_money: " + sql
            rs = cursor.fetchall()
            if len(rs) != 1:
                raise Exception("the account does not have enough money")
        finally:
            cursor.close()


if __name__ == "__main__":
    source_acctid = sys.argv[1]
    target_acctid = sys.argv[2]
    money = sys.argv[3]

    conn = MySQLdb.connect(
        host = "127.0.0.1", user = '******', passwd = '******', port = 3306, db = '******'
    )
    tr_money = TransferMoney(conn)

    try:
        tr_money.transfer(source_acctid, target_acctid, money)
    except Exception as e:
        print e
    finally:
        conn.close()

總結

通過對數據庫的操作就可以實現一個簡單的銀行轉賬系統,所以在系統開發的時候,我們應該盡最大的可能,讓整個系統不只是多個組件的拼接,應該實現1+1>2。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM