Python:使用元类实现 orm & sqlalchemy 连接池

1. orm

  • 什么是 orm
    -- 对象关系映射Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术,简单的说 ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中,本质上就是将数据从一种形式转换到另外一种形式,这也同时暗示者额外的执行开销;
    -- ORM 的全程是对象关系映射,这种思想的要点在于要把后台对数据库的各种操作如增删改查中的一些步骤给抽象出来,把数据表映射成一个类,表中的行给作为一个实例,行中的每个字段作为属性,这样以来以前对数据库插入一行数据的操作、就可以简化为类似下面代码:
user=User(id="100001",name="Andy",password="*****")
user.save()  //保存到数据库

-- 简单理解:表格就是类,字段名就是属性,一行记录就是一个对象;

  • orm 的优势是什么
    -- ORM将数据与SQL独立开来,让使用时只需要传递数据,调用方法,而不去一遍一遍的拼接sql语句,如果ORM作为一种中间件实现,则会有很多机会做优化,更重要的是用于控制转换的元数据需要提供和管理,但是同样这些花费要比维护手写的方案要少;而且就算是遵守ODMG规范的对象数据库依然需要级别的元数据;
  • 为什么需要 orm
    -- ORM,是随着面向对象的软件开发方法发展而产生的,面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统;
    -- 对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系,ORM 一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射;
  • 示例:
    -- 下面的代码,演示了如何实现 orm:

class ModelMetaclass(type):
    """
    元类,被 Model 类继承,提供最基本的
    """
    def __new__(cls, name, bases, attrs):
        """
        :param name: 子类的名称,继承该元类的子类的名称;
        :param bases: 父类,以tuple的形式传入,没有也要传入控tuple:();
        :param attrs: 绑定的方法或属性,以dict的形式传入;
        :return:
        """
        mappings = dict()
        # 判断是否需要保存
        for key, value in attrs.items():
            # 判断是否是指定的字符串或者数字的实例对象
            if isinstance(value, tuple):
                mappings[key] = value

        for key in mappings.keys():
            attrs.pop(key)

        attrs["__mappings__"] = mappings
        attrs["__table__"] = name.lower()
        return type.__new__(cls, name, bases, attrs)


class Model(object, metaclass=ModelMetaclass):
    """
    Model 类,被 Field 类(如本文中的 User 类)继承
    """
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)

    def save(self):
        fileds = []
        args = []
        for key, value in self.__mappings__.items():
            fileds.append(value[0])
            args.append(getattr(self, key, None))

        args_temp = list()
        for temp in args:
            if isinstance(temp, int):
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                args_temp.append(f"""'{temp}'""")
        sql = f"""INSERT INTO {self.__table__} ({','.join(fileds)}) VALUES ({','.join(args_temp)})"""
        print("SQL : ", sql)


class User(Model):
    user_id = ("user_id", "int usigined")                           # 类属性,传入元类 attrs
    user_name = ("username", "varchar(30)")                         # 类属性,传入元类 attrs
    user_email = ("email", "varchar(30)")                           # 类属性,传入元类 attrs


user = User(user_id=123, user_name="pan", user_email="xxx@xx.com")
user.save()

  • sqlalchemy
1431912884-5a5b6a87e85dc_articlex.png
  • 创建 database;
    -- 在 MySQL 中创建 databse user ;
mysql> create database userdb;
Query OK, 1 row affected (0.00 sec)
  • 创建 配置文件 unitConf.conf

[db]
db_host = localhost
db_port = 3306
db_user = root
db_passwd = Linux423
db_name = userdb

[concurrent]
thread= 10


  • 使用 sqlalchemy 连接 mysql:

from sqlalchemy import exists, Column, Integer, String, ForeignKey, DateTime, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
import configparser


cf = configparser.ConfigParser()
cf.read("unitConf.conf")
# 从配置文件中获取数据库信息
host = cf.get("db", "db_host")
port = cf.get("db", "db_port")
dbname = cf.get("db", "db_name")
usernm = cf.get("db", "db_user")
passwd = cf.get("db", "db_passwd")

# 连接数据库
engine_str = "mysql+mysqlconnector://{0}:{1}@{2}:{3}/{4}?auth_plugin=mysql_native_password".format(
                                                                        usernm, passwd, host, port, dbname)
engine = create_engine(
                        engine_str,
                        encoding='utf-8',
                        pool_size=10,               # 连接池中,保持的连接数
                        max_overflow=5              # 当连接数已达到10 且都被使用时,max_overflow就是允许再新建的连接数
)

# 创建DBSession类型
session = sessionmaker(bind=engine)
# declarative_base 函数实例化一个数据库表的基类,之后所有的数据库表都要继承这个基类。
Base = declarative_base()       # 


# 必须继承前面由 declaraive_base 得到的 Base 基类,Base 会通过引擎初始化数据库结构,不继承Base就没有办法连接数据库
class Users(Base):
    # 必须要有__tablename__来指出这个类对应什么表,这个表可以暂时在库中不存在,SQLAlchemy 会帮我们创建这个表
    __tablename__ = "user"

    id = Column(                        # Column 类创建一个字段
        Integer,
        autoincrement=True,
        primary_key=True
    )
    user_no = Column(
        String(20),                     # user_no 员工编号额外生成,作为员工的身份识别编号,每一个编号都是唯一的
        nullable=False,                 # nullable 就是决定是否 not null,
        unique=True,                    # unique 就是决定是否 unique,员工的身份识别编号是唯一的,不能重复
        index=True,                     # 设置 index 可以让系统自动根据这个字段为基础建立索引
        comment='用户编号'
    )
    name = Column(
        String(20),
        nullable=False,                 # 姓名不能为空
        comment='姓名'
    )
    gender = Column(
        Integer,                        # 1 表示男,0 表示 女
        nullable=False,                 # 性别不能为空
        comment='性别'
    )
    age = Column(
        Integer,
        nullable=False,                 # 年龄不能为空
        comment = '年龄'
    )
    dept = Column(
        String(20),
        nullable=False,                 # 部门不能为空
        comment='部门'
    )
    createtime = Column(
        DateTime,
        server_default=func.now(),      # 系统默认时间
        comment='创建时间'
    )
    updatetime = Column(
        DateTime,
        server_default=func.now(),      # 系统默认时间
        onupdate=func.now(),            # onupdate 用于记录【更新时间】
        comment='修改时间'
    )

    def __repr__(self):
        return "<User> {} : {}".format(self.Sname, self.Sno)


def create_table():
    """
    创建表
    :return:
    """
    Base.metadata.create_all(engine)

def delete_table():
    """
    删除表
    """
    Base.metadata.drop_all(engine)

  • 创建表

-- 从上面代码调用 create_table() 方法;

create_table()

-- 查看 mysql 中 user 表结构;

mysql> use userdb;
Database changed
mysql> desc user;
+------------+-------------+------+-----+-------------------+-------------------+
| Field      | Type        | Null | Key | Default           | Extra             |
+------------+-------------+------+-----+-------------------+-------------------+
| id         | int         | NO   | PRI | NULL              | auto_increment    |
| user_no    | varchar(20) | NO   | UNI | NULL              |                   |
| name       | varchar(20) | NO   |     | NULL              |                   |
| gender     | varchar(2)  | NO   |     | NULL              |                   |
| age        | int         | NO   |     | NULL              |                   |
| dept       | varchar(20) | NO   |     | NULL              |                   |
| createtime | datetime    | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| updatetime | datetime    | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+------------+-------------+------+-----+-------------------+-------------------+
8 rows in set (0.00 sec)
mysql>

-- user 表创建成功;

  • 删除表

-- 从上面代码调用 delete_table() 方法;

# 删除表
delete_table()

-- 在 mysql 中查看 user 表

mysql> desc user;
ERROR 1146 (42S02): Table 'userdb.user' doesn't exist
  • pool_size
    设置连接池中,保持的连接数。初始化时,并不产生连接。只有慢慢需要连接时,才会产生连接。例如我们的连接数设置成pool_size=10。如果我们的并发量一直最高是5。那么我们的连接池里的连接数也就是5。当我们有一次并发量达到了10。以后并发量虽然下去了,连接池中也会保持10个连接。

  • max_overflow
    当连接池里的连接数已达到,pool_size时,且都被使用时。又要求从连接池里获取连接时,max_overflow就是允许再新建的连接数。
    例如pool_size=10,max_overlfow=5。当我们的并发量达到12时,当第11个并发到来后,就会去再建一个连接,第12个同样。当第11个连接处理完回收后,若没有在等待进程获取连接,这个连接将会被立即释放。

  • pool_timeout
    从连接池里获取连接,如果此时无空闲的连接。且连接数已经到达了pool_size+max_overflow。此时获取连接的进程会等待pool_timeout秒。如果超过这个时间,还没有获得将会抛出异常。sqlalchemy默认30秒

  • pool_recycle
    这个指,一个数据库连接的生存时间。例如pool_recycle=3600。也就是当这个连接产生1小时后,再获得这个连接时,会丢弃这个连接,重新创建一个新的连接。
    当pool_recycle设置为-1时,也就是连接池不会主动丢弃这个连接。永久可用。但是有可能数据库server设置了连接超时时间。例如mysql,设置的有wait_timeout默认为28800,8小时。当连接空闲8小时时会自动断开。8小时后再用这个连接也会被重置。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351