SqlAlchemy ORM
ORM的解释;
简单点:对象关系映射.
需求:我们写一个主机管理,把主机信息存在数据库,一开始我们编程不熟练的时候,执行命令时候要调用数据库,会把相应的SQL语句写到代码中,这种叫做hard coding(硬生生的代码),那么随着程序越来越复杂我们要写的sql语句也越来越多,那么代码中就会出现很多原生的sql语句通过调用数据API(如pymysql,mysql-python)来操作数据库.
并且好多原生的语句是不能重用的,你会发现你在程序的好多地方调用了重复的sql语句.当然,聪明的我们会想到,把这些重复的sql语句,写成函数.但是问题在于你在机在开始开发程序的时候,你没有办法完全预料到所有的操作场景,也就是说没办法预料到要用到什么样的sql语句.所以你还会在实际编程中写很多原生的SQL语句(写死了),并且不能重用.
另外开发人员写出来的sql语句肯定有一些低效率的语句,必定不是专业的DBA.并且如果涉及到多个表查询和做分类的聚合,sql语句如果不优化效率就非常低,所以一般的情况下,DBA会把复杂的sql语句封装成存储过程,让程序员调用.
但是还是有问题,假如现在把存储过程写到代码里,你们公司说我们要把mysql换成oracle,程序员傻逼了,程序里面好多原生的sql和存储过程调用,mysql和oracle语法有很多不一样的地方,那么怎么搞,只能全部重写掉sql语句.
你可能说,我写代码的时候会要求自己写成封装,但是一个项目好多人参与,其他人你能要求,但是万一他在快离职的时候就写一些原生的sql语句,你能怎样,不可控.而且对于个人来说,你封装的sql语句也仅限于当前使用的数据库类型,如果像上面提到的mysql切换成oracle,你就要该封装程序了.
总之,这种方式不可控,而且复杂,完了,这个问题按照自己写代码解决的思路不能解决了.当然我们花上1~3个月来写一个sql的封装的程序,那么我告诉你,可以,你写的就是类似ORM框架的东西,而且90%的可能性,你写的没有SqlAlchemy好.
所以我们最明智的做法是了解SqlAlchemy框架,并且熟练使用他.
下面我们来了解SqlAlchemy是怎么真正的帮我们实现的.
SQLAlcchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简而言之:将对象转化成SQL,然后使用数据API执行SQL并获取执行结果
之前我们学过数据库的API如mysqldb-python直接写sql原生语句,sqlalchemy是建立在数据库API之上,对原生sql语句进行了封装,也就是说你用了sqlalchemy之后,你就不用写sql语句了,你不用写了,这就隔离了.你看看单独这一点就把前面我们遇到的在代码中写原生sql语句的问题解决了,因为你不用写sql语句了嘛.
那你会说代码中原生sql代码的问题解决了,那如何解决从mysql切换到oracle呢?接下来的dialect会根据你配置的不同的数据库类型来调用sqlalchemy内部定义的对于不同数据库类型的api接口.至于你担心的我之前调用的是mysql,现在是oracle的问题,sqlalchemy内部在实例化engine时,根据你连接的数据库类型来选择封装成什么类型的sql原生语句.
紧接着你会问支持多少种数据类型.往下看
(我个人理解的思路是我们知道mysql也好oracle也好,使用的语法会有些不同,那sqlalchemy就像一个中间件,源头是mysql或者oracle又或者是其他的数据库,经过sqlalchemy那么一封装,变成了对于python来说统一的调用模式.就那么简单)
对上图的具体解释:
SQLALchemy ORM: 就是它的ORM,是对象封装的过程,它把sql语句封装成对象了,他这个对象是怎么封装的?不是说一连上sqlalchemy,就把数据库里所有的东西都封装了,而是你自己要声明,我要把这个表封装成什么对象,把那个表封装成什么对象.整个过程 1.先把原生的表映射成类.2.然后才能通过对象的形势调用它.
schema/Types: 你怎么进行对象关系映射?定义了一种映射格式,就是说你要把这个表映射成这个类,就通过这个框架格式
SQL Expression Language:增删改查的SQL封装语句.
Engine: 封装后你需要通过一个引擎操作它
connection pooling:连接池
Dialect:根据你的配置文件,连接不同的数据库
DBAPI:具体的API,mysql api或者oracle api
Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
MySQL-Python
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
pymysql
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
MySQL-Connector
mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
cx_Oracle
oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
更多详见:http://docs.sqlalchemy.org/en/latest/dialects/index.html
案例一:
使用 Engine/ConnectionPooling/Dialect 进行数据库操作,Engine使用ConnectionPooling连接数据库,然后再通过Dialect执行SQL语句。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 from sqlalchemy import create_engine 5 6 7 engine = create_engine("mysql+mysqldb://root:123@127.0.0.1:3306/s11", max_overflow=5) 8 9 engine.execute( 10 "INSERT INTO ts_test (a, b) VALUES ('2', 'v1')" 11 ) 12 13 engine.execute( 14 "INSERT INTO ts_test (a, b) VALUES (%s, %s)", 15 ((555, "v1"),(666, "v1"),) 16 ) 17 engine.execute( 18 "INSERT INTO ts_test (a, b) VALUES (%(id)s, %(name)s)", 19 id=999, name="v1" 20 ) 21 22 result = engine.execute('select * from ts_test') 23 result.fetchall()
总结:使用sqlalchemy可以像使用数据库api一样直接调用原生代码。这种方式没有什么意义,我们只需要知道可以这么做即可。
步骤二sqlalchemy使用的中间状态:
使用 Schema Type/SQL Expression Language/Engine/ConnectionPooling/Dialect 进行数据库操作。
Engine使用Schema Type创建一个特定的结构对象,之后通过SQL Expression Language将该对象转换成SQL语句,然后通过 ConnectionPooling 连接数据库,再然后通过 Dialect 执行SQL,并获取结果。
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 sqlalchemy 的用法案例 5 使用 Schema Type/SQL Expression Language/Engine/ConnectionPooling/Dialect 进行数据库操作 6 ''' 7 #!/usr/bin/env python 8 # -*- coding:utf-8 -*- 9 10 from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey 11 # 老师:MetaData需要理解下,就是在创建类时,sqlalchemy在MetaData里封装了好多的关于这个类的一些属性.包括怎么把Column,Integer,String,Table等等,转成相应的语句. 12 # 网络上资料解释: MetaData是元数据,元数据就是描述数据的数据,比如小明 身高:175cm,身高就是元数据,175cm就是实际数据.而在sqlalchemy里的MetaData实例化一个对象,就是一个元数据对象,里面具体的元数据有表名Table,列名Column,以及怎么把Column,Integer,String,Table等等,转成相应的语句方法名称. 13 # 总之,元数据必须有 14 metadata = MetaData() # 实例化一个属性对象 15 user = Table('user', metadata, # 实例化一个Table,并和metadata进行绑定.这些表的信息都属于metadata里的. 16 Column('id', Integer, primary_key=True), 17 Column('name', String(20)), 18 ) 19 20 color = Table('color', metadata, 21 Column('id', Integer, primary_key=True), 22 Column('name', String(20)), 23 ) 24 # engine = create_engine("mysql+mysqldb://root:123.com@localhost:3306/test", max_overflow=5) 25 26 engine = create_engine("mysql+pymysql://root:123456@localhost:3307/test",max_overflow=5,echo=True) 27 28 # 创建一个连接引擎 29 # create_engine("数据库类型+数据库驱动://数据库用户名:数据库密码@IP地址:端口/数据库",其他参数) 30 # 上文当中echo=True是开启调试,这样当我们执行文件的时候会提示相应的文字。 31 32 metadata.create_all(engine) 33 # 34 # 这一步的具体执行内容就是下面说的. 35 # Engine使用Schema Type创建一个特定的结构对象,之后通过SQL Expression Language将该对象转换成SQL语句,然后通过 ConnectionPooling 连接数据库,再然后通过 Dialect 执行SQL,并获取结果。
步骤二sqlalchemy使用的中间状态的增删改查语法:
(建议只参考下,不需要记住,因为我们最终不会使用这种状态)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey 5 6 metadata = MetaData() # 实例化元数据对象 7 # 创建表结构,我个人的理解是这里的创建相当于在python的sqlalchemy中虚拟一个表结构,如果数据库中有相应的表,那么不会在数据库中创建,如果实际数据库中没有,那么需要通过metadata.create_all(engine)来创建,可参见案例一 8 # 所以可知,创建表结构有两个功能:1.不紧可以声明元数据 2.而且可以用来在数据库中创建表. 9 user = Table('user', metadata, 10 11 Column('id', Integer, primary_key=True), 12 Column('name', String(20)), 13 ) 14 15 color = Table('color', metadata, 16 Column('id', Integer, primary_key=True), 17 Column('name', String(20)), 18 ) 19 20 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接,要真的连接需要调用connect方法 21 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) 22 23 conn = engine.connect() # 引擎执行connect()方法,连接到数据库,获取到光标(游标) 24 25 # 创建SQL语句,INSERT INTO "user" (id, name) VALUES (:id, :name) 26 conn.execute(user.insert(),{'id':10,'name':'seven'}) # 这里我们之所以可以使用user.insert()进行数据插入,而不用写原生sql,是因为我们前面做了将user变量加入到了元数据实例metadata中,这样metadata才知道怎样将user.insert这段代码转化成对应的存储引擎的sql语句 27 # 所以说metadata必须加 28 conn.close() 29 30 # 插入 31 # sql = user.insert().values(id=123, name='wu') # user表 32 # conn.execute(sql) 33 # conn.close() 34 35 # 删除 36 # sql = user.delete().where(user.c.id > 1) # user.c.id 37 38 # 改 39 # sql = user.update().values(fullname=user.c.name) 40 # sql = user.update().where(user.c.name == 'jack').values(name='ed') 41 42 # 查询,查询和上面的插入,删除,改不一样,他使用单独的select方法,不需要像user.select,原因是查询的时候有时候会进行外连接,不是只针对一个表进行的.所以从实际出发sqlalchemy模块中有一个单独的select方法 43 # sql = select([user, ]) # 查询所有 44 # sql = select([user.c.id, ]) # 查字段 45 # sql = select([user.c.name, color.c.name]).where(user.c.id==color.c.id) # 查字段,设置where条件 46 # sql = select([user.c.name]).order_by(user.c.name) # 查名称,排序 47 # sql = select([user]).group_by(user.c.name) # 分组统计 48 49 # 执行查询 50 # result = conn.execute(sql) 51 # print result.fetchall() # 52 # conn.close()
接下来,我们就看看实际编程中我们使用sqlalchemy的终极状态案例:
SqlAlchemy完整的例子(简单)_用法的终极状态.py
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine # 导入引擎类 5 from sqlalchemy.ext.declarative import declarative_base # 导入sqlalchemy的一个扩展模块declarative_base,此模块调用返回一个封装好的 metadata元数据和表名以及表字段 的类.注意返回的是个类,不是实例. 6 from sqlalchemy import Column,Integer,String # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类(是一个包含了数据库API信息和连接地址端口以及用户名和密码信息的类)不是一个实例 8 # 这句话的意思,拿到数据库API(传入的bind=engine参数作中记录了数据库的API和连接信息),创建一个连接的类.这个类里有连接信息,和数据库API信息,接下来要创建一个连接session就要实例化这个类 9 10 ''' 11 使用sqlalchemy ORM完成对数据库的操作整个代码实现过程,可以理解成如下: 12 1.如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类,这里请注意,一个SqlORM类实际上操作数据库的单位不是库,而是表,因为连接的时候直接选择到库. 13 2.把表结构映射出去后,sqlalchemy内部需要一个内部映射的规则比如表名怎么映射,字段怎么映射,String类型怎么映射,Integer怎么映射,删除怎么搞等等,也就是前面说的元数据metadata ,元数据和映射后的表结构绑定后,就可以操作这张表(增删改查). 14 这里是使用declarative_base模块将前两步需要做的事情封装到一个类中.然后下面要创建的SqlORM子类就要继承这个基类了. 封装了映射方法和元数据方法的模块就是declarative_base 15 3.上面两部完成后是只是说可以对映射的数据表进行操作了,但是操作是在本地的,你需要通过连接把SqlORM中的操作最终翻译成对应数据库类型的原生SQL语句,在调用数据库API进行操作. 那么这个引擎的实现模块就是create_engine 16 那么这个连接信息我们称之为引擎. 17 4.引擎相当于一个连接工具,那么谁来使用这个引擎来对数据进行操作呢? 那就是session,session通过引擎调用一系列映射后的表的操作方法,然后翻译成原生SQL后调用数据库API.最终完成对数据库的操作.这个实现session的模块即是sessionmaker 18 ''' 19 20 Base = declarative_base() #生成一个SqlORM基类,接下来所有要创建的SqlORM的子类(理解成如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类) 21 22 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 23 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 24 25 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 26 __tablename__ = 'hosts' # 声明表名 27 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 28 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 29 ip_addr = Column(String(128),unique=True,nullable=False) 30 port = Column(Integer,default=22) 31 32 # 创建表结构方法 33 Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. 34 35 if __name__ == '__main__': 36 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 37 session = SessionCls() # 实例化一个连接huihua 38 ''' 39 # 增加纪录 40 h1 = Host(hostname='localhost',ip_addr='127.0.0.1') # 使用前面定义好SqlORM类Host,实例化一个对象,此对象包含了转换原生sql语句的规范. 41 h2 = Host(hostname='ubuntu',ip_addr='10.0.0.1',port=2000) 42 h3 = Host(hostname='ubuntu2',ip_addr='192.168.2.244',port=20000) 43 session.add(h3) # 可以一条一条添加 44 session.add_all([h1,h2]) # 也可以一次添加多条 45 #h2.hostname = 'ubuntu_test' # 只要没提交,此时修改也没问题 46 #session.rollback() # 撤销 47 session.commit() # 最后要提交. 48 ''' 49 50 ''' 51 # 更改纪录 (更改纪录首先要查询到这条纪录,然后在进行更改) 52 res = session.query(Host).filter(Host.id == 3 ).first() # 调用session.query(查哪个sqlORM类).fitler(查询条件).first(取几行),res为返回的是一条纪录 53 # res = session.query(Host).filter(Host.hostname == 'ubuntu' ).first() # 调用session.query(查哪个sqlORM类).fitler(查询条件).all()取所有 54 # res = session.query(Host.hostname,Host.id).filter(Host.hostname == 'ubuntu' ).first() # 查询指定字段 55 print("++>",res,res.hostname) 56 res.hostname = 'test server' # 直接更改, 57 session.commit() # 然后提交 58 ''' 59 60 61 # 删除操作(删除纪录,先获取对象,然后在删除) 62 res = session.query(Host).filter(Host.hostname.like('%ubuntu%')).all() # 调用session.query获取纪录对象结果集 63 print("------------>",res[0].hostname,res[1].hostname) 64 65 session.delete(res[0]) 66 # print("------------>22",res[0].hostname,res[1].hostname) 67 # session.commit()
SqlAlchemy完整的例子(一对多外键联接1)_用法终极状态
1.首先1对多外联,必须创建外键
#!/usr/bin/env python3.5 #__author__:ted.zhou import pymysql from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base # 注意返回的是个类,不是实例. from sqlalchemy import Column,Integer,String,ForeignKey from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类(是一个包含了数据库API信息和连接地址端口以及用户名和密码信息的类)不是一个实例 from sqlalchemy.orm import relationship Base = declarative_base() #生成一个SqlORM基类,接下来所有要创建的SqlORM的子类(理解成如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类) # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 __tablename__ = 'hosts' # 声明表名 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False ip_addr = Column(String(128),unique=True,nullable=False) port = Column(Integer,default=22) groupid = Column(Integer,ForeignKey('group.id')) # 添加一个字段,并设置此字段类型为外键. # 使用ForeignKey设置外键时注意亮点: # 1. ForeignKey需要从SqlAlchemy中导入 from sqlalchemy import ForeignKey # 2. ForeignKey(参数),参数是实实在在的表名,而不是封装了的SqlAlchemy 的ORM类名 ''' 执行时原生语句的内容如下: CREATE TABLE hosts ( id INTEGER NOT NULL AUTO_INCREMENT, hostname VARCHAR(64) NOT NULL, ip_addr VARCHAR(128) NOT NULL, port INTEGER, groupid INTEGER, PRIMARY KEY (id), UNIQUE (hostname), UNIQUE (ip_addr), FOREIGN KEY(groupid) REFERENCES `group` (id) # 这里就是设置外键的语句,可以参考记住 ) ''' class Group(Base): __tablename__ = 'group' # 定义表名 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) groupname = Column(String(128),unique=True,nullable=False) Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. if __name__ == '__main__': SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 session = SessionCls() # 实例化一个连接huihua ''' # 先创建一个组 g1 = Group(groupname='g1') g2 = Group(groupname='g2') g3 = Group(groupname='g3') session.add_all([g1,g2,g3]) # 创建 h1 = Host(hostname='linux1',ip_addr='10.0.0.1',port='22',groupid=g1.id) h2 = Host(hostname='ubuntu1',ip_addr='10.0.0.2',port='33') session.add_all([h1,h2]) session.commit() 我们在这一连串的代码中实现了,添加组,然后创建主机,设置h1的groupid = g1.id 但是结果是 h1的groupid最终加到数据库中的值为NULL,为什么? 原因是 你虽然在映射后的sqlalchemy的orm类实例化了g1,但是在数据库表中没有,并且你在实例化g1时,没有直接指定g1.id值,所以接下来h1实例化的时候取不到g1.id的值 那么问题来了,如果我在实例化g1的时候指定了id的值能不能成功呢.当然实际应用中自增长字段是 不会自己指定值的,但这里测试.我猜测是可以的.下面我们做这个测试. ''' ''' # 测试 g4 = Group(id=4,groupname='g4') h3 = Host(hostname='ubuntu2',ip_addr='10.0.0.3',port=2000) h4 = Host(hostname='linux2',ip_addr='10.0.0.4',port=200,groupid=g4.id) session.add_all([g4,h3,h4]) session.commit() # 执行结果: # mysql> select * from hosts; # +----+----------+----------+------+---------+ # | id | hostname | ip_addr | port | groupid | # +----+----------+----------+------+---------+ # | 1 | linux1 | 10.0.0.1 | 22 | NULL | # | 2 | ubuntu1 | 10.0.0.2 | 33 | NULL | # | 3 | ubuntu2 | 10.0.0.3 | 2000 | NULL | # | 4 | linux2 | 10.0.0.4 | 200 | 4 | # +----+----------+----------+------+---------+ # 4 rows in set (0.00 sec) # 所以上面上面的判断是正确的 '''
上面的例子就是在Host中添加了一个groupid外键到Group.id。
''' 接下来,我想知道id=4的host所属组的组名,怎么获取? h = session.query(Host).filter(Host.id == 4).first() print('+++++++++++++++>',h.groupname) # 打印h.groupname # 结果: AttributeError: 'Host' object has no attribute 'groupname' 说明直接调用是不行的,那么怎样才能获得groupname呢? 答案:要用到关联查询了,要导入relationship不是relationships,也就是说你希望在Host类里,通过什么字段就可以掉用到groupname, 于是在class Host类中加入: group = relationship('Group')这里的参数是类,而不是表名.说明通过反射的方法,把Group实例嵌到Host实例中的group字段中.这个时候就可以使用group.groupname获得名称了.
代码改成如下:
SqlAlchemy完整的例子(一对多外键联接)2_用法终极状态:
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine # 导入引擎类 5 from sqlalchemy.ext.declarative import declarative_base # 导入sqlalchemy的一个扩展模块declarative_base,此模块调用返回一个封装好的 metadata元数据和表名以及表字段 的类.注意返回的是个类,不是实例. 6 from sqlalchemy import Column,Integer,String,ForeignKey # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类(是一个包含了数据库API信息和连接地址端口以及用户名和密码信息的类)不是一个实例 8 # 这句话的意思,拿到数据库API(传入的bind=engine参数作中记录了数据库的API和连接信息),创建一个连接的类.这个类里有连接信息,和数据库API信息,接下来要创建一个连接session就要实例化这个类 9 from sqlalchemy.orm import relationship # 导入ralationship模块 10 11 Base = declarative_base() #生成一个SqlORM基类,接下来所有要创建的SqlORM的子类(理解成如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类) 12 13 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 14 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 15 16 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 17 __tablename__ = 'hosts' # 声明表名 18 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 19 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 20 ip_addr = Column(String(128),unique=True,nullable=False) 21 port = Column(Integer,default=22) 22 groupid = Column(Integer,ForeignKey('group.id')) # 添加一个字段,并设置此字段类型为外键. 23 group = relationship('Group') # 建立关联关系 24 25 class Group(Base): 26 __tablename__ = 'group' # 定义表名 27 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 28 groupname = Column(String(128),unique=True,nullable=False) 29 30 # 创建表结构方法 31 Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. 32 33 if __name__ == '__main__': 34 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 35 session = SessionCls() # 实例化一个连接huihua 36 ''' 37 再次测试!!! 38 ''' 39 h = session.query(Host).filter(Host.id == 4).first() 40 print('+++++++++++++++>',h.group.groupname) # 打印h.group.groupname 41 # 执行结果: +++++++++++++++> g4
总结:要用到关联查询,两部:1.导入sqlalchemy.orm模块中的relationship 2.使用relationship建立关联关系.
刚刚我们实现了一对多,一个主机对应一个组,并且通过ralationship()建立关系后,使用host.group.groupname获得组名。
那么问题来了,我们如何实现通过组,获得组中包含多少个主机呢?
答案:前面我们只是在Host 类中声明了relationship(),所以通过host.group.groupname能过获得groupname的信息,
所以如果我们想通过group.hosts获得主机的话,就需要在relationship()中声明和Host的关系。
于是代码改成如下:
SqlAlchemy例子(一对多外键联接3)_通过组获取主机_双方声明关系
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine # 导入引擎类 5 from sqlalchemy.ext.declarative import declarative_base # 导入sqlalchemy的一个扩展模块declarative_base,此模块调用返回一个封装好的 metadata元数据和表名以及表字段 的类.注意返回的是个类,不是实例. 6 from sqlalchemy import Column,Integer,String,ForeignKey # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类(是一个包含了数据库API信息和连接地址端口以及用户名和密码信息的类)不是一个实例 8 # 这句话的意思,拿到数据库API(传入的bind=engine参数作中记录了数据库的API和连接信息),创建一个连接的类.这个类里有连接信息,和数据库API信息,接下来要创建一个连接session就要实例化这个类 9 from sqlalchemy.orm import relationship # 导入ralationship模块 10 11 Base = declarative_base() #生成一个SqlORM基类,接下来所有要创建的SqlORM的子类(理解成如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类) 12 13 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 14 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 15 16 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 17 __tablename__ = 'hosts' # 声明表名 18 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 19 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 20 ip_addr = Column(String(128),unique=True,nullable=False) 21 port = Column(Integer,default=22) 22 groupid = Column(Integer,ForeignKey('group.id')) # 添加一个字段,并设置此字段类型为外键. 23 group = relationship('Group') # 建立关联关系 24 25 class Group(Base): 26 __tablename__ = 'group' # 定义表名 27 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 28 groupname = Column(String(128),unique=True,nullable=False) 29 hosts = relationship('Host') # 在组类中声明和Host类的关联 30 31 # 创建表结构方法 32 Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. 33 34 if __name__ == '__main__': 35 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 36 session = SessionCls() # 实例化一个连接huihua 37 ''' 38 # 我们先测试 39 g4 = session.query(Group).filter(Group.id == 4).all() 40 print("+++++++++>",g4.host) 41 #结果不行,因为Group中没有host字段 42 # 是不是应该在Group类中同样要通过relationship('Host') 绑定关系,在Group类中添加语句如下: hosts = relationship('Host') 43 ''' 44 45 ''' 46 # 接着测试: 47 g4 = session.query(Group).filter(Group.id == 4).first() # 这里要用first(),不能用all()否则得到的是一个列表 48 print("+++++++++>",g4.hosts) 49 # 结果:+++++++++> [<__main__.Host object at 0x103843048>] ,如果有多个,那么得到的是两个obj对象64 '''
反向就查出来了,是不是很方便,能够这么使用的原因:1.Host中的groupid和Group中的id外键关联,如果没有关联关系肯定不行
2. Group类中也要调用relationship('Host')进行关联
那么问题来了,一个父亲,要和多个儿子做ralationship()方法的关联,这代码看上去是不是很麻烦.
sqlalchemy语法也考虑到了,只需要在儿子类中把relationship()语法写成: hosts = relationship('Host',backref='hosts')
于是代码改成如下:
SqlAlchemy例子(一对多外键联接4)_通过组获取主机_一方声明双方起作用
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine # 导入引擎类 5 from sqlalchemy.ext.declarative import declarative_base 6 from sqlalchemy import Column,Integer,String,ForeignKey # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类 9 from sqlalchemy.orm import relationship # 导入ralationship模块 10 11 Base = declarative_base() 12 13 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 14 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 15 16 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 17 __tablename__ = 'hosts' # 声明表名 18 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 19 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 20 ip_addr = Column(String(128),unique=True,nullable=False) 21 port = Column(Integer,default=22) 22 groupid = Column(Integer,ForeignKey('group.id')) # 添加一个字段,并设置此字段类型为外键. 23 group = relationship('Group',backref='hosts') # 建立关联关系,这一句很关键,一句代码就实现了双向关系的声明 24 25 26 class Group(Base): 27 __tablename__ = 'group' # 定义表名 28 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 29 groupname = Column(String(128),unique=True,nullable=False) 30 # hosts = relationship('Host') # 这里就不需要用了 31 32 # 创建表结构方法 33 Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. 34 35 if __name__ == '__main__': 36 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 37 session = SessionCls() # 实例化一个连接huihua 38 39 40 # 接着测试: 41 g4 = session.query(Group).filter(Group.id == 4).first() # 这里要用first(),不能用all()否则得到的是一个列表 42 print("+++++++++>",g4.hosts) 43 # 结果:+++++++++> [<__main__.Host object at 0x103843048>] ,如果有多个,那么得到的是两个obj对象
总结:用group = relationship('Group',backref='hosts') # 可以建立双向关系
同时还有一个很类似backref 的用法,就是relationship('Group',back_populates='hosts')
那么我们会问为什会有一个累死backref的功能呢,它用在什么时候.一点区别没有吗.
区别是: 用法上不一样relationship('Group',back_populates='hosts')必须是成对出现的,
也就是说在儿子处group = relationship('Group',back_populates='hosts'),在父亲处就必须有 hosts = relationship('Host',back_populates='group')
那么我们猜想应用场景也就是做统一管理接口的时候,就是在父亲处统一管理,让儿子可不可以外链它.增加了安全性。
前面一对多外联的基本用法演示完成,我们知道实际开发中有很多使用join的方法进行外联查询
SqlAlchemy例子(一对多外键联接5)_innerjoin_leftjoin_rightjoin
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine # 导入引擎类 5 from sqlalchemy.ext.declarative import declarative_base # 导入sqlalchemy的一个扩展模块declarative_base,此模块调用返回一个封装好的 metadata元数据和表名以及表字段 的类.注意返回的是个类,不是实例. 6 from sqlalchemy import Column,Integer,String,ForeignKey,func # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类(是一个包含了数据库API信息和连接地址端口以及用户名和密码信息的类)不是一个实例 8 # 这句话的意思,拿到数据库API(传入的bind=engine参数作中记录了数据库的API和连接信息),创建一个连接的类.这个类里有连接信息,和数据库API信息,接下来要创建一个连接session就要实例化这个类 9 from sqlalchemy.orm import relationship # 导入ralationship模块 10 11 Base = declarative_base() #生成一个SqlORM基类,接下来所有要创建的SqlORM的子类(理解成如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类) 12 13 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 14 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 15 16 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 17 __tablename__ = 'hosts' # 声明表名 18 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 19 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 20 ip_addr = Column(String(128),unique=True,nullable=False) 21 port = Column(Integer,default=22) 22 groupid = Column(Integer,ForeignKey('group.id')) # 添加一个字段,并设置此字段类型为外键. 23 group = relationship('Group',backref='hosts') # 建立关联关系 24 25 26 class Group(Base): 27 __tablename__ = 'group' # 定义表名 28 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 29 groupname = Column(String(128),unique=True,nullable=False) 30 # hosts = relationship('Host') # 这里就不需要用了 31 32 class test_table(Base): 33 __tablename__ = 'test_table' 34 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 35 name2 = Column(String(64),unique=True,nullable=False) 36 37 # 创建表结构方法 38 Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. 39 40 if __name__ == '__main__': 41 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 42 session = SessionCls() # 实例化一个连接huihua 43 ''' 44 # inner join 45 obj = session.query(Host).join(Host.group).filter().all() 46 print('+++++++++>',obj) 47 # 执行时,调用的原生SQL语句是:SELECT hosts.id AS hosts_id, hosts.hostname AS hosts_hostname, hosts.ip_addr AS hosts_ip_addr, hosts.port AS hosts_port, hosts.groupid AS hosts_groupid FROM hosts INNER JOIN `group` ON `group`.id = hosts.groupid 48 # 分析原生sql,得出结论:join(Host.group)这段代码,首先说明1. Host 中有group字段,并且group字段是通过relationship()做关联的,那通过什么关联的呢 继而得出条件2.Host 和Group有外键 49 50 # 上面得到的也只有一张表的数据,那如果想得到两张表的数据呢 51 obj2 = session.query(Host,Group).join(Host.group).filter().all() 52 print("obj2:",obj2) 53 # 解析成原生sql为: 54 # SELECT hosts.id AS hosts_id, hosts.hostname AS hosts_hostname, hosts.ip_addr AS hosts_ip_addr, hosts.port AS hosts_port, hosts.groupid AS hosts_groupid, `group`.id AS group_id, `group`.groupname AS group_groupname FROM hosts INNER JOIN `group` ON `group`.id = hosts.groupid 55 ''' 56 ''' 57 # 上面两个都是有建立过外键的情况下进行查询,假如没有创建过字段为外键的情况下能查询呢 58 obj3 = session.query(Host,Group).join(Host.groupid==Group.id).filter().all() 59 print("obj3:",obj3) 60 # 经测试,行不通,难道说join查询在sqlalchemy中只支持带外键的查询吗.未知???这将对我了解数据库有很大帮助 61 ''' 62 63 # 使用group_by 对查询结果进行分类聚合 64 obj4 = session.query(func.count(Host,Group)).join(Group).filter().group_by(Group.groupname).first() 65 print('obj4:',obj4) 66 ''' 67 # 下面我们测试下当没有外键关系的表,外链查询的结果 68 obj5 = session.query(Host,test_table).join(test_table).filter().all() 69 print('obj5:',obj5) 70 # 结果报错:sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'hosts' and 'test_table'. 71 # 说明要执行join查询需要有外键关系. 72 # 但是原生的sql是可以查询没有外键关系的两种表的 73 # SELECT hosts.id AS hosts_id, hosts.hostname AS hosts_hostname, hosts.ip_addr AS hosts_ip_addr, hosts.port AS hosts_port, hosts.groupid AS hosts_groupid FROM hosts INNER JOIN `test_table` ON `test_table`.id = hosts.groupid; 74 # 结果:Empty set (0.00 sec) 75 # 那么接下来又一个问题要解决,就是在sqlalchemy中如何实现没有外键关系进行 join查询??? 76 '''
前面SqlAlchemy完整的例子(一对多外键联接1-5) 这些都是一对多的外链,从基础到高级用法.
但实际开发中,经常有多对多的场景,比如一个主机属于多个组.前面1-5个例子都无法实现,因为每一个主机一条纪录,主机名是唯一的,你没有办法为同一个主机名创建2条纪录,而只是主机组id不一样.
即使可以,也不符合我们作为技术的宗旨,同一样的纪录最好只需要录入一遍.所以如果想实现一个主机属于多个组,就应该单独创建一张主机——组的关系表.表中只纪录主机id和组id.并且不论你用不用orm所有数据库表结构的many to many的关系,也都是通过关系表实现
在原生sql中,拿mysql来说,看起来是没有什么难度的.
假设我们现在创建了一个表:
表名:HostToGroup
id host_id group_id
1 1 1
2 1 2
3 2 1
那么我们想通过关系表查找 主机名,组名,是不是每次查都需要各种来回的join查询.
而orm的存在简化这种操作.实现了不用调用中间表,通过
Host.groups()就可以查出这个主机所属的组的列表
group.hosts() 就可以查出这个组中包含的所有主机的列表.
具体内部代码如何实现的,我们不需要知道,只要知道如何使用sqlalchemy代码实现就可以了.
代码范例:
SqlAlchemy例子(对对多外键联接1)_对原生sql优化的内容
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine 5 from sqlalchemy.ext.declarative import declarative_base 6 from sqlalchemy import Column,Integer,String,ForeignKey,func # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker 9 from sqlalchemy.orm import relationship # 导入ralationship模块 11 from sqlalchemy import Table # 要先导入Table,接下来我们使用Table来创建关系表,而不是使用类的形势创建关系表 12 13 Base = declarative_base() #生成一个SqlORM基类 14 15 Host2Group = Table('host_2_group',Base.metadata, # 这里为什么用Table方式创建表,而不使用class方式,首先我们要知道Table方式创建的表返回的是一个实例,我们可以通过实例.insert()插入数据.而class插入数据的时候却要先实例化.所以我们这里是要用到它返回实例可以操作的特性,用到Table,而不用class 16 Column('host_id',ForeignKey('hosts.id'),primary_key=True), # 这里我们看这个表中有两个主键,为什么呢 17 Column('group_id',ForeignKey('group.id'),primary_key=True), # 因为确保 (主机id-组id) 唯一,且他们两个都不能为空 18 ) 19 ''' 20 这里表创建好了,并且使用的是Table, 21 我们说了sqlalchemy优化了这种many to many的关系实现方式.目前知道能简化的方式: 22 1.不用通过join调用,直接通过Host.groups()获得某个主机所属的组,直接通过group.hosts()获得某个组中的主机 23 那我现在告诉你第二个优化的点 24 2.在新建一个主机 并且关联到某一个组时,自动的往关系表中添加一条host_id和group_id的纪录.(是不是很高级~自动添加,不用像原生SQL一样,要多写一条sql语句) 25 这也是前面使用Table创建这个关系表的原因 26 紧接着你会问怎么实现呢? 別着急,很简单,还记得我们使用relationship()方法,来指定hosts表和group表的关联关系吗? 27 我们只需要在原有relationship()方法中,加secondary=Host2Group,并且把之前创建的外键字段删除 28 最终应该是 29 groups = relationship('Group',secondary=Host2Group,backref='hosts') 30 ''' 31 32 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 33 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 34 35 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 36 __tablename__ = 'hosts' # 声明表名 37 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 38 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 39 ip_addr = Column(String(128),unique=True,nullable=False) 40 port = Column(Integer,default=22) 41 # groupid = Column(Integer,ForeignKey('group.id')) # 此时要删除这个外键了 42 # group = relationship('Group',backref='hosts') # 这个关系也要改了 43 groups = relationship('Group', 44 secondary=Host2Group, # 指定中间表的实例 45 backref='hosts') # 最终改成这个样子了 46 47 ''' 48 # 因为关系表两个外键一个host_id,外键的是hosts.id 49 # 另外一个外溅group_id,外键的是group.id 50 # 就是根据这两个外键,并且通过relationship()方法才做了优化 many to many关系的操作语法. 51 ''' 52 53 class Group(Base): 54 __tablename__ = 'group' # 定义表名 55 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 56 groupname = Column(String(128),unique=True,nullable=False) 57 # hosts = relationship('Host') # 这里就不需要用了 58 59 60 # 创建表结构方法 61 Base.metadata.create_all(engine) 62 63 if __name__ == '__main__': 64 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 65 session = SessionCls() # 实例化一个连接huihua 66 67 ''' 68 一 首先我们先创建主机和组 69 # 创建3个组 70 # g1 = Group(groupname='g1',id=1) 71 # g2 = Group(groupname='g2',id=2) 72 # g3 = Group(groupname='g3') 73 74 # 创建一个主机 75 # h1 = Host(hostname='linux1',ip_addr='10.0.0.1',port=22) 76 77 # 78 # session.add_all([g1,g2,g3,h1]) 79 # session.commit() 80 ''' 81 ''' 82 二. 然后我们将 主机和组关联起来 83 groups = session.query(Group).filter().all() # 查出所有组 84 h = session.query(Host).filter(Host.hostname == 'linux1').first() # 查出一个主机 85 h.groups = groups # 将这个主机的groups 直接赋值 上面所查到的groups 86 session.commit() # 提交后,就会在关系表中host_2_group 自动创建3条纪录,前提就是1.relationship()的设置,和2.关系表外键的设置 87 ''' 88 89 ''' 90 # 三 .接下来我们来试试查询 91 # 查看主机所属的所有组 92 h = session.query(Host).filter(Host.hostname=='linux1').first() 93 print('++++++++++++>>>>>',h.groups) 94 # 执行结果: 95 # ++++++++++++>>>>> [<__main__.Group object at 0x103717748>, <__main__.Group object at 0x1037177b8>, <__main__.Group object at 0x103717828>] 96 # 我们看到有3个组的对象 97 # 查看组里所有的主机 98 g = session.query(Group).filter(Group.groupname == 'g2').first() 99 print('----------->>>>>>',g.hosts) 100 # 执行结果: 101 # ----------->>>>>> [<__main__.Host object at 0x10383d320>] 102 # 我们看到有一个主机对象.
总结:sqlalchemy是多么的神奇.好的很
前面查询的host.groups 和 group.hosts的结果都是对象类型,那么我们最终要打印的肯定不是对象,而是字符串.
问题:怎样将结果打印成字符串呢. sqlalchemy语法本身是没有方法的,我们看我们是通过class创建的orm类,那么我们可以定义__repr__()方法,来随意指定返回的内容
因此 我们在创建 Host和Group类中定义 def __repr__(self):
于是代码改成如下:
SqlAlchemy例子(对对多外键联接2)_将返回的结果转换成字符串
1 #!/usr/bin/env python3.5 2 #__author__:ted.zhou 3 import pymysql 4 from sqlalchemy import create_engine # 导入引擎类 5 from sqlalchemy.ext.declarative import declarative_base # 导入sqlalchemy的一个扩展模块declarative_base,此模块调用返回一个封装好的 metadata元数据和表名以及表字段 的类.注意返回的是个类,不是实例. 6 from sqlalchemy import Column,Integer,String,ForeignKey,func # 导入和创建表结构相关的模块 7 from sqlalchemy.orm import sessionmaker # 导入sessionmaker模块,它自己是一个类,实例化它返回的还是一个类(是一个包含了数据库API信息和连接地址端口以及用户名和密码信息的类)不是一个实例 8 # 这句话的意思,拿到数据库API(传入的bind=engine参数作中记录了数据库的API和连接信息),创建一个连接的类.这个类里有连接信息,和数据库API信息,接下来要创建一个连接session就要实例化这个类 9 from sqlalchemy.orm import relationship # 导入ralationship模块 10 11 from sqlalchemy import Table # 要先导入Table,接下来我们使用Table来创建关系表,而不是使用类的形势创建关系表 12 13 Base = declarative_base() #生成一个SqlORM基类,接下来所有要创建的SqlORM的子类(理解成如果想用sqlalchemy来操作数据库中的表,就需要把数据库的表结构映射成SqlORM类) 14 15 Host2Group = Table('host_2_group',Base.metadata, # 这里为什么用Table方式创建表,而不使用class方式,首先我们要知道Table方式创建的表返回的是一个实例,我们可以通过实例.insert()插入数据.而class插入数据的时候却要先实例化.所以我们这里是要用到它返回实例可以操作的特性,用到Table,而不用class 16 Column('host_id',ForeignKey('hosts.id'),primary_key=True), # 这里我们看这个表中有两个主键,为什么呢 17 Column('group_id',ForeignKey('group.id'),primary_key=True), # 因为确保 (主机id-组id) 唯一,且他们两个都不能为空 18 ) 19 20 # 实例化引擎,只是把连接信息作为参数传入,并未真的连接. 21 engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3307/test",echo=True) # echo=True可以显示转化后的原生SQL语句执行过程 22 23 class Host(Base): # 定义一个SqlORM类,继承Base类这样他就默认绑定了matedata和数据结构 24 __tablename__ = 'hosts' # 声明表名 25 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) # 声明字段id,类型为int,为主键,自增长属性为真 26 hostname = Column(String(64),unique=True,nullable=False) # 声明字段hostname,类型为String,是否唯一限制为True,是否可为空False 27 ip_addr = Column(String(128),unique=True,nullable=False) 28 port = Column(Integer,default=22) 29 groups = relationship('Group',secondary=Host2Group,backref='hosts') # 最终改成这个样子了 30 31 def __repr__(self): # 定义__repr__方法,默认返回字符串 32 return "<id=%s,hostname=%s,ip_addr=%s>"%(self.id, 33 self.hostname, 34 self.ip_addr) 35 36 class Group(Base): 37 __tablename__ = 'group' # 定义表名 38 id = Column(Integer,primary_key=True,autoincrement=True,nullable=False) 39 groupname = Column(String(128),unique=True,nullable=False) 40 41 def __repr__(self): # 定义__repr__方法,默认返回字符串 42 return "<id=%s,name=%s>"%(self.id,self.groupname) 43 # 创建表结构方法 44 Base.metadata.create_all(engine) # 通过引擎engine,SqlORM基类 调用metadata中的create_all方法,这样远程数据库中如果存在子类中的表则不做动作,如果不存在则添加.如果存表名但是字段不一样也不做操作. 45 46 if __name__ == '__main__': 47 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例,也就意味着一切连接都就绪了,就查你点连接了 48 session = SessionCls() # 实例化一个连接huihua 49 50 51 # 查看主机所属的所有组 52 h = session.query(Host).filter(Host.hostname=='linux1').first() 53 print('++++++++++++>>>>>',h.groups) 54 55 # 打印结果: 56 # ++++++++++++>>>>> [<id=1,name=g1>, <id=2,name=g2>, <id=3,name=g3>] 57 58 59 g = session.query(Group).filter(Group.groupname == 'g2').first() 60 print('----------->>>>>>',g.hosts) 61 # 打印结果 62 # ----------->>>>>> [<id=1,hostname=linux1,ip_addr=10.0.0.1>]
请仔细查看上面两个范例代码,保证能看到你想看到的东西
至此,sqlalchemy的一些简单用法,外键用法,join用法,以及多对多关系的用法这节课就讲完了。基本上能够满足70%了,其他的实际需求要自己查。