環境
- Flask
- SQLAlchemy
- MariaDB
問題
SQLAlchemyでセッションを作成し、それを用いてDBからデータを取得。そのデータをテンプレートに渡し、テンプレート内でDBクラスのrelationを参照した時にDetachedInstanceErrorが出ました。
例
def PenaltyList():
session = Session():
pens = session.query(Penalty).all()
session.close()
return render_template('penalty_list.html', pens=pens)
ここで、penalty_list.html内で Penalty
クラスのrelationshipになっているクラスを参照していると、DetachedInstanceErrorが出ます。
原因は?
このエラーの原因は以下のとおりであると考えられます。
- SQLAlchemyはrelationshipの対象となるインスタンスのためのデータを、遅延して取得する。(例えば、PenaltyクラスがrelationshipとしてUserクラスのインスタンスをメンバーとして持っているとき、PenaltyをDBから取得しインスタンスを生成した時点ではUserクラスのインスタンスのためのデータを取得していない。)
- (当然ながら)セッションが関連付けられていない、あるいは、関連付けられていたセッションがcloseされてしまうと、relationshipの遅延ロードをするときにエラーが生じる
解決法
まずはコードから。
def PenaltyList():
try:
session = Session():
pens = session.query(Penalty).all()
return render_template('penalty_list.html', pens=pens)
except:
flash('エラー', 'error')
return redirect('/top')
finally:
try:
session.close()
except:
pass
DBアクセス部分をtryで囲ってきちんと例外処理をします。(基本的なことなのにしていなかった。)エラー処理の内容は割とどうでもいいのですが、大事なことは、セッションのcloseはfinally節で行うということです。こうすると、render_templateを処理した後、returnの直前にfinally節が実行されるはずです。
要するに、セッションのcloseは render_template
の後に行わなければならないということです。例外処理をせず本質を抜き出すなら、次のようにも書けます。
def PenaltyList():
session = Session():
pens = session.query(Penalty).all()
rendered = render_template('penalty_list.html', pens=pens)
session.close()
return rendered
あとがき
多くの人にとって、エラー処理をするんだからこれは当たり前の書き方なのかもしれませんが、私は知らなくて苦労しました。誰かの参考になればいいなと思います。もっと標準的な書き方があれば教えてください。