Django ORM F查询和Q查询,原生SQL使用,django中开启事务和锁

主要是ORM语句中不能传参,而且条件只能用A且B的条件.

所以我们需要用到F和Q的值,F是用来传参,Q是用来对多条件的且或非进行使用的.

class Book(models.Model): 
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    '''
    如果之前表模型已经数据库创建过,这个时候再额外增加字段的话需要给他赋一个默认值.
    否则会报错 让你select fix,设置默认值后可以按照之前的makemigrations和migrate 进行数据库写入这几个新增字段
    '''
    comment=models.IntegerField(default=0)
    read_num=models.IntegerField(default=0)

Book表加入2个字段后我们随便写点数据如下

#我们在查询过程中放入一个变量,默认是不支持变量的,这个时候需要引入一个F,相当于sql语句中的where
    #在F中,这个字段就变成了一个变量的概念.不是一个死值
    # pub1=models.Book.objects.filter(comment__gt=read_num) #错误示范
    from django.db.models import F
    # 查询Book中comment数大于read_num的记录
    pub1=models.Book.objects.filter(comment__gt=F("read_num")) #把read_num传入到F()中去.
    # print(pub1)
    #Boook每个comment的值都加1
    bk=models.Book.objects.update(comment = F("comment")+1)

    #Q 这个参数是用来实现多个筛选条件搭配的
    # 比如我们要找价格小于45,评论数大于8的书籍,但是下面这种默认写法只支持且的筛选,如果要用或的筛选需要用Q()把条件都写在Q()里
    # 且用& 或用| 非用~Q
    bk = models.Book.objects.filter(price__lt=45,comment__gt=8)

    from django.db.models import Q
    #~Q是代表非的意思
    bk = models.Book.objects.filter(Q(price__lt=45) | Q(comment__gt=8))
    bk = models.Book.objects.filter(Q(price__lt=45) | ~Q(comment__gt=8))
    bk = models.Book.objects.filter((Q(price__lt=45) | ~Q(comment__gt=8) & Q(read_num__gt=10)))
    #如果有Q语句和默认的语句,默认的语句一定要放前面.
    bk = models.Book.objects.filter(price__lt=45 & (~Q(comment__gt=40)&Q(title="python")))

F函数还可以用来该字段名
这里示例将原先跨表查询的publish__name字段改成了hisname

from django.db.models import F
bk = models.Book.objects.filter(author__author_detail__city__startswith="宁波").annotate(hisname=F('publish__name')).values("title", "hisname")

Q的进阶用法

 方式一:
 Q(nid__gt=10)
 Q(nid=8) | Q(nid__gt=10)
 Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')

 方式二:
username = request.data.get("username")
pwd = request.data.get("pwd")
mobile_phone = request.data.get("mobile_phone")
query = Q(pwd=pwd)
if username:
    query &= Q(username=username)
elif mobile_phone:
    query &= Q(mobile_phone=mobile_phone)
else:
    raise ExtraException("账号和手机号必须提供一个")
obj = models.Userinfo.objects.filter(query).first()

 方式三:
 con = Q()
 q1 = Q()
 q1.connector = 'OR'
 q1.children.append(('id', 1))
 q1.children.append(('id', 10))
 q1.children.append(('id', 9))
 q2 = Q()
 q2.connector = 'OR'
 q2.children.append(('c1', 1))
 q2.children.append(('c1', 10))
 q2.children.append(('c1', 9))
 con.add(q1, 'AND')
 con.add(q2, 'AND')

 models.Tb1.objects.filter(con)

方式三代码实现了一个复杂的查询条件,其中包含两个子查询条件。第一个子查询条件要求id为1、10或9中的任何一个;第二个子查询条件要求c1为1、10或9中的任何一个。这两个子查询条件通过AND组合在一起,也就是说,查询结果必须同时满足这两个子条件。

具体来说,这段代码会创建一个Q对象,并在其中添加两个子条件q1和q2。接着,分别设置q1和q2的connector属性为'OR',表示其中的每个子条件之间是"或"的关系。然后,将q1和q2添加到con对象中,通过add方法设置它们之间的连接关系为"and",表示两个子条件之间是"且"的关系。最终,con对象就是一个完整的查询条件,可以用于进行查询操作。

原生SQL的使用

一共有2种,分别是用orm的raw方法进行原生查询和from django.db import connections的connections查询
注意用原生查询要注意防注入问题
1,使用orm操作数据库增删改查:因为orm采用的是参数化形式执行sql语句.
2,如果万一要执行原生sql语句,建议不要拼接url,而是使用参数化的形式.

#如何使用原生sql
res=models.Author.objects.raw('select * from app01_author where nid>1')
for author in res:
    print(author.name)

res = models.Author.objects.raw('select * from app01_book where nid>1')
    for book in res:
        print(book.price)

#经过上面的对比可以看出,执行原生的sql,跟对象的类型无关,查出什么字段,就可以直接使用该字段
#用connections查询
from django.db import connections
with connections['default'].cursor() as cursor:
  cursor.execute("UPDATE TbEmp SET sal=sal+10 WHERE dno=30")
  cursor.execute("SELECT ename, job FROM TbEmp WHERE dno=10")
  row = cursor.fetchall()

#使用这个方法,[]中括号中参数使用哪个数据库,这里示例的是default库
#拿游标对象
#拿数据方法  fetchall()全部数据          fetchone()取一条数据     fetchmany(n)  n是想取多少条数据
#原生查询防注入的示例
id = 11001
#这种字符串拼接的方式容易被sql注入
#sql = "select id name from student where id="+id
sql = "select id name from student where id= %s"
cursor = connection.cursor()
param = []
param.append(id)
try:
  cursor.execute(sql,param)
  result = cursor.fetchall()
  for result1 in result:
    // 代码块
    pass
finally:
  cursor.close()

事务

事务的特性

例如:A 给 B 转账 100,那就会涉及2个步骤。

  • A账户 减100
  • B账户 加 100

这两个步骤必须同时完成才算完成,并且如果第一个完成、第二步失败,还是回滚到初始状态。

事务,就是来解决这种情况的。 大白话:要成功都成功;要失败都失败。

事务的具有四大特性(ACID):

  • 原子性(Atomicity)

    原子性是指事务包含的所有操作不可分割,要么全部成功,要么全部失败回滚。
    
  • 一致性(Consistency)

    执行的前后数据的完整性保持一致。
    
  • 隔离性(Isolation)

    一个事务执行的过程中,不应该受到其他事务的干扰。
    
  • 持久性(Durability)

    事务一旦结束,数据就持久到数据库
    

事务在Mysql中的语法操作

mysql> select * from users;
+----+---------+---------+
| id | name    | amount  |
+----+---------+---------+
|  1 |  a   |    5    |
|  2 |  b   |    6    |
+----+---------+---------+
3 rows in set (0.00 sec)

mysql> begin;  -- 开启事务 start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update users set amount=amount-2 where id=1;   -- 执行操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update users set amount=amount+2 where id=2;   -- 执行操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;  -- 提交事务  rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users;
+----+---------+---------+
| id | name    | amount  |
+----+---------+---------+
|  1 |  a  |    3    |
|  2 |  b  |    8    |
+----+---------+---------+
3 rows in set (0.00 sec)

Python代码

import pymysql

conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
cursor = conn.cursor()

# 开启事务
conn.begin()

try:
    cursor.execute("update users set amount=1 where id=1")
    int('asdf')
    cursor.execute("update tran set amount=2 where id=2")
except Exception as e:
    # 回滚
    print("回滚")
    conn.rollback()
else:
    # 提交
    print("提交")
    conn.commit()

cursor.close()
conn.close()

事务中的锁

在用MySQL时,不知你是否会疑问:同时有很多做更新、插入、删除动作,MySQL如何保证数据不出错呢?

MySQL中自带了锁的功能,可以帮助我们实现开发过程中遇到的同时处理数据的情况。对于数据库中的锁,从锁的范围来讲有:

  • 表级锁,即A操作表时,其他人对整个表都不能操作,等待A操作完之后,才能继续。
  • 行级锁,即A操作表时,其他人对指定的行数据不能操作,其他行可以操作,等待A操作完之后,才能继续。
MYISAM支持表锁,不支持行锁;
InnoDB引擎支持行锁和表锁。

即:在MYISAM下如果要加锁,无论怎么加都会是表锁。
   在InnoDB引擎支持下如果是基于索引查询的数据则是行级锁,否则就是表锁。

所以,一般情况下我们会选择使用innodb引擎,并且在 搜索 时也会使用索引(命中索引)。

接下来的操作就基于innodb引擎来操作:

CREATE TABLE `L1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

在innodb引擎中,update、insert、delete的行为内部都会先申请锁(排它锁),申请到之后才执行相关操作,最后再释放锁。

所以,当多个人同时像数据库执行:insert、update、delete等操作时,内部加锁后会排队逐一执行。

而select则默认不会申请锁。

select * from xxx;

如果,你想要让select去申请锁,则需要配合 事务 + 特殊语法来实现。

for update,排它锁,加锁之后,其他不可以读写。

begin; 
	select * from L1 where name="武沛齐" for update;    -- name列不是索引(表锁)
commit;
begin; -- 或者 start transaction;
	select * from L1 where id=1 for update;			  -- id列是索引(行锁)
commit;

Python代码实现

import pymysql
import threading


def task():
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # cursor = conn.cursor()
	
    # 开启事务
    conn.begin()

    cursor.execute("select id,age from tran where id=2 for update")
    # fetchall      ( {"id":1,"age":10},{"id":2,"age":10}, )   ((1,10),(2,10))
    # {"id":1,"age":10}   (1,10)
    result = cursor.fetchone()
    current_age = result['age']
    
    if current_age > 0:
        cursor.execute("update tran set age=age-1 where id=2")
    else:
        print("已售罄")

    conn.commit()

    cursor.close()
    conn.close()


def run():
    for i in range(5):
        t = threading.Thread(target=task)
        t.start()


if __name__ == '__main__':
    run()

事务在django和drf应用

事务的使用

#数据库操作在该代码块中书写的操作 同属于一个事务
from django.db import transaction

with transaction.atomic():
    models.Book.objects.create()
    models.Publish.objects.create()
    # 添加书籍和出版社 就是同一个事务 要么一起成功要么一起失败
    print('出了 代码块 事务就结束')

事务加锁

事务是为了保证原子性的统一,加锁是为了同时只能有一个人对一条数据进行操作

from django.db import transaction

with transaction.atomic():
    customer_obj = models.Customer.objects.filter(pk=pk).select_for_update().first()
    # 这里对orm对象用select_for_update方法进行加锁.同一时间只有一个人可以对这个orm对象进行数据操作

项目中全局开启事务

效率低:项目中一般不会使用。

数据库配置多设置一个参数:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbhot4',
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'ATOMIC_REQUESTS': True
    }
}
  • 只要视图函数执行异常,无论是什么原因触发,均自动回滚。

    class Demo1View(APIView):
        def get(self, request, *args, **kwargs):
            models.UserInfo.objects.create(name='v1', age=1)
            models.UserInfo.objects.create(xxxxxxx='v2', age=1) # 错误
            return Response("...")
    
    class Demo1View(APIView):
        def get(self, request, *args, **kwargs):
            models.UserInfo.objects.create(name='v1', age=1)
            models.UserInfo.objects.create(name='v2', age=1)
            int("asdf")  # 错误
            return Response("...")
    
  • 如果视图函数执行不报错(try处理异常,也叫不报错),则不会回滚

    class Demo1View(APIView):
        def get(self, request, *args, **kwargs):
            try:
                models.UserInfo.objects.create(name='v1', age=1)
                models.UserInfo.objects.create(xxxxxxx='v2', age=1)
                int("xxx")
            except Exception as e:
                pass
            return Response("...")
    
    # 视图函数执行没有报错,不会滚回。
    

开启了全局事务,想要免除某个指定的函数不需要开启事务

这个是django的装饰器,drf基于django,所以也适用

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models
from django.utils.decorators import method_decorator

@method_decorator(transaction.non_atomic_requests, name='dispatch')
class Demo1View(APIView):

    def get(self, request, *args, **kwargs):
        models.UserInfo.objects.create(name='v100', age=1)
        models.UserInfo.objects.create(name="v200", age="xxx") # 报错
        return Response("...")

项目局部使用事务

基于上下文管理,如果出现异常则自动回滚;无异常则自动提交。

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models


class Demo1View(APIView):
    def get(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                models.UserInfo.objects.create(name='v1', age=1)
                models.Order.objects.create(name='v1', age=1)
        except Exception as e:
            print("异常,自动回滚")

        return Response("...")

事务提交的回调函数(本质上就是事务完成后,自动执行一个函数),利用partial 传参

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models
from functools import partial


def db_success_callback(*args, **kwargs):
    print(args, **kwargs)

class Demo1View(APIView):
    def get(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                # 回调函数,事务正常提交自动执行
                transaction.on_commit(db_success_callback)
                # 事务中回调函数报错 不会触发回滚,因为和事务无关
                transaction.on_commit( partial(db_success_callback, 11, 22, 33) )

                models.UserInfo.objects.create(name='v1', age=1)
                models.Order.objects.create(title='v1', count=1)
        except Exception as e:
            print("异常,自动回滚") # on_commit回调函数内部异常时不会回滚

        return Response("...")

回滚到 指定事务点:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models


class Demo1View(APIView):
    def get(self, request, *args, **kwargs):
        with transaction.atomic():
            n1 = transaction.savepoint()
            # 回调函数,事务正常提交自动执行

            try:
                new_obj = models.UserInfo.objects.create(name='v1', age=1)
                new_obj.save()
            except Exception as e:
                # 必须在事务里面,回顾到指定 事务点,后续东西不提交
                transaction.savepoint_rollback(n1)
                print("异常回滚")
            transaction.savepoint_commit(n1)
        return Response("完成")

    '''
    transaction.atomic()  # 开启事务
    sid = transaction.savepoint() # 设置保存点
    transaction.savepoint_rollback(sid) # 回滚到保存点
    transaction.savepoint_commit(sid) #提交保存点
    '''

项目局部使用视图事务

针对整个视图进行开启事务:

  • 视图内,有数据库操作异常,自动回滚
  • 视图内,有其他异常,不会回滚。
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models


class Demo1View(APIView):

    @transaction.atomic
    def get(self, request, *args, **kwargs):
        try:
            models.UserInfo.objects.create(name='v100', age=1)
            models.UserInfo.objects.create(name="v200", age="xxx")  # 有异常,回滚,即:v100不会保存
            int("asdf")   # 有异常,不回滚,即:两条数据正常保存到数据库
        except Exception as e:
            pass
        return Response("...")

定义事务点,自定义回滚位置:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models

class Demo1View(APIView):

    @transaction.atomic
    def get(self, request, *args, **kwargs):
        try:
            models.UserInfo.objects.create(name='v10', age=1)
            n1 = transaction.savepoint()
            models.UserInfo.objects.create(name="v11", age=1)
            n2 = transaction.savepoint()
            models.UserInfo.objects.create(name='v12', age=1)
            n3 = transaction.savepoint()
            models.UserInfo.objects.create(name='v13', age=1)
            
            # 后续读取到某些值后,发现 v12不应该创建,那么就可以主动回滚
            transaction.savepoint_rollback(n1)
        except Exception as e:
            print("有异常", e)
        return Response("...")

drf中可以在视图上面添加,但是在django的cbv视图中可能会无效,就需要到dispatch中去添加

@transaction.atomic
def get(self, request, *args, **kwargs):

基于django的写法有2种

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models
from django.utils.decorators import method_decorator

class Demo1View(APIView):
     # 第一种自定义dispatch,然后加装饰器
    @transaction.atomic()
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(self, request, *args, **kwargs)

    #@transaction.atomic()
    def get(self, request, *args, **kwargs):
        models.UserInfo.objects.create(name='v1', age=1)
        models.UserInfo.objects.create(name='v2', age=1)
        return Response("...")

第二种写法
@method_decorator(transaction.atomic(), name='dispatch')
class Demo1View(APIView):

    #@transaction.atomic()
    def get(self, request, *args, **kwargs):
        models.UserInfo.objects.create(name='v1', age=1)
        models.UserInfo.objects.create(name='v2', age=1)
        return Response("...")

posted @ 2021-08-28 22:48  零哭谷  阅读(229)  评论(0编辑  收藏  举报