python 之路,Day11 (下)- sqlalchemy ORM
python 之路,Day11 - sqlalchemy ORM
?
本節內容
- ORM介紹
- sqlalchemy安裝
- sqlalchemy基本使用
- 多外鍵關聯
- 多對多關系
- 表結構設計作業
?
1.?ORM介紹
orm英文全稱object relational mapping,就是對象映射關系程序,簡單來說我們類似python這種面向對象的程序來說一切皆對象,但是我們使用的數據庫卻都是關系型的,為了保證一致的使用習慣,通過orm將編程語言的對象模型和數據庫的關系模型建立映射關系,這樣我們在使用編程語言對數據庫進行操作的時候可以直接使用編程語言的對象模型進行操作就可以了,而不用直接使用sql語言。
orm的優點:
- 隱藏了數據訪問細節,“封閉”的通用數據庫交互,ORM的核心。他使得我們的通用數據庫交互變得簡單易行,并且完全不用考慮該死的SQL語句。快速開發,由此而來。
- ORM使我們構造固化數據結構變得簡單易行。
缺點:
- 無可避免的,自動化意味著映射和關聯管理,代價是犧牲性能(早期,這是所有不喜歡ORM人的共同點)。現在的各種ORM框架都在嘗試使用各種方法來減輕這塊(LazyLoad,Cache),效果還是很顯著的。
?
2. sqlalchemy安裝
在Python中,最有名的ORM框架是SQLAlchemy。用戶包括openstack\Dropbox等知名公司或應用,主要用戶列表http://www.sqlalchemy.org/organizations.html#openstack
Dialect用于和數據API進行交流,根據配置文件的不同調用不同的數據庫API,從而實現對數據庫的操作,如:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 | pip?install?SQLAlchemy<br><br>pip?install?pymysql??#由于mysqldb依然不支持py3,所以這里我們用pymysql與sqlalchemy交互 | 
?
3.sqlalchemy基本使用
下面就開始讓你見證orm的nb之處,盤古開天劈地之前,我們創建一個表是這樣的
| 1 2 3 4 5 6 | CREATE?TABLE?user?(????id?INTEGER?NOT?NULL?AUTO_INCREMENT,????name?VARCHAR(32),????password?VARCHAR(64),????PRIMARY?KEY?(id)) | 
這只是最簡單的sql表,如果再加上外鍵關聯什么的,一般程序員的腦容量是記不住那些sql語句的,于是有了orm,實現上面同樣的功能,代碼如下
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import?sqlalchemyfrom?sqlalchemy?import?create_enginefrom?sqlalchemy.ext.declarative?import?declarative_basefrom?sqlalchemy?import?Column, Integer, Stringengine?=?create_engine("mysql+pymysql://root:alex3714@localhost/testdb",????????????????????????????????????encoding='utf-8', echo=True)Base?=?declarative_base()?#生成orm基類class?User(Base):????__tablename__?=?'user'?#表名????id?=?Column(Integer, primary_key=True)????name?=?Column(String(32))????password?=?Column(String(64))Base.metadata.create_all(engine)?#創建表結構 | 
你說,娘那個腚的,并沒有感覺代碼量變少啊,呵呵, 孩子莫猴急,好戲在后面
Lazy Connecting
The Engine, when first returned by create_engine(), has not actually tried to connect to the database yet; that happens only the first time it is asked to perform a task against the database.
除上面的創建之外,還有一種創建表的方式,雖不常用,但還是看看吧
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from?sqlalchemy?import?Table, MetaData, Column, Integer, String, ForeignKeyfrom?sqlalchemy.orm?import?mappermetadata?=?MetaData()user?=?Table('user', metadata,????????????Column('id', Integer, primary_key=True),????????????Column('name', String(50)),????????????Column('fullname', String(50)),????????????Column('password', String(12))????????)class?User(object):????def?__init__(self, name, fullname, password):????????self.name?=?name????????self.fullname?=?fullname????????self.password?=?passwordmapper(User, user)?#the table metadata is created separately with the Table construct, then associated with the User class via the mapper() function | 
事實上,我們用第一種方式創建的表就是基于第2種方式的再封裝。
?
最基本的表我們創建好了,那我們開始用orm創建一條數據試試
| 1 2 3 4 5 6 7 8 9 10 11 | Session_class?=?sessionmaker(bind=engine)?#創建與數據庫的會話session class ,注意,這里返回給session的是個class,不是實例Session?=?Session_class()?#生成session實例user_obj?=?User(name="alex",password="alex3714")?#生成你要創建的數據對象print(user_obj.name,user_obj.id)??#此時還沒創建對象呢,不信你打印一下id發現還是NoneSession.add(user_obj)?#把要創建的數據對象添加到這個session里, 一會統一創建print(user_obj.name,user_obj.id)?#此時也依然還沒創建Session.commit()?#現此才統一提交,創建數據 | 
我擦,寫這么多代碼才創建一條數據,你表示太tm的費勁了,正要轉身離開,我拉住你的手不放開,高潮還沒到。。
查詢
| 1 2 | my_user?=?Session.query(User).filter_by(name="alex").first()print(my_user) | 
此時你看到的輸出是這樣的應該
| 1 | <__main__.User?object?at?0x105b4ba90> | 
我擦,這是什么?這就是你要的數據呀, 只不過sqlalchemy幫你把返回的數據映射成一個對象啦,這樣你調用每個字段就可以跟調用對象屬性一樣啦,like this..
| 1 2 3 4 | print(my_user.id,my_user.name,my_user.password)輸出1?alex alex3714 | 
不過剛才上面的顯示的內存對象對址你是沒辦法分清返回的是什么數據的,除非打印具體字段看一下,如果想讓它變的可讀,只需在定義表的類下面加上這樣的代碼
| 1 2 3 | def?__repr__(self):????return?"<User(name='%s',? password='%s')>"?%?(????????self.name,?self.password) | 
修改
| 1 2 3 4 5 | my_user?=?Session.query(User).filter_by(name="alex").first()my_user.name?=?"Alex Li"Session.commit() | 
回滾
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | my_user?=?Session.query(User).filter_by(id=1).first()my_user.name?=?"Jack"fake_user?=?User(name='Rain', password='12345')Session.add(fake_user)print(Session.query(User).filter(User.name.in_(['Jack','rain'])).all() )??#這時看session里有你剛添加和修改的數據Session.rollback()?#此時你rollback一下print(Session.query(User).filter(User.name.in_(['Jack','rain'])).all() )?#再查就發現剛才添加的數據沒有了。# Session# Session.commit() | 
獲取所有數據
| 1 | print(Session.query(User.name,User.id).all() ) | 
?
多條件查詢
| 1 | objs?=?Session.query(User).filter(User.id>0).filter(User.id<7).all() | 
上面2個filter的關系相當于 user.id >1 AND user.id <7 的效果
統計和分組
| 1 | Session.query(User).filter(User.name.like("Ra%")).count() | 
分組
| 1 2 | from?sqlalchemy?import?funcprint(Session.query(func.count(User.name),User.name).group_by(User.name).all() ) | 
相當于原生sql為
| 1 2 | SELECT count(user.name) AS count_1, user.name AS user_nameFROM user GROUP BY user.name | 
輸出為
[(1, 'Jack'), (2, 'Rain')]
?
外鍵關聯
我們創建一個addresses表,跟user表關聯
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | from?sqlalchemy?import?ForeignKeyfrom?sqlalchemy.orm?import?relationshipclass?Address(Base):????__tablename__?=?'addresses'????id?=?Column(Integer, primary_key=True)????email_address?=?Column(String(32), nullable=False)????user_id?=?Column(Integer, ForeignKey('user.id'))????user?=?relationship("User", backref="addresses")?#這個nb,允許你在user表里通過backref字段反向查出所有它在addresses表里的關聯項????def?__repr__(self):????????return?"<Address(email_address='%s')>"?%?self.email_address | 
The?
relationship.back_populates?parameter is a newer version of a very common SQLAlchemy feature calledrelationship.backref. The?relationship.backref?parameter hasn’t gone anywhere and will always remain available! The?relationship.back_populates?is the same thing, except a little more verbose and easier to manipulate. For an overview of the entire topic, see the section?Linking Relationships with Backref.
表創建好后,我們可以這樣反查試試
| 1 2 3 4 5 6 | obj?=?Session.query(User).first()for?i?in?obj.addresses:?#通過user對象反查關聯的addresses記錄????print(i)addr_obj?=?Session.query(Address).first()print(addr_obj.user.name)??#在addr_obj里直接查關聯的user表 | 
創建關聯對象
| 1 2 3 4 5 6 7 8 | obj?=?Session.query(User).filter(User.name=='rain').all()[0]print(obj.addresses)obj.addresses?=?[Address(email_address="r1@126.com"),?#添加關聯對象?????????????????Address(email_address="r2@126.com")]Session.commit() | 
?
?
常用查詢語法
Common Filter Operators
Here’s a rundown of some of the most common operators used in filter():
-  equals: query.filter(User.name == 'ed') 
-  not equals: query.filter(User.name != 'ed') 
-  LIKE: query.filter(User.name.like('%ed%')) 
-  IN: 
-  NOT IN: 
 query.filter(~User.name.in_(['ed', 'wendy', 'jack']))
-  IS NULL: 
-  IS NOT NULL: 
-  AND: 
 2.1. ObjectRelationalTutorial 17
query.filter(User.name.in_(['ed', 'wendy', 'jack']))
# works with query objects too:
query.filter(User.name.in_( session.query(User.name).filter(User.name.like('%ed%'))
))
query.filter(User.name == None)
# alternatively, if pep8/linters are a concern
query.filter(User.name.is_(None))
query.filter(User.name != None)
# alternatively, if pep8/linters are a concern
query.filter(User.name.isnot(None))
SQLAlchemy Documentation, Release 1.1.0b1
# use and_()
from sqlalchemy import and_
query.filter(and_(User.name == 'ed', User.fullname == 'Ed Jones'))
# or send multiple expressions to .filter()
query.filter(User.name == 'ed', User.fullname == 'Ed Jones')
# or chain multiple filter()/filter_by() calls
query.filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')
Note: Makesureyouuseand_()andnotthePythonandoperator! ? OR:
Note: Makesureyouuseor_()andnotthePythonoroperator! ? MATCH:
query.filter(User.name.match('wendy'))
Note: match() uses a database-specific MATCH or CONTAINS f?
?
4.多外鍵關聯
One of the most common situations to deal with is when there are more than one foreign key path between two tables.
Consider a?Customer?class that contains two foreign keys to an?Address?class:
下表中,Customer表有2個字段都關聯了Address表?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from?sqlalchemy?import?Integer, ForeignKey, String, Columnfrom?sqlalchemy.ext.declarative?import?declarative_basefrom?sqlalchemy.orm?import?relationshipBase?=?declarative_base()class?Customer(Base):????__tablename__?=?'customer'????id?=?Column(Integer, primary_key=True)????name?=?Column(String)????billing_address_id?=?Column(Integer, ForeignKey("address.id"))????shipping_address_id?=?Column(Integer, ForeignKey("address.id"))????billing_address?=?relationship("Address")?????shipping_address?=?relationship("Address")class?Address(Base):????__tablename__?=?'address'????id?=?Column(Integer, primary_key=True)????street?=?Column(String)????city?=?Column(String)????state?=?Column(String) | 
創建表結構是沒有問題的,但你Address表中插入數據時會報下面的錯
| 1 2 3 4 5 6 | sqlalchemy.exc.AmbiguousForeignKeysError: Could?not?determine joincondition between parent/child tables on relationshipCustomer.billing_address?-?there are multiple foreign keypaths linking the tables.? Specify the?'foreign_keys'?argument,providing a?list?of those columns which should becounted as containing a foreign key reference to the parent table. | 
解決辦法如下
| 1 2 3 4 5 6 7 8 9 10 | class?Customer(Base):????__tablename__?=?'customer'????id?=?Column(Integer, primary_key=True)????name?=?Column(String)????billing_address_id?=?Column(Integer, ForeignKey("address.id"))????shipping_address_id?=?Column(Integer, ForeignKey("address.id"))????billing_address?=?relationship("Address", foreign_keys=[billing_address_id])????shipping_address?=?relationship("Address", foreign_keys=[shipping_address_id]) | 
這樣sqlachemy就能分清哪個外鍵是對應哪個字段了
5.多對多關系
現在來設計一個能描述“圖書”與“作者”的關系的表結構,需求是
- 一本書可以有好幾個作者一起出版
- 一個作者可以寫好幾本書
此時你會發現,用之前學的外鍵好像沒辦法實現上面的需求了,因為
當然你更不可以像下面這樣干,因為這樣就你就相當于有多條書的記錄了,太low b了,改書名還得都改。。。
?
那怎么辦呢? 此時,我們可以再搞出一張中間表,就可以了
這樣就相當于通過book_m2m_author表完成了book表和author表之前的多對多關聯
用orm如何表示呢?
#一本書可以有多個作者,一個作者又可以出版多本書from sqlalchemy import Table, Column, Integer,String,DATE, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmakerBase = declarative_base()book_m2m_author = Table('book_m2m_author', Base.metadata,Column('book_id',Integer,ForeignKey('books.id')),Column('author_id',Integer,ForeignKey('authors.id')),)class Book(Base):__tablename__ = 'books'id = Column(Integer,primary_key=True)name = Column(String(64))pub_date = Column(DATE)authors = relationship('Author',secondary=book_m2m_author,backref='books')def __repr__(self):return self.nameclass Author(Base):__tablename__ = 'authors'id = Column(Integer, primary_key=True)name = Column(String(32))def __repr__(self):return self.name    ?
接下來創建幾本書和作者
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Session_class?=?sessionmaker(bind=engine)?#創建與數據庫的會話session class ,注意,這里返回給session的是個class,不是實例s?=?Session_class()?#生成session實例b1?=?Book(name="跟Alex學Python")b2?=?Book(name="跟Alex學把妹")b3?=?Book(name="跟Alex學裝逼")b4?=?Book(name="跟Alex學開車")a1?=?Author(name="Alex")a2?=?Author(name="Jack")a3?=?Author(name="Rain")b1.authors?=?[a1,a2]b2.authors?=?[a1,a2,a3]s.add_all([b1,b2,b3,b4,a1,a2,a3])s.commit() | 
此時,手動連上mysql,分別查看這3張表,你會發現,book_m2m_author中自動創建了多條紀錄用來連接book和author表
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | mysql> select?*?from?books;+----+------------------+----------+|?id?| name???????????? | pub_date |+----+------------------+----------+|??1?| 跟Alex學Python?? | NULL???? ||??2?| 跟Alex學把妹???? | NULL???? ||??3?| 跟Alex學裝逼???? | NULL???? ||??4?| 跟Alex學開車???? | NULL???? |+----+------------------+----------+4?rows?in?set?(0.00?sec)mysql> select?*?from?authors;+----+------+|?id?| name |+----+------+|?10?| Alex ||?11?| Jack ||?12?| Rain |+----+------+3?rows?in?set?(0.00?sec)mysql> select?*?from?book_m2m_author;+---------+-----------+| book_id | author_id |+---------+-----------+|???????2?|????????10?||???????2?|????????11?||???????2?|????????12?||???????1?|????????10?||???????1?|????????11?|+---------+-----------+5?rows?in?set?(0.00?sec) | 
?
此時,我們去用orm查一下數據
| 1 2 3 4 5 6 7 8 9 | print('--------通過書表查關聯的作者---------')book_obj?=?s.query(Book).filter_by(name="跟Alex學Python").first()print(book_obj.name, book_obj.authors)print('--------通過作者表查關聯的書---------')author_obj?=s.query(Author).filter_by(name="Alex").first()print(author_obj.name , author_obj.books)s.commit() | 
輸出如下
| 1 2 3 4 | --------通過書表查關聯的作者---------跟Alex學Python [Alex, Jack]--------通過作者表查關聯的書---------Alex [跟Alex學把妹, 跟Alex學Python] | 
牛逼了我的哥!!完善實現多對多
多對多刪除
刪除數據時不用管boo_m2m_authors , sqlalchemy會自動幫你把對應的數據刪除
通過書刪除作者
| 1 2 3 4 5 6 | author_obj?=s.query(Author).filter_by(name="Jack").first()book_obj?=?s.query(Book).filter_by(name="跟Alex學把妹").first()book_obj.authors.remove(author_obj)?#從一本書里刪除一個作者s.commit() | 
直接刪除作者
刪除作者時,會把這個作者跟所有書的關聯關系數據也自動刪除
| 1 2 3 4 | author_obj?=s.query(Author).filter_by(name="Alex").first()# print(author_obj.name , author_obj.books)s.delete(author_obj)s.commit() | 
處理中文
sqlalchemy設置編碼字符集一定要在數據庫訪問的URL上增加charset=utf8,否則數據庫的連接就不是utf8的編碼格式
eng = create_engine('mysql://root:root@localhost:3306/test2?charset=utf8',echo=True)
6.本節作業
主題:學員管理系統
需求:
- 用戶角色,講師\學員, 用戶登陸后根據角色不同,能做的事情不同,分別如下
- 講師視圖- 管理班級,可創建班級,根據學員qq號把學員加入班級
- 可創建指定班級的上課紀錄,注意一節上課紀錄對應多條學員的上課紀錄, 即每節課都有整班學員上, 為了紀錄每位學員的學習成績,需在創建每節上課紀錄是,同時 ? ? ? ? 為這個班的每位學員創建一條上課紀錄
- 為學員批改成績, 一條一條的手動修改成績
 
- 學員視圖
- 提交作業
- 查看作業成績
- 一個學員可以同時屬于多個班級,就像報了Linux的同時也可以報名Python一樣, 所以提交作業時需先選擇班級,再選擇具體上課的節數
- 附加:學員可以查看自己的班級成績排名
?
?
?
?
?
?
分類:?Python自動化開發之路 好文要頂?關注我?收藏該文?? 金角大王
關注 - 0
粉絲 - 639 +加關注 0 0 ??上一篇:Python Select 解析 posted @?2016-10-19 18:35?金角大王?閱讀(636) 評論(0) ?編輯?收藏
刷新評論刷新頁面返回頂部
轉載于:https://www.cnblogs.com/weiman3389/p/6222369.html
總結
以上是生活随笔為你收集整理的python 之路,Day11 (下)- sqlalchemy ORM的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 2.4G高频PCB天线设计
- 下一篇: 医保一个月交多少钱啊?
