# Flask實作_ext_01_Flask-SQLAlchemy_初探 ###### tags: `flask` `flask_ext` `python` `SQLAlchemy` ## 說明 :::danger 官方文件: * [SQLAlchemy](https://www.sqlalchemy.org/) * [Flask-SQLAlchemy](http://flask-sqlalchemy.pocoo.org/2.3/) ::: 目前來說,只要在企業內要自建一個系統就一定會牽扯到對資料庫的操作,如果每一個操作都寫著一句SQL語法,那維護起來版面著實嚇人,ORM的出現幫忙程式設計師簡化了大量這類基礎SQL語法的操作,也可以減少`SQL injection`攻擊<sub><a href='https://zh.wikipedia.org/zh-tw/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A'>(SQL injection)</a></sub>。 Flask並沒有限定一定要使用`SQLAlchemy`這類ORM操作的套件,但是`SQLAlchemy`可以搭配sqlite、Mysql、PostgreSQL、MSSql、Oracle,這已經涵蓋市面上常用的資料庫。而`flask-sqlachemy`是為了簡化Flask開發人員操作而出現的套件,它適度的包裝了`SQLAlchemy`。 直觀來看,透過ORM我們可以將資料庫當做物件來操作,這讓開發人員不用花心思的去處理資料庫的DDL<sub>(Create, Drop...)</sub>、DML<sub>(Select, Delete, Update, Insert..)</sub>。如果你開發過`MVC`那就一定很了解這一塊就是`MODEL`。 另外,<font color=red>強烈建議一定要去閱讀SQLAchemy相關文件</font>! ## 安裝 ```= pip install flask-sqlalchemy ``` 安裝`flask-sqlalchemy`的時候會自動下載相依套件`sqlachemy`,不用特別另外安裝。 實務上我們會將`Model`另外設置一個Python文件管理,但是這是一個單題的說明,不會這麼麻煩,只要知道有這麼一件事就可以。 ## 範例 ### 範例_初始化資料庫 以下是`model.py`文件 ```python= from flask import Flask # from flask.ext.sqlalchemy import SQLAlchemy<--新版取消了 from flask_sqlalchemy import SQLAlchemy import os # 取得目前文件資料夾路徑 pjdir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__) # 新版本的部份預設為none,會有異常,再設置True即可。 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True # 設置sqlite檔案路徑 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \ os.path.join(pjdir, 'data.sqlite') db = SQLAlchemy(app) #...下略...# ``` 利用我們在實作中的專案來簡單調整為這次的範例,`sqlite`是檔案式資料庫,不需要連線ip,只要知道檔案所在即可,這在`android`手機上很常見的應用。 既然ORM是把資料庫當物件來操作,那代表就需要建置類別,不同於`sqlachemy`,`flask-sqlachemy`簡化了整個操作,在import進來的時候,它已經將`sqlalchemy`與`sqlalchemy.orm`通通載入並且提供了`Model`類別。 ```python=18 #...接上...# class User(db.Model): __tablename__ = 'Users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) def __init__(self, username, email): self.username = username self.email = email def __repr__(self): return '<User %r>' % self.username ``` 第19行:繼承稍早所初始化的`db.Model` 完成資料庫的設置之後需要至命令模式下初始化資料庫,如下: ```shell >>> from model_user import db >>> db.create_all() ``` 透過指令產生資料庫之後,就會看到在專案內多了一個`data.sqlite`檔案,如下圖: ![](https://i.imgur.com/5ObYn5d.png) ### 範例_新增使用者 初始化資料庫之後,我們就可以驗證自己建置的ORM是否可以正常的工作,只需要在Python shell命令中去import新增的類別操作,如下: ```python= from model_user import User # 實作類別 admin = User('admin', 'admin@abc.com') user1 = User('user1', 'user1@abc.com') # 寫入資料 db.session.add(admin) db.session.add(user1) db.session.commit() ``` 先確認一下自己的資料庫是乾淨的: ![](https://i.imgur.com/ewlXZ4j.png) `commit`之後,確實的寫入資料: ![](https://i.imgur.com/7NWQFPc.png) 也可以確認一下自己`__repr__`的回傳,如下: ```python= >>> admin <User 'admin'> >>> user1 <User 'user1'> ``` 透過這個案例可以感受到ORM帶來的便利,我們完全沒有寫到任何一句SQL Statement就完成寫入的工作。 ### 範例_搜尋使用者 在`flask-sqlalchemy`內已經透過`Model`類(在宣告`User`時繼承的`db.Model`)提供了`query`的method,因此可以直接透過該類別來查詢,如下: ```python= # 全部取回 >>> all_users = User.query.all() >>> all_users [<User 'admin'>, <User 'user1'>] ``` 查看一下型別 ```python= >>> type(all_users) <class 'list'> ``` 利用取回的資料找尋使用者資料 ```python= >>> all_users[1] <User 'user1'> >>> all_users[1].id 2 >>> all_users[1].username 'user1' >>> all_users[1].email 'user1@abc.com' ``` 透過條件尋找使用者 ```python= >>> query_admin = User.query.filter_by(username='admin').first() >>> query_admin <User 'admin'> ``` 如果沒有first? ```python= >>> query_admin = User.query.filter_by(username='admin') >>> query_admin <flask_sqlalchemy.BaseQuery object at 0x03631E90> ``` ### 範例_刪除使用者 ```python= # 透過delete刪除 >>> db.session.delete(admin) >>> db.session.commit() # commit之後查詢一下目前所有使用者 >>> user_all = User.query.all() >>> user_all [<User 'user1'>] ``` ### 範例_修改使用者 ```python= # 變更使用者名稱 >>> admin.username='newAdmin' >>> db.session.add(admin) >>> db.session.commit() >>> all_user = User.query.all() >>> all_user [<User 'newAdmin'>, <User 'guest'>] ``` 在查詢之後可直接修正屬性再透過`db.session.add`的方式直接做更新 ## 總結 這是對`SQLAlchemy`的初探,我們所用的不過就是它的百分之一的功能,但也有前輩說過,再強大的軟體,也許常用的也只有那百分之一而以,而這百分之一也讓我們感受到ORM所帶來的便利性。 後續尚有資料庫的關聯設置以及其它QUERY的說明,記得繼續學習。 **Flask-SQLAlchemy_關聯**:[Flask實作_ext_02_Flask-SQLAlchemy_關聯](https://hackmd.io/s/S1Pj3fCMz) **Flask-SQLAlchemy_Query**:[Flask實作_ext_18_Flask-SQLAlchemy_Query](https://hackmd.io/s/rkFtMQg47) ## 延伸閱讀 ### 管理工具 `SQLite`有圖形化介面可以使用,個人使用[sqlitebrowser](http://sqlitebrowser.org/) ### 資料庫連接方式 :::info 不同的資料庫連接有不同的相依套件需要下載,像`Oracel`就需要`cx_Oracle`,更詳細部份可參考[官方文件](https://docs.sqlalchemy.org/en/latest/dialects/#included-dialects)說明。 ::: * MySQL * mysql://username:password@hostname/database * package: pymysql * Postgres * postgresql://username:password@hostname/database * package: PostgreSQL * SQLite(Unix) * sqlite:////filepath * SQLite(Windows) * sqlite:///filepath * Oracle * oracle://username:password@ip:port/databasename', echo=True * package: cx_Oracle ### 常用型別 | SQLAlchemy | python | 說明 | | -------- | -------- | -------- | |Integer|int|整數(32bit)| |SmallInteger|int|整數(16bit)| |BigInteger|int|整數(不限精度)| |Float|float|浮點數| |Numeric|decimal.Decimal|定點數| |String|str|文字| |Text|str|長字串| |Unicode|unicode|| |UnicodeText|unicode|| |Boolean|bool|| |Date|datetime.date|| |Time|datetime.time|| |DateTime|datetime.datetime|| |PickleType|object|序列化文件| |LargeBinary|str|二進制文件| ### 宣告資料物件常用參數 | 參數 | 用途 | | -------- | -------- | | primary_key | 主鍵| |unique|唯一值| |index|設置索引| |nullable|允許null| |default|設置欄位預設值| ### 搜尋的表達式 #### 常用過濾器 #### 常用查詢執行函數 |方法|說明| |-----|-----| |all()|回傳所有結果| |first()|回傳第一個結果,沒有就回傳None| |first_or_404()|應用如下說明| |get()|利用指定主鍵查詢,沒有就回傳None| |get_or_404()|同下說明| |count()|回傳查詢結果數量| |paginate()|分頁| ##### 404應用 在view中如果希望搜尋不到值的時候拋出404而不是None的話,可透過`get_or_404()`、`first_or_404()`來設置,再利用`app_errorhandler`來做異常處理。 ```python= @app.route('/user/<username>') def get_user(username): user = User.query.filter_by(username=username).first_or_404() return render_template('Yourpage.html', user=user) ```