對Flask感興趣的,能夠看下這個視頻教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002前端
要在 python
中鏈接數據庫,則要從 sqlalchemy
中導入 create_engine
,而且要配置好數據庫的信息,以下代碼所示:python
# 導入模塊 from sqlalchemy import create_engine # 配置數據庫 DIALECT = 'mysql' DRIVER = 'mysqldb' # python2 寫 mysqldb;python3 寫 pymysl USERNAME = 'root' PASSWORD = 'root' HOST = '127.0.0.1' PORT = '3306' DATABASE = 'db_demo1' # DB_URL 的格式是:dialect+driver://username:password@host:port/database # 因此要將配置變量組合成固定格式 DB_URL = "mysql+mysqldb://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOST,port=PORT,db=DATABASE) # 建立數據庫引擎 engine = create_engine(DB_URL) # 判斷是否鏈接成功 conn = engine.connect()
注意,以上方法未涉及 Flask
的內容,包括 17. Flask 下使用 SQLalchemy
節以前的內容,只是在純 python
代碼中經過 sqlalchemy
進行數據庫的操做。mysql
ORM``(Object Relationship Mapping)
:對象關係映射。實際上就是模型與數據庫表的映射。sql
drop database db_name; 刪除數據庫 create database db_name charset utf8; 建立數據庫 show tables; 查詢數據庫中的全部 table drop table person; 刪除名稱爲 person 的 table desc person; 查看 person 的具體屬性
要在 Flask 中使用 sqlalchemy 鏈接數據庫,應該先導入 create_engine
:數據庫
from sqlalchemy import create_engine
再作好鏈接數據庫的相關配置:flask
HOSTNAME = '127.0.0.1' PORT = '3306' DATABASE = 'mydb01' USERNAME = 'root' PASSWORD = 'root' # dialect+driver://username:password@host:port/database DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)
鏈接數據庫安全
engine = create_engine(DB_URL)
建立 ORM 模型服務器
要建立 ORM
模型,這個模型必須繼承自 sqlalchemy
給咱們定義好的基類。這個基類是經過 sqlalchemy.ext.declarative
下的一個函數(declarative_base
)來初始化的,因此咱們要先導入它:session
from sqlalchemy.ext.declarative import declarative_base
導入完成後,還須要使用它進行基類的初始化,即便用它建立一個基類:app
Base = declarative_base(engine) # 指定數據庫引擎建立一個基類,並賦給 Base 變量
其中,基類和類與對象之間的關係是:基類建立類 -> 類實例化出對象。建立這個基類,是由於這個基類已經幫咱們封裝好了映射到數據庫中的一些方法,自定義的模型若繼承自這個基類,會更方便咱們經過 python
去操做數據庫。
完成以上步驟後,就能夠經過建立出來的基類再建立一個類,而這個類就是 ORM
中的模型,以下:
class Person(Base): # 必須繼承自基類 __tablename__ = 'person' # 使用 __tablename__ 來指定映射到數據庫中的表名
定義模型屬性,即定義數據庫表的字段
上一步的代碼,只是建立了一個能夠映射到數據庫中的一個表,可是該表並無任何字段,咱們須要完善其中的屬性。而這些屬性在數據庫中是一個個的數據類型(如 int
| char
| varchar
等),這些數據類型也在 sqlalchemy
中定義好了,咱們能夠直接用,但要先導入:
from sqlalchemy import Column,Integer,String class Person(Base): __tablename__ = 'person' id = Column(Integer,primary_key=True,autoincrement=True) # 一個 Column 能夠定義表中的一個列,能夠在括號內指定數據類型(Integer),主鍵,自增加等數據庫屬性 name = Column(String(10)) age = Column(Integer)
將建立好的模型映射到數據庫中
Base.metadata.create_all()
須要注意的是:一旦使用 Base.metadata.create_all()
將模型映射到數據庫中,以後若要改變表中的字段(添加字段或刪除字段)再從新映射,那麼是不會生效的。
去數據庫中驗證是否成功
show tables;
完整代碼以下:
from sqlalchemy import create_engine,Column,Integer,String from sqlalchemy.ext.declarative import declarative_base HOSTNAME = '127.0.0.1' PORT = '3306' DATABASE = 'mydb01' USERNAME = 'root' PASSWORD = 'root' # dialect+driver://username:password@host:port/database DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE) engine = create_engine(DB_URL) Base = declarative_base(engine) # 1. 建立一個 ORM 模型,這個模型必須繼承自 sqlalchemy 給咱們定義好的基類 class Person(Base): __tablename__ = 'person' # 2. 在這個 ORM 模型中建立一些屬性,對應於表中的一些字段,而這些屬性必須是 sqlalchemy 給咱們定義好的數據類型 id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(10)) age = Column(Integer) # 3. 將建立好的 ORM 模型映射到數據庫中 Base.metadata.create_all()
魔術方法
在定義一個類的時候,能夠定義一個 __str__()
方法,這個方法的做用是:當這個類被 print
方法調用時,那麼 print
會首先調用這個類裏面的 __str__()
方法。而在 __str__()
方法中,通常都是 return
一個字符串。例如,對剛剛的類定義的 __str__()
方法:
class Person(Base): __tablename__ = 'person' id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(10)) age = Column(Integer) def __str__(self): return 'Person(name:%s,age:%s)' % (self.name,self.age)
全部的數據庫 ORM
操做都必須經過一個叫作 session
的會話對象來實現,那麼這個對象的建立是經過如下代碼來實現的:
from sqlalchemy.orm import sessionmaker engine = create_engine(DB_URL) Session = sessionmaker(engine) session = Session()
其中:sessionmaker
和 declarative_base
的原理相似,也是一個方法。後者接收一個 engine
建立一個基類,再建立一個模型;前者也要接收一個 engine
,從而對 engine
中的數據進行操做(增刪改查等)。
後兩行代碼能夠簡化寫成:session = sessionmaker(engine)()
。
建立對象,並使用 session
對象添加且提交:
p = Person(name='myyd',age=18) # 建立對象(對於自增加的屬性,主鍵不用寫) session.add(p) # 將對象添加到會話對象中 session.commit() # 使用 commit 將會話中的對象提交到數據庫中
若是要添加多條數據,則必須以列表的形式傳給 session
對象的 add_all()
方法:
p1 = Person(name='MYYD',age=19) p2 = Person(name='Myyd',age=20) session.add_all([p1,p2]) # 注意是 add_all session.commit()
能夠查詢所某個數據庫中某個表的全部數據,也可使用條件查詢該表中的符合條件的數據。
查找表中的全部數據:
person_all = session.query(Person).all() for person in person_all: print(person)
查找表中符合條件的全部數據(方法一):
person_all = session.query(Person).filter_by(age=18).all() for person in person_all: print(person)
查找表中符合條件的全部數據(方法二):
person_all = session.query(Person).filter(Person.age==18).all() for person in person_all: print(person)
查找表中符合條件的全部數據(區別):
區別在於,filter()
要指定類名,而且判斷時要使用雙等於號 ==
,要相對麻煩一點。可是這兩種方法,在大項目中是會同時用到的,因此兩個都要學會!
使用 get()
方法根據主鍵查找數據:
get()
方法會根據表中的主鍵進行查找數據,如有則返回數據,若無則返回 None。
person1 = session.query(Person).get(1) person2 = session.query(Person).get(100) print(person1,person2) # 會返回 get(1) 的數據,get(100) 的數據是 None
使用 first()
方法獲取表中的第一條數據:
person3 = session.query(Person).first() print(person3)
要修改表中的數據,思路很簡單:先經過查詢,將指定數據選出來並賦予一個變量;再修改該變量的屬性;最後用 session.commit()
提交便可。以下所示:
person = session.query(Person).first() person.name = 'mayiyoudu' session.add(person) session.commit()
和修改相似,先查詢找到指定的數據,再經過 session
進行刪除。以下所示:
person = session.query(Person).first() session.delete(person) session.commit()
Integer
),有微整型,整型和長整型Float
,Double
),Float
是 32
位;Double
是 64
位Boolean
),在數據庫中能夠用微整型(0
和1
)來實現DECIMAL
),用來處理精度丟失的問題,至關於將輸入的浮點數當成文本處理Enum
),只能輸入預先指定的內容Date
),只能存儲年月日,傳入datetime.date()
DateTime
),能夠存儲年月日時分秒,傳入datetime.datetime()
Time
),只能存儲時分秒,傳入datetime.time()
String
),至關於數據庫中的varchar類型,便可變長字符類型Text
),至關於數據庫中的text類型,最多隻能存儲6W多個字LONGTEXT
),若是文字較多,可用LONGTEXT類型,只MySQL支持,要從另外的包中導入下面咱們分別來介紹各個數據類型的特性:
整型、浮點型、文本類型、字符類型比較經常使用,咱們放到一個例子來說。以下代碼所示:
class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=True) content = Column(Text,nullable=True) price = Column(Float,nullable=True) article = Articles(title='MYYD',content='HelloWorld',price=12.34563)
上述代碼表示:
- id 字段爲整型,其值必須爲一個整數;
- title 字段爲字符類型,對應數據庫中的 varchar 類型,是一個可變長度的字符,括號中的數字爲該字段所能接受的最大字母數;
- content 字段爲文本類型,可接受最大字符長度爲6W多字;
- price 字段爲浮點類型,對於 Float 來講只能表示4位小數,對於 Double 來講能夠接受8位小數,若是數字太大,可使用定點類型。
定點類型
class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) price = Column(DECIMAL(10,6)) article = Articles(price=2345.67891)
其中,DECIMAL(10,6)
表明一共只能表示10個數字,分別是:整數最多隻能有4位,小數最多隻能表示6位。若是小數位數多了則四捨五入表示6位;若是整數位數多了,則直接報錯。即小數位數能夠大於6而整數位數不能夠大於4。
日期時間類型
from datetime import date,datetime,time class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) create_date = Column(Date) create_datetime = Column(DateTime) create_time = Column(Time) article = Articles(create_date=date(2011,11,11),create_datetime=datetime(2011,11,11,11,11,11),create_time=time(11,11,11))
要使用日期和時間,須要另外從 datetime
模塊中導入 date
,datetime
,time
;與模型字段中的三個數據類型 Date
,Datetiem
,Time
一一對應。
枚舉類型(方法一)
class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) Language = Column(Enum('Python','Java','PHP','C++')) article = Articles(Language='python')
其中,在建立實例的時候,Language
字段必需要從枚舉指定的 Python
Java
PHP
C++
中選擇,不然報錯。
枚舉類型(方法二)
定義枚舉類型還有另一種更好的方法,就是藉助 python3
中自帶的 enum
模塊進行定義。要注意四個地方:
- 導入
enum
模塊- 建立所需的
enum
類並繼承自enum.Enum
- 建立模型而且使用枚舉數據類型時,從自定義的
enum
類中引用- 根據模型建立實例時也能夠從自定義的
enum
類中取
以下代碼所示:
import enum class MyEnum(enum.Enum): Python = 'Python' Java = 'Java' PHP = 'PHP' C = 'C++' class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) Language = Column(Enum(MyEnum)) article = Articles(Language=MyEnum.Python)
長文本類型
長文本類型只有 MySQL
數據庫才支持,因此若是想使用長文本類型,則須要從 sqlalchemy.dialects.mysql
中導入:
from sqlalchemy.dialects.mysql import LONGTEXT class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) content = Column(LONGTEXT) article = Articles(content='over 60000 words')
primary_key
:設置是否爲主鍵autoincrement
:設置是否爲自增加default
:設置默認值,即當實例化的時候沒有指定該屬性的值時,該屬性的值。能夠在 create_time
屬性中使用。nullable
:設置該屬性的值是否能夠爲空,若是沒有給該屬性設置該參數,則默認爲 True
,即默承認空。但主鍵默認爲 False
。unique
:設置是否惟一,好比手機號碼、郵箱等屬性,都是惟一的,即要指定 unique
爲 True
。不設置時,默認是 False
。onupdate
:若設置了該屬性,則當其餘屬性有改動的時候,該屬性也會更改。最典型的應用是:
update_time
用來設置文章的更新時間,當文章的標題或者內容被更新時,update_time
也會隨之被更新,以下代碼所示:
class Aricles(Base) __tablename__ = 'articles' id = Column(Integer,primary_key=True) title = Column(String(50),nullable=False) content = Column(Text,nullable=Flase) update_time=Column(DateTime,onupdate=datetime.now,default=datetime.now) article = Articles(datetime.now())
當對象 article
被建立後,在某一時刻其 title
或者 content
屬性被修改,那麼其 update_time
屬性因爲被指定了 onupdate=datetime.now
參數,也會隨之更改。
onupdate
的值,而是使用 default
的值。name
:用來指定某個模型中的屬性映射到數據庫後,該屬性對應字段的名稱。也就是說,你在定義模型的時候,有一個 title
屬性,可是你想讓該屬性映射到數據庫中的時候變成其餘名字的字段,就可使用 name 參數來實現。如:
class Aricles(Base) id = Column(Integer,primary_key=True) __tablename__ = 'articles' title = Column(String(50),nullable=False,name='My_title') # 若是把 name 參數放到該屬性第一個位置,則不須要 name 關鍵字,以下便可: title = Column('My_title',String(50),nullable=False) # 可是不能夠把該參數放到第一個位置的同時還指定參數名,由於關鍵字參數必需要放在未知參數以後!(name='myyd'這種叫關鍵字參數;int(12)這種叫未知參數)。 title = Column(name='My_title',String(50),nullable=False) # 這樣是不行的
準備工做:
在查詢以前,咱們須要在建立好的模型中定義 __repr__()
函數,這個函數的做用是:當用 print
輸出由這個類組成的列表時,會按照這個函數定義的格式輸出。
要注意與 __str__()
函數的區別,__str__()
函數是隻有在 print
輸出去這個類的具體實例的時候纔會被調用。
兩個函數的定義實現以下:
def __str__(self): return 'id=%s;title=%s;price=%s' % (self.id,self.title,self.price) def __repr__(self): return 'id=%s;title=%s;price=%s' % (self.id, self.title, self.price)
能夠用 query
獲取 ORM
模型中實例的全部屬性:
result = session.query(Articles).all() for article in result: print(article)
能夠用 query
獲取 ORM
模型中實例的指定屬性:
result = session.query(Articles.title,Articles.price).all() for article in result: print(article)
能夠用 query
內置的一些聚合函數來實現對查找到的數據作進一步的操做,在使用這些聚合函數以前要先導入這些函數所在的類(func
)。
from sqlalchemy import func
這些聚合函數是:
func.count
:統計行的數量
session.query(func.count(Articles.id)).first() # 注意要指定表的字段,即模型的屬性 # 輸出該表中的數據條目數量,例如:有六條數據就輸出(6,)
func.avg
:求平均值
求平均值也是相似的用法:
result = session.query(func.avg(Articles.price)).first() print(result) # (78.28571428571429,)
func.max
:求最大值
result = session.query(func.max(Articles.price)).first() print(result) # (97,)
func.min
:求最小值
result = session.query(func.min(Articles.price)).first() print(result) # (51,)
func.sum
:求和
result = session.query(func.sum(Articles.price)).first() print(result) # (548.0,)
實際上,func
對象中並無定義任何函數,由於它底層的實現是把傳入的函數翻譯成 sql
語句後再進行操做。因此只要是 mysql
中有的聚合函數,均可以使用 func.
來調用。
equal
article = session.query(Articles).filter(Articles.id==1).all() print(article)
not equal
articles = session.query(Articles).filter(Articles.id!=1).all() print(articles)
like & ilike
(不區分大小寫)
articles = session.query(Articles).filter(Articles.title.like('MYY%')).all() # 其中,% 在 sql 語句中是通配符 print(articles)
in
這裏要注意,使用的 in 的時候要傳入一個列表,最終查找的結果是:既在數據庫中又是列表中指定的數據被找到。
articles = session.query(Articles).filter(Articles.title.in_(['MYYD','title1','title2'])).all() # 爲何是 in_ ?由於 python 中有關鍵字 in,爲了區分因此不能用 in,而 _in 表明了類中的私有方法,但這裏很明顯應該是公有方法,因此定義爲 in_ print(articles)
not in
即相對於上例而言,取反的結果。
方法一:波浪號
articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2'])).all() # 注意這裏要加一個波浪號 print(articles)
方法二:notin_()
articles = session.query(Articles).filter(Articles.title.notin_(['MYYD','title1','title2'])).all() # 注意這裏是 notin_() print(articles)
is null
和 is not null
用來根據某個字段是否爲空來查找數據,以下:
articles = session.query(Articles).filter(Articles.content==None).all() print(articles) # 查找 Articles.content 字段爲空的數據
is not null
的示例以下:
articles = session.query(Articles).filter(Articles.content!=None).all() print(articles) # 查找 Articles.content 字段爲空的數據
and
用來查找更精細的範圍,如 content='abc'
而且 title='MYYD'
的數據,以下:
articles = session.query(Articles).filter(Articles.content=='abc',Articles.title=='MYYD').all() print(articles)
會查找到 title 爲 MYYD 而且 content 爲 abc 的數據條目。
or
只要知足指定條件之一便可。以下:
from sqlalchemy import or_ articles = session.query(Articles).filter(or_(Article.title=='MYYD',Articles.content=='MYYD')).all() print(articles)
用得比較多的狀況是,搜索一個關鍵字,這個關鍵字可能出如今標題中,也可能出如今內容中。這裏要注意的是:要從 sqlalchemy 中導入 or_ 這個方法。
實際上,and 也有這種方法,以下:
from sqlalchemy impor and_ articles = session.query(Aritlces).filter(and_(Articles.title=='MYYD',Articles.content=='abc')).all() pint(articles)
小知識,若是想要獲取翻譯成的 sql
語句,能夠在查詢的時候不加 .all()
或者 .first()
,以下:
articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2'])) print(articles) 輸出以下: SELECT articles.id AS articles_id, articles.title AS articles_title, articles.price AS articles_price FROM articles WHERE articles.title NOT IN (%(title_1)s, %(title_2)s, %(title_3)s, %(title_4)s)
外鍵可使表與表之間的關係更加緊密,Sqlalchemy
也支持對外鍵的操做。而 Sqlalchemy
操做外鍵是經過 ForeignKey
來實現的。最多見的例子是:有用戶和文章這兩張表,每張表都有本身的屬性,可是文章是經過用戶來發表的,因此這兩張表中必然存在某種聯繫;使用外鍵就能夠將兩張表聯繫起來。
那麼怎麼使用 sqlalchemy
建立兩張具備約束關係的表呢?以下所示:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(20),nullable=False) class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) # 注意這個地方要使用數據庫表名,而不是建立模型時的類名
使外鍵有四個能夠約束的功能,在使用外鍵鏈接時用 ondelete
關鍵字指定。指定方法是:
user_id = Column(Integer,ForeignKey('users.id',ondelete='RESTRICT')) user_id = Column(Integer,ForeignKey('users.id',ondelete='NO ACTION')) user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE')) user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))
四個約束功能介紹以下:
RESTRICT
:父表數據被刪除時會因爲子表數據還在引用而阻止刪除
這個約束是默認就會存在的,不用特地指定。
NO ACTION
:與 MySQL
中的 RESTRICT
相同
既然相同,那麼也不用特地指定。
CASCADE
:級聯刪除,即當父表中的某個條目被刪除了,子表中關聯了該條目的數據也會被刪除
user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE'))
SET NULL
:父表中的某個條目被刪除,子表中關聯了該條目的數據不會被刪除,可是外鍵字段會被置爲 NULL
user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))
即原來是:
mysql> select * from articles; +----+-------+---------+---------+ | id | title | content | user_id | +----+-------+---------+---------+ | 1 | NB | abc | 1 | +----+-------+---------+---------+
當 users
表中 id=1
的數據被刪除後,articles
表中關於該字段的值就會被置爲 NULL
:
mysql> select * from articles; +----+-------+---------+---------+ | id | title | content | user_id | +----+-------+---------+---------+ | 1 | NB | abc | NULL | +----+-------+---------+---------+
要注意的是,在使用外鍵時,是不能夠爲其設置 SET NULL
字段的同時還設置 nullable=False
的。
如今有一個需求,要查找某一篇文章標題爲 NB
的做者的信息,怎麼才能實現這一需求?
NB
的文章users
的外鍵字段的值users
表中查找到該做者代碼實現以下:
article_NB = session.query(Article).filter_by(title='NB').first() print(article_NB) uid = article_NB.user_id author = session.query(User).get(uid) print(author)
要使用 ORM
提供的這個方法,就必須從 sqlalchemy.orm
模塊中導入:
from sqlalchemy.orm import sessionmaker,relationship
而後在定義外鍵的時候,同時定義外鍵所連接到的表的字段,並指定 relationship
字段:
class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) # 注意這個地方要使用數據庫表名,而不是建立模型時的類名 author = relationship('User') # 注意這個地方要使用建立模型時的類名,而不是數據庫表名
作好上述定之後,就能夠直接經過 relationships
提供的方法,來獲取文章的做者了,以下所示:
article = session.query(Article).filter(Article.title=='NB').first() print(article.author) # article.author 便是所要找的做者 print(article.author.username) # 打印做者的用戶名
上面的例子是:查找一篇文章對應的做者,那麼文章和做者之間的關係是多對一的,即一個做者可能發表多篇文章,而一篇文章只能有一個做者。
在 8.2
中,已經使用 relationship
引用了文章的做者,那麼可否使用 relationship
引用做者的文章呢?答案是確定的,以下所示:
from sqlalchemy.orm import sessionmaker,relationship class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(20),nullable=False) articles = relationship('Article')
經過做者找文章的方法和經過文章找做者的方法相似,以下:
user = session.query(User).filter_by(username='MYYD').filter() print(user.articles) # 會打印出該 user 的全部 article
在以前的例子中,咱們在 User
和 Article
中都分別使用了 relationship
來互相指定對方和本身的關係。實際上,不用這麼麻煩,咱們只須要在一個模型中指定其和另外一個模型的關係便可,這時須要藉助另外一個參數:bakeref
。
因此咱們只須要在 Article
模型中指定便可:
class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) author = relationship('User',backref='articles')
這樣,一樣可使得 user.articles
可以正常使用。而不用再在 User
模型中使用 relationship
再指定一次關係了。
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(20),nullable=False) def __repr__(self): return 'username:%s'% self.username class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL')) author = relationship('User',backref='articles') def __repr__(self): return 'title:%s\tcontent:%s'%(self.title,self.content)
在指定了一對多的關係後,如 users
和 articles
這二者之間的關係。實際上,在建立 articles
的時候能夠不用指定其 user_id
字段的值,當一篇文章被建立完成後,再指定做者也是被容許的。由於在一對多的關係中,users
的 articles
屬性是一個 LIST
,可使用 append
方法爲其添加值。以下代碼所示:
article1 = Article(title='abc',content='abc') article2 = Article(title='MYYD',content='myyd') session.add_all([article1,article2]) user1 = User(username='MYYD') session.add(user1) user1.articles.append(article1) # 使用 append() 方法 user1.articles.append(article2) session.commit() user = session.query(User).filter_by(username='MYYD').first() print(user.articles) # 輸出 [title:abc content:abc, title:MYYD content:myyd]
以上,是對 ORM
中一對多關係的一點補充。藉此,咱們能夠引出 ORM
中一對一的關係:
有一個需求:爲用戶建立一個 users
表,表中定義了一些屬性,這些屬性有:姓名,學校等。其中學校這個屬性不是經常使用屬性,咱們把它放到另一張 extends
表中。這樣一來,兩張表就是一對一的關係了。那麼,怎麼用 ORM
來實現這一需求呢?能夠在建立 extends
表的時候在使用 relationship
參數的字段中,傳入 uselist=False
參數,即不能夠 uselist
,以下代碼所示:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) def __repr__(self): return 'id:%s\tusername:%s'%(self.id,self.username) class UserExtend(Base): __tablename__ = 'extends' id = Column(Integer,primary_key=True,autoincrement=True) school = Column(String(20)) user_id = Column(Integer,ForeignKey('users.id')) my_user = relationship('User',backref=backref('extends',uselist=False)) # 注意這裏使用 relationship 參數的方法,和傳入 backref 與以前的區別 def __repr__(self): return 'school:%s'%self.school user1 = User(username='MYYD') session.add(user1) extend = UserExtend(school='JMU') session.add(extend) user1.extends.append(extend) # 此時再使用 append 方法時就會報錯:AttributeError: 'NoneType' object has no attribute 'append' session.commit()
這就是 ORM
中一對一關係的使用。
多對多關係的需求經常使用在文章與標籤上,譬如能夠按照內容劃分文章的標籤爲:音樂、體育、娛樂、搞笑等;而一篇文章可能涉及多個方面的內容,這時候文章和標籤就是多對多的關係。可是咱們使用 ORM
定義兩張表的多對多關係時,須要藉助一箇中間表來實現。
爲兩張表建立多對多關係的步驟以下:
導入定義中間表所需的 Table
類:
from sqlalchemy import Table
建立兩張表(artciels
和tags
):
按照常規的方法建立兩張表便可:
class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) def __repr__(self): return 'title:%s\tcontent%s'%(self.title,self.content) class Tag(Base): __tablename__ = 'tags' id = Column(Integer,primary_key=Text,autoincrement=True) name = Column(String(50),nullable=False) def __repr__(self): return 'TagName:%s'%self.name
重點在這裏!建立中間表:
這個中間表用來鏈接剛剛定義的兩張表,中間表必須繼承自這個 Table
類。而且還要使用 metadate
進行初始化,同時要設置聚合主鍵,以下所示:
article_tag = Table( 'article_tag', Base.metadata, Column('articles_id',Integer,ForeignKey('articles.id'),primary_key=True), Column('tags_id',Integer,ForeignKey('tags.id'),primary_key=True) # tag_id 是字段名稱,Integer 的類型要和本表外鍵引用的字段類型相同 )
其中:
Base.metadata
用來對這個中間表進行初始化;- 而後用
Column
對其進行相關字段的建立。- 爲兩個字段同時設置主鍵,這樣就能兩張表中的惟一一篇文章。
使用 relationship
關聯兩張表:
最後,還要在兩張表的其中一張表中使用 relationship
參數來互相關聯一下,同時指定 secondary
參數來指定經過實現多對多關係的中間表,以下:
在 articles
表中使用:
tags = relationship('Tag',backref='articles',secondary=article_tag)
在 tags
表中使用:
articles = relationship('Article',backref='tags',secondary=article_tag)
須要注意的是:若是使用中間表來實現多對多的映射關係後,就沒必要在兩張被映射的表中指定外鍵關係了。由於已經經過 secondary
來指定中間表格了,而中間表格會實現外鍵約束。
咱們先用定義的兩張表建立數據並關聯一下:
爲每張表建立兩個數據
article1 = Article(title='ABC',content='ABC') article2 = Article(title='abc',content='abc') tag1 = Tag(name='tag1') tag2 = Tag(name='tag2')
爲文章添加標籤
article1.tags.append(tag1) article1.tags.append(tag2)
添加並提交 session
session.add_all([article1,article2]) session.commit()
到數據庫中查找
# articles 表 mysql> select * from articles; +----+-------+---------+ | id | title | content | +----+-------+---------+ | 1 | ABC | ABC | | 2 | abc | abc | +----+-------+---------+ 2 rows in set (0.00 sec) # tags 表 mysql> select * from tags; +----+------+ | id | name | +----+------+ | 1 | tag1 | | 2 | tag2 | +----+------+ 2 rows in set (0.00 sec) # article_tag 表 mysql> select * from article_tag; +-------------+---------+ | articles_id | tags_id | +-------------+---------+ | 1 | 1 | | 2 | 1 | | 1 | 2 | | 2 | 2 | +-------------+---------+ 4 rows in set (0.00 sec)
在用咱們的 query
去查詢:
article = session.query(Article).first() print(article.tags) # [TagName:tag1, TagName:tag2] tag = session.query(Tag).first() print(tag.articles) # [title:ABC contentABC, title:abc contentabc]
在 ORM
層面對錶中的數據進行刪除,好比 articles
表中的 uid
字段經過外鍵引用到 users
表種的 id
字段。在使用 sql
語句對 users
表中的某個數據進行刪除的時候,會因爲 articles
表中的外鍵引用,而拒絕刪除。
可是在 ORM
層面使用 python
代碼對錶中的數據進行刪除,那麼其實是能夠刪除成功的。刪除的結果是:users
表中該條數據被刪除,可是引用了該條數據的 articles
表中的數據關聯的 id
字段會被置爲空。
這是由於,ORM
在底層對這個操做的實現分爲兩步:先將 articles
表中該數據的 uid
字段置爲 NULL
,再刪除 users
表中的數據。不過前提是,articles
中的 uid
字段容許設置爲空。
可是這個機制實際上並不怎麼好,由於這樣可能會致使誤刪除的發生。那如何避免這種狀況呢?實際上也很簡單:在 articles
的外鍵字段上,設置 nullable=False
便可。
總結:
ORM
層面對數據庫刪除操做,會無視 mysql
級別的外鍵約束,要想避免這種狀況發生,須要將外鍵字段設置爲 nullable=False
。
relationship
方法中的 cascade
參數能夠在建立關係的時候,指定一些屬性。cascade
參數一共有如下幾個值:
save-update
(默認屬性)
這是一個默認屬性,即當不指定 cascade
參數的時候,默認 cascade='sace-update'
。這個屬性的意思是:
若是給 cascade
指定了 save-update
屬性,那麼在添加一條數據的時候,會同時添加該條數據關聯的其餘模型中的數據。例如:
Article
模型在與 User
模型創建關係的時候,若是指定了 cascade='save-update'
屬性,那麼當使用 session.add()
添加 Article
實例(article
)的時候,若是該實例關聯的 User
實例(user
)已經建立,則將 user
也一併添加。即不用再使用 session.add(user)
進行添加。
具體的使用方法以下:
author = relationship('User',backref='articles',cascade="save-update")
若是不想讓 sqlalchemy
自動執行這個操做,那麼能夠將 cascade
置爲空,即 cascade=""
。須要注意的是,這個屬性僅對 Article
這個模型生效。也就是說,置爲空後,session.add(article)
不會自動執行 session.add(user)
,可是 session.add(user)
仍是會自動執行 session.add(article)
。明白?
delete
delete
的做用就是,當刪除一條數據的時候,會同時刪除該條數據關聯的其餘模型中的數據。
其使用方法和 save-update 一致。
注意,cascade
屬性能夠同時指定多個值,例如同時指定 save-update
和 delete
,能夠這麼寫:
author = relationship('User',backref='articles',cascade="save-update,delete")
若是想在關聯模型中反向使用該屬性,還能夠將 cascade
放到 backref()
方法中:
author = relationship('User',backref=backref('articles',cascade="save-update,delete",cascade="save-update,delete")) # 若是 backref 只接收一個參數,能夠寫成 backref='參數',若是接收多個參數,能夠調用 backref() 方法
delete-orphan
若是父表中的相關數據被刪除,會使得子表中的數據的某個字段置爲空。若是指定了 cascade='delete-orphan'
,那麼因爲父表的數據不存在,子表中的數據也會被刪除。不過在指定 delete-orphan
的時候要同時指定 delete
屬性。那麼刪除父表數據帶動子表數據被刪除的操做也有多是 delete
屬性完成的,因此咱們能夠將父表數據中關於子表數據的字段置空,這樣子表的相關字段也會被置空,進而被刪除。這樣就能體現出 delete-orphan
的做用了。
注意這個屬性只能在父表中指定,而且只能用在一對多或者多對多的關係中。
merge
(默認屬性)
這個屬性不只是 relationship
方法中 cascade
的屬性,仍是 session
中的屬性。其做用是:改變數據庫中的值。例如:
users
表的 ORM
模型以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(50),nullable=False)
若表中已經存在一個用戶(user1
):id=1,name='MYYD'
;若是再建立一個用戶(user2
):id=1,name='myyd'
。使用 session.add(user2)
後再 commit
,會報錯。由於 id
是主鍵,session.add()
不容許添加主鍵重複的數據。
那麼若是使用 session.merge(user2)
,則會用 user2
的 id
去匹配 user
表中的 id
字段,並將匹配中的數據中的 name
改成 myyd
。
實際上這個屬性用得比較少。由於這對於已經存在的數據,至關於修改操做,而修改操做徹底沒必要要使用這種方法。那麼這個方法的存在還有什麼意義呢?
這個方法還真有本身存在的意義,由於這種方法能夠添加數據,而修改操做卻不能添加。使用 session.merge()
的時候,首先會判斷表中是否已經存在這條數據,若存在則修改;若不存在則添加。
除此以外,還有一個做用:若是在父表的 cascade
中指定了這個屬性,那麼父表下全部關聯的子表,在使用 session.merge(父表數據)
的時候,也會實現這個屬性。
expunge
expunge
屬性也是刪除,用 session.expunge()
實現。session.expunge()
與 session.delete()
的區別是:前者僅僅是將數據從 session
中刪除,並不會刪除數據庫中的內容。這是 expunge
在 session
中的體現。
而 expunge 在 cascade
中的體現和 merge
相似:當在父表的 cascade
中指定了這個屬性,那麼在使用 session.cascade(父表數據)
時,父表下全部關聯的子表,也會實現這個屬性。
all
若是在 cascade
中指定了 all
屬性,那麼就至關於包含了以上除了 delete-orphan
外的全部屬性。
order_by
能夠在查詢的時候,使用 order_by
參數對查詢到的數據進行排序。默認排序是從小到大,從前到後,若是想使用降序排序則在前面加一個 -
號。
articles = session.query(Article).order_by(-Article.create_time).all() for article in articles: print(article)
或者使用 desc 進行排序:
articles = session.query(Article).order_by(Article.create_time.desc()).all() for article in articles: print(article)
或者還能夠直接傳入模型字段的名稱,搭配 -
號使用:
articles = session.query(Article).order_by('-create_time').all() for article in articles: print(article)
模型中的 order_by
若是對於一個常常須要排序的模型來講,在每次查詢的時候都使用 order_by
參數進行排序,會有點麻煩,因此咱們再定義模型的時候就能夠定義一個 order_by
屬性。以下:
先在模型中定義 __mapper_args__
屬性:
__mapper_args__ = { 'order_by':create_time.desc() }
而後查詢的時候就沒必要指定 order_by 關鍵字了:
articles = session.query(Article).all() for article in articles: print(article)
輸出以下:
title:zhenniubi create_time:2018-03-28 23:33:32 title:MYYD create_time:2018-03-28 23:33:07
可是若是想在某一次查詢中不使用倒序排序呢?很簡單,在查詢的時候指定 order_by
關鍵字便可。
在 relationship
中使用 order_by
參數
能夠在定義 relationship
關係的時候直接指定父表下子表的排序方式,以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) create_time = Column(DateTime,nullable=False,default=datetime.now) uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles',order_by=create_time.desc()))
當搜索到一個 user
時,獲取 user
下的全部 articles
,這些 articles
的排序方式就是 order_by=create_time.desc()
指定的倒序。以下:
user = session.query(User).filter(User.id=='1').first() for article in user.articles: print(article) # 打印的內容以下: title:title2 create_time:2018-03-29 00:19:18 title:title1 create_time:2018-03-29 00:19:13
limit
這個屬性能夠對每次查詢的結果數量進行限制,例如能夠在查詢時只取 10
個數據:session.query(Article).limit(10).all()
。
須要注意的是,使用以上操做進行數據的查詢時,默認是從第 1
條數據進行查找的。若是咱們想要從指定位置開始查找,可使用 offset
操做:
offset
這個屬性能夠指定咱們開始查找的位置,例如想要獲取第十一到第二十條數據,能夠這麼操做:session.query(Article).offset(10).limit(10).all()
。
這個 offset
和 limit
誰先誰後無所謂。
實際上,經過 articles = session.query(Article).all()
獲得的數據,articles 是一個列表變量,因此咱們能夠經過列表中的切片屬性,來獲取指定的內容。例如想要從獲取到的全部數據中提取指定範圍內的數據(如第3個到第7個數據),有兩種方法能夠實現:
slice
方法
這種方法是:article = session.query(Article).slice(2,7).all()
list
切片方法
這種方法是:articles = session.query(Article).all()[2:7]
。
以上三個操做,均可以搭配上一節中提到的排序方法靈活使用。
在現實的項目中,經常會遇到這種需求:當我點進一個用戶的主頁中,該用戶的主頁會顯示當天發表的文章,而不是其全部的文章。要實現這個功能實際上很簡單:先將該用戶的全部文章都查找出來,而後對每一篇的建立時間進行過濾後再渲染回前端。以下代碼所示:
articles = session.query(Article).all() for article in articles: if article.create_time.day == 29: print(article)
以上方法,確實簡單粗暴。可是我只須要展現該用戶當天的文章,實際上不必從數據庫中查找出全部的文章,畢竟這麼作很浪費資源。咱們可使用 sqlalchemy
給咱們提供的懶加載技術,實現從數據庫中查找出來的文章就是當天發表的文章的需求。
可是咱們使用 articles = session.query(Article).all()
獲取到的對象是一個 LIST,而 LIST 是沒有辦法進行進一步的 filter 的。可是咱們的 Query 對象是能夠進行 filter 的,因此咱們能夠將 articles 轉換成 Query 對象,而後再對 articles 進行過濾。想要實現這個功能,那麼在建立模型關係的時候必需要在 relationship 中的 backref() 方法中,添加一個 lazy=dynamic
值。以下:
class Article(Base): 略.. author = relationship('User',backref=backref('articles',lazy='dynamic'))
而後使用 articles = session.query(Article).all()
查找到的 articles
就是一個 AppenderQuery
對象,該對象除了能夠實現 Query
對象的全部功能(包括filter
),還能實現 LIST 的全部功能(包括append
)。
因此這個需求咱們就能垂手可得的實現了:
user = session.query(User).filter_by(id=1).first() print(type(user.articles)) # 輸出:<class 'sqlalchemy.orm.dynamic.AppenderQuery'> print(user.articles.filter(Article.create_time.day==29).all()) # 輸出全部 29 號發表的文章 # 注意這裏是去數據庫中篩選符合條件的數據,而不是將全部的數據都取出來,這樣就節省了資源的消耗
注意:這個地方 lazy='dynamic'
只能對一對多
或者 多對多
中的 多
使用,即對於上述兩個模型,只能用在 User
對 Article
上,也就是說對符合條件的文章的過濾只能經過 user.articles.filter()
操做進行實現。
附:相對完整的代碼以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) create_time = Column(DateTime,nullable=False,default=datetime.now) uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles',lazy='dynamic')) # 注意這裏 user = session.query(User).first() print(user.articles.filter(Article.id>9).all())
這個是 sqlalchemy
中的默認選項,即若是不設置 lazy='xxx'
這個屬性,則默認爲 lazy='select
' 生效。
lazy='select'
的做用是,在沒有調用 user.articles
以前,sqlalchemy
不會去獲取 user.articles
這個屬性下的數據;而一旦調用了 user.articles
,那麼 sqlalchemy
就會自動的去獲取 user.articles
這個屬性下的全部數據。
這也就是爲何咱們在實現上述需求的時候,使用 lazy='dynamic'
的另外一大緣由。
對於定義的 ORM 模型,和插入的數據以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) age = Column(Integer,nullable=False,default=0) gender = Column(Enum('male','female','secret'),default='secret') user1 = User(username='張偉',age=13,gender='male') user2 = User(username='無名',age=14,gender='secret') user3 = User(username='翠花',age=19,gender='female') user4 = User(username='狗剩',age=17,gender='male') user5 = User(username='李蛋',age=16,gender='female') session.add_all([user1,user2,user3,user4,user5]) session.commit()
group_by 會根據指定的某個字段進行分組。例如,要根據性別來分組,統計每一個性別的人數,可使用以下代碼實現:
result = session.query(User.gender,func.count(User.id)).group_by(User.gender).all() # 指定查詢 User.gender,而且以 User.id 爲標識來統計,最後按 User.gander 進行分組 print(result) # 輸出:[('male', 2), ('female', 2), ('secret', 1)]
實際上轉換成的 SQL
語句以下:
SELECT users.gender AS users_gender, count(users.id) AS count_1 FROM users GROUP BY users.gender
having
是對查找結果的進一步過濾,和 sql
語句中的 where
相似。例如:查看未成年人的人數,能夠先對年齡進行分組統計,再對分組進行過濾。代碼以下所示:
result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age<=18).all() print(result) # 輸出:[(13, 1), (14, 1), (16, 1), (17, 1)]
先來段代碼壓壓驚:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) def __repr__(self): return 'username:%s'%self.username class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) create_time = Column(DateTime,nullable=False,default=datetime.now) uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles')) def __repr__(self): return 'title:%s\tcreate_time:%s'%(self.title,self.create_time) user1 = User(username='mayi') user2 = User(username='youdu') for i in range(1): article = Article(title='title %s'%i) article.author = user1 session.add(article) session.commit() for i in range(1,3): article = Article(title='title %s'%i) article.author = user2 session.add(article) session.commit() # 以上代碼建立了兩個模型:User 和 Article。 # 其中 Article 的 uid 屬性與 User 的 id 屬性創建了外鍵約束,而且兩者相互調用的方法是 User.articles 和 Article.author。 # User 創建了兩個實例 user1 和 user2 # Article 創建了三個實例 title0、title1 和 title2 # 其中,user1 關聯了 titile0,user2 關聯了 title1 和 title2
join
分爲 left join(左外鏈接)
、right join(右外連接)
和內連接(等值連接)
,其中左外連接將A表連接到B表的左邊,右外連接將A表連接到B表的右邊;且老是以左表爲主表,右表爲副表。join
方法其實是兩張表聯合在一塊兒進行查詢,例如:要對全部用戶按發表文章的數量進行進行由大到小的排序,可使用以下代碼實現。
# 原生 SQL 語句查詢以下 select users.username,count(articles.id) from users join articles on users.id=articles.uid group by users.id order by count(articles.id) desc; # sqlalchemy 方式查詢以下 result = session.query(User,func.count(Article.id)).join(Article,User.id==Article.uid).group_by(User.id).order_by(func.count(Article.id).desc()).all() print(result) # 輸出:[(username:youdu, 2), (username:mayi, 1)]
用 sqlalchemy
查找時候要注意幾點:
query()
括號中的參數決定,而不是由 join
鏈接的兩張表決定。join
的兩張表之間有且只有一個外鍵創建起了關係,那麼在 join
的時候就能夠不寫 User.id==Article.uid
。join
完成;左外鏈接用 outterjoin
來完成;即在一個 select
中使用一個 select
。子查詢能夠在一次訪問數據庫的時候對查詢結果再進行一次查詢,這樣能夠減小對數據庫的操做。當你的網站訪問量很高的時候,建議使用子查詢;當你的網站訪問量比較少,那麼能夠不考慮這個問題。
例如,有以下代碼:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) age = Column(Integer,nullable=False,default=0) city = Column(String(20),nullable=False) user1 = User(username='張偉',age=20,city='廣州') user2 = User(username='無名',age=21,city='廣州') user3 = User(username='翠花',age=22,city='廈門') user4 = User(username='狗剩',age=21,city='長沙') user5 = User(username='李蛋',age=22,city='廈門') session.add_all([user1,user2,user3,user4,user5]) session.commit() # 以上代碼建立了 User 模型而且爲其實例化了五個對象,添加到數據庫中
有一個需求:要查找和某人(如翠花)在同一個城市且年齡同樣的全部人。這個需求有兩種實現:
先查找出翠花,再根據翠花的信息去查找其餘同城同年齡的人。
cuihua = session.query(User).filter(User.username == '翠花').first() other = session.query(User.username,User.city,User.age).filter_by(city=cuihua.city, age=cuihua.age).all() print(other)
使用子查詢
SQL 語句查詢:
mysql> select users.username,users.age,users.city from users,(select * from users where users.username='李蛋') as LD where users.city=LD.city and users.age=LD.age; # 其中 () 括起來的是子 select 語句,括號外面的 select 語句是根據括號內的 select 的結果進行 select 操做的。結果以下: +----------+-----+------+ | username | age | city | +----------+-----+------+ | 翠花 | 22 | 廈門 | | 李蛋 | 22 | 廈門 | +----------+-----+------+ 2 rows in set (0.00 sec)
sqlalchemy 語句查詢:
temp = session.query(User.city.label('city'),User.age.label('age')).filter(User.username=='李蛋').subquery() result = session.query(User.username,User.city,User.age).filter(User.city==temp.c.city,User.age==temp.c.age).all() print(result) # 其中,temp 就是子查詢獲得的變量,subquery 將該查詢轉換爲子查詢,同時使用 label 爲屬性指定別名; # result 是根據子查詢進行查詢的變量,在引用時要用 'xxx.c.xx' 的形式,打印出來的結果以下: # [('翠花', '廈門', 22), ('李蛋', '廈門', 22)]
在此以前的全部學習,都是 python
下使用 sqlalchemy
操做數據庫的內容,和 Flask
沒有任何關係。但實際上,Flask
也幫咱們對 sqlalchemy
作了一層封裝,好比將全部以前要從 sqlalchemy
中導入的東西都集成到了一個 SQLAlchemy
類中,而鏈接數據庫時,使用 SQLAlchemy
對 Flask
對象進行初始化便可,以下所示:
db = SQLAlchemy(app)
之後全部在以前 sqlalchemy
中導入的內容,均可以從 db
這個對象中獲取。例如建立 ORM
模型的時候,就不須要以前的 declarative_base
來建立基類了,直接用 db.Model
做爲基類。因此能夠用以下代碼實現:
class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer,primary_key=True,autoincrement=True) username = db.Column(db.String(50),nullable=False) def __repr__(self): return 'username:%s'%self.username class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(50),nullable=False) uid = db.Column(db.Integer,db.ForeignKey('users.id'),nullable=False) author = db.relationship('User',backref='articles') def __repr__(self): return 'title:%s\tauthor:%s'%self.title,self.author
在建立模型的時候,能夠省略 __tablename__
的表名指定動做,但不建議,由於明言勝於暗喻。
映射到數據庫中也可使用以下代碼實現:
db.drop_all() db.create_all()
session
的使用方法和以前的同樣。其中:
增
user = User(username='MYYD') article = Article(title='abc') user.articles.append(article) db.session.add(user) db.session.commit()
刪
user = User.query.filter(User.id==2).first() db.session.delete(user) db.session.commit()
改
user.query.filter(User.id==1).first() user.username = 'MYYD' db.session.commit()
查
在查詢的時候,若是是針對單張表進行查詢,那麼直接使用 TableName.query.xxx.x
的格式便可。並且在查詢的時候也可使用以前學習的 order_by
、filter
、group_by
等方法。以下代碼所示:
users = User.query.order_by(User.id.desc()).all()
Flask-script
的做用是能夠經過命令行的方式去操做 Flask
。安裝方式:在虛擬環境下 pip install flask-script
。
新建一個 manage.py
文件,將代碼寫在該文件中,而不是寫在主 app
文件中。內容以下:
from flask_script import Manager # 從 flask_script 導入 Manager from flask_script_demo1 import app # 從 flask_script_demo1 導入 app manage = Manager(app) # 初始化 app @manage.command # 裝飾器 def runserver(): # 執行命令的程序寫在這個函數下 print('服務器跑起來了。') @manage.command # 裝飾器 def stopserver(): # 執行命令的程序寫在這個函數下 print('服務器關閉了。') @manager.option('-u','--username',dest='username') # 還能夠在執行命令的時候向命令傳遞參數 @manager.option('-e','--email',dest='email') #還能夠在執行命令的時候向命令傳遞參數 def addBackendUser(username,email): user = BackendUser(username=username,email=email) db.session.add(user) db.session.commit() if __name__ == '__main__': manage.run()
命令行調用 manage.py
文件:
在虛擬環境的命令行下,用 python manage.py command
執行 manage.py
文件下的某段程序,如:python manage.py runserver
和 python manage.py stopserver
分別會執行 manage.py
文件中的 runserver()
和 stopserver()
方法。
在定義能夠傳遞參數的命令時要注意,使用的裝飾器是 @manager.option()
,以 @manager.option('-u','--username',dest='username')
爲例:-u
是在傳遞參數時要指定的選項縮寫,選項完整的寫法是 --username
,dest='username'
是指該選項後面跟的參數要傳遞給 addBackendUser(username,email)
方法中的 username
參數。
因此在執行能夠傳遞參數的命令時,應該這麼寫(例):python manage.py -u MYYD -e 90q7w0s7x@qq.com
。須要注意的是,有幾個參數就要寫幾個 option
裝飾器。
若是有一些關於數據庫的操做,咱們能夠放在一個文件中執行。如 db_script.py
文件:
from flask_script import Manager # 由於本文件不是做爲主 app 文件,因此不須要寫 if __name__ == '__main__' # 也不須要在初始化的時候傳入 app 文件 DBManage = Manager() @DBManage.command def init(): print('服務器初始化完成。') @DBManage.command def migrate(): print('數據庫遷移完成。')
這時候要想用上 db_script.py
裏定義的命令,須要在主 manage.py
文件中導入該文件並引用該文件的命令:
from db_scripts import DBManage # 導入 db_script.py 文件 manage.add_command('db',DBManage) # 引用該文件 # 以後要是想使用 db_script.py 中的命令,命令行中就要經過 python manage.py db init 來調用 # 其中,db 是 manage.add_command() 中引號內的值,調用子命令的方法就是這種格式
若是直接在主 manage.py
文件中寫命令,且該命令不須要傳遞參數,調用時只須要執行 python manage.py command_name
格式的命令便可
若是直接在主 manage.py
文件中寫命令,且該命令須要傳遞參數,調用時須要執行 python manage.py command_name -[選項] 參數
格式的命令
若是將一些命令集中在另外一個文件中,那麼就須要輸入一個父命令,好比 python manage.py db init
(python2.7
環境)
1. manage.py # encoding:utf-8 from flask_script import Manager from flask_script_demo1 import app from db_scripts import DBManage manage = Manager(app) @manage.command def runserver(): print u'服務器跑起來了。' @manage.command def stopserver(): print u'服務器中止了。' @manager.option('-u','--username',dest='username') @manager.option('-e','--email',dest='email') def addBackendUser(username,email): user = BackendUser(username=username,email=email) db.session.add(user) db.session.commit() manage.add_command('db',DBManage) if __name__ == '__main__': manage.run() 2. db_script.py # encoding: utf-8 from flask_script import Manager DBManage = Manager() @DBManage.command def init(): print u'服務器初始化完成。' @DBManage.command def migrate(): print u'數據庫遷移完成。'
以前咱們都是將數據庫的模型(類)放在主 app
文件中,可是隨着項目愈來愈大,若是對於加入的新的模型,咱們還放在主 app
文件中,就會使主 app
文件愈來愈大,同時也愈來愈很差管理。因此咱們能夠新建一個專門存放模型的文件。如 models.py
文件用來專門存放模型的文件。
將本來在主 app
文件中定義的模型(類)移動到 models.py
文件中,可是會提示錯誤,因此咱們在 models.py
文件中導入主 app
文件的 db
:from models_sep.py import db
;同時,由於咱們的主 app
文件確定會操做到 models
文件中的模型,因此要在主 app
文件中導入 models
建立的模型。兩個文件的完整代碼下所示:
1. # 主 app 文件 from flask import Flask from flask_sqlalchemy import SQLAlchemy from models import Article app = Flask(__name__) db = SQLAlchemy(app) db.create_all() @app.route('/') def index(): return 'index!' if __name__ == '__main__': app.run() 2. # models.py 文件 from flask_script_demo1 import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
執行以上文件,會報錯。
報錯提示:ImportError: cannot import name Article
,出現此類報錯,先排查路徑和導入的內容是否有錯,若保證沒錯,則極可能是出現循環引用。
報錯緣由:循環引用,即 models_sep.py
引用了 models.py
中的 Article
,而 models.py
又引用了 models_sep.py
中的 db
,從而形成循環引用。
解決循環引用:
解決方法:將 db
放在另外一個文件 exts.py
中,而後 models_sep.py
和 models.py
都從 exts.py
中引用 db
變量,這樣就能夠打破引用的循環。
三個文件的代碼以下:
1. # exts.py 文件 from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() 2. # 主 app 文件 from flask import Flask from models import Article from exts import db import config app = Flask(__name__) app.config.from_object(config) db.init_app(app) @app.route('/') def index(): return 'index' if __name__ == '__main__': app.run() 3. # models.py 文件 from exts import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincre) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
總結:
分開 models
的目的是:讓代碼更方便管理。
解決循環引用的方法:把 db
放在一個單獨文件中如 exts.py
,讓主 app
文件和 models
文件都從 exts.py
中引用。
這個時候若是咱們的模型(類)要根據需求添加一個做者字段,這時候咱們須要去修改模型 Article
,修改完成咱們須要再映射一遍。可是對於 flask-sqlalchemy
而言,當數據庫中存在了某個模型(類)後,再次映射不會修改該模型的字段,即再次映射不會奏效。
在數據庫中刪除該模型對應的表格,再將帶有新字段的模型從新進行映射。
很顯然,這種方式明顯很簡單粗暴,很是不安全,由於在企業中一個數據庫中的表格是含有大量數據的,若是刪除可能會形成重大損失。因此咱們須要一個能夠動態修改模型字段的方法,使用 flask-migrate
。先安裝:在虛擬環境下使用命令 pip install flask-migrate
便可。
使用 flask-migrate
的最簡單方法是:藉助 flask-script
使用命令行來對 flask-migrate
進行操做。一共有好幾個步驟,分別說明一下:
新建 manage.py
文件:
新建 manage.py
文件後:
導入相應的包並初始化 manager
:
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app)
要使用 flask_migrate
必須綁定 app
和 db
migrate = Migrate(app,db)
把 MigrateCommand
命令添加到 manager
中,實際上就是添加 migrate
子命令到 manager
中
manager.add_command('db',MigrateCommand)
manage.py
文件代碼以下:
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app) # 1. 要使用 flask_migrate 必須綁定 app 和 db migrate = Migrate(app,db) # 2. 把 MigrateCommand 命令添加到 manager 中 manager.add_command('db',MigrateCommand) if __name__ == '__main__': manager.run()
在 manage.py
文件中,導入須要映射的模型(類):
由於在主 app
文件中已經再也不須要對模型進行映射,而對模型的操做是在 manage.py
文件中進行的,包括 flask-migrate
動態映射,因此要導入須要映射的模型。
from models import Article
完成以上步驟後,便可到命令行中更新數據庫中的模型了:
python manage.py db init
,初始化 flask-migrate
環境,僅在第一次執行的時候使用。python manage.py db migrate
,生成遷移文件python manage.py db upgrade
,將遷移文件映射到數據庫的表格中python manage.py db --help