一、ORM简介
orm英文全称object relational mapping,就是对象映射关系程序,简单来说我们类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的,为了保证一致的使用习惯,通过orm将编程语言的对象模型和数据库的关系模型建立映射关系,这样我们在使用编程语言对数据库进行操作的时候可以直接使用编程语言的对象模型进行操作就可以了,而不用直接使用sql语言。
orm的优点:
- 隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
- ORM使我们构造固化数据结构变得简单易行。
缺点:
- 无可避免的,自动化意味着映射和关联管理,代价是牺牲性能(早期,这是所有不喜欢ORM人的共同点)。现在的各种ORM框架都在尝试使用各种方法来减轻这块(LazyLoad,Cache),效果还是很显著的。
二、python中的orm框架(SQLAlchemy)
code first:手动创建类手动创建数据库-->ORM框架-->自动生成表,SQLAchemy属于code first
SQLAchemy
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
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
三、sqlalchemy基本使用
1 pip3 install sqlalchemy
注:SQLAlchemy无法修改表结构,如果需要可以使用SQLAlchemy开发者开源的另外一个软件Alembic来完成.
官网doc:http://docs.sqlalchemy.org/en/latest/core/expression_api.html
3.1.SQL使用
使用 Engine/ConnectionPooling/Dialect 进行数据库操作,Engine使用ConnectionPooling连接数据库,然后再通过Dialect执行SQL语句
1 from sqlalchemy import create_engine 2 engine=create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test", max_overflow=5) # 创建连接,允许溢出5个连接 3 result = engine.execute('select * from student') # 使用excute执行原生sql 4 print(result.fetchall()) # 获取所有结果,与pymyql类似
事务:
1 from sqlalchemy import create_engine 2 engine=create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test", max_overflow=5) # 创建连接,允许溢出5个连接 3 result = engine.execute('select * from student') # 使用excute执行原生sql 4 with engine.begin() as conn: # 事务操作 5 conn.execute("insert into student (name, age, res_date) values ('weikang', 33, '1992-11-11')") 6 7 print(result.fetchall()) # 获取所有结果,与pymyql类似
3.2.创建表
定义数据表,才能进行sql表达式的操作,毕竟sql表达式的表的确定,是sqlalchemy制定的,如果数据库已经存在了数据表还需要定义么?当然,这里其实是一个映射关系,如果不指定,查询表达式就不知道是附加在那个表的操作,当然定义的时候,注意表名和字段名,代码和数据的必须保持一致。定义好之后,就能创建数据表,一旦创建了,再次运行创建的代码,数据库是不会创建的。
sqlalchemy内部组件调用顺序为:使用 Schema Type/SQL Expression Language/Engine/ConnectionPooling/Dialect 进行数据库操作。Engine使用Schema Type创建一个特定的结构对象,之后通过SQL Expression Language将该对象转换成SQL语句,然后通过 ConnectionPooling 连接数据库,再然后通过 Dialect 执行SQL,并获取结果。
TIPS:使用类的方式和使用metadata方式创建表时候区别在于metadata可以不指定主键,而是用class方式必须要求有主键。
1 from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey, MetaData 2 3 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 4 encoding="utf-8", 5 echo=True, 6 max_overflow=5 7 ) 8 # ?charset是字符集编码,echo=True打印输出信息和执行的sql语句默认Flase,max_overflow=5允许溢出连接池连接数量 9 meta = MetaData() # 生成源类 10 # 定义表结构 11 user = Table('user', meta, 12 Column('id', Integer, nullable=True, autoincrement=True, primary_key=True), 13 Column('name', String(20), nullable=True), 14 Column('age', Integer, nullable=True) 15 ) 16 17 host = Table('host', meta, 18 Column('ip', String(20), nullable=True), 19 Column('hostname', String(20), nullable=True), 20 ) 21 meta.create_all(engine) # 创建表,如果存在则忽视
demo2:
使用orm基类创建
1 from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey, MetaData, Date 2 from sqlalchemy.ext.declarative import declarative_base 3 4 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 5 encoding="utf-8", 6 echo=True, 7 max_overflow=5 8 ) 9 # ?charset是字符集编码,echo=True打印输出信息和执行的sql语句默认Flase,max_overflow=5允许溢出连接池连接数量 10 base = declarative_base() # 生成ORM基类 11 12 13 # 定义表结构 14 class User(base): 15 __tablename__ = 'user' # 表名 16 id = Column(Integer, primary_key=True) 17 name = Column(String(32)) 18 age = Column(Integer) 19 20 base.metadata.create_all(engine) # 创建表,如果存在则忽视
1 from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey, MetaData, Date 2 from sqlalchemy.orm import mapper 3 4 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 5 encoding="utf-8", 6 echo=True, 7 max_overflow=5 8 ) 9 # ?charset是字符集编码,echo=True打印输出信息和执行的sql语句默认Flase,max_overflow=5允许溢出连接池连接数量 10 meta = MetaData() 11 12 # 定义表结构 13 person = Table('person', meta, 14 Column('id', Integer, primary_key=True), 15 Column('name', String(20)), 16 Column('age', String(20)), 17 ) 18 19 20 class people(object): 21 def __init__(self, id, name, age): 22 self.id = id 23 self.name = name 24 self.age = age 25 mapper(people, person) # 将类和表映射起来,把类和表关键 26 meta.create_all(engine) # 创建表,如果存在则忽视
使用sqlalchemy进行增删改茶之前需要映射表,然后才可以进行相应的操作。
1 from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey 2 from sqlalchemy.orm import sessionmaker 3 from sqlalchemy.ext.declarative import declarative_base 4 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 5 max_overflow=5, 6 echo=True) 7 # 数据库连接信息为,连接类型://用户名:密码@数据库地址:端口/数据库名字?编码 8 # max_overflow创建连接,允许溢出5个连接,echo=True,输出相应的sql信息到控制台,方便调试。 9 10 base = declarative_base() # 生成orm基类 11 12 13 class user(base): # 映射表 14 __tablename__ = 'user' 15 id = Column(Integer, autoincrement=True, primary_key=True) 16 name = Column(String(20)) 17 age = Column(Integer) 18 19 sessoion_class = sessionmaker(bind=engine) # 创建与数据库的会话类,这里的sessoion_class是类 20 Session = sessoion_class() # 生成会话实例 21 user1 = user(name='Breakering', age=22) # 生成user对象 22 Session.add(user1) # 添加user1,可以使用add_all,参数为列表或者tuple 23 Session.commit() # 提交 24 # Session.rollback() # 回滚 25 Session.close() # 关闭会话
查:
常用查询语法:
1 Common Filter Operators 2 3 Here’s a rundown of some of the most common operators used in filter(): 4 5 equals: 6 7 query.filter(User.name == 'ed') 8 not equals: 9 10 query.filter(User.name != 'ed') 11 LIKE: 12 13 query.filter(User.name.like('%ed%')) 14 15 IN: 16 17 NOT IN: 18 query.filter(~User.name.in_(['ed', 'wendy', 'jack'])) 19 20 IS NULL: 21 22 IS NOT NULL: 23 24 AND: 25 2.1. ObjectRelationalTutorial 17 26 27 query.filter(User.name.in_(['ed', 'wendy', 'jack'])) 28 # works with query objects too: 29 query.filter(User.name.in_( session.query(User.name).filter(User.name.like('%ed%')) 30 31 )) 32 33 query.filter(User.name == None) 34 # alternatively, if pep8/linters are a concern 35 query.filter(User.name.is_(None)) 36 query.filter(User.name != None) 37 # alternatively, if pep8/linters are a concern 38 query.filter(User.name.isnot(None)) 39 SQLAlchemy Documentation, Release 1.1.0b1 40 41 # use and_() 42 43 from sqlalchemy import and_ 44 query.filter(and_(User.name == 'ed', User.fullname == 'Ed Jones')) 45 46 # or send multiple expressions to .filter() 47 query.filter(User.name == 'ed', User.fullname == 'Ed Jones') 48 # or chain multiple filter()/filter_by() calls 49 query.filter(User.name == 'ed').filter(User.fullname == 'Ed Jones') 50 Note: Makesureyouuseand_()andnotthePythonandoperator! • OR: 51 52 Note: Makesureyouuseor_()andnotthePythonoroperator! • MATCH: 53 54 query.filter(User.name.match('wendy')) 55 Note: match() uses a database-specific MATCH or CONTAINS f 56 57 常用查询语法
1 from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey 2 from sqlalchemy.orm import sessionmaker 3 from sqlalchemy.ext.declarative import declarative_base 4 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 5 max_overflow=5, 6 # echo=True 7 ) 8 # 数据库连接信息为,连接类型://用户名:密码@数据库地址:端口/数据库名字?编码 9 # max_overflow创建连接,允许溢出5个连接,echo=True,输出相应的sql信息到控制台,方便调试。 10 11 base = declarative_base() # 生成orm基类 12 13 14 class user(base): # 映射表 15 __tablename__ = 'user' 16 id = Column(Integer, autoincrement=True, primary_key=True) 17 name = Column(String(20)) 18 age = Column(Integer) 19 20 def __repr__(self): # 定义 21 return "(%s,%s,%s)" % (self.id, self.name, self.age) 22 23 sessoion_class = sessionmaker(bind=engine) # 创建与数据库的会话类,这里的sessoion_class是类 24 Session = sessoion_class() # 生成会话实例 25 26 # data=Session.query(user).get(2) # get语法获取primrykey中的关键字,在这里主键为id,获取id为2的数据 27 # data=Session.query(user).filter(user.age>22,user.name=='mack').first() 28 # filter语法两个等于号,filter_by语法一个等于号,可以有多个filter,如果多个数据返回列表,first代表获取第一个,为all()获取所有 29 data = Session.query(user).filter(user.age > 20, user.name.in_(['Breakering', 'wd']), user.id == 1).all() # in语法 30 print(data[0]) # 打印第一个结果 31 Session.commit() # 提交,如果回滚的话,数据将不存在了 32 Session.close() # 关闭会话
修改:
1 data = Session.query(user).filter(user.age > 20).update({"name": 'Breakering'}) # update语法 2 # data = Session.query(user).filter(user.age == 22).first() # 面向对象语法 data.name='coco' # 如果data中数据多条需要使用for循环设置 3 Session.commit() # 提交 4 Session.close() # 关闭会话
删除:
1 data=Session.query(user).filter(user.age==33).delete() 2 Session.commit() # 提交 3 Session.close() # 关闭会话
获取所有数据:
1 data=Session.query(user).all() # 获取user表所有数据 2 for i in data: 3 print(i) 4 5 Session.commit() # 提交 6 Session.close() # 关闭会话
统计:
1 # count=Session.query(user).count() # 获取所有的条数 2 count=Session.query(user).filter(user.name.like("ja%")).count() # 获取某些条数 3 print(count) 4 Session.commit() # 提交 5 Session.close() # 关闭会话
分组:
1 from sqlalchemy import func # 需要导入func函数 2 res=Session.query(func.count(user.name),user.name).group_by(user.name).all() 3 print(res) 4 Session.commit() # 提交 5 Session.close() # 关闭会话
3.4.外键关联
1 from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey 2 from sqlalchemy.orm import sessionmaker 3 from sqlalchemy.ext.declarative import declarative_base 4 from sqlalchemy.orm import relationship 5 6 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 7 encoding="utf-8", 8 # echo=True, 9 max_overflow=5 10 ) 11 # ?charset是连接数据库的字符集编码(和数据库的编码一样),echo=True打印输出信息和执行的sql语句默认Flase,max_overflow=5允许溢出连接池连接数量 12 13 Base = declarative_base() 14 15 16 class user(Base): 17 __tablename__ = 'user' 18 id = Column(Integer, primary_key=True, autoincrement=True) 19 name = Column(String(20)) 20 age = Column(Integer) 21 22 def __repr__(self): 23 return "<id:%s,name:%s,age:%s>" % (self.id, self.name, self.age) 24 25 26 class host(Base): 27 __tablename__ = 'host' 28 user_id = Column(Integer, ForeignKey('user.id')) # user_id关联user表中的id 29 hostname = Column(String(20)) 30 ip = Column(String(20), primary_key=True) 31 host_user = relationship('user', backref='user_host') 32 # 通过host_user查询host表中关联的user信息,通过user_host,在user表查询关联的host,与生成的表结构无关,只是为了方便查询 33 34 def __repr__(self): 35 return "<user_id:%s,hostname:%s,ip:%s>" % (self.user_id,self.hostname,self.ip) 36 37 Base.metadata.create_all(engine) 38 Session_class = sessionmaker(bind=engine) 39 Session = Session_class() 40 # user1 = user(name="Breakering", age=25) 41 # user2 = user(name="Profhua", age=26) 42 # host1 = host(user_id=1, hostname='Breakering-server', ip='192.168.48.20') 43 # Session.add(user1) 44 # Session.add(user2) 45 # Session.add(host1) 46 # Session.commit() 47 48 h1 = Session.query(host).first() 49 print(h1.host_user, 1) 50 print(h1, 2) 51 u1 = Session.query(user).first() 52 print(u1.user_host, 3)
3.5.多外键关联一个表中的一个字段
应用场景:当我们购物时候,你会发现有一个收发票地址,和一个收货地址。关系如下:默认情况下,发票地址和收获地址是一致的,但是也有可能我想买东西送给别人,而发票要自己留着,那收货的地址和寄送发票的地址可以不同。即:同一个人的两个收获地址可以不同,多个收获地址关联同一个人。
1 from sqlalchemy import Integer, ForeignKey, String, Column, create_engine 2 from sqlalchemy.ext.declarative import declarative_base 3 from sqlalchemy.orm import relationship, sessionmaker 4 5 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 6 max_overflow=5, 7 # echo=True 8 ) 9 10 Base = declarative_base() 11 12 13 class Customer(Base): 14 __tablename__ = 'customer' 15 id = Column(Integer, primary_key=True) 16 name = Column(String(64)) 17 18 billing_address_id = Column(Integer, ForeignKey("address.id")) 19 shipping_address_id = Column(Integer, ForeignKey("address.id")) 20 21 billing_address = relationship("Address", foreign_keys=[billing_address_id]) 22 shipping_address = relationship("Address", foreign_keys=[shipping_address_id]) 23 # 同时关联同一个字段,使用relationship需要指定foreign_keys,为了让sqlalchemy清楚关联的外键 24 25 def __repr__(self): 26 return "id:%s, name:%s" % (self.id, self.name) 27 28 29 class Address(Base): 30 __tablename__ = 'address' 31 id = Column(Integer, primary_key=True) 32 street = Column(String(64)) 33 city = Column(String(64)) 34 state = Column(String(64)) 35 36 def __repr__(self): 37 return "street:%s, city:%s, state:%s" % (self.street, self.city, self.state) 38 39 Base.metadata.create_all(engine) # 创建表,如果存在则忽视 40 41 sessoion_class = sessionmaker(bind=engine) # 创建与数据库的会话类,这里的sessoion_class是类 42 Session = sessoion_class() # 生成会话实例 43 # address1 = Address(street="文一路", city="西湖区", state="杭州") 44 # address2 = Address(street="文二路", city="西湖区", state="杭州") 45 # address3 = Address(street="文三路", city="西湖区", state="杭州") 46 # customer1 = Customer(name="Breakering", billing_address_id=1, shipping_address_id=2) 47 # customer2 = Customer(name="Profhua", billing_address_id=3, shipping_address_id=3) 48 # Session.add_all([address1, address2, address3, customer1, customer2]) 49 # Session.commit() 50 51 c1 = Session.query(Customer).filter(Customer.name == "Breakering").first() 52 print(c1.billing_address) 53 print(c1.shipping_address) 54 55 c2 = Session.query(Customer).filter(Customer.name == "Profhua").first() 56 print(c2.billing_address) 57 print(c2.shipping_address)
3.6.多对多外键关联
很多时候,我们会使用多对多外键关联,例如:书和作者,学生和课程,即:书可以有多个作者,而每个作者可以写多本书,orm提供了更简单方式操作多对多关系,在进行删除操作的时候,
1 from sqlalchemy import Column, Table, String, Integer, ForeignKey 2 from sqlalchemy.ext.declarative import declarative_base 3 from sqlalchemy import create_engine 4 from sqlalchemy.orm import relationship 5 6 engine = create_engine("mysql+pymysql://root:123@192.168.48.20:3306/test?charset=utf8", 7 encoding="utf-8", 8 # echo=True, 9 max_overflow=5 10 ) 11 12 Base = declarative_base() 13 14 stu_cour = Table('stu_cour', Base.metadata, 15 Column('stu_id', Integer, ForeignKey('student.id')), 16 Column('cour_id', Integer, ForeignKey('course.id')) 17 ) 18 19 20 class student(Base): 21 __tablename__ = 'student' 22 id = Column(Integer, autoincrement=True, primary_key=True) 23 stu_name = Column(String(32)) 24 stu_age = Column(String(32)) 25 courses = relationship('course', secondary=stu_cour, backref='students') 26 # course是关联的第一张表,stu_cour是关联的第二张表,当然,也可以在第三张关联表中使用两个relationship关联student表和course表 27 28 def __repr__(self): 29 return '<%s>' % self.stu_name 30 31 32 class course(Base): 33 __tablename__ = 'course' 34 id = Column(Integer, autoincrement=True, primary_key=True) 35 cour_name = Column(String(32)) 36 37 def __repr__(self): 38 return '<%s>' % self.cour_name 39 40 if __name__ == '__main__': 41 Base.metadata.create_all(engine)
插入数据:
1 import create_table 2 from sqlalchemy.orm import sessionmaker 3 4 session_class = sessionmaker(bind=create_table.engine) 5 session = session_class() 6 7 stu1 = create_table.student(stu_name='wd', stu_age='22') 8 stu2 = create_table.student(stu_name='jack', stu_age=33) 9 stu3 = create_table.student(stu_name='rose', stu_age=18) 10 c1 = create_table.course(cour_name='linux') 11 c2 = create_table.course(cour_name='python') 12 c3 = create_table.course(cour_name='go') 13 stu1.courses = [c1, c2] # 添加学生课程关联 14 stu2.courses = [c1] 15 stu3.courses = [c1, c2, c3] 16 17 session.add_all([stu1, stu2, stu3, c1, c2, c3]) 18 session.commit()
使用查询:
1 import create_table 2 from sqlalchemy.orm import sessionmaker 3 4 session_class = sessionmaker(bind=create_table.engine) 5 session = session_class() 6 7 s1 = session.query(create_table.student).filter(create_table.student.stu_name == 'wd').first() 8 print(s1.courses)
删除多对多:
1 import create_table 2 from sqlalchemy.orm import sessionmaker 3 4 session_class = sessionmaker(bind=create_table.engine) 5 session = session_class() 6 7 s1 = session.query(create_table.student).filter(create_table.student.stu_name == 'wd').first() 8 print(s1.courses) 9 c1 = session.query(create_table.course).filter(create_table.course.cour_name == 'linux').first() 10 print(s1.courses.remove(c1)) 11 session.commit()