一、安裝
Sqlite3是Python3標准庫不需要另外安裝,只需要安裝SQLAlchemy即可。本文sqlalchemy版本為1.3.13
pip install sqlalchemy

二、ORM操作
除了第一步創建引擎時連接URL不一樣,其他操作其他mysql等數據庫和sqlite都是差不多的。
2.1 創建數據庫連接格式說明
sqlite創建數據庫連接就是創建數據庫,而其他mysql等應該是需要數據庫已存在才能創建數據庫連接;建立數據庫連接本文中有時會稱為建立數據庫引擎。
2.1.1 sqlite創建數據庫連接
以相對路徑形式,在當前目錄下創建數據庫格式如下:
# sqlite://<nohostname>/<path> # where <path> is relative: engine = create_engine('sqlite:///foo.db')
以絕對路徑形式創建數據庫,格式如下:
#Unix/Mac - 4 initial slashes in total engine = create_engine('sqlite:////absolute/path/to/foo.db') #Windows engine = create_engine('sqlite:///C:\\path\\to\\foo.db') #Windows alternative using raw string engine = create_engine(r'sqlite:///C:\path\to\foo.db')
sqlite可以創建內存數據庫(其他數據庫不可以),格式如下:
# format 1 engine = create_engine('sqlite://') # format 2 engine = create_engine('sqlite:///:memory:', echo=True)
2.1.2 其他數據庫創建數據庫連接
PostgreSQL:
# default engine = create_engine('postgresql://scott:tiger@localhost/mydatabase') # psycopg2 engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/mydatabase') # pg8000 engine = create_engine('postgresql+pg8000://scott:tiger@localhost/mydatabase')
MySQL:
# default engine = create_engine('mysql://scott:tiger@localhost/foo') # mysql-python engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo') # MySQL-connector-python engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo') # OurSQL engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')
Oracle:
engine = create_engine('oracle://scott:tiger@127.0.0.1:1521/sidname') engine = create_engine('oracle+cx_oracle://scott:tiger@tnsname')
MSSQL:
# pyodbc engine = create_engine('mssql+pyodbc://scott:tiger@mydsn') # pymssql engine = create_engine('mssql+pymssql://scott:tiger@hostname:port/dbname')
2.2 創建數據庫連接
我們以在當前目錄下創建foo.db為例,后續各步同使用此數據庫。
在create_engine中我們多加了兩樣東西,一個是echo=Ture,一個是check_same_thread=False。
echo=Ture----echo默認為False,表示不打印執行的SQL語句等較詳細的執行信息,改為Ture表示讓其打印。
check_same_thread=False----sqlite默認建立的對象只能讓建立該對象的線程使用,而sqlalchemy是多線程的所以我們需要指定check_same_thread=False來讓建立的對象任意線程都可使用。否則不時就會報錯:
sqlalchemy.exc.ProgrammingError: (sqlite3.ProgrammingError) SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 35608 and this is thread id 34024. [SQL: 'SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password \nFROM users \nWHERE users.name = ?\n LIMIT ? OFFSET ?'] [parameters: [{}]] (Background on this error at: http://sqlalche.me/e/f405)
示例:
import sqlalchemy
sqlalchemy.create_engine("sqlite:///test.db?check_same_thread=False", echo=True)
2.3 定義映射
先建立基本映射類,后邊真正的映射類都要繼承它
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base()
然后創建真正的映射類,我們這里以一下User映射類為例,我們設置它映射到users表。ORM中表是不需要先存在的,反而是后續要通過映射類來創建表,這一點是需要明確的。
from sqlalchemy import Column, Integer, String # 定義映射類User,其繼承上一步創建的Base class User(Base): # 指定本類映射到users表 __tablename__ = 'users' # 指定id映射到id字段; id字段為整型,為主鍵 id = Column(Integer, primary_key=True) # 指定name映射到name字段; name字段為字符串類形, name = Column(String(20)) fullname = Column(String(32)) password = Column(String(32)) # object 基類也存在該方法,這里重寫該方法 # _repr_方法默認返回該對象實現類的“類名+object at +內存地址”值 def __repr__(self): return "<User(name='%s', fullname='%s', password='%s')>" % ( self.name, self.fullname, self.password)
2.4 創建數據表
# 查看映射對應的表 User.__table__ # 創建數據表 Base.metadata.create_all(engine)
創建打印的語句, 跟echo=True有關系:
student 2020-02-23 16:54:47,279 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2020-02-23 16:54:47,279 INFO sqlalchemy.engine.base.Engine () 2020-02-23 16:54:47,279 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2020-02-23 16:54:47,279 INFO sqlalchemy.engine.base.Engine () 2020-02-23 16:54:47,281 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("student") 2020-02-23 16:54:47,281 INFO sqlalchemy.engine.base.Engine () 2020-02-23 16:54:47,281 INFO sqlalchemy.engine.base.Engine PRAGMA temp.table_info("student") 2020-02-23 16:54:47,281 INFO sqlalchemy.engine.base.Engine () 2020-02-23 16:54:47,281 INFO sqlalchemy.engine.base.Engine CREATE TABLE student ( id INTEGER NOT NULL, name VARCHAR(20), PRIMARY KEY (id) ) 2020-02-23 16:54:47,281 INFO sqlalchemy.engine.base.Engine () 2020-02-23 16:54:47,293 INFO sqlalchemy.engine.base.Engine COMMIT Process finished with exit code 0
2.5 建立會話
增查改刪(CRUD)操作需要使用session進行操作
from sqlalchemy.orm import sessionmaker # engine是2.2中創建的連接 Session = sessionmaker(bind=engine) # 創建Session類實例 session = Session()
2.6 增(向users表中插入記錄)
# 創建User類實例 ed_user = User(name='ed', fullname='Ed Jones', password='edspassword') # 將該實例插入到users表 session.add(ed_user) # 一次插入多條記錄形式 session.add_all( [User(name='wendy', fullname='Wendy Williams', password='foobar'), User(name='mary', fullname='Mary Contrary', password='xxg527'), User(name='fred', fullname='Fred Flinstone', password='blah')] ) # 當前更改只是在session中,需要使用commit確認更改才會寫入數據庫 session.commit()
2.7 查(查詢users表中的記錄)
2.7.1 查實現
query相當前select xxx from xxx部分。
filter_by相當於where部分,外另可用filter。他們的區別是filter_by參數為sql形式,filter參數為python形式。
# 指定User類查詢users表,查找name為'ed'的第一條數據 our_user = session.query(User).filter_by(name='ed').first() print(our_user) # 比較ed_user與查詢到的our_user是否為同一條記錄 print(ed_user is our_user)
更多查詢語句見:https://docs.sqlalchemy.org/en/latest/orm/tutorial.html#querying
不過要注意該鏈接Common Filter Operators節中形如equals的query.filter(User.name == 'ed'),在真正使用時都得改成session.query(User).filter(User.name == 'ed')形式,不然只后看到報錯“NameError: name 'query' is not defined”。
2.7.2 參數傳遞問題
我們上邊的sql直接是our_user = session.query(User).filter_by(name='ed').first()形式,但到實際中時User部分和name=‘ed’這部分是通過參數傳過來的,使用參數傳遞時就要注意以下兩個問題。
首先,是參數不要使用引號括起來。比如如下形式是錯誤的(使用引號),將報錯sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column
table_and_column_name = "User" filter = "name='ed'" our_user = session.query(table_and_column_name).filter_by(filter).first()
其次,對於有等號參數需要變換形式。如下去掉了引號,對table_and_column_name沒問題,但filter = (name='ed')這種寫法在python是不允許的
table_and_column_name = User # 下面這條語句不符合語法 filter = (name='ed') our_user = session.query(table_and_column_name).filter_by(filter).first()
對參數中帶等號的這種形式,現在能想到的只有使用filter代替filter_by,即將sql語句中的=號轉變為python語句中的==。正確寫法如下:
table_and_column_name = User filter = (User.name=='ed') our_user = session.query(table_and_column_name).filter(filter).first()
2.8 改(修改users表中的記錄)
# 要修改需要先將記錄查出來 mod_user = session.query(User).filter_by(name='ed').first() # 將ed用戶的密碼修改為modify_paswd mod_user.password = 'modify_passwd' # 確認修改 session.commit()
2.9 刪(刪除users表中的記錄)
# 要刪除需要先將記錄查出來 del_user = session.query(User).filter_by(name='ed').first() # 打印一下,確認未刪除前記錄存在 del_user # 將ed用戶記錄刪除 session.delete(del_user) # 確認刪除 session.commit() # 遍歷查看,已無ed用戶記錄 for user in session.query(User): print(user)
2.10 直接執行SQL語句
雖然使用框架規定形式可以在一定程度上解決各數據庫的SQL差異,比如獲取前兩條記錄各數據庫形式如下。
# mssql/access select top 2 * from table_name; # mysql select * from table_name limit 2; # oracle select * from table_name where rownum <= 2;
但框架存消除各數據庫SQL差異的同時會引入各框架CRUD的差異,而開發人員往往就有一定的SQL基礎,如果一個框架強制用戶只能使用其規定的CRUD形式那反而增加用戶的學習成本,這個框架注定不能成為成功的框架。直接地執行SQL而不是使用框架設定的CRUD不應當是一種低級的操作應當是一種被鼓厲的標准化行為。
# 正常的SQL語句 sql = "select * from users" # sqlalchemy使用execute方法直接執行SQL records = session.execute(sql)
參考:
