# 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)
```