ORM模型
ORM Object Relational Mapping关系对象映射 是为了解决一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术,由于查询数据库需要大量重复的代码,如果程序能够通过操作对象的属性和方法,来避免反复的语句就好了,ORM就是这样一种中继数据,把不同的数据库都可以抽象成对象交给程序使用.教学博客地址
Python中使用的ORM模块是SQLalchemy,先来看使用方法:
导入并建立连接
# 导入模块然后连接数据库 from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, DateTime, Enum, ForeignKey, UniqueConstraint, ForeignKeyConstraint, Index from sqlalchemy.orm import sessionmaker egine=create_engine('mysql+pymysql://root@127.0.0.1:3306/db1?charset=utf8',max_overflow=5) # 创建数据库引擎连接 Base=declarative_base()
从模块内导入了很多内容,其中create_engine相当于pymysql里边创建一个连接,用于指定数据库API引擎然后创建一个到数据库的连接.各种参数用一个字符串来表示,分别是数据库+引擎名://用户名:密码@主机地址:端口/数据库名称?连接参数,引擎参数. 例子中密码为空所以留空,charset用这种形式来表示.而max_overflow=5,表示允许SQLalchemy最多同时建立5个连接,相当于一个连接池.
从模块内导入的declarative_base类是所以表类的基类,首先要实例化出Base类,然后所有的表格类,都必须继承Base类.
建立与删除表=继承Base的类
# 类与对象=表与行的关系 class Service(Base): # 创建一个继承Base类的类,就是创建一个表.类名用于在python程序内使用 __tablename__='service' # 这是告诉MySQL要创建的表名,和类名没有关系 id=Column(Integer,primary_key=True,autoincrement=True) # 这一些直接写在类里的属性,就是对表内的列进行设置,其中的类型,都是上边从模块导入的.本行创建了主键自增INT类型,列名叫id sname=Column(String(32),nullable=False,index=True) # 列名为sname,数据类型为可为空的32位字符串,直接创建了普通索引 ip=Column(String(15),nullable=False) # 创建15长度的字符串列,列名为ip port=Column(Integer,nullable=False) business_id=Column(Integer,ForeignKey('business.id')) # 创建外键,注意,外键的参数里business是表名,不是类名,因为这个是去告诉MySQL的,所以不要写成类名. __table_args__=( # 这个属性用于后续单独建立约束和索引,类似于MySQL在写完创建列的语句之后,在下边单独写的创建索引等工作.如果是在建立列的时候设置约束和属性,则无需写在这里 UniqueConstraint(ip,port,name='uix_ip_port'), Index('ix_id_sname',id,sname) ) class Business(Base): __tablename__ = 'business' id = Column(Integer, primary_key=True, autoincrement=True) bname = Column(String(32), nullable=False, index=True) Base.metadata.create_all(egine) # 建立完表之后,用这个特殊的方法,使用数据库引擎连接去创建所有表.这里的create_all会自动寻找所有继承了Base类的类,然后将其中的内容转换成SQL语句交给引擎,最终让数据库执行SQL语句. Base.metadata.drop_all(egine) # 还有drop_all,就是删除所有表
执行上述语句,即在MySQL里创建了如类属性所标示的表.后续就是对这个表的内容操作.
操作表内容
将表创建好之后,类似于PyMySQL里的conn与cursor的关系,对表内数据操作需要建立一个session对象,通过session来操作数据
建立sessionSession=sessionmaker(bind=egine) # 用sessionmake生成一个连接对应的Session,再实例化出一个session对象 session=Session() # 之后就是用这个对象来操作表增加数据-add方法
这里需要了解,表类的实例化对象是一行数据,向表内添加数据都是一行行的,所以先实例化一行,再把这行添加到表里.以后
row_obj=Business(bname='销售') # 初始化一个对象,是一行数据,用关键字把数据赋给行的列名属性,由于id是自增列,无需给id赋值 session.add(row_obj) # 可以用add方法添加一行, session.add_all([Business(dname='技术'),Business(dname='运营'),Business(dname='人事')]) # 用add_all方法,传一个列对象的列表,即可一次性添加多行 session.commit() # 增删改之后,都需要提交修改查询数据-query方法
查询数据是MySQL的主要功能,因此查的内容很多,这里先看查询的方法
sql = session.query(Business) # query方法实际上是生成了sql语句,如果print(sql)即可看到SELECT 账户 result = session.query(Business).all() # all()方法是获取所有结果如果直接print(result),能够看到result是一个Bussiness对象清单,表明查询的每一行结果,如果想要看,只要获取每一个对象的列名属性即可
# 按列名属性获取数据 for row in result: print(row.id,row.bname)
实际上,query的参数可以传入类名.列名,表示取哪一列,还可以加上其他方法来进行条件查询,在后边详述.比如,如果指定了查询列,返回值将会变成元组:
sql = session.query(Business.id,Business.bname) result = sql.all() print(result) for row in result: print(row)这个时候就能看到,结果是一系列元组 删除数据-delete方法
删除数据实际上就要用到条件查询了
session.query(Dep).filter(Dep.id > 3).delete() # 用delete方法,跟在条件查询之后,删除对应的内容 session.commit()修改数据-update方法
与原始MySQL里类似,yes是通过条件查询来进行修改,这里要注意的是,修改的是字符串还是数值,需要指定一个额外的参数
session.query(Business).filter(Business.id > 4).update({'bname':'哇哈哈'}) # 这是直接传入一个列名与数据对应的字典,来修改所有id大于4的行的bname字段 session.query(Dep).filter(Dep.id > 0).update({'dname':Dep.dname+'_SB'},synchronize_session=False) # 这里是将原来的名称后边加上_SB字符,注意后边的参数,要按照这个来写. session.query(Dep).filter(Dep.id > 0).update({'id':Dep.id*100},synchronize_session='evaluate') # 如果需要运算数值,后边的参数需要设置成evaluate来进行数值运算.update方法主要是参数为字典,然后注意数据运算即可
查询详解
简单查询 | |
查询全部 | query()方法中只传类(表)名,后边加.all(),相当于SELECT * FROM table,返回是一系列行对象的列表 |
查询某些列 | query()方法内传列名,还可以用.order_by(列名)来排序 |
all和first | all()是取全部结果,first()是取第一个 |
filter | 条件查询,filter()内部传入表达式,默认各个表达式之间是and关系 |
filter_by | filter_by()参数传列名关键字,只能传入一个参数,底层还是调用filter |
between,_in和~ | between作用于要查找的列,_in后边的参数是一个列表,~表示NOT |
and_ or_ | 需要引入这两个类,这两个的参数内部就是指定的and或者or关系 |
通配符%和_ | 在条件的列上使用like方法,以字符串形式传入模糊匹配的内容. |
limit | res=session.query(Emp)[0:5:2]直接使用类似切片的功能即可 |
排序 | 使用.order_by(列名)即可,列名可以用多个,列名其后.desc表示降序,.asc表示升序 |
分组与聚合函数 | 分组通过.group_by()方法来实现,如果需要使用聚合函数,需要从sqlalchemy.sql中引入func,然后用func.聚合函数名来使用 |
连表-笛卡尔积 | 直接查询两个类名,就可以获得笛卡尔积 |
连表-通过filter进行连表 | 同时查询两个表,然后用filter指定相等的字段 |
连表-join方法 | 查询多个表的时候,在后边加上.join(table_name)来连表,默认的jion是INNER JOIN,如果要进行左连接或者右连接,需要在jion参数内指定isouter=True |
组合 | res1=q1.union(q2)或者res1=q1.union_all(q2),其中q1和q2是两个query对象(返回SQL语句的对象),不是查询结果 |
子查询 | 在查询的结果之后加上.subquery(),就可以将这个结果转换成一个临时表使用.在in_后边,可以使用一个单列查询当做in的参数.此外.as_scalar()相当于给一段语句加上括号 |
relationship的应用
relationship是创建特殊的关系,并不会在数据库内创建列,而是在创建表格的时候,通过relationship做关联.就是通过外键,自动将另外一张表里匹配的数据关联出来,定义了表属性之后,每个行对象都拥有这个属性,通过这个属性可以直接拿到与之关联的指定表的行对象(这个叫做正向引用).
如果一个表(A)里边relationship指定了backre='refname''参数,与之关联(有外键指向)的表(B)内会隐含了refname的属性,此时调用B表的对象的refname属性,会得到一个匹配这个外键字段的所有A表行对象的清单.
relationship一定要写在设置外键的表内.两个表的外键关系,其实就是一对多的关系.假如B表有个外键关联到A表,就说明按照外键的匹配,A表的一行对应B表的多行,B表的一行对应A表的一行,relationship写在B表内,B表的对象调用relationship就是前边的一对一的关系,A表的行对象调用relationship里定义的backrep,拿到的就是一对多的关系.
编写一个班级与学生的表格来说明relationship的使用
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import sessionmaker, relationship egine = create_engine( 'mysql+pymysql://root@127.0.0.1:3306/mail_list?charset=utf8', max_overflow=5) Base = declarative_base() class Student(Base): __tablename__ = 'student' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False, index=True) clas_id = Column(Integer, ForeignKey('class.id')) class_query = relationship('Classdata', backref='findstudent') class Classdata(Base): __tablename__ = 'class' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(32), nullable=False, index=True) def init_db(): Base.metadata.create_all(egine) def drop_db(): Base.metadata.drop_all(egine) # drop_db() init_db() Session = sessionmaker(bind=egine) session = Session() res = session.query(Student) for row in res: print(row.id, row.name, row.class_query.name) res = session.query(Classdata).order_by(Classdata.id) for row in res: print(row.id, row.name, row.findstudent)表的设置是一个学生对应一个班级,一个班级内有多个学生,所以班级和学生是一对多的关系,自然外键需要放在学生表中.
在第一个查询中,由于定义了类属性class_query,行对象的class_query属性返回的是一个行对象,就是学生数据通过外键对应到的班级行的数据,对其直接打印name属性,就可以拿到班级名称
第二个查询中,班级表是被外键引用的,因此去调用在外键类的relationship里的backref 参数赋予的名称findstudent,返回的是一个列表(即使只有一个结果也是返回一个列表),每一个元素是班级表通过该外键对应的学生之一,整个列表就是该班级的所有学生的行数据.
通过例子可以看出,relationship的功能也可以由连表实现,但使用relationship可以大大简化操作,还便于取得一对多的所有对象进行遍历,这在后续web框架学习中非常常见.