• 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)  # 如果没有异常,成功提交事务

      

  • 相关阅读:
    Facebook第三方登录切换账号的问题
    内部类中调用外部变量
    前端还原UI设计图CSS样式工具(ps+蓝湖+PxCook+imgcook)
    git 报错 eslint fix found some errors. Please fix them and try committing again.
    在vue中使用vuebaidumap实现地址定位标点及关键字搜索定位(获取地址数据并获取经纬度)
    elementui的时间范围组件,限制选了第一个时间后,第二个只能在第一个的前后一周
    JavaScript API v2.0+ v3.0
    BFC块级格式化上下文
    解决小程序api无法返回promise对象
    移动端适配 视口(viewport)和vw、vh、rem
  • 原文地址:https://www.cnblogs.com/yblackd/p/14533439.html
Copyright © 2020-2023  润新知