36
SQLマッピングフレームワーク Kobati」のはなし 南谷千城 2015/02/27

SQLマッピングフレームワーク「Kobati」のはなし

Embed Size (px)

Citation preview

SQLマッピングフレームワーク「Kobati」のはなし

南谷千城

2015/02/27

内容

• Kobatiの概要

• Kobatiの使い方

• 実装の説明スライド無し

• 新規インストールした環境では動かない

– XPathライブラリにエンバグ中?

Kobati?

• RDBを利用した永続化フレームワーク– http://smalltalkhub.com/#!/~kaminami/Kobati

– iBatis、MyBatisっぽい何か

–命名は駄洒落

• 特徴– とにかく全てを自分でコントロール

–設定はXMLファイルに記述

– クエリ結果とオブジェクトのマッピングに注力

やりたかったこと

• やっていることが透けて見える

–原因と結果の関係が、容易に想像できる

• Smalltalk側はスッキリさせる

–面倒事は外の世界に任せる

<select id="selectAllEntries" resultMap="selectEntryMap">SELECT

en.id AS entry_id, en.title AS entry_title, en.contents AS entry_contents, en.updated AS entry_updated, tag.id AS tag_id, tag.tag AS tag_tag, au.id AS author_id, au.name AS author_name

FROMEntry AS en

lEFT OUTER JOIN Entry2Tag AS e2t

ON en.id = e2t.entry_idLEFT OUTER JOIN

Tag AS tagON tag.id = e2t.tag_id

JOINEntry2Author as e2a

ON e2a.entry_id = en.idJOIN

Author as auON au.id = e2a.author_id

ORDER BY en.id, au.id, tag.id

</select>

<resultMap id="selectEntryMap" type="KbEntry"><id property="id" column="entry_id" /><result property="title" column="entry_title" /><result property="contents" column="entry_contents" /><result property="updated" column="entry_updated" /><association property="author" resultMap="selectAuthorMap" /><collection property="tags" resultMap="selectTagMap" />

</resultMap>

<resultMap id="selectAuthorMap" type="KbAuthor"><id property="id" column="author_id" /><result property="name" column="author_name" />

</resultMap>

<resultMap id="selectTagMap" type="KbTag"><id property="id" column="tag_id" /><result property="tag" column="tag_tag" />

</resultMap>

| acc result |acc := KbDatabaseAccessor for: (KbConfig from: KbExample config).^ acc transaction:

[:session |(session getMapper: 'Example-xmlMapper') selectAllEntries].

沿革①

• 2012年春

– Glorpと付き合いたくない

– PragmaにSQLを書けばシンプルでいいのでは?

• VisualWorksでPMapperライブラリ作成開始

selectById: id<resultType: 'PMapper.PmEntry'><selectOne: 'SELECT id, title, contents, updated

FROM PmEntryWHERE id = :id'>

^self executeQuery

沿革②

• すぐに破綻が見える

–ちょっと大きくなるとシンプルさが消失selectById: id

<selectOne: ‘SELECT

id as tag_id,tag as tag_name

FROM PmTagWHERE id = :id'>

<resultMap: #('PMapper.PmTag'

#(id tag_id)#(tag tag_name))>

^self executeQuery

沿革③

• 「PragmaにSQLを書く」コンセプトはダメ

– メソッドの中に、複数の、多くの情報を持ったプラグマを記述する・・・

–プラグマに構造表現は向いていない

• プラグマやアノテーションで頑張り過ぎるのは良くない

• 構造を取り扱うならXMLがベター

沿革④

• 2012年夏

– Kobati for VisualWorksをひっそりとリリース

• Cincom Public Storeから

沿革④

• 2014年秋

– Pharoに移植

• Pharo対応時に結構手を入れている

• VisualWorks版は追随していない状態

動作環境と使用ライブラリ

• 動作環境– Pharo3.0– PostgreSQL

• 使用ライブラリ– PostgresV3

• http://www.squeaksource.com/PostgresV3.html• PostgresV3-Core• PostgresV3-Pool

– XPath• http://www.smalltalkhub.com/#!/~hernan/XPath

– WideStringPatch• http://smalltalkhub.com/#!/~kaminami/WideStringPatch

インストール

Gofer itsmalltalkhubUser: 'PharoExtras' project: 'XPath';configuration;loadStable.

Gofer iturl: 'http://www.squeaksource.com/PostgresV3';package: 'PostgresV3-Core';package: 'PostgresV3-Pool';load.

Gofer new smalltalkhubUser: 'kaminami'project: 'Kobati';package: 'Kobati';load.

Gofer new smalltalkhubUser: 'kaminami'project: 'WideStringPatch';package: 'WideStringPatch';load.

※2015年2月27日時点では、これだと動かない。(XPathを用いた設定のパースに失敗する)

おそらく、XMLParserか、XPathの

バージョンアップでエンバグしている。

WideStringPatch

• マルチバイト文字列の操作が遅い

1. String >> at:put:

2. primitive error

3. WideString >> at:put:

• パッチをあてる

– 100倍程度の速度改善

– Stringでインスタンス生成をする箇所は全てボトルネックとなり得る

String >> convertFromWithConverter: converter

| readStream c |readStream := self readStream.^ WideString new: self size streamContents: [ :writeStream|

converter ifNil: [^ self].[readStream atEnd] whileFalse: [

c := converter nextFromStream: readStream.c

ifNotNil: [writeStream nextPut: c] ifNil: [^ writeStream contents]]].

定義するもの

• コンフィグ

– DBとの接続設定、マッパーの指定

• マッパー

– SQL

– SQLとメソッドのマッピング

– クエリ結果とSmalltalkオブジェクトのマッピング

• Smalltalkのクラス定義

Smalltalk側のクラス定義

• 通常のSmalltalkクラスを定義

• 値を設定するためのメソッドを定義

–必ずしも変数名と合致する必要はない

DB側のイメージ

DBとの接続設定と、マッパーの指定

<configuration>

<environment><property name="host" value="localhost"/><property name="port" value="5432"/><property name="database" value="test"/><property name="username" value="test"/><property name="password" value="test"/><property name="encoding" value="utf8"/>

</environment>

<mappers><!--

<mapper resource="ExampleXmlMapper.xml"/>-->

<mapper eval="KbExample xmlMapper"/></mappers>

</configuration>

マッパー

• 記述するもの

– SQL

– SQLとメソッドのマッピング

– クエリ結果とSmalltalkオブジェクトのマッピング

マッパーで使用する主なエレメント

• insert– selectKey

• update

• delete

• select

• selectOne

• resultMap

• sql

insert①<!-- Mapper --><insert id="insertTag" arguments="tag">

INSERT INTO Tag(

tag)VALUES (

#{tag.tag})

<selectKey keyProperty="tag.id" order="after">select currval('tag_id_seq')

</selectKey></insert>

<!-- Smalltalk -->insertTag: aTag

| acc result |acc := KbDatabaseAccessor for: (KbConfig from: self config).acc transaction:

[:session |result := (session getMapper: 'Example-xmlMapper') insertTag: aTag].

^ result

この部分は適宜キャッシュする

#{}で囲まれた部分は値に展開される

insert②“idのみでメソッドを省略した場合、idをselector扱いする引数がある場合は自動で : を補完(1個まで)する”<insert id="insertTag" arguments="tag">

“Smalltalkから呼び出す際のselectorを指定”<insert id=" insertTag " selector=" insertTag:" arguments=“tag">

“引数を複数とるような場合”<insert id="insertEntryToFeed" selector="insertEntry:toFeed:" arguments="entry, feed">

“自動採番した値を取得する場合に使用する”<selectKey keyProperty="tag.id" order="after">

select currval('tag_id_seq')</selectKey>

値の展開

• #{}で囲まれた部分は値に展開される– 最終的には、asKobatiSqlLiteralを送信して得られた値がBindされる

• 例– メッセージ送信無し

• #{id}

– メッセージ送信有り• #{tag.id}

• #{anArray.asInValueList}

• #{someArray@1}

• #{someDictonary@someKey}

update

• 使い方はinsertと同様

<update id="updateEntry" arguments="entry">UPDATE

EntrySET

title = #{entry.title}, contents = #{entry.contents}, updated = now()

WHEREid = #{entry.id}

</update>

delete

• 使い方はInsertと同様

<delete id="deleteEntry" arguments="entry">DELETE FROM Entry WHERE id=#{entry.id};

</delete>

selectOne

• クエリ結果1件分をオブジェクトとして返す

–それ以外は、次に紹介するselectと同様

<selectOne id="selectAuthorById" arguments="id" resultType="KbAuthor">SELECT

id, name

FROM Author

WHERE id = #{id}

</selectOne>

select①

• クエリ結果をコレクションに詰めて返す

• resultMapと連携させる

–入れ子構造なオブジェクトにも対応できる

– N+1問題も回避できる

select②

• 使用例

<resultMap id="selectAuthorMap" type="KbAuthor"><id property="id" column="author_id" /><result property="name" column="author_name" />

</resultMap>

<select id="selectAllAuthors" resultType="KbAuthor"><![CDATA[

SELECT id as author_id, name as author_name

FROM Author

]]></select>

CDATAで囲むことも可能この例では必要ないが、<>等の演算子を含める場合に必要

テーブルをJOINした場合などにも使いまわしたいので、columnに別名を付けている

select③<select id="selectAllEntries" resultMap="selectEntryMap">

SELECT en.id AS entry_id, en.title AS entry_title, en.contents AS entry_contents, en.updated AS entry_updated, tag.id AS tag_id, tag.tag AS tag_tag, au.id AS author_id, au.name AS author_name

FROMEntry AS en

lEFT OUTER JOIN Entry2Tag AS e2t

ON en.id = e2t.entry_idLEFT OUTER JOIN

Tag AS tagON tag.id = e2t.tag_id

JOINEntry2Author as e2a

ON e2a.entry_id = en.idJOIN

Author as auON au.id = e2a.author_id

ORDER BY en.id, au.id, tag.id

</select>

resultMap①

• クエリ結果とオブジェクトのマッピング

• associationエレメント、collectionエレメントで入れ子を表現することができる

<resultMap id="selectEntryMap" type="KbEntry"><id property="id" column="entry_id" /><result property="title" column="entry_title" /><result property="contents" column="entry_contents" /><result property="updated" column="entry_updated" /><association property="author" resultMap="selectAuthorMap" /><collection property="tags" resultMap="selectTagMap" />

</resultMap>

<resultMap id="selectAuthorMap" type="KbAuthor"><id property="id" column="author_id" /><result property="name" column="author_name" />

</resultMap>

<resultMap id="selectTagMap" type="KbTag"><id property="id" column="tag_id" /><result property="tag" column="tag_tag" />

</resultMap>

resultMap②

• 入れ子をまとめて記述することも可能

<resultMap id="selectEntryMap2" type="KbEntry"><id property="id" column="entry_id" /><result property="title" column="entry_title" /><result property="contents" column="entry_contents" /><result property="updated" column="entry_updated" /><association property="author" type="KbAuthor">

<id property="id" column="author_id" /><result property="name" column="author_name" />

</association><collection property="tags" type="Set" ofType="KbTag" >

<id property="id" column="tag_id" /><result property="tag" column="tag_tag" />

</collection></resultMap>

type指定なしの場合はOrderedCollection

sql

• コード片の再利用に使用

<sql id="selectAllTagsSql">SELECT

id, tag

FROM Tag

</sql>

<select id="selectAllTags" resultType="KbTag"><include refid="selectAllTagsSql"/>

</select>

<selectOne id="selectTagById" arguments="id" resultType="KbTag"><include refid="selectAllTagsSql"/>WHERE

id = #{id}</selectOne>

| acc result |acc := KbDatabaseAccessor for: (KbConfig from: KbExample config).^ acc execute: “実行”

[:session |(session getMapper: 'Example-xmlMapper') selectAllEntries].

Smalltalk側からの呼び出し

| aAuthor acc result |aAuthor = KbAuthor new.acc := KbDatabaseAccessor for: (KbConfig from: KbExample config).^ acc transaction: “トランザクション”

[:session |(session getMapper: 'Example-xmlMapper') insertAuthor: aAuthor].

| acc result |acc := KbDatabaseAccessor for: (KbConfig from: KbExample config).^ acc transaction:

[:session |session prepare: ‘任意のSQL文字列’; execute].

オブジェクトの組み立て

• 2段階、その分のオーバーヘッドがかかる

• 組み立て順序

– KbObjectBuilder >> buildObjects

1. クエリ結果(Ordered)

2. DictionaryのCollection

3. resultType指定したオブジェクトに詰め替え

終わりに

• まだ実績はないが、ギリギリ使える、はず

• Glorpに疲れたり、馴染めない人の選択肢となれるか?