Upload
atsushi-odagiri
View
121
Download
8
Embed Size (px)
DESCRIPTION
Citation preview
SQLAlchemyBPStyle #4小田切 aodag 篤
アクティブレコードとデータマッパーSQLAlchemyデータマッピング宣言的な方法関連継承Session
アクティブレコードとデータマッパー
アクティブレコードクラスとテーブルを1対1に割り当てるマッピング(オブジェクトと行が対応する)簡単融通は利かない多対多関連ではマッピングされないテーブルが存在する
データマッパークラスに対するテーブルマッピングを指定する柔軟性が高いちょっと面倒多対多関連で関連属性もマッピング可能
SQLAlchemy
データマッパータイプのORマッパーSQLをPythonオブジェクトで構築Unit of workパターン
対応データベース
SQLiteMySQLPostgreSQLFirebirdOracleSQL ServerDB2
など
データマッピング(1) スキーマ定義
person_table = Table("person", meta, Column("person_id", Integer, primary_key), Column("first_name", Unicode(20)), Column("last_name", Unicode(20)), Column("birthday", Date))
要するにテーブル
データマッピング(2) マッピング先
class Person(object): """ A person """
def __init__(self, first_name, last_name, birthday): self.first_name, self.last_name, self.birthday = first_name, last_name, birthday
普通のクラス
データマッピング(3) マッピング
person_mapper = mapper(Person, person_table)
Columnがそのままアトリビュートになる
ぶっちゃけめんどくさい
宣言的マッピング
Base = declarative_base()
class Person(Base): __tablename__ = 'person' person_id = Column(Integer, primary_key=True) first_name = Column(Unicode(20)), last_name = Column(Unicode(20)), birthday = Column(Date))
クラスとスキーマを同時に定義
関連マッピング
class Employee(Base): __tablename__ = 'employee' id = Column(Integer, primary_key=True) ..... company_id = Column(Integer, ForeignKey('company.id'))
class Company(Base): __tablename__ = 'company' id = Column(Integer, primary_key=True) employees = relation(Employee, backref="company")
多対多
user_keyword_table = Table('user_keyword', meta, Column('user_id', ForeignKey('user.id')), Column('keyword_id', ForeignKey('keyword.id')))
class User(Base): id = Column(Integer, primary_key=True)
class Keyword(Base): id = Column(Integer, primary_key=True) users = relation(User, backref='keywords', secondary=user_keyword_table)
関連属性(1)
class User(Base): id = Column(Integer, primary_key=True) name = Column(String(255), unique=True)
class Keyword(Base): id = Column(Integer, primary_key=True) word = Column(String(255), unique=True)
関連属性(2)
class UserKeywords(Base): user_id = Column(Integer, ForeignKey('user.id')) keyword_id = Column(Integer, ForeignKey('keyword.id')) registered = Column(DateTime) kw= relation(Keyword, backref="users") us = relation(User, backref="keywords")
user = User()user.name = 'aodag'keyword = Keyword()keyword.word = 'python'
user_keyword = UserKeyword()user_keyword.registered = datetime.now()user_keyword.us = useruser_keyword.kw= keyword
user.kw[0].registereduser.kw[0].kw.wordword.us[0].user.name
2HOPするのがうざい
関連属性 AssociationProxy
class User(Base): id = Column(Integer, primary_key=True) name = Column(String(255), unique=True) keywords = association_proxy('kw', 'keyword')
class Keyword(Base): id = Column(Integer, primary_key=True) word = Column(String(255), unique=True) users= association_proxy('us', 'user')
user.kw[0].registereduser.keywords[0].wordkeyword.users[0].name
user.kw[0].keyword.word-> user.keyword[0].word
keyword.us[0].user.name-> keyword.users[0].name
継承
RDBの継承実装方法
結合テーブルスーパータイプのテーブルとサブタイプ固有のデータを持つテーブル
単一テーブルすべてのサブタイプのデータを含む1テーブル
完全テーブルサブタイプごとにすべてのデータを持つテーブル
継承(1) スーパークラスの設定
class Person(Base): ... typename = Column(String(20)) __mapper_args__ = {'polymorphic_on':'typename'}
タイプフラグのカラムを追加オプションで、カラムを指定
継承(2) 結合テーブルの場合
class Employee(Person): __tablename__ = 'employee' employee_id = Column(Integer, ForeignKey('person.person_id')) __mapper_args__ = {'polymorphic_identity':'employee'}
サブタイプはテーブルを持つスーパータイプのテーブルを参照する外部参照制約タイプフラグの値を指定
継承(2) 単一テーブル継承
class Employee(Person): __mapper_args__ = {'polymorphic_identity':'employee'}
サブタイプはテーブルを持たない
継承(3) 完全テーブル
class Employee(Person): __tablename__ = 'employee' person_id = Column(Integer, primary_key=True) first_name = Column(Unicode(20)), last_name = Column(Unicode(20)), birthday = Column(Date)) __mapper_args__ = {'concrete':True}
サブタイプはテーブルを持つオプションで完全に別テーブルとする指定スーパータイプにはタイプフラグが必要ないカラムを再度定義
Unit of work
データ処理をマーキングして管理一度にデータベースに反映
メモリ上でのトランザクション処理
SessionとDB接続
DB接続情報engine = create_engine('sqlite:///')
クラスファクトリSession = sessionmaker(bind=engine)
セッションオブジェクトsession = Session()
Session
p = Person(u'篤', u'小田切', datetime(1979, 8, 2))session.add(p)p = Person(u'John', u'Doe', datetime(1970, 1, 1))session.add(p)
session.commit()
新規オブジェクトは、セッションに追加する
Session
p = session.query(Person).filter_by(id=1)p.birthday = date.today()
session.commit()
p = session.query(Person).filter_by(id=1)p.delete()session.commit()
sessionから取り出したオブジェクトはそのまま。
scoped_session
スレッドローカルなモノステートオブジェクト
Session = scoped_session(sessionmaker(bind=engine))Session.query(...)
直接グローバルオブジェクトでメソッド呼び出しできる。同一スレッド内であれば、同一セッション。
内部状態のリセットはremoveメソッドで。Session.remove()
マルチDB
MasterSession = session_maker(bind=create_engine('mysql://db1/db')
SlaveSession =session_maker(bind=create_engine('sqlite://db2/db')
p = Person(...)s1 = MasterSession()s1.add()s1.commit()
s2 = SlaveSession()s2.query(Person).filter_by(id=1)
まとめ
恐ろしく柔軟今回紹介したのはSqlAlchemyができることの1/10くらいその他、コネクションプロキシ2フェーズコミット複数テーブルから1クラスにマッピング1テーブルから複数クラスにマッピングクエリからクラスにマッピング関連の実装クラスをdictやsetに変更アトリビュート単位での遅延ローディング垂直分割、水平分割(sharding)の対応などなど
足りないのはadminだけ!多分FormAlchemyで作れば、それほど問題ないかと。マイグレーションは、sqlalchemy-migrate