zoukankan      html  css  js  c++  java
  • django ORM的总结

    1.django分表的方案:   https://mp.weixin.qq.com/s?__biz=MjM5NjA3Nzk3Ng==&mid=2648154502&idx=1&sn=db51de56a47c2a6a2d0e758a92856331&chksm=becdc97189ba4067dd8995b97a2ed29284bd43369a4c7dd4da19d35d491595bfda856d8c6d7f%23rd

    由来

    知乎上的一个问题:Django 分表 怎么实现?

    这个问题戳到了Django ORM的痛点,对于多数据库/分库的问题,Django提供了很好的支持,通过using和db router可以很好的完成多数据库的操作。但是说到分表的问题,就有点不那么友好了。但也不是那么难处理,只是处理起来不太优雅。

    解析

    在Django中,数据库访问的逻辑基本上是在Queryset中完成的,一个查询请求,比如:User.objects.filter(group_id=10)

    其中的objects其实就是models.Manager,而Manager又是对QuerySet的一个包装。而QuerySet又是最终要转换为sql的一个中间层(就是ORM种,把Model操作转换为SQL语句的部分)。所以当我们写下User.objects的时候,就已经确定了要访问的是哪个表了,这是由class Meta中的db_table决定的。

    class User(models.Model):
    
        username = models.CharField(max_length=255)
    
    
        class Meta:
    
            db_table = 'user'

    理论上讲,我们可以通过在运行时修改db_table来完成分表CRUD的逻辑,但是the5fire在看了又看源码之后,还是没找到如何下手。还是上面的问题,当执行到User.objects的时候,表已经确定了,当执行到User.objects.filter(group=10)的时候只不过是在已经生成好的sql语句中增加了一个where部分语句。所以并没有办法在执行filter的时候来动态设置db_table。

    对于问题中说的get也是一样,因为get本身就是在执行完filter之后从_result_cache列表中获取的数据(_result_cache[0])。

    方案一

    根据the5fire上面的分析,要想在执行具体查询时修改db_table已经是不可能了(当然,如果你打算去重写Model中Meta部分的逻辑以及Queryset部分的逻辑,就当我没说,我只能表示佩服)。

    所以只能从定义层面下手了。也就是我需要定义多个Model,同样的字段,不同的db_table。大概是这样。

    class User(models.Model):
    
        username = models.CharField(max_length=255)
    
    
        class Meta:
    
            abstract = True
    
    
    class User1(User):
    
    
        class Meta:
    
            db_table = 'user_1'  # 默认情况下不设置db_table属性时,Django会使用``<app>_<model_name>``.lower()来作为表名
    
    
    
    class User2(User):
    
    
        class Meta:
    
            db_table = 'user_2'

    这样在User.objects.get(id=3)的时候,如果按照模2计算,那就是User01.objects.get(id=3),笨点的方法就是写一个dict:

    user_sharding_map = {
    
        1: User1,
    
        2: User2
    
    }
    
    
    def get_sharding_model(id):
    
        key = id % 2 + 1
    
        return user_sharding_map[key]
    
    
    
    ShardingModel = get_sharding_model(3)
    
    ShardingModel.objects.get(id=3)

    如果真的这么写那Python作为动态语言,还有啥用,你分128张表试试。我们应该动态创建出User01,User02,....UserN这样的表。

    class User(models.Model):
    
    
        @classmethod
    
        def get_sharding_model(cls, id=None):
    
            piece = id % 2 + 1
    
    
            class Meta:
    
                db_table = 'user_%s' % piece
    
    
            attrs = {
    
                '__module__': cls.__module__,
    
                'Meta': Meta,
    
            }
    
    
            return type(str('User%s' % piece), (cls, ), attrs)
    
    
    
        username = models.CharField(max_length=255, verbose_name="the5fire blog username")
    
    
        class Meta:
    
            abstract = True
    
    
    
    ShardingUser = User.get_sharding_model(id=3)
    
    user = ShardingUser.objects.get(id=3)

      嗯,这样看起来似乎好了一下,但是还有问题,id=3需要传两次,如果两次不一致,那就麻烦了。Model层要为上层提供统一的入口才行。

    class MyUser(models.Model):
    
        # 增加方法 BY the5fire
    
        @classmethod
    
        def sharding_get(cls, id=None, **kwargs):
    
            assert id, 'id is required!'
    
            Model = cls.get_sharding_model(id=id)
    
            return Model.objects.get(id=id, **kwargs)

    对上层来书,只需要执行MyUser.sharding_get(id=10)即可。不过这改变了之前的调用习惯 objects.get 。

    不管怎么说吧,这也是个方案,更完美的方法就不继续探究了,在Django的ORM中钻来钻去寻找可以hook的点实在憋屈。

    我们来看方案二吧

    方案二

    ORM的过程是这样的,Model——> SQL ——> Model,在方案一中我们一直在处理Model——> SQL的部分。其实我们可以抛开这一步,直接使用raw sql。

    QuerySet提供了raw这样的接口,用来让你忽略第一层转换,但是有可以使用从SQL到Model的转换。只针对SELECT的案例:

    class MyUser(models.Model):
    
        id = models.IntegerField(primary_key=True, verbose_name='ID')
    
        username = models.CharField(max_length=255)
    
    
        @classmethod
    
        def get_sharding_table(cls, id=None):
    
            piece = id % 2 + 1
    
            return cls._meta.db_table + str(piece)
    
    
        @classmethod
    
        def sharding_get(cls, id=None, **kwargs):
    
            assert isinstance(id, int), 'id must be integer!'
    
            table = cls.get_sharding_table(id)
    
            sql = "SELECT * FROM %s" % table
    
            kwargs['id'] = id
    
            condition = ' AND '.join([k + '=%s' for k in kwargs])
    
            params = [str(v) for v in kwargs.values()]
    
            where = " WHERE " + condition
    
            try:
    
                return cls.objects.raw(sql + where, params=params)[0]  # the5fire:这里应该模仿Queryset中get的处理方式
    
            except IndexError:
    
                # the5fire:其实应该抛Django的那个DoesNotExist异常
    
                return None
    
    
        class Meta:
    
            db_table = 'user_'

    大概这么个意思吧,代码可以再严谨些。

    总结

    单纯看方案一的话,可能会觉得这么大量数据的项目,就别用Django了。其实the5fire第一次尝试找一个优雅的方式hack db_table时,也是一头灰。但是,所有的项目都是由小到大的,随着数据/业务的变大,技术人员应该也会更加了解Django,等到一定阶段之后,可能发现,用其他更灵活的框架,跟直接定制Django成本差不多。

    2.type动态创建类https://blog.csdn.net/wangbowj123/article/details/77162828

    type还有一种完全不同的功能,动态的创建类。

    type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

    type可以像这样工作:

    type(类名,由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值)) 
    代码如下:

    #运用type创建类、添加属性
    Test = type("Test",(),{'age':13,'name':"wangbo"})
    test = Test()
    print(test.age)
    #利用type添加方法
    @classmethod #类方法
    def testClass(cls):
        print(cls.name)
    
    @staticmethod #静态方法
    def testStatic():
        print("static method.....")
    
    def echo_age(self):
        print(self.age)
    Test2 = type("Test2",(Test,),{'echo_age':echo_age,'testStatic':testStatic,'testClass':testClass})
    test2 = Test2()
    test2.echo_age()
    test2.testStatic()
    test2.testClass()

    元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。

    元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

    MyClass = MetaClass() #使用元类创建出一个对象,这个对象称为“类” 
    MyObject = MyClass() #使用“类”来创建出实例对象 
    你已经看到了type可以让你像这样做:

    MyClass = type(‘MyClass’, (), {})

    函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查class属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。

    >>> age = 35
    >>> age.__class__
    <type 'int'>
    >>> name = 'bob'
    >>> name.__class__
    <type 'str'>
    >>> def foo(): pass
    >>>foo.__class__
    <type 'function'>
    >>> class Bar(object): pass
    >>> b = Bar()
    >>> b.__class__
    <class '__main__.Bar'>

    现在,对于任何一个class的class属性又是什么呢?

    >>> a.__class__.__class__
    <type 'type'>
    >>> age.__class__.__class__
    <type 'type'>
    >>> foo.__class__.__class__
    <type 'type'>
    >>> b.__class__.__class__
    <type 'type'>

    因此,元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。

    3.queryset对象的合并  https://www.cnblogs.com/haoshine/p/6134242.html           http://www.yihaomen.com/article/python/533.htm

     在用python或者django写一些小工具应用的时候,有可能会遇到合并多个list到一个 list 的情况。单纯从技术角度来说,处理起来没什么难度,能想到的办法很多,但我觉得有一个很简单而且效率比较高的方法是我以前没注意到的。那就是利用 chain 方法来合并多个list. 同样也可以用来合并django 的 QuerySet. 

    1. python用chain 来合并多个list
    chain 是用C实现的,自然性能上比较可靠。下面看下基本用法:

    #coding:utf-8
    
    from itertools import chain
    
    a = [1,2,"aaa",{"name":"roy","age":100}]
    b = [3,4]
    c = [5,6]
    
    #items = a + b + c
    items = chain(a,b,c)
    for item in items:
        print item

    输出结果如下:

    1
    2
    aaa
    {'age': 100, 'name': 'roy'}
    3
    4
    5
    6

    由此可见可以很好的合并成功。

    2. 在Django 总用 chain 合并多个QuerySet.
    本身如果在Django中如果要合并同一个model的多个QuerySet 的话,是可以采用这种方式的.

    #coding:utf-8
    
    from itertools import chain
    from yihaomen.common.models import Article
    
    articles1 = Article.objects.order_by("autoid").filter(autoid__lt = 16).values('autoid','title')
    articles2 = Article.objects.filter(autoid = 30).values('autoid','title')
    
    articles = articles1 | articles2 # 注意这里采用的方式。如果 Model相同,而且没有用切片,并且字段一样时可以这样用
    print articles1
    print articles2
    print articles

    这样能很好的工作,但有些局限性,对于Django 来说很多情况下也够用了,合并到一个 QuerySet 中,然后返回到模板引擎中去处理。

    当然也可以用chain 来实现,用chain 来实现会更方便,也没那么多限制条件,即使是不同的MODEL中查询出来的数据,都可以很方便的合并到一个 list 中去.

    #coding:utf-8
    
    from itertools import chain
    from yihaomen.common.models import Article, UserID
    
    articles1 = Article.objects.order_by("autoid").filter(autoid__lt = 16).values('autoid','title')
    users = UserID.objects.all()
    
    items = chain(articles1, users)
    for item in items:
        print item

    这样做更方便,也很实用, 对于处理某些需要合并的list 然后再传输到某一个地方去的情况下,这样做很方便。 

    4.django多数据库的使用:https://code.ziqiangxuetang.com/django/django-multi-database.html

         本文讲述在一个 django project 中使用多个数据库的方法, 多个数据库的联用 以及多数据库时数据导入导出的方法。

       直接给出一种简单的方法吧,想了解更多的到官方教程,点击此处

        代码文件下载:project_name.zip(2017年05月01日更新)

    1. 每个app都可以单独设置一个数据库

    settings.py中有数据库的相关设置,有一个默认的数据库 default,我们可以再加一些其它的,比如:

    # Database
    # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        },
        'db1': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'dbname1',
            'USER': 'your_db_user_name',
            'PASSWORD': 'yourpassword',
            "HOST": "localhost",
        },
        'db2': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'dbname2',
            'USER': 'your_db_user_name',
            'PASSWORD': 'yourpassword',
            "HOST": "localhost",
        },
    }
     
    # use multi-database in django
    # add by WeizhongTu
    DATABASE_ROUTERS = ['project_name.database_router.DatabaseAppsRouter']
    DATABASE_APPS_MAPPING = {
        # example:
        #'app_name':'database_name',
        'app1': 'db1',
        'app2': 'db2',
    }

    在project_name文件夹中存放 database_router.py 文件,内容如下:

    # -*- coding: utf-8 -*-
    from django.conf import settings
     
    DATABASE_MAPPING = settings.DATABASE_APPS_MAPPING
     
     
    class DatabaseAppsRouter(object):
        """
        A router to control all database operations on models for different
        databases.
     
        In case an app is not set in settings.DATABASE_APPS_MAPPING, the router
        will fallback to the `default` database.
     
        Settings example:
     
        DATABASE_APPS_MAPPING = {'app1': 'db1', 'app2': 'db2'}
        """
     
        def db_for_read(self, model, **hints):
            """"Point all read operations to the specific database."""
            if model._meta.app_label in DATABASE_MAPPING:
                return DATABASE_MAPPING[model._meta.app_label]
            return None
     
        def db_for_write(self, model, **hints):
            """Point all write operations to the specific database."""
            if model._meta.app_label in DATABASE_MAPPING:
                return DATABASE_MAPPING[model._meta.app_label]
            return None
     
        def allow_relation(self, obj1, obj2, **hints):
            """Allow any relation between apps that use the same database."""
            db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label)
            db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label)
            if db_obj1 and db_obj2:
                if db_obj1 == db_obj2:
                    return True
                else:
                    return False
            return None
     
        # for Django 1.4 - Django 1.6
        def allow_syncdb(self, db, model):
            """Make sure that apps only appear in the related database."""
     
            if db in DATABASE_MAPPING.values():
                return DATABASE_MAPPING.get(model._meta.app_label) == db
            elif model._meta.app_label in DATABASE_MAPPING:
                return False
            return None
     
        # Django 1.7 - Django 1.11
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            print db, app_label, model_name, hints
            if db in DATABASE_MAPPING.values():
                return DATABASE_MAPPING.get(app_label) == db
            elif app_label in DATABASE_MAPPING:
                return False
            return None

    这样就实现了指定的 app 使用指定的数据库了,当然你也可以多个sqlite3一起使用,相当于可以给每个app都可以单独设置一个数据库!如果不设置或者没有设置的app就会自动使用默认的数据库。

    2.使用指定的数据库来执行操作

    在查询的语句后面用 using(dbname) 来指定要操作的数据库即可

    # 查询
    YourModel.objects.using('db1').all() 
    或者 YourModel.objects.using('db2').all()
     
    # 保存 或 删除
    user_obj.save(using='new_users')
    user_obj.delete(using='legacy_users')

    3.多个数据库联用时数据导入导出

    使用的时候和一个数据库的区别是:

    如果不是defalut(默认数据库)要在命令后边加 --database=数据库对应的settings.py中的名称  如: --database=db1  或 --database=db2

    数据库同步(创建表)

    # Django 1.6及以下版本
    python manage.py syncdb #同步默认的数据库,和原来的没有区别
     
    # 同步数据库 db1 (注意:不是数据库名是db1,是settings.py中的那个db1,不过你可以使这两个名称相同,容易使用)
    python manage.py syncdb --database=db1
     
    # Django 1.7 及以上版本
    python manage.py migrate --database=db1

    数据导出

    python manage.py dumpdata app1 --database=db1 > app1_fixture.json
    python manage.py dumpdata app2 --database=db2 > app2_fixture.json
    python manage.py dumpdata auth > auth_fixture.json

    数据库导入

    python manage.py loaddata app1_fixture.json --database=db1
    python manage.py loaddata app2_fixture.json --database=db2
  • 相关阅读:
    kata rootfs 文件系统
    crt task
    如何在不到1个月内过四级?
    Android Studio软件技术基础 —Android项目描述---1-类的概念-android studio 组件属性-+标志-Android Studio 连接真机不识别其他途径
    如何把Eclipse项目迁移到AndroidStudio(如何把项目导入安卓)--这我很困惑
    设置Git--在Git中设置您的用户名--创建一个回购--Fork A Repo--社会化
    JavaScript代码笔记重点:
    Android编程权威指南笔记3:Android Fragment讲解与Android Studio中的依赖关系,如何添加依赖关系
    Android编程权威指南笔记2:解决R文件爆红问题和SDK概念
    Java代码题目:计算奖金和完全平方数
  • 原文地址:https://www.cnblogs.com/1a2a/p/8690147.html
Copyright © 2011-2022 走看看