Upload
kazuki-minamitani
View
308
Download
0
Embed Size (px)
Citation preview
Kobati?
• RDBを利用した永続化フレームワーク– http://smalltalkhub.com/#!/~kaminami/Kobati
– iBatis、MyBatisっぽい何か
–命名は駄洒落
• 特徴– とにかく全てを自分でコントロール
–設定はXMLファイルに記述
– クエリ結果とオブジェクトのマッピングに注力
<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がベター
動作環境と使用ライブラリ
• 動作環境– 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のクラス定義
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>
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 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指定したオブジェクトに詰め替え