SQLAlchemy是python中最著名的ORM(Object Relationship Mapping)框架了。
前言:什么是ORM?
ORM操作是所有完整軟件中后端處理最重要的一部分,主要完成了后端程序和數據庫之間的數據同步和持久化的操作。
數據庫表示一個二維表,包含多行多列。把一個表的內容用python的數據結構表示出來的話,可以用一個list表示多行,list的每一個元素是tuple,表示一行記錄,比如,包含id和name的user表:
[
('1', 'james'),
('2', 'durant'),
('3', 'curry')
]
Python的DB-API返回的數據結構就是像上面這樣表示的。但是用tuple表示一行很難看出表的結構,如果把一個tuple用class實例來表示,就可以更容易的看出表的結構來:
class User(object):
def __init__(self, id, name):
self.id = id
self.name = name
[
User('1', 'james'),
User('2', 'durant'),
User('3', 'curry')
]
這就是傳說中的ORM技術:Object-Relational Mapping ,把關系數據庫的表結構映射到對象上。但是由誰來做這個轉化呢?所以ORM框架應運而生。目前來說也是描述程序中對象和數據庫中數據記錄之間的映射關系的統稱,是一種進行程序和數據庫之間數據持久化的一種編程思想。
ORM思想的核心是隱藏了數據訪問細節,提供了通用的數據庫交互,並且完全不用考慮SQL語句,從而快速開發。
一句話解釋ORM就是:一種可以把model中的模型和數據庫中的一條數據相互轉換的工具。
舉個例子:

#sql中的表
#創建表:
CREATE TABLE employee(
id INT PRIMARY KEY auto_increment ,
name VARCHAR (20),
gender BIT default 1,
birthday DATA ,
department VARCHAR (20),
salary DECIMAL (8,2) unsigned,
);
#sql中的表紀錄
#添加一條表紀錄:
INSERT employee (name,gender,birthday,salary,department)
VALUES ("alex",1,"1985-12-12",8000,"保潔部");
#查詢一條表紀錄:
SELECT * FROM employee WHERE age=24;
#更新一條表紀錄:
UPDATE employee SET birthday="1989-10-24" WHERE id=1;
#刪除一條表紀錄:
DELETE FROM employee WHERE name="alex"
#python的類
class Employee(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=32)
gender=models.BooleanField()
birthday=models.DateField()
department=models.CharField(max_length=32)
salary=models.DecimalField(max_digits=8,decimal_places=2)
#python的類對象
#添加一條表紀錄:
emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保潔部")
emp.save()
#查詢一條表紀錄:
Employee.objects.filter(age=24)
#更新一條表紀錄:
Employee.objects.filter(id=1).update(birthday="1989-10-24")
#刪除一條表紀錄:
Employee.objects.filter(name="alex").delete()
在Python中,最有名的ORM框架是SQLAlchemy。我們來看看其語法。
一,初始化數據庫連接
SQLAlchemy本身無法操作數據庫,其必須通過pymysql等第三方插件。上圖中Dialect用於和數據API進行交流,根據配置文件的不同調用不同的數據庫API,從而實現對數據庫的操作。
使用sqlalchemy進行數據庫操作,首先我們需要建立一個指定數據庫的鏈接引擎對象,而建立引擎對象的方式被封裝在了sqlalchemy.create_engine函數中,通過指定的數據庫連接信息即可創建。
create_engine()用來初始化數據庫連接。SQLAlchemy用一個字符串表示連接信息:
'數據庫類型+數據庫驅動名稱://用戶名:口令@機器地址:端口號/數據庫名'
你只需要根據需要替換掉用戶名,口令等信息即可。舉個例子:
# 初始化數據庫鏈接
engine = create_engine('mysql+mysqlconnector://root:123456@localhost:3306/test')
我使用了mysql數據庫,數據庫連接框架用的是mysqlconnector,用戶名為root,密碼是123456,端口號是localhost(127.0.0.1),端口號是3306(mysql服務器默認端口號),test是數據庫的名字。
二,創建user模型及其數據庫建表操作
2.1,安裝SQLAlchemy
如果沒有安裝SQLAlchemy請先安裝,這個不是python自帶的。
pip install sqlalchemy
2.2,數據庫建表
在test數據庫中,創建user表,SQL如下:
create table user (id varchar(20) primary key, name varchar(20))
創建表,表名為user,表中有兩個字段。一個是id,varchar類型,最多支持20個字符,設置為主鍵,另一個是name,varchar類型,最多支持20個字符。
(PS:主鍵時唯一的,當你重復插入時會報錯,並終止插入操作)。
插入數據,如下:
insert into user(id,name) values('1001','james');
insert into user(id,name) values('1002','durant');
insert into user(id,name) values('1003','curry');

更改表名
alter table origin_name rename to new_name;
2.3,創建user模型
在model.py中創建 一個User類,用來和User表中的字段進行關聯。
# load module
from sqlalchemy import Column, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 創建對象的基類
Base = declarative_base()
# 定義User對象
class User(Base):
# 表的名字
__tablename__ = 'user'
# 表的結構
id = Column(String(20), primary_key=True)
name = Column(String(20))
這段代碼的意思,User類的字段名和表中完全一致,而且字段的屬性也一樣,以id字段為例,表中id的屬性是Varchar(20),primary key,模型中String(20),primary_key=True。我們可以清晰的看到varchar在程序中對應String,primary_key對應程序中的primary_key屬性,而且是一個bool類型。
(PS:這里的String是sqlAlchemy中的一個類,這樣類似的類我們還會用到Column)
2.4,Column類的學習
構建函數為
Column.__init__(self, name, type_, *args, **kwargs)
name 列名
type_ 類型 ,更多類型SQLAlchemy.types
下面是*args參數定義
- Constraint(約束)
- ForeignKey(外鍵)
- ColumnDefault(默認)
- Sequenceobjects(序列)定義
下面是**kwargs參數定義
- primary_key 如果為True,則是主鍵
- nullable 是否可謂Null,默認是True
- default 默認值,默認是None
- index 是否是索引,默認是True
- unique 是否唯一鍵,默認是False
- onupdate 指定一個更新時候的值,這個操作是定義在SQLAlchemy中,不是在數據庫里的,當更新一條數據時設置,大部分用於update Time這類字段
- autoincrement 設置為整型自動增長,只有沒有默認值,並且是Integer類型,默認是True
- quote 如果列名是關鍵字,則強制轉義,默認False
三,CRUD(Create Read Update Delete,增查改刪)
在SQLAlchemy中,增刪改查操作是通過一個session對象(DBSession是由sessionmaker創建的)來完成的。
3.1,初始化DBSession,連接會話
我們的程序中的對象要使用sqlalchemy的管理,實現對象的ORM操作,就需要按照框架指定的方式進行類型的創建操作,SQLAlchemy封裝了基礎類的聲明操作和字段屬性的定義限制方式,開發人員要做的事情就是引入需要的模塊並在創建對象的時候使用他們即可。
基礎類封裝在sqlalchemy.ext.declarative_base模塊中,字段屬性的定義封裝在SQLAlchemy模塊中,通過sqlalchemy.Column定義屬性,通過封裝的Integer,String,Float等定義屬性的限制。
首先我們需要創建DBSession,在此基礎上就可以進行增刪改查操作了。
# load module
from sqlalchemy import Column, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 創建對象的基類
Base = declarative_base()
# 定義User對象
class User(Base):
# 表的名字
__tablename__ = 'user'
# 表的結構
id = Column(String(20), primary_key=True)
name = Column(String(20))
# 初始化數據庫連接
engine = create_engine('mysql+mysqlconnector://root:password@localhost:3306/test')
# 創建DBSession類型
DBSession = sessionmaker(bind=engine)
上面的代碼完成SQLAlchemy的初始化和具體每個表的class定義。如果有多個表,就繼續定義其他class。
注意:如果在創建會話的時候還沒有指定數據庫引擎,可以通過如下的方式完成會話:
Session = sessionmaker() .. Session.configure(bind=engine) session = Session()
完整代碼如下:
def MySQLConnect(connection_info:str, id, status):
engine = create_engine(connection_info)
DBSession = sessionmaker(bind=engine)
session = DBSession()
traininfo = session.query(TrainInfo).filter(TrainInfo.id == id).one()
if traininfo is None:
info = TrainInfo(id=id, status=status)
session.add(info)
else:
traininfo.status = '1'
session.commit()
session.close()
3.2,添加操作
程序中存在一個對象Object數據,通過ORM核心模塊進行增加的函數定義將對象保存在數據庫的操作過程,就是增加操作。比如注冊操作中,通過用戶輸入的賬號密碼等信息創建了一個獨立的對象,通過add()函數將對象增加保存到數據庫中,數據庫中就存在用戶這個對象數據了。
下面,我們看看如何向數據庫表中添加一行記錄。
由於有ORM,我們向數據庫表中添加一行記錄,可以視為添加一個User對象:
# 創建session對象 session = DBSession() # 創建新的User對象 new_user = User(id='1001', name='james') # 添加到session session.add(new_user) # 提交即保存到數據庫 session.commit() # 關閉session session.close()
可見,關鍵是獲取session,然后把對象添加到session,最后提交並關閉。DBSession對象可視為當前數據庫連接。
3.3,查詢操作
查詢時通過Session的query()方法創建一個查詢對象,這個函數的參數可以是任何類或者類的描述的集合。查詢出來的數據是一個對象,直接通過對象的屬性調用。
如何從數據庫表中查詢數據呢?有了ORM,查詢出來的可以不再是tuple,而是User對象。SQLAlchemy提供的查詢接口如下:
# 創建session
session = DBSession()
# 創建Query查詢,filter是where條件,最后調用one()返回唯一行,如果調用all()則返回所有行
user = session.query(User).filter(User.id=='1001').one()
# 打印類型和對象的name屬性
print('type:', type(user))
print('name:', user.name)
# 關閉Session:
session.close()
可見,ORM就是把數據庫表的行與相應的對象建立關聯,互相轉換。
由於關系數據庫的多個表還可以用外鍵實現一對多,多對多等關聯,相應的,ORM框架也可以提供兩個對象之間的一對多,多對多等功能。
例如,如果一個User擁有多個Book,就可以定義一對多關系如下:
class User(Base):
__tablename__ = 'user'
id = Column(String(20), primary_key=True)
name = Column(String(20))
# 一對多:
books = relationship('Book')
class Book(Base):
__tablename__ = 'book'
id = Column(String(20), primary_key=True)
name = Column(String(20))
# “多”的一方的book表是通過外鍵關聯到user表的:
user_id = Column(String(20), ForeignKey('user.id'))
當我們查詢一個User對象時,該對象的books屬性將返回一個包含若干個Book對象的list .
3.4,更新操作
程序中存在的一個對象Object數據,有自己的id編號(可以使程序中自行賦值定義,更多的操作是從數據庫中查詢出來存在的一個對象),通過ORM核心模塊進行修改函數的定義將對象改變的數據更新到數據庫中已經存在的記錄中的過程,就是更新操作。比如用戶更改登錄密碼操作時,根據程序中查詢得到的一個用戶{id編號,賬號,密碼},在程序中通過改變其密碼屬性數據,然后通過update()函數將改變的數據更新保存到數據庫中,數據庫原來的數據就發生了新的改變。
更新操作要多一步,就是要先根據篩選條件拿到要更改的對象,然后給對象賦值,再次提交(commit)即可。
# 創建session對象 session = DBSession() # 查找需要更新的字段id user_result = session.query(User).filter_by(id='1001').first() # 更新字段名稱 user_result.name = 'durant' # 提交即保存到數據庫 session.commit() # 關閉session session.close()
3.5,刪除操作
程序中存在的一個對象或者已知的id編號,通過主鍵編號或者對象的任意屬性進行數據庫中數據記錄的刪除的操作過程稱為刪除操作。如管理員刪除某個會員賬號的操作,通過獲取要刪除會員的賬號,然后通過delete()函數將要刪除的會員信息告知數據庫執行刪除操作,數據庫中的某條存在的數據記錄就被刪除掉了。
# 創建session對象 session = DBSession() # 查找需要刪除的字段id user_result = session.query(User).filter_by(id='1001').first() # 刪除字段內容 session.delete(user_result) # 提交即保存到數據庫 session.commit() # 關閉session session.close()
3.6,代碼整合
# 導入依賴
from sqlalchemy import Column, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 創建對象的基類
Base = declarative_base()
# 定義User對象
class User(Base):
# 表的名字
__tablename__ = 'user'
# 表的結構
id = Column(String(20), primary_key=True)
name = Column(String(20))
# 初始化數據庫鏈接
engine = create_engine('mysql+mysqlconnector://root:123456@localhost:3306/test')
# 創建DBSession類型
DBSession = sessionmaker(bind=engine)
# 添加
# 創建Session對象
session = DBSession()
# 創建User對象
new_user = User(id='1001', name='james')
# 添加到session
session.add(new_user)
# 提交
session.commit()
# 關閉session
session.close()
# 查詢
# 創建session
session = DBSession()
# 利用session創建查詢,query(對象類).filter(條件).one()/all()
user = session.query(User).filter(User.id=='1001').one()
print('type:{0}'.format(type(user)))
print('name:{0}'.format(user.name))
# 關閉session
session.close()
# 更新
session = DBSession()
user_result = session.query(User).filter_by(id='1001').first()
user_result.name = "durant"
session.commit()
session.close()
# 刪除
session = DBSession()
user_willdel = session.query(User).filter_by(id='1001').first()
session.delete(user_willdel)
session.commit()
session.close()
四,查詢對象Query
Session是sqlalchemy和數據庫交互的橋梁,Session提供了一個Query對象實現數據庫中數據的查詢操作。
4.1,常規查詢query
直接指定類型進行查詢
user_list = session.query(User)
for user in user_list:
print(user.name)
4.2,指定排序查詢
通過類型的屬性指定排序方式
# 默認順序 user_list = session.query(User).order_by(User.id) # 指定倒序 user_list = session.query(User).order_by(-User.id) # 多個字段 user_list = session.query(User).order_by(-User.id, User.name)
4.3,指定列查詢
指定查詢數據對象的屬性,查詢目標數據
user_list = session.query(User, User.name).all()
for u in user_list:
print(u.User, u.name)
4.4,指定列屬性別名
對於名詞較長的字段屬性,可以指定名稱在使用時簡化操作
user_list = session.query(User.name.label('n')).all()
for user in user_list:
print(user.n)
4.5,指定類型別名
對於類型名稱較長的情況,同樣可以指定別名進行處理
from sqlalchemy.orm import aliased
user_alias = aliased(User, name=’u_alias’)
user_list = session.query(u_alias, u_alias.name).all()
for u in user_list:
print(u.u_alias, u.name)
4.6,切片查詢
對於經常用於分頁操作單額切片查詢,在使用過程中直接使用python內置的切片即可。
user_list = session.query(User).all()[1:3]
五,條件篩選filter
上面主要對數據查詢對象query有一個比較直觀的感受和操作,在實際使用過程中經常用到條件查詢,主要通過filter 和 filter_by 進行操作,下面重點學習最為頻繁的filter條件篩選函數。
5.1,等值條件——equals / not equals
# equals 相等判斷 session.query(User).filter(User.id == 1) # not equals 不等判斷 session.query(User).filter(User.name != ‘james’)
5.2,模糊條件——like
session.query(User).filter(User.name.like(‘%james%’))
5.3,范圍條件——in / not in
# IN
session.query(User).filter(User.id.in_([1,2,3,4]))
session.query(User).filter(User.name.in_([
session.query(User.name).filter(User.id.in_[1,2,3,4])
]))
# NOT IN
session.query(User).filter(~User.id.in_([1,2,3]))
5.4,空值條件——is null / is not null
# IS NULL session.query(User).filter(User.name == None) session.query(User).filter(User.name.is_(None)) # pep8 # IS NOT NULL session.query(User).filter(User.name != None) session.query(User).filter(User.name.isnot(None)) # pep8
5.5,並且條件——and
from sqlalchemy import and_ session.query(User).filter(User.name=’james’).filter(User.age=12) session.query(User).filter(User.name=’james, User.age=12) session.query(User).filter(and_(User.name=’james’, User.age=12))
5.6,或者條件——or
from sqlalchemy import or_ session.query(User).filter(or_(User.name=’james’, User.name=’durant’))
5.7,SQL語句查詢
某些特殊的情況下,我們也可能在自己的程序中直接使用sql語句進行操作。
from sqlalchemy import text session.query(User).from_statement( text(‘select * from users where name=:name and age=:age’)) .params(name=’james’, age=32).all()
5.8,filter() 和 filter_by()的區別
首先看一個例子:
# load module
from sqlalchemy import Column, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
import warnings
warnings.filterwarnings('ignore')
# 創建對象的基類
Base = declarative_base()
# 定義User對象
class User(Base):
# 表的名字
__tablename__ = 'user'
# 表的結構
id = Column(String(20), primary_key=True)
name = Column(String(20))
engine = create_engine('mysql+pymysql://root:wangjian@localhost:3306/sqlalchemy')
DBSession = sessionmaker(bind=engine)
session = DBSession()
res1 = session.query(User).filter(User.id == 1001)
res2 = session.query(User).filter_by(id = 1001)
for i in res1:
print(i.id, i.name)
for i in res2:
print(i.id, i.name)
結果如下:
1001 james 1001 james
所以,從例子可以看出,filter可以像寫SQL的where條件那樣寫 < , > 等條件,但引用列名時,需要通過類名,屬性名的方式。 filter_by可以使用python的正常參數傳遞條件,指定列名時,不需要額外指定類名,參數名對應類中的屬性名,不能用 < , > 等條件。
filter不支持組合查詢,只能連續調用filter變相實現,filter_by的參數是 **kwargs,直接支持組合查詢。
filter_res = {'id':1002, 'name':'durant'}
res = session.query(User).filter_by(**filter_res)
for i in res:
print(i.id, i.name)
結果:
1002 durant
六,查詢結果
6.1,all()函數返回查詢列表
返回一個列表,可以通過遍歷列表獲取每個對象。
session.query(User).all()
6.2,filter()函數返回單項數據的列表生成器
session.query(User).filter(..)
6.3,one() / one_or_none() / scalar() 返回單獨的一個數據對象
one()返回且僅返回一個查詢結果,當結果數量不足或者多余一個時會報錯。
session.query(User).filter(..).one()/one_or_none()/scalar()
6.4,first() 返回一個單項結果
返回至多一個結果,而且以單項形式,而不是只有一個元素的tuple形式返回。
session.query(User).filter(User.id > 1001).first()
七,遇到的問題及其解決方法
7.1 sqlalchemy查詢中,如果沒有符合條件的結果,會返回一個空的對象,如何判斷這個對象是空的?
users = session.query(user).filter(user.id == '234).all()#users為object列表
if(len(user) == 0):
print "數據庫中沒有id為234的用戶‘’
else:
print "數據庫中包含id為234的用戶“
#然后視情況看是可能有多個返回值,還是只有一個返回值,(當id為主鍵時只可能返回一個object數據)
users1 = users[0]
解決問題的代碼:
from sqlalchemy import Column, String, create_engine, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
warnings.filterwarnings("ignore")
Base = declarative_base()
class TrainInfo(Base):
__tablename__ = 'phm'
phmid = Column(String(20), primary_key=True)
status = Column(Integer, default=0)
def MySQLConnect(connection_info:str, phmid, status):
engine = create_engine(connection_info)
DBSession = sessionmaker(bind=engine)
session = DBSession()
traininfo = session.query(TrainInfo).filter(TrainInfo.phmid == phmid).one()
if traininfo is None:
info =TrainInfo(phmid=phmid, status=status)
session.add(info)
else:
traininfo.status = '1'
session.commit()
session.close()
八,擴展知識
8.1 Python雙下划線開頭的函數和變量
python 用下划線作為變量前綴和后綴指定特殊變量。
- __XXX 不能用 ‘ from module import * ’ 導入
- __XXX__ 系統定義名稱
- __XXX 類中的私有變量名
- 核心風格:避免用下划線作為變量名的開始。
因為下划線對解釋器有特殊的意義,而且是內建標識符所使用的符號,我們建議程序員避免使用下划線作為變量名的開始。一般來說,變量名__XXX 被看做是“私有”的,在模塊或類外不可以使用。當變量是私有的時候,用_XXX來表示變量是很好地習慣。因為變量名__XXX___對python來說有特殊含義,對於普通的變量應當避免這種命名風格。
“單下划線”開始的成員變量叫做保護變量,意思是只有類對象和子類對象自己能訪問到這些變量;
“雙下划線”開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。
以單下划線開頭(__foo)的代表不能直接訪問的類屬性,需通過類提供的接口進行訪問,不能用“from XXX import * ”而導入;
以雙下划線開頭的(__foo)代表類的私有成員;
以雙下划線開頭和結尾的(__foo__)代表python里特殊方法專用的標識,如__init__() 代表類的構造函數。
8.2 if __name__ == ''__main__''
所有的python模塊都是對象並且有幾個有用的屬性,你可以使用這些屬性方便的測試你所書寫的模塊。
模塊是對象,並且所有的模塊都有一個內置屬性__name__。一個模塊的__name__的值要你您如何應用模塊。如果import模塊,那么__name__的值通常為模塊的文件名,不帶路徑或者文件擴展名。但是我們也可以像一個標准的程序一樣直接運行模塊,在這種情況下__name__的值將會是一個特別的缺省值:__main__。
參考文獻:https://www.jianshu.com/p/20593da77c04
