本節內容
- 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,從而實現對數據庫的操作,如:
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
pip install SQLAlchemy
pip install pymysql #由於mysqldb依然不支持py3,所以這里我們用pymysql與sqlalchemy交互
3.sqlalchemy基本使用
下面就開始讓你見證orm的nb之處,盤古開天劈地之前,我們創建一個表是這樣的
CREATE TABLE user ( id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(32), password VARCHAR(64), PRIMARY KEY (id) )
這只是最簡單的sql表,如果再加上外鍵關聯什么的,一般程序員的腦容量是記不住那些sql語句的,於是有了orm,實現上面同樣的功能,代碼如下
import sqlalchemy from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String engine = 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.
除上面的創建之外,還有一種創建表的方式,雖不常用,但還是看看吧
from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper metadata = 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 = password mapper(User, user) #the table metadata is created separately with the Table construct, then associated with the User class via the mapper() function
事實上,我們用第一種方式創建的表就是基於第2種方式的再封裝。
最基本的表我們創建好了,那我們開始用orm創建一條數據試試
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發現還是None Session.add(user_obj) #把要創建的數據對象添加到這個session里, 一會統一創建 print(user_obj.name,user_obj.id) #此時也依然還沒創建 Session.commit() #現此才統一提交,創建數據
我擦,寫這么多代碼才創建一條數據,你表示太tm的費勁了,正要轉身離開,我拉住你的手不放開,高潮還沒到。。
查詢
my_user = Session.query(User).filter_by(name="alex").first() print(my_user)
此時你看到的輸出是這樣的應該
<__main__.User object at 0x105b4ba90>
我擦,這是什么?這就是你要的數據呀, 只不過sqlalchemy幫你把返回的數據映射成一個對象啦,這樣你調用每個字段就可以跟調用對象屬性一樣啦,like this..
print(my_user.id,my_user.name,my_user.password) 輸出 1 alex alex3714
不過剛才上面的顯示的內存對象對址你是沒辦法分清返回的是什么數據的,除非打印具體字段看一下,如果想讓它變的可讀,只需在定義表的類下面加上這樣的代碼
def __repr__(self): return "<User(name='%s', password='%s')>" % ( self.name, self.password)
修改
my_user = Session.query(User).filter_by(name="alex").first() my_user.name = "Alex Li" Session.commit()
回滾
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()
獲取所有數據
print(Session.query(User.name,User.id).all() )
多條件查詢
objs = Session.query(User).filter(User.id>0).filter(User.id<7).all()
上面2個filter的關系相當於 user.id >1 AND user.id <7 的效果
統計和分組
Session.query(User).filter(User.name.like("Ra%")).count()
分組
from sqlalchemy import func print(Session.query(func.count(User.name),User.name).group_by(User.name).all() )
相當於原生sql為
SELECT count(user.name) AS count_1, user.name AS user_name FROM user GROUP BY user.name
輸出為
[(1, 'Jack'), (2, 'Rain')]
外鍵關聯
我們創建一個addresses表,跟user表關聯
from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship class 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
. Therelationship.backref
parameter hasn’t gone anywhere and will always remain available! Therelationship.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.
表創建好后,我們可以這樣反查試試
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表
創建關聯對象
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表
from sqlalchemy import Integer, ForeignKey, String, Column from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = 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表中插入數據時會報下面的錯
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Customer.billing_address - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
解決辦法如下
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 sessionmaker Base = 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.name class Author(Base): __tablename__ = 'authors' id = Column(Integer, primary_key=True) name = Column(String(32)) def __repr__(self): return self.name
接下來創建幾本書和作者
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表
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查一下數據
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()
輸出如下
--------通過書表查關聯的作者--------- 跟Alex學Python [Alex, Jack] --------通過作者表查關聯的書--------- Alex [跟Alex學把妹, 跟Alex學Python]
牛逼了我的哥!!完善實現多對多
多對多刪除
刪除數據時不用管boo_m2m_authors , sqlalchemy會自動幫你把對應的數據刪除
通過書刪除作者
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()
直接刪除作者
刪除作者時,會把這個作者跟所有書的關聯關系數據也自動刪除
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一樣, 所以提交作業時需先選擇班級,再選擇具體上課的節數
- 附加:學員可以查看自己的班級成績排名