【ORM】SQLAlchemyを使ってみた感想

# はじめに

最近はPythonを書いてます。どうも僕です。

PythonでDBを扱う際、`SQLAlchemy`というORMが選択肢に入ってくるかと思います。

今回はこいつをある程度触った感想なりを書いていきます。


# 感想
使ってみた所感を。
\
**「んーーーー微妙。。。。」**
\
といった感じ。
（なんかいろいろ言われそう怖い…）


なにが微妙な点なのか。

もちろん、いい点もあり、なんとも言えない部分もあります

# 基本的な使い方


SQLAlchemyでは、DBへの接続を`session`として扱い、クエリを実行します。

```python:sessionの定義
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

# mysql の設定
DATABASE = 'mysql://%s:%s@%s:%s/%s?charset=utf8' % (
  "認証情報"
)

ENGINE = create_engine(
    DATABASE,
    encoding = "utf-8",
    echo = False
)

# session の作成
session = scoped_session(
    sessionmaker(
        autocommit=False,
        autoflush=False,
        bind = ENGINE
    )
)

# model で使用する
Base = declarative_base()
Base.query = session.query_property()
```
この`session`を呼び出して、SQLを実行します。

また、テーブルもPython側で定義します。


```python:テーブルの定義
class Test(Base): # ここのBaseはsessionのとこで定義したBase
    __tablename__ = "testTable"
    id = Column("id", Integer, primary_key=True)
    text = Column("text", String(127), nullable=False)
    created = Column("created_at", DATETIME, default=datetime.now, nullable=False)
    updated = Column("updated_at", TIMESTAMP, default=datetime.now, onupdate=datetime.now, nullable=False)
```

これらを呼び出して処理を行う。

## selectとinsertしてみる

### select
```python
# idが一致するものの最初のデータを取得
def selectOne(id, session):
    data = session.query(Test).filter_by(id = id).first()
    return data
```

### insert
```python
def insert(id, text, session):
    data = Test()
    data.id = id
    data.text = text
    session.add(data)
    session.commit()
```

### APIから呼び出す時の書き方
APIを用いてDBにCRUDする際の関数の書き方について、僕のたどり着いた書き方を紹介します。

ex) APIに投げた値をTestテーブルに書き込む
```python:main.py
@app.route("/test", methods=["GET"])
def testFunc():
    data = request.get_json()
    insertTest(data['id'], data['text'])
    return jsonify({"result": "success"}) 
```

```python:handler.py
def insertTest(id, text):
    try:
        session = <sessionを定義したところから持ってくる>
        db.Test().insert(id,text,session)
    except Exception as error:
        logger.error("err --> %s", str(error))
        session.rollback()
        abort(400, "error")
    finally:
        session.close()
```

```python:controller.py
class Test():
    def insert(self, id, text, session):
        test = Test()
        test.id = id
        test.text = text
        session.add(problem)
        session.commit()
        return
```

このような流れになります。
処理フローとしては、
1. `/test`にGETリクエストが来る
2. `insertTest`関数が呼び出される
3. `class Test`の`insert`関数が呼び出される
4. insert処理が走る
5. `commit` して終了

ポイントとしては、`insertTest`関数です
いちいちinsertの処理を別関数を呼んでます。

これはいくつかのメリットがあるからです

1. 　テーブルごとにクラスを割り振ることで、わかりやすくするため
2. 　`try`を用いることで処理が失敗した際のロールバックが可能
3. DBに対する処理を同じセッションで管理できる⇨重要　

同じセッションで管理できるのが大事で、例えば二つのテーブルに値を挿入する時、片方のテーブルは成功したけど、片方では失敗した場合。
同じセッションで管理していたので、ロールバック処理が可能です。これが関数を分けるメリットです。


# 微妙な点

こんなコードがあったとします。

```python
def qiitaFunc(self):
    session = module.DBSetting.session
    testData = session.query(Test).filter_by(id = int(id)).first()
    print(testData)
    session.close
```

これは`Test`テーブルにidでセレクトをして、それの一番最初にヒットするデータを持ってくる関数ですね
では、これの出力結果を見ましょう。

```bash
<Test object at 0x7f70ce0c4cc0>
```

そう、これの出力結果は`object at hogehoge`の形式で出てきます。
こいつは辞書の形式？なのかな

とりあえず、

```python
print(testData.id)
```
というふうに、パラメータを指定しないと出力されません。

例えば、Go言語のDBライブラリの`Gorm`とかだと、構造体を定義してそれに当てはめたりするので、このようなことは起きません。

僕個人的には、データがちゃんときているか、というのを確認するため、しょっちゅう標準出力するので、この仕様はストレスでしかなかったです。

以上が、微妙だなと思った点です。

# 総括
まぁ先ほどの面倒な出力問題に目を閉じれば結構使いやついものだなと思いました。

あと、SQLインジェクションも変なことしない限り大丈夫みたいな文献も見たので、その辺もいいと思う点ですね。

classを用いてどのテーブルになにしてるかを簡潔にできるのはいい点だなと思います。

では。