Python Django,事務,transaction.atomic,事務保存點



from django.shortcuts import render
from django.http import HttpResponse
from django.views.generic import View
from django.db import transaction # 導入事務


# 類視圖 (事務,@transaction.atomic裝飾器)
class MyView(View):

@transaction.atomic
# transaction.atomic裝飾器可以保證該函數中所有的數據庫操作都在一個事務中。
def post(self, request):

# 數據庫操作1。。。
# 數據庫操作2。。。

return HttpResponse('ok')


# 類視圖 (事務,保存點的使用)
class MyView2(View):
@transaction.atomic
def post(self, request):

# 設置事務保存點
s1 = transaction.savepoint() # 可以設置多個保存點

# 數據庫操作。。。

# 事務回滾 (如果發生異常,就回滾事務)
transaction.savepoint_rollback(s1) # 可以回滾到指定的保存點

# 提交事務 (如果沒有異常,就提交事務)
transaction.savepoint_commit(s1)

# 返回應答

原文鏈接:https://blog.csdn.net/houyanhua1/article/details/85246618

 

本人案例:直接上代碼了

from rest_framework import serializers
from apps.goods.models import SKU, SKUSpecification
from django.db import transaction
from celery_tasks.detail.tasks import task_generate


# 數據模型類:SKU具體規格-->SKUSpecification中定義了外鍵關聯SKU數據模型類,從而在SKUSpecification表中獲得spec_id,option_id兩個值
class SkuSpecRelatedSerializer(serializers.Serializer):
    spec_id = serializers.IntegerField()
    option_id = serializers.IntegerField()


class SkuSerializer(serializers.ModelSerializer):
    # 標准商品spu在SKU 數據模型類model中作為外鍵, 下面將標准商品以字符串輸出
    spu = serializers.StringRelatedField(read_only=True)
    # 標准商品作為外鍵,默認有一個隱藏屬性spu_id, 盡管下面fields= "__all__",但是隱藏屬性字段不會返回數據,如果想要數據需要明確定義
    spu_id = serializers.IntegerField()

    # 指定三級分類category_id作為外鍵 輸出名稱
    category = serializers.StringRelatedField(read_only=True)
    # 定義隱藏屬性
    category_id = serializers.IntegerField()

    # 規格信息調用上面的SkuSpecRelatedSerializer
    # specs = SkuSpecRelatedSerializer(read_only=True, many=True)
    # 創建Sku時需要規格數據,刪除read_only=True參數
    specs = SkuSpecRelatedSerializer(many=True)

    class Meta:
        model = SKU
        # fields = "__all__"
        exclude = ['create_time', 'update_time']

    # 重寫ModelSerializer —— create方法
    def create(self, validated_data):
        # SKU對應的表中沒有屬性specs,所以需要從字典中刪除
        specs = validated_data.pop('specs')
        # 調用父類方法實現SKU對象創建
        # instance = super().create(validated_data)
        with transaction.atomic():  # 禁止自動提交,保證該函數中的所有數據庫操作在同一個事物中,第一個數據庫操作1即使成功保存到數據庫中,只要第2個數據操作失敗,那么所有該段代碼所有設計的都會更改回滾到原來
            sid = transaction.savepoint()  # 開啟事務設置事務保存點
            try:
                # 直接創建SKU對象
                instance = SKU.objects.create(**validated_data)
                # 遍歷,創建SKU規格對象
                for item in specs:
                    spec_id = item.get('spec_id')
                    option_id = item.get('option_id')
                    SKUSpecification.objects.create(sku_id=instance.id, spec_id=spec_id, option_id=option_id)
            except:
                transaction.savepoint_rollback(sid)  # 失敗回滾事務(如果數據庫操作發生異常,回滾到設置的事務保存點)
                raise serializers.ValidationError("數據保存失敗")
            else:
                transaction.savepoint_commit(sid)  # 如果沒有異常,成功提交事物

        # 為Sku生成靜態文件
        task_generate.delay(instance.id)
        return instance

  案例2:事務--支付:

#coding=utf-8
import sys
import MySQLdb
class TransferMoney(object):
  def __init__(self,conn):
    self.conn = conn
  #檢查賬戶是否合法
  def check_acct_avaiable(self,acctid):
    cursor = self.conn.cursor()
    try:
      sql = "select * from account where acctid=%s" % acctid
      cursor.execute(sql)
      print "check account:" + sql
      rs = cursor.fetchall()
      if len(rs) != 1:
        raise Exception("account %s illega" % acctid)
    finally:
      cursor.close()
  #檢查是否有足夠的錢
  def has_enough_money(self,acctid,money):
    cursor = self.conn.cursor()
    try:
      sql = "select * from account where acctid=%s and money > %s" % (acctid,money)
      cursor.execute(sql)
      print "has enough money:" + sql
      rs = cursor.fetchall()
      if len(rs) != 1:
        raise Exception("account %s not enough money" % acctid)
    finally:
      cursor.close()
  #賬戶減錢
  def reduce_money(self,acctid,money):
    cursor = self.conn.cursor()
    try:
      sql = "update account set money = money-%s where acctid = %s" % (money,acctid)
      cursor.execute(sql)
      print "reduce_money:" + sql
      if cursor.rowcount != 1:
        raise Exception("reduce money fail %s" % acctid)
    finally:
      cursor.close()
  #賬戶加錢
  def add_money(self,acctid,money):
    cursor = self.conn.cursor()
    try:
      sql = "update account set money = money+%s where acctid = %s" % (money,acctid)
      cursor.execute(sql)
      print "add_money:" + sql
      if cursor.rowcount != 1:
        raise Exception("add money fail %s" % acctid)
    finally:
      cursor.close()
  #主執行語句
  def transfer(self,source_acctid,target_acctid,money):
    try:
      self.check_acct_avaiable(source_acctid)
      self.check_acct_avaiable(target_acctid)
      self.has_enough_money(source_acctid,money)
      self.reduce_money(source_acctid,money)
      self.add_money(target_acctid,money)
      self.conn.commit()
    except Exception as e:
      self.conn.rollback()
      raise e
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',port=3306,user='root',passwd='',db='test',charset='utf8')
  tr_money = TransferMoney(conn)
  try:
    tr_money.transfer(source_acctid,target_acctid,money)
  except Exception as e:
    print "Happen:" + str(e)
  finally:
    conn.close()

  針對個人代碼總結:

    • 導包transaction

    • 設置該函數中的所有數據庫操作在同一個事物中,第一個數據庫操作1即使成功保存到數據庫中,只要第2個數據操作失敗,那么所有該段代碼所有涉及的數據庫操作都會更改回滾到原來。

      with transaction.atomic()
    • 設置事務保存點並開啟事務

      sid = transaction.savepoint()  # 開啟事務設置事務保存點
    • 數據庫讀寫操作

      操作1

      操作2

    • 失敗回滾事務(條件:任意一個操作失敗,數據庫操作發生異常,回滾到設置的事務保存點)

      transaction.savepoint_rollback(sid)
      raise serializers.ValidationError("數據保存失敗")
    • 成功(條件:操作1和2都成功,提交事務)

      transaction.savepoint_commit(sid)  # 如果沒有異常,成功提交事務

  


免責聲明!

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



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