flask資料庫操作(二)
使用ORM:
隨著項目越來越大,採用寫原生SQL的方式在代碼中會出現大量的SQL語句,那麼問題就出現了:
1. SQL語句重複利用率不高,越複雜的SQL語句條件越多,代碼越長。會出現很多相近的SQL語句。
2. 很多SQL語句是在業務邏輯中拼出來的,如果有資料庫需要更改,就要去修改這些邏輯,這會很容易漏掉對某些SQL語句的修改。
3. 寫SQL時容易忽略web安全問題,給未來造成隱患。
ORM,全稱Object Relational Mapping,中文叫做對象關係映射,通過ORM我們可以通過類的方式去操作資料庫,而不用再寫原生的SQL語句。通過把表映射成類,把行作為示例,把欄位作為屬性,ORM在執行對象操作的時候最終還是會把對應的操作轉換為資料庫原生語句。使用ORM有許多優點:
Advertisements
1. 易用性:使用ORM做資料庫的開發可以有效的減少重複SQL語句的概率,寫出來的模型也更加直觀、清晰。
2. 性能損耗小:ORM轉換成底層資料庫操作指令確實會有一些開銷。但從實際的情況來看,這種性能損耗很少(不足5%),只要不是對性能有嚴苛的要求,綜合考慮開發效率、代碼的閱讀性,帶來的好處要遠遠大於性能損耗,而且項目越大作用越明顯。
3. 設計靈活:可以輕鬆的寫出複雜的查詢。
4. 可移植性:SQLAlchemy封裝了底層的資料庫實現,支持多個關係資料庫引擎,包括流行的MySQL、PostgreSQL和SQLite。可以非常輕鬆的切換資料庫。
使用SQLAlchemy:要使用ORM來操作資料庫,首先需要創建一個類來與對應的表進行映射。現在以User表來做為例子,它有自增長的id、name、fullname、password這些欄位,那麼對應的類為:
Advertisements
from sqlalchemy import Column,Integer,String
from constatns import DB_URI
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine(DB_URI,echo=True)
# 所有的類都要繼承自declarative_base這個函數生成的基類
Base = declarative_base()
class User(Base):
# 定義表名為users
__tablename__ = 'users'
# 將id設置為主鍵,並且默認是自增長的
id = Column(Integer,primary_key=True)
# name欄位,字元類型,最大的長度是50個字元
name = Column(String(50))
fullname = Column(String(50))
password = Column(String(100))
# 讓列印出來的數據更好看,可選的
def __repr__(self):
return "<User(id='%s',name='%s',fullname='%s',password='%s')>" % (self.id,self.name,self.fullname,self.password)
SQLAlchemy會自動的設置第一個Integer的主鍵並且沒有被標記為外鍵的欄位添加自增長的屬性。因此以上例子中id自動的變成自增長的。以上創建完和表映射的類后,還沒有真正的映射到資料庫當中,執行以下代碼將類映射到資料庫中:
Base.metadata.create_all()
在創建完數據表,並且做完和資料庫的映射后,接下來讓我們添加數據進去:
python
ed_user = User(name='ed',fullname='Ed Jones',password='edspassword')
# 列印名字
print ed_user.name
> ed
# 列印密碼
print ed_user.password
> edspassword
# 列印id
print ed_user.id
> None
可以看到,name和password都能正常的列印,唯獨id為None,這是因為id是一個自增長的主鍵,還未插入到資料庫中,id是不存在的。接下來讓我們把創建的數據插入到資料庫中。和資料庫打交道的,是一個叫做Session的對象:
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
# 或者
# Session = sessionmaker()
# Session.configure(bind=engine)
session = Session()
ed_user = User(name='ed',fullname='Ed Jones',password='edspassword')
session.add(ed_user)
現在只是把數據添加到session中,但是並沒有真正的把數據存儲到資料庫中。如果需要把數據存儲到資料庫中,還要做一次commit操作:
python
session.commit()
# 列印ed_user的id
print ed_user.id
> 1
這時候,ed_user就已經有id。 說明已經插入到資料庫中了。有人肯定有疑問了,為什麼添加到session中后還要做一次commit操作呢,這是因為,在SQLAlchemy的ORM實現中,在做commit操作之前,所有的操作都是在事務中進行的,因此如果你要將事務中的操作真正的映射到資料庫中,還需要做commit操作。既然用到了事務,這裡就並不能避免的提到一個回滾操作了,那麼看以下代碼展示了如何使用回滾(接著以上示例代碼):
# 修改ed_user的用戶名
ed_user.name = 'Edwardo'
# 創建一個新的用戶
fake_user = User(name='fakeuser',fullname='Invalid',password='12345')
# 將新創建的fake_user添加到session中
session.add(fake_user)
# 判斷fake_user是否在session中存在
print fake_user in session
> True
# 從資料庫中查找name=Edwardo的用戶
tmp_user = session.query(User).filter_by(name='Edwardo')
# 列印tmp_user的name
print tmp_user
# 列印出查找到的tmp_user對象,注意這個對象的name屬性已經在事務中被修改為Edwardo了。
> <User(name='Edwardo', fullname='Ed Jones', password='edspassword')>
# 剛剛所有的操作都是在事務中進行的,現在來做回滾操作
session.rollback()
# 再列印tmp_user
print tmp_user
> <User(name='ed', fullname='Ed Jones', password='edspassword')>
# 再看fake_user是否還在session中
print fake_user in session
> False
接下來看下如何進行查找操作,查找操作是通過session.query()方法實現的,這個方法會返回一個Query對象,Query對象相當於一個數組,裝載了查找出來的數據,並且可以進行迭代。具體裡面裝的什麼數據,就要看向session.query()方法傳的什麼參數了,如果只是傳一個ORM的類名作為參數,那麼提取出來的數據就是都是這個類的實例,比如:
for instance in session.query(User).order_by(User.id):
print instance
# 輸出所有的user實例
> <User (id=2,name='ed',fullname='Ed Json',password='12345')>
> <User (id=3,name='be',fullname='Be Engine',password='123456')>
如果傳遞了兩個及其兩個以上的對象,或者是傳遞的是ORM類的屬性,那麼查找出來的就是元組,例如:
for instance in session.query(User.name):
print instance
# 輸出所有的查找結果
> ('ed',)
> ('be',)
以及:
for instance in session.query(User.name,User.fullname):
print instance
# 輸出所有的查找結果
> ('ed', 'Ed Json')
> ('be', 'Be Engine')
或者是:
for instance in session.query(User,User.name).all():
print instance
# 輸出所有的查找結果
> (<User (id=2,name='ed',fullname='Ed Json',password='12345')>, 'Ed Json')
> (<User (id=3,name='be',fullname='Be Engine',password='123456')>, 'Be Engine')
另外,還可以對查找的結果(Query)做切片操作:
for instance in session.query(User).order_by(User.id)[1:3]
instance
如果想對結果進行過濾,可以使用filter_by和filter兩個方法,這兩個方法都是用來做過濾的,區別在於,filter_by是傳入關鍵字參數,filter是傳入條件判斷,並且filter能夠傳入的條件更多更靈活,請看以下例子:
# 第一種:使用filter_by過濾:
for name in session.query(User.name).filter_by(fullname='Ed Jones'):
print name
# 輸出結果:
> ('ed',)
# 第二種:使用filter過濾:
for name in session.query(User.name).filter(User.fullname=='Ed Jones'):
print name
# 輸出結果:
> ('ed',)
Column常用參數:
* default:默認值。
* nullable:是否可空。
* primary_key:是否為主鍵。
* unique:是否唯一。
* autoincrement:是否自動增長。
* name:該屬性在資料庫中的欄位映射。
sqlalchemy常用數據類型:
* Integer:整形。
* Boolean:傳遞True/False進去。
* Date:傳遞datetime.date()進去。
* DateTime:傳遞datetime.datetime()進去。
* Float:浮點類型。
* String:字元類型,使用時需要指定長度,區別於Text類型。
* Text:文本類型。
* Time:傳遞datetime.time()進去。
過濾條件:過濾是數據提取的一個很重要的功能,以下對一些常用的過濾條件進行解釋,並且這些過濾條件都是只能通過filter方法實現的:
* equals:
query.filter(User.name == 'ed')
* not equals:
query.filter(User.name != 'ed')
* like:
query.filter(User.name.like('%ed%'))
* in:
query.filter(User.name.in_(['ed','wendy','jack']))
# 同時,in也可以作用於一個Query
query.filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))))
* not in:
query.filter(~User.name.in_(['ed','wendy','jack']))
* is null:
query.filter(User.name==None)
# 或者是
query.filter(User.name.is_(None))
* is not null:
query.filter(User.name != None)
# 或者是
query.filter(User.name.isnot(None))
* and:
from sqlalchemy import and_
query.filter(and_(User.name=='ed',User.fullname=='Ed Jones'))
# 或者是傳遞多個參數
query.filter(User.name=='ed',User.fullname=='Ed Jones')
# 或者是通過多次filter操作
query.filter(User.name=='ed').filter(User.fullname=='Ed Jones')
* or:
from sqlalchemy import or_ query.filter(or_(User.name=='ed',User.name=='wendy'))
查找方法:介紹完過濾條件后,有一些經常用到的查找數據的方法也需要解釋一下:
* all():返回一個Python列表(list):
query = session.query(User).filter(User.name.like('%ed%').order_by(User.id)
# 輸出query的類型
print type(query)
> <type 'list'>
# 調用all方法
query = query.all()
# 輸出query的類型
print type(query)
> <class 'sqlalchemy.orm.query.Query'>
* first():返回Query中的第一個值:
user = session.query(User).first()
print user
> <User(name='ed', fullname='Ed Jones', password='f8s7ccs')>
* one():查找所有行作為一個結果集,如果結果集中只有一條數據,則會把這條數據提取出來,如果這個結果集少於或者多於一條數據,則會拋出異常。總結一句話:有且只有一條數據的時候才會正常的返回,否則拋出異常:
# 多於一條數據
user = query.one()
> Traceback (most recent call last):
> ...
> MultipleResultsFound: Multiple rows were found for one()
# 少於一條數據
user = query.filter(User.id == 99).one()
> Traceback (most recent call last):
> ...
> NoResultFound: No row was found for one()
# 只有一條數據
> query(User).filter_by(name='ed').one()
* one_or_none():跟one()方法類似,但是在結果集中沒有數據的時候也不會拋出異常。
* scalar():底層調用one()方法,並且如果one()方法沒有拋出異常,會返回查詢到的第一列的數據:
session.query(User.name,User.fullname).filter_by(name='ed').scalar()
文本SQL:SQLAlchemy還提供了使用**文本SQL**的方式來進行查詢,這種方式更加的靈活。而文本SQL要裝在一個text()方法中,看以下例子:
from sqlalchemy import text
for user in session.query(User).filter(text("id<244")).order_by(text("id")).all():
print user.name
如果過濾條件比如上例中的244存儲在變數中,這時候就可以通過傳遞參數的形式進行構造:
session.query(User).filter(text("id<:value and name=:name")).params(value=224,name='ed').order_by(User.id)
在文本SQL中的變數前面使用了:來區分,然後使用params方法,指定需要傳入進去的參數。另外,使用from_statement方法可以把過濾的函數和條件函數都給去掉,使用純文本的SQL:
sesseion.query(User).from_statement(text("select * from users where name=:name")).params(name='ed').all()
使用from_statement方法一定要注意,from_statement返回的是一個text裡面的查詢語句,一定要記得調用all()方法來獲取所有的值。
計數(Count):Query對象有一個非常方便的方法來計算裡面裝了多少數據:
session.query(User).filter(User.name.like('%ed%')).count()
當然,有時候你想明確的計數,比如要統計users表中有多少個不同的姓名,那麼簡單粗暴的採用以上count是不行的,因為姓名有可能會重複,但是處於兩條不同的數據上,如果在原生資料庫中,可以使用distinct關鍵字,那麼在SQLAlchemy中,可以通過func.count()方法來實現:
from sqlalchemy import func
session.query(func.count(User.name),User.name).group_by(User.name).all()
# 輸出的結果
> [(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')]
另外,如果想實現select count(*) from users,可以通過以下方式來實現:
session.query(func.count(*)).select_from(User).scalar()
當然,如果指定了要查找的表的欄位,可以省略select_from()方法:
session.query(func.count(User.id)).scalar()
下一節將對ORM中表關係做介紹。
需要更多資料可加群:496257369